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 <bbaude@redhat.com>
This commit is contained in:
Brent Baude
2025-02-18 16:21:38 -06:00
parent ca1c029c43
commit cbc73457ab
5 changed files with 129 additions and 29 deletions

View File

@ -1,6 +1,7 @@
package artifact package artifact
import ( import (
"errors"
"fmt" "fmt"
"github.com/containers/podman/v5/cmd/podman/common" "github.com/containers/podman/v5/cmd/podman/common"
@ -11,40 +12,67 @@ import (
var ( var (
rmCmd = &cobra.Command{ rmCmd = &cobra.Command{
Use: "rm ARTIFACT", Use: "rm [options] ARTIFACT",
Short: "Remove an OCI artifact", Short: "Remove an OCI artifact",
Long: "Remove an OCI from local storage", Long: "Remove an OCI artifact from local storage",
RunE: rm, RunE: rm,
Aliases: []string{"remove"}, Aliases: []string{"remove"},
Args: cobra.ExactArgs(1), Args: func(cmd *cobra.Command, args []string) error { //nolint: gocritic
return checkAllAndArgs(cmd, args)
},
ValidArgsFunction: common.AutocompleteArtifacts, ValidArgsFunction: common.AutocompleteArtifacts,
Example: `podman artifact rm quay.io/myimage/myartifact:latest`, Example: `podman artifact rm quay.io/myimage/myartifact:latest
podman artifact rm -a`,
Annotations: map[string]string{registry.EngineMode: registry.ABIMode}, Annotations: map[string]string{registry.EngineMode: registry.ABIMode},
} }
// The lint avoid here is because someday soon we will need flags for
// this command rmOptions = entities.ArtifactRemoveOptions{}
rmFlag = rmFlagType{} //nolint:unused
) )
// TODO at some point force will be a required option; but this cannot be func rmFlags(cmd *cobra.Command) {
// until we have artifacts being consumed by other parts of libpod like flags := cmd.Flags()
// volumes flags.BoolVarP(&rmOptions.All, "all", "a", false, "Remove all artifacts")
type rmFlagType struct { //nolint:unused
force bool
} }
func init() { func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{ registry.Commands = append(registry.Commands, registry.CliCommand{
Command: rmCmd, Command: rmCmd,
Parent: artifactCmd, Parent: artifactCmd,
}) })
rmFlags(rmCmd)
} }
func rm(cmd *cobra.Command, args []string) error { 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 { if err != nil {
return err 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 return nil
} }

View File

@ -9,7 +9,7 @@ subject to change.*
podman\-artifact\-rm - Remove an OCI from local storage podman\-artifact\-rm - Remove an OCI from local storage
## SYNOPSIS ## SYNOPSIS
**podman artifact rm** *name* **podman artifact rm** [*options*] *name*
## DESCRIPTION ## DESCRIPTION
@ -18,6 +18,11 @@ qualified artifact name or a full or partial artifact digest.
## OPTIONS ## 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** #### **--help**
Print usage statement. Print usage statement.
@ -29,14 +34,21 @@ Remove an artifact by name
``` ```
$ podman artifact rm quay.io/artifact/foobar2:test $ podman artifact rm quay.io/artifact/foobar2:test
e7b417f49fc24fc7ead6485da0ebd5bc4419d8a3f394c169fee5a6f38faa4056 Deleted: e7b417f49fc24fc7ead6485da0ebd5bc4419d8a3f394c169fee5a6f38faa4056
``` ```
Remove an artifact by partial digest Remove an artifact by partial digest
``` ```
$ podman artifact rm e7b417f49fc $ podman artifact rm e7b417f49fc
e7b417f49fc24fc7ead6485da0ebd5bc4419d8a3f394c169fee5a6f38faa4056 Deleted: e7b417f49fc24fc7ead6485da0ebd5bc4419d8a3f394c169fee5a6f38faa4056
```
Remove all artifacts in local storage
```
$ podman artifact rm -a
Deleted: cee15f7c5ce3e86ae6ce60d84bebdc37ad34acfa9a2611cf47501469ac83a1ab
Deleted: 72875f8f6f78d5b8ba98b2dd2c0a6f395fde8f05ff63a1df580d7a88f5afa97b
``` ```
## SEE ALSO ## SEE ALSO

View File

@ -59,6 +59,8 @@ type ArtifactPushOptions struct {
} }
type ArtifactRemoveOptions struct { type ArtifactRemoveOptions struct {
// Remove all artifacts
All bool
} }
type ArtifactPullReport struct{} type ArtifactPullReport struct{}
@ -79,5 +81,5 @@ type ArtifactAddReport struct {
} }
type ArtifactRemoveReport struct { type ArtifactRemoveReport struct {
ArtfactDigest *digest.Digest ArtifactDigests []*digest.Digest
} }

View File

@ -12,6 +12,7 @@ import (
"github.com/containers/podman/v5/pkg/domain/entities" "github.com/containers/podman/v5/pkg/domain/entities"
"github.com/containers/podman/v5/pkg/libartifact/store" "github.com/containers/podman/v5/pkg/libartifact/store"
"github.com/containers/podman/v5/pkg/libartifact/types" "github.com/containers/podman/v5/pkg/libartifact/types"
"github.com/opencontainers/go-digest"
) )
func getDefaultArtifactStore(ir *ImageEngine) string { 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) 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()) artStore, err := store.NewArtifactStore(getDefaultArtifactStore(ir), ir.Libpod.SystemContext())
if err != nil { if err != nil {
return nil, err return nil, err
} }
artifactDigest, err := artStore.Remove(ctx, name)
if opts.All {
allArtifacts, err := artStore.List(ctx)
if err != nil { if err != nil {
return nil, err 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{ artifactRemoveReport := entities.ArtifactRemoveReport{
ArtfactDigest: artifactDigest, ArtifactDigests: artifactDigests,
} }
return &artifactRemoveReport, err return &artifactRemoveReport, err
} }

View File

@ -175,12 +175,41 @@ var _ = Describe("Podman artifact", func() {
// Removing that artifact should work // Removing that artifact should work
rmWorks := podmanTest.PodmanExitCleanly("artifact", "rm", artifact1Name) rmWorks := podmanTest.PodmanExitCleanly("artifact", "rm", artifact1Name)
// The digests printed by removal should be the same as the digest that was added // 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 // Inspecting that the removed artifact should fail
inspectArtifact := podmanTest.Podman([]string{"artifact", "inspect", artifact1Name}) inspectArtifact := podmanTest.Podman([]string{"artifact", "inspect", artifact1Name})
inspectArtifact.WaitWithDefaultTimeout() inspectArtifact.WaitWithDefaultTimeout()
Expect(inspectArtifact).Should(ExitWithError(125, fmt.Sprintf("Error: %s: artifact does not exist", artifact1Name))) 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() { It("podman artifact inspect with full or partial digest", func() {