Skip to content

Commit 6760ebf

Browse files
committed
add push_test.go
Signed-off-by: Akihiro Suda <[email protected]>
1 parent b6cc9fc commit 6760ebf

6 files changed

Lines changed: 162 additions & 2 deletions

File tree

Vagrantfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ Vagrant.configure("2") do |config|
99
config.vm.provider :virtualbox do |v|
1010
v.memory = memory
1111
v.cpus = cpus
12+
# The default CIDR conflicts with slirp4netns CIDR (10.0.2.0/24)
13+
v.customize ["modifyvm", :id, "--natnet1", "192.168.42.0/24"]
1214
end
1315
config.vm.provider :libvirt do |v|
1416
v.memory = memory

pkg/imgutil/pull/pull.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ func Pull(ctx context.Context, client *containerd.Client, ref string, config *Co
7373
}
7474
opts = append(opts, config.RemoteOpts...)
7575

76+
// client.Pull is for single-platform (TODO: support multi)
7677
img, err := client.Pull(pctx, ref, opts...)
7778
stopProgress()
7879
if err != nil {

pkg/imgutil/push/push.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,16 @@ import (
3232
"github.com/containerd/containerd/images"
3333
"github.com/containerd/containerd/log"
3434
"github.com/containerd/containerd/pkg/progress"
35+
"github.com/containerd/containerd/platforms"
3536
"github.com/containerd/containerd/remotes"
3637
"github.com/containerd/containerd/remotes/docker"
3738
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
3839
"github.com/pkg/errors"
3940
"golang.org/x/sync/errgroup"
4041
)
4142

42-
func Push(ctx context.Context, client *containerd.Client, resolver remotes.Resolver, stdout io.Writer, localRef, remoteRef string) error {
43+
func Push(ctx context.Context, client *containerd.Client, resolver remotes.Resolver, stdout io.Writer,
44+
localRef, remoteRef string, platform platforms.MatchComparer) error {
4345
img, err := client.ImageService().Get(ctx, localRef)
4446
if err != nil {
4547
return errors.Wrap(err, "unable to resolve image to manifest")
@@ -66,6 +68,7 @@ func Push(ctx context.Context, client *containerd.Client, resolver remotes.Resol
6668
return client.Push(ctx, remoteRef, desc,
6769
containerd.WithResolver(resolver),
6870
containerd.WithImageHandler(jobHandler),
71+
containerd.WithPlatformMatcher(platform),
6972
)
7073
})
7174

pkg/testutil/testutil.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,5 @@ const (
214214
AlpineImage = "mirror.gcr.io/library/alpine:3.13"
215215
NginxAlpineImage = "mirror.gcr.io/library/nginx:1.19-alpine"
216216
NginxAlpineIndexHTMLSnippet = "<title>Welcome to nginx!</title>"
217+
RegistryImage = "mirror.gcr.io/library/registry:2"
217218
)

push.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@
1818
package main
1919

2020
import (
21+
"context"
22+
2123
"github.com/AkihiroSuda/nerdctl/pkg/imgutil"
2224
"github.com/AkihiroSuda/nerdctl/pkg/imgutil/dockerconfigresolver"
2325
"github.com/AkihiroSuda/nerdctl/pkg/imgutil/push"
26+
"github.com/containerd/containerd/images/converter"
27+
"github.com/containerd/containerd/platforms"
2428
refdocker "github.com/containerd/containerd/reference/docker"
2529
"github.com/containerd/containerd/remotes"
2630
"github.com/pkg/errors"
@@ -57,8 +61,20 @@ func pushAction(clicontext *cli.Context) error {
5761
}
5862
defer cancel()
5963

64+
// Push fails with "400 Bad Request" when the manifest is multi-platform but we do not locally have multi-platform blobs.
65+
// So we create a tmp single-platform image to avoid the error.
66+
// TODO: support pushing multi-platform
67+
singlePlatform := platforms.DefaultStrict()
68+
singlePlatformRef := ref + "-tmp-single"
69+
singlePlatformImg, err := converter.Convert(ctx, client, singlePlatformRef, ref,
70+
converter.WithPlatform(singlePlatform))
71+
if err != nil {
72+
return errors.Wrapf(err, "failed to create a tmp single-platform image %q", singlePlatformRef)
73+
}
74+
defer client.ImageService().Delete(context.TODO(), singlePlatformImg.Name)
75+
6076
pushFunc := func(r remotes.Resolver) error {
61-
return push.Push(ctx, client, r, clicontext.App.Writer, ref, ref)
77+
return push.Push(ctx, client, r, clicontext.App.Writer, singlePlatformRef, ref, singlePlatform)
6278
}
6379

6480
var dOpts []dockerconfigresolver.Opt

push_test.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
Copyright (C) nerdctl authors.
3+
Copyright (C) containerd authors.
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package main
19+
20+
import (
21+
"fmt"
22+
"net"
23+
"strings"
24+
"testing"
25+
26+
"github.com/AkihiroSuda/nerdctl/pkg/testutil"
27+
"github.com/containerd/containerd/errdefs"
28+
"github.com/pkg/errors"
29+
"gotest.tools/v3/assert"
30+
)
31+
32+
func getNonLoopbackIPv4() (net.IP, error) {
33+
addrs, err := net.InterfaceAddrs()
34+
if err != nil {
35+
return nil, err
36+
}
37+
for _, addr := range addrs {
38+
ip, _, err := net.ParseCIDR(addr.String())
39+
if err != nil {
40+
continue
41+
}
42+
ipv4 := ip.To4()
43+
if ipv4 == nil {
44+
continue
45+
}
46+
if ipv4.IsLoopback() {
47+
continue
48+
}
49+
return ipv4, nil
50+
}
51+
return nil, errors.Wrapf(errdefs.ErrNotFound, "non-loopback IPv4 address not found, attempted=%+v", addrs)
52+
}
53+
54+
type testRegistry struct {
55+
ip net.IP
56+
listenIP net.IP
57+
listenPort int
58+
cleanup func()
59+
}
60+
61+
func newTestRegistry(base *testutil.Base, name string) *testRegistry {
62+
hostIP, err := getNonLoopbackIPv4()
63+
assert.NilError(base.T, err)
64+
// listen on 0.0.0.0 to enable 127.0.0.1
65+
listenIP := net.ParseIP("0.0.0.0")
66+
const listenPort = 5000 // TODO: choose random empty port
67+
base.T.Logf("hostIP=%q, listenIP=%q, listenPort=%d", hostIP, listenIP, listenPort)
68+
69+
registryContainerName := "reg-" + name
70+
cmd := base.Cmd("run",
71+
"-d",
72+
"-p", fmt.Sprintf("%s:%d:5000", listenIP, listenPort),
73+
"--name", registryContainerName,
74+
testutil.RegistryImage)
75+
cmd.AssertOK()
76+
if _, err = httpGet(fmt.Sprintf("http://%s:%d/v2", hostIP.String(), listenPort), 30); err != nil {
77+
base.Cmd("rm", "-f", registryContainerName).Run()
78+
base.T.Fatal(err)
79+
}
80+
return &testRegistry{
81+
ip: hostIP,
82+
listenIP: listenIP,
83+
listenPort: listenPort,
84+
cleanup: func() { base.Cmd("rm", "-f", registryContainerName).Run() },
85+
}
86+
}
87+
88+
func TestPushPlainHTTPFails(t *testing.T) {
89+
base := testutil.NewBase(t)
90+
reg := newTestRegistry(base, "test-push-plain-http-fails")
91+
defer reg.cleanup()
92+
93+
base.Cmd("pull", testutil.AlpineImage).AssertOK()
94+
testImageRef := fmt.Sprintf("%s:%d/test-push-plain-http-fails:%s",
95+
reg.ip.String(), reg.listenPort, strings.Split(testutil.AlpineImage, ":")[1])
96+
t.Logf("testImageRef=%q", testImageRef)
97+
base.Cmd("tag", testutil.AlpineImage, testImageRef).AssertOK()
98+
99+
res := base.Cmd("push", testImageRef).Run()
100+
resCombined := res.Combined()
101+
t.Logf("result: exitCode=%d, out=%q", res.ExitCode, res.Combined())
102+
assert.Assert(t, res.ExitCode != 0)
103+
assert.Assert(t, strings.Contains(resCombined, "server gave HTTP response to HTTPS client"))
104+
}
105+
106+
func TestPushPlainHTTPLocalhost(t *testing.T) {
107+
base := testutil.NewBase(t)
108+
reg := newTestRegistry(base, "test-push-plain-localhost")
109+
defer reg.cleanup()
110+
localhostIP := "127.0.0.1"
111+
t.Logf("localhost IP=%q", localhostIP)
112+
113+
base.Cmd("pull", testutil.AlpineImage).AssertOK()
114+
testImageRef := fmt.Sprintf("%s:%d/test-push-plain-http-insecure:%s",
115+
localhostIP, reg.listenPort, strings.Split(testutil.AlpineImage, ":")[1])
116+
t.Logf("testImageRef=%q", testImageRef)
117+
base.Cmd("tag", testutil.AlpineImage, testImageRef).AssertOK()
118+
119+
base.Cmd("push", testImageRef).AssertOK()
120+
}
121+
122+
func TestPushPlainHTTPInsecure(t *testing.T) {
123+
// Skip docker, because "dockerd --insecure-registries" requires restarting the daemon
124+
testutil.DockerIncompatible(t)
125+
126+
base := testutil.NewBase(t)
127+
reg := newTestRegistry(base, "test-push-plain-http-insecure")
128+
defer reg.cleanup()
129+
130+
base.Cmd("pull", testutil.AlpineImage).AssertOK()
131+
testImageRef := fmt.Sprintf("%s:%d/test-push-plain-http-insecure:%s",
132+
reg.ip.String(), reg.listenPort, strings.Split(testutil.AlpineImage, ":")[1])
133+
t.Logf("testImageRef=%q", testImageRef)
134+
base.Cmd("tag", testutil.AlpineImage, testImageRef).AssertOK()
135+
136+
base.Cmd("--insecure-registry", "push", testImageRef).AssertOK()
137+
}

0 commit comments

Comments
 (0)