From a859fc9ea6eb9b6ff203da9330da5c5234ea4c8a Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Mon, 25 Nov 2024 15:13:52 -0500 Subject: [PATCH] Add podman manifest rm --ignore When removing manifests, users should be allowed to ignore ones that no longer exists. Signed-off-by: Daniel J Walsh --- cmd/podman/manifest/rm.go | 11 +++++-- docs/source/markdown/podman-manifest-rm.1.md | 8 ++++- .../markdown/podman-manifest-rm.1.md.in | 31 +++++++++++++++++++ pkg/api/handlers/compat/images_remove.go | 2 ++ pkg/api/handlers/libpod/images.go | 3 +- pkg/api/handlers/libpod/manifests.go | 17 +++++++++- pkg/api/server/register_manifest.go | 4 +++ pkg/domain/entities/engine_image.go | 2 +- pkg/domain/infra/abi/manifest.go | 4 +-- pkg/domain/infra/tunnel/manifest.go | 4 +-- test/apiv2/15-manifest.at | 3 ++ test/system/012-manifest.bats | 4 +++ 12 files changed, 82 insertions(+), 11 deletions(-) create mode 100644 docs/source/markdown/podman-manifest-rm.1.md.in diff --git a/cmd/podman/manifest/rm.go b/cmd/podman/manifest/rm.go index f5f13a86e6..c182b46480 100644 --- a/cmd/podman/manifest/rm.go +++ b/cmd/podman/manifest/rm.go @@ -6,13 +6,15 @@ import ( "github.com/containers/podman/v5/cmd/podman/common" "github.com/containers/podman/v5/cmd/podman/registry" + "github.com/containers/podman/v5/pkg/domain/entities" "github.com/containers/podman/v5/pkg/errorhandling" "github.com/spf13/cobra" ) var ( - rmCmd = &cobra.Command{ - Use: "rm LIST [LIST...]", + rmOptions = entities.ImageRemoveOptions{} + rmCmd = &cobra.Command{ + Use: "rm [options] LIST [LIST...]", Short: "Remove manifest list or image index from local storage", Long: "Remove manifest list or image index from local storage.", RunE: rm, @@ -27,10 +29,13 @@ func init() { Command: rmCmd, Parent: manifestCmd, }) + + flags := rmCmd.Flags() + flags.BoolVarP(&rmOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified manifest is missing") } func rm(cmd *cobra.Command, args []string) error { - report, rmErrors := registry.ImageEngine().ManifestRm(context.Background(), args) + report, rmErrors := registry.ImageEngine().ManifestRm(context.Background(), args, rmOptions) if report != nil { for _, u := range report.Untagged { fmt.Println("Untagged: " + u) diff --git a/docs/source/markdown/podman-manifest-rm.1.md b/docs/source/markdown/podman-manifest-rm.1.md index 2c5bbf0096..0613db19fa 100644 --- a/docs/source/markdown/podman-manifest-rm.1.md +++ b/docs/source/markdown/podman-manifest-rm.1.md @@ -4,11 +4,17 @@ podman\-manifest\-rm - Remove manifest list or image index from local storage ## SYNOPSIS -**podman manifest rm** *list-or-index* [...] +**podman manifest rm** [*options*] *list-or-index* [...] ## DESCRIPTION Removes one or more locally stored manifest lists. +## OPTIONS + +#### **--ignore**, **-i** + +If a specified manifest does not exist in the local storage, ignore it and do not throw an error. + ## EXAMPLE podman manifest rm `` diff --git a/docs/source/markdown/podman-manifest-rm.1.md.in b/docs/source/markdown/podman-manifest-rm.1.md.in new file mode 100644 index 0000000000..d9fbf25323 --- /dev/null +++ b/docs/source/markdown/podman-manifest-rm.1.md.in @@ -0,0 +1,31 @@ +% podman-manifest-rm 1 + +## NAME +podman\-manifest\-rm - Remove manifest list or image index from local storage + +## SYNOPSIS +**podman manifest rm** *list-or-index* [...] + +## DESCRIPTION +Removes one or more locally stored manifest lists. + +## OPTIONS + +#### **--ignore**, **-i** + +If a specified manifest does not exist in the local storage, ignore it and do not throw an error. + +## EXAMPLE + +podman manifest rm `` + +podman manifest rm listid1 listid2 + +**storage.conf** (`/etc/containers/storage.conf`) + +storage.conf is the storage configuration file for all tools using containers/storage + +The storage configuration file specifies all of the available container storage options for tools using shared container storage. + +## SEE ALSO +**[podman(1)](podman.1.md)**, **[podman-manifest(1)](podman-manifest.1.md)**, **[containers-storage.conf(5)](https://github.com/containers/storage/blob/main/docs/containers-storage.conf.5.md)** diff --git a/pkg/api/handlers/compat/images_remove.go b/pkg/api/handlers/compat/images_remove.go index 5dd7a56958..8379da99b1 100644 --- a/pkg/api/handlers/compat/images_remove.go +++ b/pkg/api/handlers/compat/images_remove.go @@ -22,6 +22,7 @@ func RemoveImage(w http.ResponseWriter, r *http.Request) { query := struct { Force bool `schema:"force"` NoPrune bool `schema:"noprune"` + Ignore bool `schema:"ignore"` }{ // This is where you can override the golang default value for one of fields } @@ -42,6 +43,7 @@ func RemoveImage(w http.ResponseWriter, r *http.Request) { options := entities.ImageRemoveOptions{ Force: query.Force, NoPrune: query.NoPrune, + Ignore: query.Ignore, } report, rmerrors := imageEngine.Remove(r.Context(), []string{possiblyNormalizedName}, options) if len(rmerrors) > 0 && rmerrors[0] != nil { diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index c5db3c7387..ee65fbe78e 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -668,6 +668,7 @@ func ImagesRemove(w http.ResponseWriter, r *http.Request) { query := struct { Force bool `schema:"force"` LookupManifest bool `schema:"lookupManifest"` + Ignore bool `schema:"ignore"` }{ Force: false, } @@ -677,7 +678,7 @@ func ImagesRemove(w http.ResponseWriter, r *http.Request) { return } - opts := entities.ImageRemoveOptions{Force: query.Force, LookupManifest: query.LookupManifest} + opts := entities.ImageRemoveOptions{Force: query.Force, LookupManifest: query.LookupManifest, Ignore: query.Ignore} imageEngine := abi.ImageEngine{Libpod: runtime} rmReport, rmErrors := imageEngine.Remove(r.Context(), []string{utils.GetName(r)}, opts) diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go index d6db94dbd8..837f3d196b 100644 --- a/pkg/api/handlers/libpod/manifests.go +++ b/pkg/api/handlers/libpod/manifests.go @@ -739,16 +739,31 @@ func ManifestModify(w http.ResponseWriter, r *http.Request) { // ManifestDelete removes a manifest list from storage func ManifestDelete(w http.ResponseWriter, r *http.Request) { + decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) imageEngine := abi.ImageEngine{Libpod: runtime} + query := struct { + Ignore bool `schema:"ignore"` + }{ + // Add defaults here once needed. + } + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusBadRequest, + fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) + return + } + opts := entities.ImageRemoveOptions{} + opts.Ignore = query.Ignore + name := utils.GetName(r) if _, err := runtime.LibimageRuntime().LookupManifestList(name); err != nil { utils.Error(w, http.StatusNotFound, err) return } - results, errs := imageEngine.ManifestRm(r.Context(), []string{name}) + results, errs := imageEngine.ManifestRm(r.Context(), []string{name}, opts) errsString := errorhandling.ErrorsToStrings(errs) report := handlers.LibpodImagesRemoveReport{ ImageRemoveReport: *results, diff --git a/pkg/api/server/register_manifest.go b/pkg/api/server/register_manifest.go index a6de54d0b6..35a77903b9 100644 --- a/pkg/api/server/register_manifest.go +++ b/pkg/api/server/register_manifest.go @@ -322,6 +322,10 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error { // type: string // required: true // description: The name or ID of the list to be deleted + // - in: query + // name: ignore + // description: Ignore if a specified manifest does not exist and do not throw an error. + // type: boolean // responses: // 200: // $ref: "#/responses/imagesRemoveResponseLibpod" diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go index 65844f676f..4919a033c6 100644 --- a/pkg/domain/entities/engine_image.go +++ b/pkg/domain/entities/engine_image.go @@ -39,7 +39,7 @@ type ImageEngine interface { //nolint:interfacebloat ManifestAddArtifact(ctx context.Context, name string, files []string, opts ManifestAddArtifactOptions) (string, error) ManifestAnnotate(ctx context.Context, names, image string, opts ManifestAnnotateOptions) (string, error) ManifestRemoveDigest(ctx context.Context, names, image string) (string, error) - ManifestRm(ctx context.Context, names []string) (*ImageRemoveReport, []error) + ManifestRm(ctx context.Context, names []string, imageRmOpts ImageRemoveOptions) (*ImageRemoveReport, []error) ManifestPush(ctx context.Context, name, destination string, imagePushOpts ImagePushOptions) (string, error) ManifestListClear(ctx context.Context, name string) (string, error) Sign(ctx context.Context, names []string, options SignOptions) (*SignReport, error) diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go index 253e173474..0cfb960a92 100644 --- a/pkg/domain/infra/abi/manifest.go +++ b/pkg/domain/infra/abi/manifest.go @@ -461,8 +461,8 @@ func (ir *ImageEngine) ManifestRemoveDigest(ctx context.Context, name, image str } // ManifestRm removes the specified manifest list from storage -func (ir *ImageEngine) ManifestRm(ctx context.Context, names []string) (report *entities.ImageRemoveReport, rmErrors []error) { - return ir.Remove(ctx, names, entities.ImageRemoveOptions{LookupManifest: true}) +func (ir *ImageEngine) ManifestRm(ctx context.Context, names []string, opts entities.ImageRemoveOptions) (report *entities.ImageRemoveReport, rmErrors []error) { + return ir.Remove(ctx, names, entities.ImageRemoveOptions{LookupManifest: true, Ignore: opts.Ignore}) } // ManifestPush pushes a manifest list or image index to the destination diff --git a/pkg/domain/infra/tunnel/manifest.go b/pkg/domain/infra/tunnel/manifest.go index 49585a198a..e0b521878c 100644 --- a/pkg/domain/infra/tunnel/manifest.go +++ b/pkg/domain/infra/tunnel/manifest.go @@ -161,8 +161,8 @@ func (ir *ImageEngine) ManifestRemoveDigest(ctx context.Context, name string, im } // ManifestRm removes the specified manifest list from storage -func (ir *ImageEngine) ManifestRm(ctx context.Context, names []string) (*entities.ImageRemoveReport, []error) { - return ir.Remove(ctx, names, entities.ImageRemoveOptions{LookupManifest: true}) +func (ir *ImageEngine) ManifestRm(ctx context.Context, names []string, opts entities.ImageRemoveOptions) (report *entities.ImageRemoveReport, rmErrors []error) { + return ir.Remove(ctx, names, entities.ImageRemoveOptions{LookupManifest: true, Ignore: opts.Ignore}) } // ManifestPush pushes a manifest list or image index to the destination diff --git a/test/apiv2/15-manifest.at b/test/apiv2/15-manifest.at index 44f3c7c83e..630d267e79 100644 --- a/test/apiv2/15-manifest.at +++ b/test/apiv2/15-manifest.at @@ -66,6 +66,9 @@ t POST "/v4.0.0/libpod/manifests/xyz:latest/registry/localhost:$REGISTRY_PORT%2F # /v3.x cannot delete a manifest list t DELETE /v4.0.0/libpod/manifests/$id_abc 200 t DELETE /v4.0.0/libpod/manifests/$id_xyz 200 +t GET /v4.0.0/libpod/manifests/$id_xyz/exists 404 +t DELETE /v4.0.0/libpod/manifests/$id_xyz 404 +t DELETE /v4.0.0/libpod/manifests/$id_xyz?ignore=true 200 # manifest add --artifact tests truncate -s 20M $WORKDIR/zeroes diff --git a/test/system/012-manifest.bats b/test/system/012-manifest.bats index 920a09e3e0..4b70e82519 100644 --- a/test/system/012-manifest.bats +++ b/test/system/012-manifest.bats @@ -94,6 +94,10 @@ function validate_instance_compression { --tls-verify=false $mid \ $manifest1 run_podman manifest rm $manifest1 + run_podman 1 manifest rm $manifest1 + is "$output" "Error: $manifest1: image not known" "Missing manifest is reported" + run_podman manifest rm --ignore $manifest1 + is "$output" "" "Missing manifest is ignored" # Default is to require TLS; also test explicit opts for opt in '' '--insecure=false' '--tls-verify=true' "--authfile=$authfile"; do