Add GET /quadlets/{name}/file

Fixes: https://issues.redhat.com/browse/RUN-3716

Signed-off-by: Nicola Sella <nsella@redhat.com>
This commit is contained in:
Nicola Sella
2025-11-12 16:15:07 +01:00
parent 046206fe53
commit ee0efb9fc6
5 changed files with 110 additions and 3 deletions

View File

@@ -12,6 +12,7 @@ import (
"github.com/containers/podman/v6/pkg/domain/entities" "github.com/containers/podman/v6/pkg/domain/entities"
"github.com/containers/podman/v6/pkg/domain/infra/abi" "github.com/containers/podman/v6/pkg/domain/infra/abi"
"github.com/containers/podman/v6/pkg/util" "github.com/containers/podman/v6/pkg/util"
"github.com/sirupsen/logrus"
) )
func ListQuadlets(w http.ResponseWriter, r *http.Request) { func ListQuadlets(w http.ResponseWriter, r *http.Request) {
@@ -35,3 +36,23 @@ func ListQuadlets(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, quadlets) utils.WriteResponse(w, http.StatusOK, quadlets)
} }
func GetQuadletPrint(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
name := utils.GetName(r)
containerEngine := abi.ContainerEngine{Libpod: runtime}
quadletContents, err := containerEngine.QuadletPrint(r.Context(), name)
if err != nil {
utils.Error(w, http.StatusNotFound, fmt.Errorf("no such quadlet: %s: %w", name, err))
return
}
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
if _, err := w.Write([]byte(quadletContents)); err != nil {
logrus.Errorf("Failed to write quadlet contents: %v", err)
return
}
}

View File

@@ -79,6 +79,13 @@ type podNotFound struct {
Body errorhandling.ErrorModel Body errorhandling.ErrorModel
} }
// No such quadlet
// swagger:response
type quadletNotFound struct {
// in:body
Body errorhandling.ErrorModel
}
// No such manifest // No such manifest
// swagger:response // swagger:response
type manifestNotFound struct { type manifestNotFound struct {

View File

@@ -527,3 +527,10 @@ type quadletListResponse struct {
// in:body // in:body
Body []entities.ListQuadlet Body []entities.ListQuadlet
} }
// Quadlet file
// swagger:response
type quadletFileResponse struct {
// in:body
Body string
}

View File

@@ -32,5 +32,27 @@ func (s *APIServer) registerQuadletHandlers(r *mux.Router) error {
// 500: // 500:
// $ref: "#/responses/internalError" // $ref: "#/responses/internalError"
r.HandleFunc(VersionedPath("/libpod/quadlets/json"), s.APIHandler(libpod.ListQuadlets)).Methods(http.MethodGet) r.HandleFunc(VersionedPath("/libpod/quadlets/json"), s.APIHandler(libpod.ListQuadlets)).Methods(http.MethodGet)
// swagger:operation GET /libpod/quadlets/{name}/file libpod QuadletFileLibpod
// ---
// tags:
// - quadlets
// summary: Get quadlet file
// description: Get the contents of a Quadlet, displaying the file including all comments
// produces:
// - text/plain
// parameters:
// - in: path
// name: name
// type: string
// required: true
// description: the name of the quadlet with extension (e.g., "myapp.container")
// responses:
// 200:
// $ref: "#/responses/quadletFileResponse"
// 404:
// $ref: "#/responses/quadletNotFound"
// 500:
// $ref: "#/responses/internalError"
r.HandleFunc(VersionedPath("/libpod/quadlets/{name}/file"), s.APIHandler(libpod.GetQuadletPrint)).Methods(http.MethodGet)
return nil return nil
} }

View File

@@ -6,10 +6,60 @@
# NOTE: Once podman-remote quadlet support is added we can enable the podman quadlet tests in # NOTE: Once podman-remote quadlet support is added we can enable the podman quadlet tests in
# test/system/253-podman-quadlet.bats which should cover it in more detail then. # test/system/253-podman-quadlet.bats which should cover it in more detail then.
## list volume ## Test list endpoint
t GET libpod/quadlets/json 200 t GET libpod/quadlets/json 200
# Example with filter applied (uncomment once needed) # Test 404 for non-existent quadlet
# t GET libpod/quadlets/json?filters='{"name":["name.*"]}' 200 t GET libpod/quadlets/nonexistent.container 404
# Install a quadlet with a unique name
quadlet_name=quadlet-test-$(cat /proc/sys/kernel/random/uuid)
quadlet_container_name="$quadlet_name.container"
quadlet_build_name="$quadlet_name.build"
TMPDIR=$(mktemp -d podman-apiv2-test.quadlet.XXXXXXXX)
quadlet_container_file_content=$(cat << EOF
[Container]
Image=$IMAGE
EOF
)
quadlet_build_file_content=$(cat << EOF
[Build]
ImageTag=localhost/$quadlet_name
EOF
)
echo "$quadlet_container_file_content" > $TMPDIR/$quadlet_container_name
echo "$quadlet_build_file_content" > $TMPDIR/$quadlet_build_name
# this should ensure the .config/containers/systemd directory is created
podman quadlet install $TMPDIR/$quadlet_container_name
podman quadlet install $TMPDIR/$quadlet_build_name
filter_param=$(printf '{"name":["%s"]}' "$quadlet_name")
t GET "libpod/quadlets/json?filters=$filter_param" 200 \
length=2 \
.[0].Name="$quadlet_build_name" \
.[1].Name="$quadlet_container_name"
filter_param=$(printf '{"name":["%s"]}' "$quadlet_container_name")
t GET "libpod/quadlets/json?filters=$filter_param" 200 \
length=1 \
.[0].Name="$quadlet_container_name"
t GET "libpod/quadlets/$quadlet_name/file" 404
t GET "libpod/quadlets/$quadlet_container_name/file" 200
is "$output" "$quadlet_container_file_content"
t GET "libpod/quadlets/$quadlet_build_name/file" 200
is "$output" "$quadlet_build_file_content"
podman quadlet rm $quadlet_container_name
podman quadlet rm $quadlet_build_name
rm -rf $TMPDIR
# vim: filetype=sh # vim: filetype=sh