Skip to content

Commit e4b16ed

Browse files
committed
fixed lint errors + added integration test
1 parent c180e0a commit e4b16ed

File tree

4 files changed

+133
-11
lines changed

4 files changed

+133
-11
lines changed

cmd/nerdctl/image/image_push.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ func pushOptions(cmd *cobra.Command) (types.ImagePushOptions, error) {
130130
IpfsEnsureImage: ipfsEnsureImage,
131131
IpfsAddress: ipfsAddress,
132132
Quiet: quiet,
133-
AllTags: allTags,
133+
AllTags: allTags,
134134
AllowNondistributableArtifacts: allowNonDist,
135135
Stdout: cmd.OutOrStdout(),
136136
}, nil

cmd/nerdctl/image/image_push_linux_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package image
1818

1919
import (
20+
"encoding/json"
2021
"errors"
2122
"fmt"
2223
"net/http"
@@ -33,6 +34,11 @@ import (
3334
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry"
3435
)
3536

37+
type registryTagList struct {
38+
Name string `json:"name"`
39+
Tags []string `json:"tags"`
40+
}
41+
3642
func TestPush(t *testing.T) {
3743
nerdtest.Setup()
3844

@@ -145,6 +151,79 @@ func TestPush(t *testing.T) {
145151
},
146152
Expected: test.Expects(0, nil, nil),
147153
},
154+
{
155+
Description: "all-tags pushes all tags for a repository",
156+
Require: require.Not(nerdtest.Docker),
157+
Setup: func(data test.Data, helpers test.Helpers) {
158+
helpers.Ensure("pull", "--quiet", testutil.CommonImage)
159+
160+
repo := fmt.Sprintf("%s:%d/%s",
161+
registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier())
162+
data.Labels().Set("testImageRepo", repo)
163+
164+
tag1 := repo + ":v1"
165+
tag2 := repo + ":v2"
166+
data.Labels().Set("testImageRefV1", tag1)
167+
data.Labels().Set("testImageRefV2", tag2)
168+
169+
helpers.Ensure("tag", testutil.CommonImage, tag1)
170+
helpers.Ensure("tag", testutil.CommonImage, tag2)
171+
},
172+
Cleanup: func(data test.Data, helpers test.Helpers) {
173+
if v := data.Labels().Get("testImageRefV1"); v != "" {
174+
helpers.Anyhow("rmi", "-f", v)
175+
}
176+
if v := data.Labels().Get("testImageRefV2"); v != "" {
177+
helpers.Anyhow("rmi", "-f", v)
178+
}
179+
},
180+
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
181+
return helpers.Command(
182+
"push",
183+
"--insecure-registry",
184+
"--all-tags",
185+
data.Labels().Get("testImageRepo"),
186+
)
187+
},
188+
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
189+
return &test.Expected{
190+
ExitCode: 0,
191+
Output: func(stdout string, t tig.T) {
192+
tagsURL := fmt.Sprintf("http://%s:%d/v2/%s/tags/list",
193+
registryNoAuthHTTPRandom.IP.String(),
194+
registryNoAuthHTTPRandom.Port,
195+
data.Identifier(),
196+
)
197+
resp, err := http.Get(tagsURL)
198+
assert.NilError(t, err, "error making HTTP request for tag list")
199+
defer func() {
200+
if resp.Body != nil {
201+
_ = resp.Body.Close()
202+
}
203+
}()
204+
205+
assert.Equal(t, resp.StatusCode, http.StatusOK, "expected tag list endpoint to be available")
206+
207+
var tl registryTagList
208+
err = json.NewDecoder(resp.Body).Decode(&tl)
209+
assert.NilError(t, err, "failed to decode tag list JSON")
210+
211+
foundV1 := false
212+
foundV2 := false
213+
for _, tag := range tl.Tags {
214+
if tag == "v1" {
215+
foundV1 = true
216+
}
217+
if tag == "v2" {
218+
foundV2 = true
219+
}
220+
}
221+
assert.Assert(t, foundV1, "expected tag v1 to be pushed")
222+
assert.Assert(t, foundV2, "expected tag v2 to be pushed")
223+
},
224+
}
225+
},
226+
},
148227
{
149228
Description: "with insecure, with login",
150229
Require: require.Not(nerdtest.Docker),
@@ -278,6 +357,48 @@ func TestPush(t *testing.T) {
278357
},
279358
Expected: test.Expects(0, nil, nil),
280359
},
360+
{
361+
Description: "soci with all-tags pushes multiple tags without duplicate index failure",
362+
Require: require.All(
363+
nerdtest.Soci,
364+
require.Not(nerdtest.Docker),
365+
),
366+
Setup: func(data test.Data, helpers test.Helpers) {
367+
helpers.Ensure("pull", "--quiet", testutil.UbuntuImage)
368+
369+
repo := fmt.Sprintf("%s:%d/%s",
370+
registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier())
371+
data.Labels().Set("testImageRepo", repo)
372+
373+
tag1 := repo + ":image_tag"
374+
tag2 := repo + ":latest"
375+
data.Labels().Set("testImageRef1", tag1)
376+
data.Labels().Set("testImageRef2", tag2)
377+
378+
helpers.Ensure("tag", testutil.UbuntuImage, tag1)
379+
helpers.Ensure("tag", testutil.UbuntuImage, tag2)
380+
},
381+
Cleanup: func(data test.Data, helpers test.Helpers) {
382+
if v := data.Labels().Get("testImageRef1"); v != "" {
383+
helpers.Anyhow("rmi", "-f", v)
384+
}
385+
if v := data.Labels().Get("testImageRef2"); v != "" {
386+
helpers.Anyhow("rmi", "-f", v)
387+
}
388+
},
389+
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
390+
return helpers.Command(
391+
"push",
392+
"--snapshotter=soci",
393+
"--insecure-registry",
394+
"--all-tags",
395+
"--soci-span-size=2097152",
396+
"--soci-min-layer-size=0",
397+
data.Labels().Get("testImageRepo"),
398+
)
399+
},
400+
Expected: test.Expects(0, nil, nil),
401+
},
281402
},
282403
}
283404
testCase.Run(t)

pkg/api/types/image_types.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,8 @@ type ImageInspectOptions struct {
183183

184184
// ImagePushOptions specifies options for `nerdctl (image) push`.
185185
type ImagePushOptions struct {
186-
Stdout io.Writer
187-
Stderr io.Writer
186+
Stdout io.Writer
187+
Stderr io.Writer
188188
// ProgressOutputToStdout directs progress output to stdout instead of stderr
189189
ProgressOutputToStdout bool
190190

@@ -208,10 +208,10 @@ type ImagePushOptions struct {
208208
AllowNondistributableArtifacts bool
209209

210210
// AllTags if true, push all local tags for the repository when no tag is specified
211-
AllTags bool
211+
AllTags bool
212212

213-
// SkipSoci when true, skip creating/pushing SOCI index (used when pushing multiple tags to avoid overwriting)
214-
SkipSoci bool
213+
// SkipSoci when true, skip creating/pushing SOCI index (used when pushing multiple tags to avoid overwriting)
214+
SkipSoci bool
215215
}
216216

217217
// RemoteSnapshotterFlags are used for pulling with remote snapshotters

pkg/cmd/image/push.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ func Push(ctx context.Context, client *containerd.Client, rawRef string, options
6363
}
6464

6565
if parsedReference.Protocol != "" {
66-
if options.AllTags {
67-
return fmt.Errorf("--all-tags is not supported for %q references", parsedReference.Protocol)
68-
}
66+
if options.AllTags {
67+
return fmt.Errorf("--all-tags is not supported for %q references", parsedReference.Protocol)
68+
}
6969

7070
if parsedReference.Protocol != referenceutil.IPFSProtocol {
7171
return fmt.Errorf("ipfs scheme is only supported but got %q", parsedReference.Protocol)
@@ -110,6 +110,7 @@ func Push(ctx context.Context, client *containerd.Client, rawRef string, options
110110
return nil
111111
}
112112

113+
// Handle --all-tags
113114
if options.AllTags {
114115
repo := ""
115116
if parsedReference.Domain != "" {
@@ -135,8 +136,8 @@ func Push(ctx context.Context, client *containerd.Client, rawRef string, options
135136

136137
for i, tagRef := range tagRefs {
137138
tagOpts := options
138-
tagOpts.AllTags = false // avoid infinite recursion
139-
tagOpts.SkipSoci = i > 0 // avoid SOCI indexing for the same image
139+
tagOpts.AllTags = false // avoid infinite recursion
140+
tagOpts.SkipSoci = i > 0 // avoid SOCI indexing for the same image
140141

141142
if err := Push(ctx, client, tagRef, tagOpts); err != nil {
142143
return err

0 commit comments

Comments
 (0)