diff --git a/cmd/podman/artifact/rm.go b/cmd/podman/artifact/rm.go index 3d9b2b5214..0f07315c6b 100644 --- a/cmd/podman/artifact/rm.go +++ b/cmd/podman/artifact/rm.go @@ -23,6 +23,7 @@ var ( podman artifact rm quay.io/myimage/myartifact:latest podman artifact rm -a podman artifact rm c4dfb1609ee2 93fd78260bd1 c0ed59d05ff7 + podman artifact rm -i c4dfb1609ee2 `, } @@ -32,6 +33,7 @@ var ( func rmFlags(cmd *cobra.Command) { flags := cmd.Flags() flags.BoolVarP(&rmOptions.All, "all", "a", false, "Remove all artifacts") + flags.BoolVarP(&rmOptions.Ignore, "ignore", "i", false, "Ignore error if artifact does not exist") } func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ diff --git a/docs/source/markdown/podman-artifact-rm.1.md b/docs/source/markdown/podman-artifact-rm.1.md index b833f3d6f8..c2bbea7a53 100644 --- a/docs/source/markdown/podman-artifact-rm.1.md +++ b/docs/source/markdown/podman-artifact-rm.1.md @@ -22,6 +22,9 @@ providing a name or digest of the artifact. Print usage statement. +#### **--ignore**, **-i** + +Remove artifacts in the local store, ignoring errors when trying to remove artifacts that do not exist. ## EXAMPLES @@ -49,6 +52,11 @@ Deleted: cee15f7c5ce3e86ae6ce60d84bebdc37ad34acfa9a2611cf47501469ac83a1ab Deleted: 72875f8f6f78d5b8ba98b2dd2c0a6f395fde8f05ff63a1df580d7a88f5afa97b ``` +Remove artifacts ignoring the errors if the artifact does not exist. +``` +$ podman artifact rm -i 3f78d5b8ba98b2 +``` + ## SEE ALSO **[podman(1)](podman.1.md)**, **[podman-artifact(1)](podman-artifact.1.md)** diff --git a/pkg/api/handlers/libpod/artifacts.go b/pkg/api/handlers/libpod/artifacts.go index 3418606881..41d32d97b1 100644 --- a/pkg/api/handlers/libpod/artifacts.go +++ b/pkg/api/handlers/libpod/artifacts.go @@ -169,6 +169,7 @@ func BatchRemoveArtifact(w http.ResponseWriter, r *http.Request) { query := struct { All bool `schema:"all"` Artifacts []string `schema:"artifacts"` + Ignore bool `schema:"ignore"` }{} if err := decoder.Decode(&query, r.URL.Query()); err != nil { @@ -191,11 +192,16 @@ func BatchRemoveArtifact(w http.ResponseWriter, r *http.Request) { removeOptions := entities.ArtifactRemoveOptions{ Artifacts: query.Artifacts, All: query.All, + Ignore: query.Ignore, } artifacts, err := imageEngine.ArtifactRm(r.Context(), removeOptions) if err != nil { if errors.Is(err, libartifact_types.ErrArtifactNotExist) { + if removeOptions.Ignore { + utils.WriteResponse(w, http.StatusOK, artifacts) + return + } utils.ArtifactNotFound(w, "", err) return } diff --git a/pkg/api/server/register_artifacts.go b/pkg/api/server/register_artifacts.go index a67aba5145..22aba9def2 100644 --- a/pkg/api/server/register_artifacts.go +++ b/pkg/api/server/register_artifacts.go @@ -116,6 +116,10 @@ func (s *APIServer) registerArtifactHandlers(r *mux.Router) error { // in: query // description: Remove all artifacts // type: boolean + // - name: ignore + // in: query + // description: Ignore errors if artifact do not exist + // type: boolean // responses: // 200: // $ref: "#/responses/artifactRemoveResponse" diff --git a/pkg/bindings/artifacts/types.go b/pkg/bindings/artifacts/types.go index 633c15b5ce..e9d06dadac 100644 --- a/pkg/bindings/artifacts/types.go +++ b/pkg/bindings/artifacts/types.go @@ -52,6 +52,8 @@ type RemoveOptions struct { All *bool // Artifacts is a list of Artifact IDs or names to remove Artifacts []string + // Ignore errors if IDs or names are not defined + Ignore *bool } // AddOptions are optional options for removing images diff --git a/pkg/bindings/artifacts/types_remove_options.go b/pkg/bindings/artifacts/types_remove_options.go index d61846f5ed..48d8046629 100644 --- a/pkg/bindings/artifacts/types_remove_options.go +++ b/pkg/bindings/artifacts/types_remove_options.go @@ -46,3 +46,18 @@ func (o *RemoveOptions) GetArtifacts() []string { } return o.Artifacts } + +// WithIgnore set field Ignore to given value +func (o *RemoveOptions) WithIgnore(value bool) *RemoveOptions { + o.Ignore = &value + return o +} + +// GetIgnore returns value of field Ignore +func (o *RemoveOptions) GetIgnore() bool { + if o.Ignore == nil { + var z bool + return z + } + return *o.Ignore +} diff --git a/pkg/domain/entities/artifact.go b/pkg/domain/entities/artifact.go index 7eede3dfee..48dc62a9f6 100644 --- a/pkg/domain/entities/artifact.go +++ b/pkg/domain/entities/artifact.go @@ -96,6 +96,8 @@ type ArtifactRemoveOptions struct { All bool // Artifacts is a list of Artifact IDs or names to remove Artifacts []string + // Ignore if a specified artifact does not exist and do not throw any error. + Ignore bool } type ArtifactRemoveReport = entitiesTypes.ArtifactRemoveReport diff --git a/pkg/domain/infra/abi/artifact.go b/pkg/domain/infra/abi/artifact.go index c53ce2eac6..26c6a203ea 100644 --- a/pkg/domain/infra/abi/artifact.go +++ b/pkg/domain/infra/abi/artifact.go @@ -4,6 +4,7 @@ package abi import ( "context" + "errors" "fmt" "io" "os" @@ -12,6 +13,7 @@ import ( "github.com/containers/podman/v5/pkg/domain/entities" "github.com/containers/podman/v5/pkg/libartifact/types" "github.com/opencontainers/go-digest" + "github.com/sirupsen/logrus" "go.podman.io/common/libimage" ) @@ -124,6 +126,10 @@ func (ir *ImageEngine) ArtifactRm(ctx context.Context, opts entities.ArtifactRem for _, namesOrDigest := range namesOrDigests { artifactDigest, err := artStore.Remove(ctx, namesOrDigest) if err != nil { + if opts.Ignore && errors.Is(err, types.ErrArtifactNotExist) { + logrus.Debugf("Artifact with name or digest %q does not exist, ignoring error as request", namesOrDigest) + continue + } return nil, err } artifactDigests = append(artifactDigests, artifactDigest) diff --git a/pkg/domain/infra/tunnel/artifact.go b/pkg/domain/infra/tunnel/artifact.go index 34e79c9dc5..8f647a89a4 100644 --- a/pkg/domain/infra/tunnel/artifact.go +++ b/pkg/domain/infra/tunnel/artifact.go @@ -57,6 +57,7 @@ func (ir *ImageEngine) ArtifactRm(_ context.Context, opts entities.ArtifactRemov removeOptions := artifacts.RemoveOptions{ All: &opts.All, Artifacts: opts.Artifacts, + Ignore: &opts.Ignore, } return artifacts.Remove(ir.ClientCtx, "", &removeOptions) diff --git a/test/apiv2/python/rest_api/test_v2_0_0_artifact.py b/test/apiv2/python/rest_api/test_v2_0_0_artifact.py index 182f31ec75..c584ca386a 100644 --- a/test/apiv2/python/rest_api/test_v2_0_0_artifact.py +++ b/test/apiv2/python/rest_api/test_v2_0_0_artifact.py @@ -527,6 +527,20 @@ class ArtifactTestCase(APITestCase): rjson = r.json() self.assertEqual(len(rjson), 0) + def test_remove_with_ignore(self): + # Test remove non existent artifacts with ignore + removeparameters: dict[str, str | list[str]] = { + "Artifacts": "fake_artifact", + "ignore": "true", + } + + url = self.uri("/artifacts/remove") + r = requests.delete(url, params=removeparameters) + rjson = r.json() + + # Assert correct response code + self.assertEqual(r.status_code, 200, r.text) + def test_remove_absent_artifact_fails(self): ARTIFACT_NAME = "localhost/fake/artifact:latest" url = self.uri("/artifacts/" + ARTIFACT_NAME) diff --git a/test/e2e/artifact_test.go b/test/e2e/artifact_test.go index 8b3a836271..c58caebb84 100644 --- a/test/e2e/artifact_test.go +++ b/test/e2e/artifact_test.go @@ -245,6 +245,22 @@ var _ = Describe("Podman artifact", func() { // There should be no artifacts in the store rmAll := podmanTest.PodmanExitCleanly("artifact", "ls", "--noheading") Expect(rmAll.OutputToString()).To(BeEmpty()) + + // Trying to remove an artifact that does not exist should pass with -i + podmanTest.PodmanExitCleanly("artifact", "rm", "-i", "foobar") + + // Add an artifact to test remove with --ignore flag + artifact3File, err := createArtifactFile(4192) + Expect(err).ToNot(HaveOccurred()) + artifact3Name := "localhost/test/artifact3" + _ = podmanTest.PodmanExitCleanly("artifact", "add", artifact3Name, artifact3File) + + // Trying to remove an existing artifact should also pass with -i + podmanTest.PodmanExitCleanly("artifact", "rm", "-i", artifact3Name) + + // There should be no artifacts in the store at this point + rmAll = podmanTest.PodmanExitCleanly("artifact", "ls", "--noheading") + Expect(rmAll.OutputToString()).To(BeEmpty()) }) It("podman artifact inspect with full or partial digest", func() {