Skip to content

Commit 73519b2

Browse files
committed
Add --ignore option to podman artifact rm command
This commit implements the --ignore functionality for the artifact rm command, allowing users to ignore errors when specified artifacts do not exist. Changes made: - Add Ignore field to ArtifactRemoveOptions entity types - Add --ignore CLI flag (-i short form) to artifact rm command - Implement ignore logic in ABI backend to continue processing on errors - Update API handlers and tunnel implementation for podman-remote support - Add comprehensive documentation and examples to man page - Add e2e and system BATS tests for --ignore functionality - Clean up whitespace in BATS test files The --ignore option follows the same pattern as other podman ignore options like 'podman image rm --ignore' and 'podman pod rm --ignore'. It allows scripts to continue execution when trying to remove artifacts that may not exist, preventing race conditions in cleanup scenarios. Usage examples: podman artifact rm --ignore nonexistent-artifact podman artifact rm --ignore existing-artifact nonexistent-artifact Fixes: #27084 Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
1 parent d7f33a7 commit 73519b2

File tree

10 files changed

+229
-1
lines changed

10 files changed

+229
-1
lines changed

cmd/podman/artifact/rm.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ var (
3232
func rmFlags(cmd *cobra.Command) {
3333
flags := cmd.Flags()
3434
flags.BoolVarP(&rmOptions.All, "all", "a", false, "Remove all artifacts")
35+
flags.BoolVarP(&rmOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified artifact does not exist")
3536
}
3637
func init() {
3738
registry.Commands = append(registry.Commands, registry.CliCommand{

docs/source/markdown/podman-artifact-rm.1.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ providing a name or digest of the artifact.
2222

2323
Print usage statement.
2424

25+
#### **--ignore**, **-i**
26+
27+
Ignore errors when a specified artifact does not exist. A user might have decided
28+
to manually remove an artifact which would lead to a failure during script execution.
2529

2630
## EXAMPLES
2731

@@ -49,6 +53,12 @@ Deleted: cee15f7c5ce3e86ae6ce60d84bebdc37ad34acfa9a2611cf47501469ac83a1ab
4953
Deleted: 72875f8f6f78d5b8ba98b2dd2c0a6f395fde8f05ff63a1df580d7a88f5afa97b
5054
```
5155

56+
Ignore errors when removing non-existent artifacts.
57+
```
58+
$ podman artifact rm --ignore nonexistent-artifact existing-artifact
59+
Deleted: e7b417f49fc24fc7ead6485da0ebd5bc4419d8a3f394c169fee5a6f38faa4056
60+
```
61+
5262
## SEE ALSO
5363
**[podman(1)](podman.1.md)**, **[podman-artifact(1)](podman-artifact.1.md)**
5464

pkg/api/handlers/libpod/artifacts.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ func BatchRemoveArtifact(w http.ResponseWriter, r *http.Request) {
169169
query := struct {
170170
All bool `schema:"all"`
171171
Artifacts []string `schema:"artifacts"`
172+
Ignore bool `schema:"ignore"`
172173
}{}
173174

174175
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
@@ -191,6 +192,7 @@ func BatchRemoveArtifact(w http.ResponseWriter, r *http.Request) {
191192
removeOptions := entities.ArtifactRemoveOptions{
192193
Artifacts: query.Artifacts,
193194
All: query.All,
195+
Ignore: query.Ignore,
194196
}
195197

196198
artifacts, err := imageEngine.ArtifactRm(r.Context(), removeOptions)

pkg/bindings/artifacts/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ type RemoveOptions struct {
5252
All *bool
5353
// Artifacts is a list of Artifact IDs or names to remove
5454
Artifacts []string
55+
// Ignore errors when a specified artifact does not exist
56+
Ignore *bool
5557
}
5658

5759
// AddOptions are optional options for removing images

pkg/bindings/artifacts/types_remove_options.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/domain/entities/artifact.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ type ArtifactRemoveOptions struct {
9696
All bool
9797
// Artifacts is a list of Artifact IDs or names to remove
9898
Artifacts []string
99+
// Ignore errors when a specified artifact does not exist
100+
Ignore bool
99101
}
100102

101103
type ArtifactRemoveReport = entitiesTypes.ArtifactRemoveReport

pkg/domain/infra/abi/artifact.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ func (ir *ImageEngine) ArtifactPull(ctx context.Context, name string, opts entit
9393
func (ir *ImageEngine) ArtifactRm(ctx context.Context, opts entities.ArtifactRemoveOptions) (*entities.ArtifactRemoveReport, error) {
9494
var (
9595
namesOrDigests []string
96+
rmErrors []error
9697
)
9798
artStore, err := ir.Libpod.ArtifactStore()
9899
if err != nil {
@@ -124,14 +125,25 @@ func (ir *ImageEngine) ArtifactRm(ctx context.Context, opts entities.ArtifactRem
124125
for _, namesOrDigest := range namesOrDigests {
125126
artifactDigest, err := artStore.Remove(ctx, namesOrDigest)
126127
if err != nil {
128+
if opts.Ignore {
129+
// Collect the error but continue processing other artifacts
130+
rmErrors = append(rmErrors, err)
131+
continue
132+
}
127133
return nil, err
128134
}
129135
artifactDigests = append(artifactDigests, artifactDigest)
130136
}
131137
artifactRemoveReport := entities.ArtifactRemoveReport{
132138
ArtifactDigests: artifactDigests,
133139
}
134-
return &artifactRemoveReport, err
140+
141+
// Return collected errors if any, but only if not ignoring
142+
if len(rmErrors) > 0 && !opts.Ignore {
143+
return &artifactRemoveReport, rmErrors[0]
144+
}
145+
146+
return &artifactRemoveReport, nil
135147
}
136148

137149
func (ir *ImageEngine) ArtifactPush(ctx context.Context, name string, opts entities.ArtifactPushOptions) (*entities.ArtifactPushReport, error) {

pkg/domain/infra/tunnel/artifact.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ func (ir *ImageEngine) ArtifactRm(_ context.Context, opts entities.ArtifactRemov
5757
removeOptions := artifacts.RemoveOptions{
5858
All: &opts.All,
5959
Artifacts: opts.Artifacts,
60+
Ignore: &opts.Ignore,
6061
}
6162

6263
return artifacts.Remove(ir.ClientCtx, "", &removeOptions)

test/e2e/artifact_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,70 @@ var _ = Describe("Podman artifact", func() {
573573
failSession.WaitWithDefaultTimeout()
574574
Expect(failSession).Should(ExitWithError(125, "Error: append option is not compatible with type option"))
575575
})
576+
577+
It("podman artifact rm --ignore", func() {
578+
artifact1File, err := createArtifactFile(1024)
579+
Expect(err).ToNot(HaveOccurred())
580+
581+
artifact1Name := "localhost/test/artifact1"
582+
583+
// Add an artifact first
584+
podmanTest.PodmanExitCleanly("artifact", "add", artifact1Name, artifact1File)
585+
586+
// Remove the artifact normally
587+
podmanTest.PodmanExitCleanly("artifact", "rm", artifact1Name)
588+
589+
// Try to remove it again without --ignore (should fail)
590+
rmFailSession := podmanTest.Podman([]string{"artifact", "rm", artifact1Name})
591+
rmFailSession.WaitWithDefaultTimeout()
592+
Expect(rmFailSession).Should(ExitWithError(125, fmt.Sprintf("Error: %s: artifact does not exist", artifact1Name)))
593+
594+
// Try to remove it again with --ignore (should succeed)
595+
rmIgnoreSession := podmanTest.PodmanExitCleanly("artifact", "rm", "--ignore", artifact1Name)
596+
// Should produce no output when ignoring non-existent artifacts
597+
Expect(rmIgnoreSession.OutputToString()).To(BeEmpty())
598+
})
599+
600+
It("podman artifact rm --ignore mixed existing and non-existing", func() {
601+
artifact1File, err := createArtifactFile(1024)
602+
Expect(err).ToNot(HaveOccurred())
603+
artifact2File, err := createArtifactFile(2048)
604+
Expect(err).ToNot(HaveOccurred())
605+
606+
artifact1Name := "localhost/test/artifact1"
607+
artifact2Name := "localhost/test/artifact2"
608+
nonExistentName := "localhost/test/nonexistent"
609+
610+
// Add two artifacts
611+
add1 := podmanTest.PodmanExitCleanly("artifact", "add", artifact1Name, artifact1File)
612+
add2 := podmanTest.PodmanExitCleanly("artifact", "add", artifact2Name, artifact2File)
613+
614+
// Try to remove mix of existing and non-existing without --ignore (should fail)
615+
rmFailSession := podmanTest.Podman([]string{"artifact", "rm", artifact1Name, nonExistentName, artifact2Name})
616+
rmFailSession.WaitWithDefaultTimeout()
617+
Expect(rmFailSession).Should(ExitWithError(125, fmt.Sprintf("Error: %s: artifact does not exist", nonExistentName)))
618+
619+
// Verify artifacts still exist after failed removal
620+
podmanTest.PodmanExitCleanly("artifact", "inspect", artifact1Name)
621+
podmanTest.PodmanExitCleanly("artifact", "inspect", artifact2Name)
622+
623+
// Try to remove mix of existing and non-existing with --ignore (should succeed)
624+
rmIgnoreSession := podmanTest.PodmanExitCleanly("artifact", "rm", "--ignore", artifact1Name, nonExistentName, artifact2Name)
625+
626+
// Should show digests of successfully removed artifacts
627+
output := rmIgnoreSession.OutputToString()
628+
Expect(output).To(ContainSubstring(add1.OutputToString()))
629+
Expect(output).To(ContainSubstring(add2.OutputToString()))
630+
631+
// Verify artifacts were actually removed
632+
inspectFail1 := podmanTest.Podman([]string{"artifact", "inspect", artifact1Name})
633+
inspectFail1.WaitWithDefaultTimeout()
634+
Expect(inspectFail1).Should(ExitWithError(125, fmt.Sprintf("Error: %s: artifact does not exist", artifact1Name)))
635+
636+
inspectFail2 := podmanTest.Podman([]string{"artifact", "inspect", artifact2Name})
637+
inspectFail2.WaitWithDefaultTimeout()
638+
Expect(inspectFail2).Should(ExitWithError(125, fmt.Sprintf("Error: %s: artifact does not exist", artifact2Name)))
639+
})
576640
})
577641

578642
func digestToFilename(digest string) string {
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#!/usr/bin/env bats -*- bats -*-
2+
#
3+
# Tests for podman artifact rm --ignore functionality
4+
#
5+
6+
load helpers
7+
8+
function setup() {
9+
basic_setup
10+
}
11+
12+
function teardown() {
13+
basic_teardown
14+
}
15+
16+
# Helper function to create a test artifact file
17+
create_test_file() {
18+
local size=${1:-1024}
19+
local filename=$(mktemp --tmpdir="${PODMAN_TMPDIR}" artifactfile.XXXXXX)
20+
dd if=/dev/urandom of="$filename" bs=1 count="$size" 2>/dev/null
21+
echo "$filename"
22+
}
23+
24+
# bats test_tags=ci:parallel
25+
@test "podman artifact rm --ignore nonexistent artifact" {
26+
local artifact_name="localhost/test/nonexistent-artifact"
27+
28+
# Removing nonexistent artifact without --ignore should fail
29+
run_podman 125 artifact rm "$artifact_name"
30+
assert "$output" =~ "artifact does not exist"
31+
32+
# Removing nonexistent artifact with --ignore should succeed
33+
run_podman artifact rm --ignore "$artifact_name"
34+
is "$output" "" "No output expected when ignoring nonexistent artifact"
35+
}
36+
37+
# bats test_tags=ci:parallel
38+
@test "podman artifact rm --ignore with existing artifact" {
39+
local artifact_name="localhost/test/existing-artifact"
40+
local file1
41+
42+
file1=$(create_test_file 1024)
43+
44+
# Add artifact
45+
run_podman artifact add "$artifact_name" "$file1"
46+
local digest="$output"
47+
48+
# Remove with --ignore should work normally
49+
run_podman artifact rm --ignore "$artifact_name"
50+
assert "$output" =~ "$digest" "Should output digest of removed artifact"
51+
52+
# Verify artifact was removed
53+
run_podman 125 artifact inspect "$artifact_name"
54+
assert "$output" =~ "artifact does not exist"
55+
56+
rm -f "$file1"
57+
}
58+
59+
# bats test_tags=ci:parallel
60+
@test "podman artifact rm --ignore mixed artifacts" {
61+
local existing1="localhost/test/existing1"
62+
local existing2="localhost/test/existing2"
63+
local nonexistent="localhost/test/nonexistent"
64+
local file1 file2
65+
66+
file1=$(create_test_file 512)
67+
file2=$(create_test_file 1024)
68+
69+
# Add two artifacts
70+
run_podman artifact add "$existing1" "$file1"
71+
local digest1="$output"
72+
73+
run_podman artifact add "$existing2" "$file2"
74+
local digest2="$output"
75+
76+
# Try removing mix without --ignore should fail
77+
run_podman 125 artifact rm "$existing1" "$nonexistent" "$existing2"
78+
assert "$output" =~ "artifact does not exist"
79+
80+
# Verify existing artifacts are still there after failure
81+
run_podman artifact inspect "$existing1"
82+
run_podman artifact inspect "$existing2"
83+
84+
# Try removing mix with --ignore should succeed
85+
run_podman artifact rm --ignore "$existing1" "$nonexistent" "$existing2"
86+
87+
# Output should contain digests of removed artifacts
88+
assert "$output" =~ "$digest1" "Should contain digest of first artifact"
89+
assert "$output" =~ "$digest2" "Should contain digest of second artifact"
90+
91+
# Verify artifacts were removed
92+
run_podman 125 artifact inspect "$existing1"
93+
run_podman 125 artifact inspect "$existing2"
94+
95+
rm -f "$file1" "$file2"
96+
}
97+
98+
# bats test_tags=ci:parallel
99+
@test "podman artifact rm --ignore with --all" {
100+
local artifact1="localhost/test/artifact1"
101+
local artifact2="localhost/test/artifact2"
102+
local file1 file2
103+
104+
file1=$(create_test_file 512)
105+
file2=$(create_test_file 1024)
106+
107+
# Add artifacts
108+
run_podman artifact add "$artifact1" "$file1"
109+
run_podman artifact add "$artifact2" "$file2"
110+
111+
# Remove all with --ignore should work
112+
run_podman artifact rm --ignore --all
113+
114+
# Verify artifacts were removed
115+
run_podman 125 artifact inspect "$artifact1"
116+
run_podman 125 artifact inspect "$artifact2"
117+
118+
rm -f "$file1" "$file2"
119+
}

0 commit comments

Comments
 (0)