1
0
mirror of https://github.com/containers/podman.git synced 2026-02-05 15:45:08 +01:00

Merge pull request #27936 from inknos/get-exists-quadlet-api

Add GET /quadlets/{name}/exists
This commit is contained in:
Brent Baude
2026-01-28 10:58:46 -06:00
committed by GitHub
7 changed files with 79 additions and 5 deletions

View File

@@ -29,6 +29,9 @@ var (
// does not exist.
ErrNoSuchExitCode = errors.New("no such exit code")
// ErrNoSuchQuadlet indicates the requested quadlet does not exist
ErrNoSuchQuadlet = errors.New("no such quadlet")
// ErrDepExists indicates that the current object has dependencies and
// cannot be removed before them.
ErrDepExists = errors.New("dependency exists")

View File

@@ -67,6 +67,25 @@ func GetQuadletPrint(w http.ResponseWriter, r *http.Request) {
}
}
// QuadletExists checks if a quadlet exists by name
func QuadletExists(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
name := utils.GetName(r)
containerEngine := abi.ContainerEngine{Libpod: runtime}
report, err := containerEngine.QuadletExists(r.Context(), name)
if err != nil {
utils.InternalServerError(w, err)
return
}
if !report.Value {
utils.Error(w, http.StatusNotFound, fmt.Errorf("no such quadlet: %s", name))
return
}
utils.WriteResponse(w, http.StatusNoContent, "")
}
// extractQuadletFiles extracts quadlet files from tar archive to a temporary directory
func extractQuadletFiles(tempDir string, r io.ReadCloser) ([]string, error) {
quadletDir := filepath.Join(tempDir, "quadlets")

View File

@@ -54,6 +54,28 @@ func (s *APIServer) registerQuadletHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/internalError"
r.HandleFunc(VersionedPath("/libpod/quadlets/{name}/file"), s.APIHandler(libpod.GetQuadletPrint)).Methods(http.MethodGet)
// swagger:operation GET /libpod/quadlets/{name}/exists libpod QuadletExistsLibpod
// ---
// tags:
// - quadlets
// summary: Check if quadlet exists
// description: Check if a quadlet exists by name
// produces:
// - application/json
// parameters:
// - in: path
// name: name
// type: string
// required: true
// description: the name of the quadlet with extension (e.g., "myapp.container")
// responses:
// 204:
// description: quadlet exists
// 404:
// $ref: "#/responses/quadletNotFound"
// 500:
// $ref: "#/responses/internalError"
r.HandleFunc(VersionedPath("/libpod/quadlets/{name}/exists"), s.APIHandler(libpod.QuadletExists)).Methods(http.MethodGet)
// swagger:operation POST /libpod/quadlets libpod QuadletInstallLibpod
// ---
// tags:

View File

@@ -94,6 +94,7 @@ type ContainerEngine interface { //nolint:interfacebloat
PodStop(ctx context.Context, namesOrIds []string, options PodStopOptions) ([]*PodStopReport, error)
PodTop(ctx context.Context, options PodTopOptions) (*StringSliceReport, error)
PodUnpause(ctx context.Context, namesOrIds []string, options PodunpauseOptions) ([]*PodUnpauseReport, error)
QuadletExists(ctx context.Context, name string) (*BoolReport, error)
QuadletInstall(ctx context.Context, pathsOrURLs []string, options QuadletInstallOptions) (*QuadletInstallReport, error)
QuadletList(ctx context.Context, options QuadletListOptions) ([]*ListQuadlet, error)
QuadletPrint(ctx context.Context, quadlet string) (string, error)

View File

@@ -719,6 +719,15 @@ func (ic *ContainerEngine) QuadletList(ctx context.Context, options entities.Qua
return finalReports, nil
}
// QuadletExists checks whether a quadlet with the given name exists.
func (ic *ContainerEngine) QuadletExists(_ context.Context, name string) (*entities.BoolReport, error) {
_, err := getQuadletPathByName(name)
if err != nil && !errors.Is(err, define.ErrNoSuchQuadlet) {
return nil, err
}
return &entities.BoolReport{Value: err == nil}, nil
}
// Retrieve path to a Quadlet file given full name including extension
func getQuadletPathByName(name string) (string, error) {
// Check if we were given a valid extension
@@ -737,7 +746,7 @@ func getQuadletPathByName(name string) (string, error) {
}
return testPath, nil
}
return "", fmt.Errorf("could not locate quadlet %q in any supported quadlet directory", name)
return "", fmt.Errorf("could not locate quadlet %q in any supported quadlet directory: %w", name, define.ErrNoSuchQuadlet)
}
func (ic *ContainerEngine) QuadletPrint(_ context.Context, quadlet string) (string, error) {

View File

@@ -9,6 +9,10 @@ import (
var errNotImplemented = errors.New("not implemented for the remote Podman client")
func (ic *ContainerEngine) QuadletExists(_ context.Context, _ string) (*entities.BoolReport, error) {
return nil, errNotImplemented
}
func (ic *ContainerEngine) QuadletInstall(_ context.Context, _ []string, _ entities.QuadletInstallOptions) (*entities.QuadletInstallReport, error) {
return nil, errNotImplemented
}

View File

@@ -33,6 +33,12 @@ t GET libpod/quadlets/nonexistent.container 405
# Test 404 for non-existent quadlet
t GET libpod/quadlets/nonexistent.container/file 404
# Test 404 for non-existent quadlet exists endpoint
t GET libpod/quadlets/nonexistent.container/exists 404
# Test 500 for invalid quadlet extension (not a user-facing "not found" but an input error)
t GET libpod/quadlets/invalid.badext/exists 500
# Install a quadlet with a unique name
quadlet_name=quadlet-test-$(cat /proc/sys/kernel/random/uuid)
@@ -80,6 +86,12 @@ is "$output" "$quadlet_container_file_content"
t GET "libpod/quadlets/$quadlet_build_name/file" 200
is "$output" "$quadlet_build_file_content"
# Test exists endpoint returns 204 for existing quadlet
t GET "libpod/quadlets/$quadlet_container_name/exists" 204
# Test exists endpoint returns 500 for quadlet without extension
t GET "libpod/quadlets/$quadlet_name/exists" 500
podman quadlet rm $quadlet_container_name
podman quadlet rm $quadlet_build_name
rm -rf $TMPD
@@ -319,16 +331,20 @@ EOF
echo "$quadlet_1_content" > "$TMPDIR/$quadlet_1"
podman quadlet install $TMPDIR/$quadlet_1
t GET "libpod/quadlets/$quadlet_1/exists" 204
t DELETE "libpod/quadlets/$quadlet_1" 200 \
'.Removed|length=1' \
'.QuadletErrors|length=0'
t GET "libpod/quadlets/$quadlet_1/file" 404
t DELETE "libpod/quadlets/nonexistent.container" 200 \
# Verify exists returns 404 after deletion
t GET "libpod/quadlets/$quadlet_1/exists" 404
t DELETE "libpod/quadlets/nonexistent.container" 404 \
'.Removed|length=0' \
'.Errors|length=1' \
'.Errors~.*could not locate quadlet.*'
.cause='no such quadlet'
t DELETE "libpod/quadlets/nonexistent.container?ignore=true" 200 \
'.Removed|length=1' \
@@ -353,7 +369,7 @@ podman quadlet install $TMPDIR/$quadlet_2
if systemctl --user start "${quadlet_2%.*}.service" > /dev/null 2>&1; then
if systemctl --user is-active --quiet ${quadlet_2%.*}.service; then
t DELETE "libpod/quadlets/$quadlet_2" 400 \
.cause~.*'container'.*'is running and force is not set, refusing to remove'.*
.cause~.*'quadlet is running'
t DELETE "libpod/quadlets/$quadlet_2?force=true" 200
fi