From cbc73457ab57e4b8d2f5a2ea3fbbc6f1f458cb0d Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Tue, 18 Feb 2025 16:21:38 -0600 Subject: [PATCH] Add --all to artifact rm Add the ability to remove all artifacts with a --all|-a option in podman artifact rm. Fixes: https://issues.redhat.com/browse/RUN-2512 Signed-off-by: Brent Baude --- cmd/podman/artifact/rm.go | 66 ++++++++++++++------ docs/source/markdown/podman-artifact-rm.1.md | 18 +++++- pkg/domain/entities/artifact.go | 4 +- pkg/domain/infra/abi/artifact.go | 39 ++++++++++-- test/e2e/artifact_test.go | 31 ++++++++- 5 files changed, 129 insertions(+), 29 deletions(-) diff --git a/cmd/podman/artifact/rm.go b/cmd/podman/artifact/rm.go index d8037acd63..75fdc2f878 100644 --- a/cmd/podman/artifact/rm.go +++ b/cmd/podman/artifact/rm.go @@ -1,6 +1,7 @@ package artifact import ( + "errors" "fmt" "github.com/containers/podman/v5/cmd/podman/common" @@ -11,40 +12,67 @@ import ( var ( rmCmd = &cobra.Command{ - Use: "rm ARTIFACT", - Short: "Remove an OCI artifact", - Long: "Remove an OCI from local storage", - RunE: rm, - Aliases: []string{"remove"}, - Args: cobra.ExactArgs(1), + Use: "rm [options] ARTIFACT", + Short: "Remove an OCI artifact", + Long: "Remove an OCI artifact from local storage", + RunE: rm, + Aliases: []string{"remove"}, + Args: func(cmd *cobra.Command, args []string) error { //nolint: gocritic + return checkAllAndArgs(cmd, args) + }, ValidArgsFunction: common.AutocompleteArtifacts, - Example: `podman artifact rm quay.io/myimage/myartifact:latest`, - Annotations: map[string]string{registry.EngineMode: registry.ABIMode}, + Example: `podman artifact rm quay.io/myimage/myartifact:latest +podman artifact rm -a`, + Annotations: map[string]string{registry.EngineMode: registry.ABIMode}, } - // The lint avoid here is because someday soon we will need flags for - // this command - rmFlag = rmFlagType{} //nolint:unused + + rmOptions = entities.ArtifactRemoveOptions{} ) -// TODO at some point force will be a required option; but this cannot be -// until we have artifacts being consumed by other parts of libpod like -// volumes -type rmFlagType struct { //nolint:unused - force bool +func rmFlags(cmd *cobra.Command) { + flags := cmd.Flags() + flags.BoolVarP(&rmOptions.All, "all", "a", false, "Remove all artifacts") } - func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Command: rmCmd, Parent: artifactCmd, }) + rmFlags(rmCmd) } func rm(cmd *cobra.Command, args []string) error { - artifactRemoveReport, err := registry.ImageEngine().ArtifactRm(registry.Context(), args[0], entities.ArtifactRemoveOptions{}) + var nameOrID string + if len(args) > 0 { + nameOrID = args[0] + } + artifactRemoveReport, err := registry.ImageEngine().ArtifactRm(registry.Context(), nameOrID, rmOptions) if err != nil { return err } - fmt.Println(artifactRemoveReport.ArtfactDigest.Encoded()) + for _, d := range artifactRemoveReport.ArtifactDigests { + fmt.Println(d.Encoded()) + } + return nil +} + +// checkAllAndArgs takes a cobra command and args and checks if +// all is used, then no args can be passed. note: this was created +// as an unexported local func for now and could be moved to pkg +// validate. if we add "--latest" to the command, then perhaps +// one of the existing plg validate funcs would be appropriate. +func checkAllAndArgs(c *cobra.Command, args []string) error { + all, _ := c.Flags().GetBool("all") + if all && len(args) > 0 { + return fmt.Errorf("when using the --all switch, you may not pass any artifact names or digests") + } + if !all { + if len(args) < 1 { + return errors.New("a single artifact name or digest must be specified") + } + if len(args) > 1 { + return errors.New("too many arguments: only accepts one artifact name or digest ") + } + } return nil } diff --git a/docs/source/markdown/podman-artifact-rm.1.md b/docs/source/markdown/podman-artifact-rm.1.md index 207c416cf3..f7db65cb42 100644 --- a/docs/source/markdown/podman-artifact-rm.1.md +++ b/docs/source/markdown/podman-artifact-rm.1.md @@ -9,7 +9,7 @@ subject to change.* podman\-artifact\-rm - Remove an OCI from local storage ## SYNOPSIS -**podman artifact rm** *name* +**podman artifact rm** [*options*] *name* ## DESCRIPTION @@ -18,6 +18,11 @@ qualified artifact name or a full or partial artifact digest. ## OPTIONS +#### **--all**, **-a** + +Remove all artifacts in the local store. The use of this option conflicts with +providing a name or digest of the artifact. + #### **--help** Print usage statement. @@ -29,14 +34,21 @@ Remove an artifact by name ``` $ podman artifact rm quay.io/artifact/foobar2:test -e7b417f49fc24fc7ead6485da0ebd5bc4419d8a3f394c169fee5a6f38faa4056 +Deleted: e7b417f49fc24fc7ead6485da0ebd5bc4419d8a3f394c169fee5a6f38faa4056 ``` Remove an artifact by partial digest ``` $ podman artifact rm e7b417f49fc -e7b417f49fc24fc7ead6485da0ebd5bc4419d8a3f394c169fee5a6f38faa4056 +Deleted: e7b417f49fc24fc7ead6485da0ebd5bc4419d8a3f394c169fee5a6f38faa4056 +``` + +Remove all artifacts in local storage +``` +$ podman artifact rm -a +Deleted: cee15f7c5ce3e86ae6ce60d84bebdc37ad34acfa9a2611cf47501469ac83a1ab +Deleted: 72875f8f6f78d5b8ba98b2dd2c0a6f395fde8f05ff63a1df580d7a88f5afa97b ``` ## SEE ALSO diff --git a/pkg/domain/entities/artifact.go b/pkg/domain/entities/artifact.go index e8cae00c89..5ce8acad74 100644 --- a/pkg/domain/entities/artifact.go +++ b/pkg/domain/entities/artifact.go @@ -59,6 +59,8 @@ type ArtifactPushOptions struct { } type ArtifactRemoveOptions struct { + // Remove all artifacts + All bool } type ArtifactPullReport struct{} @@ -79,5 +81,5 @@ type ArtifactAddReport struct { } type ArtifactRemoveReport struct { - ArtfactDigest *digest.Digest + ArtifactDigests []*digest.Digest } diff --git a/pkg/domain/infra/abi/artifact.go b/pkg/domain/infra/abi/artifact.go index cea9c791ca..d66ec44561 100644 --- a/pkg/domain/infra/abi/artifact.go +++ b/pkg/domain/infra/abi/artifact.go @@ -12,6 +12,7 @@ import ( "github.com/containers/podman/v5/pkg/domain/entities" "github.com/containers/podman/v5/pkg/libartifact/store" "github.com/containers/podman/v5/pkg/libartifact/types" + "github.com/opencontainers/go-digest" ) func getDefaultArtifactStore(ir *ImageEngine) string { @@ -86,17 +87,45 @@ func (ir *ImageEngine) ArtifactPull(ctx context.Context, name string, opts entit return nil, artStore.Pull(ctx, name, *pullOptions) } -func (ir *ImageEngine) ArtifactRm(ctx context.Context, name string, _ entities.ArtifactRemoveOptions) (*entities.ArtifactRemoveReport, error) { +func (ir *ImageEngine) ArtifactRm(ctx context.Context, name string, opts entities.ArtifactRemoveOptions) (*entities.ArtifactRemoveReport, error) { + var ( + namesOrDigests []string + ) + artifactDigests := make([]*digest.Digest, 0, len(namesOrDigests)) artStore, err := store.NewArtifactStore(getDefaultArtifactStore(ir), ir.Libpod.SystemContext()) if err != nil { return nil, err } - artifactDigest, err := artStore.Remove(ctx, name) - if err != nil { - return nil, err + + if opts.All { + allArtifacts, err := artStore.List(ctx) + if err != nil { + return nil, err + } + for _, art := range allArtifacts { + // Using the digest here instead of name to protect against + // an artifact that lacks a name + manifestDigest, err := art.GetDigest() + if err != nil { + return nil, err + } + namesOrDigests = append(namesOrDigests, manifestDigest.Encoded()) + } + } + + if name != "" { + namesOrDigests = append(namesOrDigests, name) + } + + for _, namesOrDigest := range namesOrDigests { + artifactDigest, err := artStore.Remove(ctx, namesOrDigest) + if err != nil { + return nil, err + } + artifactDigests = append(artifactDigests, artifactDigest) } artifactRemoveReport := entities.ArtifactRemoveReport{ - ArtfactDigest: artifactDigest, + ArtifactDigests: artifactDigests, } return &artifactRemoveReport, err } diff --git a/test/e2e/artifact_test.go b/test/e2e/artifact_test.go index 503e667229..001b142392 100644 --- a/test/e2e/artifact_test.go +++ b/test/e2e/artifact_test.go @@ -175,12 +175,41 @@ var _ = Describe("Podman artifact", func() { // Removing that artifact should work rmWorks := podmanTest.PodmanExitCleanly("artifact", "rm", artifact1Name) // The digests printed by removal should be the same as the digest that was added - Expect(addArtifact1.OutputToString()).To(Equal(rmWorks.OutputToString())) + Expect(rmWorks.OutputToString()).To(ContainSubstring(addArtifact1.OutputToString())) // Inspecting that the removed artifact should fail inspectArtifact := podmanTest.Podman([]string{"artifact", "inspect", artifact1Name}) inspectArtifact.WaitWithDefaultTimeout() Expect(inspectArtifact).Should(ExitWithError(125, fmt.Sprintf("Error: %s: artifact does not exist", artifact1Name))) + + // Add some artifacts back in + artifact2File, err := createArtifactFile(8096) + Expect(err).ToNot(HaveOccurred()) + artifact2Name := "localhost/test/artifact2" + podmanTest.PodmanExitCleanly("artifact", "add", artifact2Name, artifact2File) + podmanTest.PodmanExitCleanly("artifact", "add", artifact1Name, artifact1File) + + // Using -a and an arg should trigger an error + failArgs := podmanTest.Podman([]string{"artifact", "rm", "-a", artifact1Name}) + failArgs.WaitWithDefaultTimeout() + Expect(failArgs).Should(ExitWithError(125, "Error: when using the --all switch, you may not pass any artifact names or digests")) + + // No args is an error + failNoArgs := podmanTest.Podman([]string{"artifact", "rm"}) + failNoArgs.WaitWithDefaultTimeout() + Expect(failNoArgs).Should(ExitWithError(125, "Error: a single artifact name or digest must be specified")) + + // Multiple args is an error + multipleArgs := podmanTest.Podman([]string{"artifact", "rm", artifact1Name, artifact2File}) + multipleArgs.WaitWithDefaultTimeout() + Expect(multipleArgs).Should(ExitWithError(125, "Error: too many arguments: only accepts one artifact name or digest")) + + // Remove all + podmanTest.PodmanExitCleanly("artifact", "rm", "-a") + + // There should be no artifacts in the store + rmAll := podmanTest.PodmanExitCleanly("artifact", "ls", "--noheading") + Expect(rmAll.OutputToString()).To(BeEmpty()) }) It("podman artifact inspect with full or partial digest", func() {