Skip to content

Commit 80f993f

Browse files
committed
e2e: add private registry pull/push regression test
Add a privateregistry service (htpasswd auth, port 5001) to the e2e compose stack and a TestPullPushPrivateRepository test that verifies: - unauthenticated push/pull is rejected with an auth error - authenticated push/pull succeeds The volume path in compose-env.yaml is resolved relative to the compose file directory (e2e/), so use ./testdata/registry/auth, not ./e2e/testdata/registry/auth. Regression test for #5963. Closes #5965. Signed-off-by: aryansharma9917 <sharmaaryan9837@gmail.com> Signed-off-by: Lohit Kolluri <lohitkolluri@gmail.com>
1 parent 9f16882 commit 80f993f

5 files changed

Lines changed: 130 additions & 1 deletion

File tree

e2e/compose-env.yaml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,18 @@ services:
33
registry:
44
image: 'registry:3'
55

6+
privateregistry:
7+
build:
8+
context: ./testdata/registry
9+
environment:
10+
- REGISTRY_HTTP_ADDR=0.0.0.0:5001
11+
- REGISTRY_AUTH=htpasswd
12+
- REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm
13+
- REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd
14+
615
engine:
716
image: 'docker:${ENGINE_VERSION:-29}-dind'
817
privileged: true
9-
command: ['--insecure-registry=registry:5000', '--experimental']
18+
command: ['--insecure-registry=registry:5000', '--insecure-registry=privateregistry:5001', '--experimental']
1019
environment:
1120
- DOCKER_TLS_CERTDIR=

e2e/image/private_test.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package image
2+
3+
import (
4+
"strings"
5+
"testing"
6+
"time"
7+
8+
"github.com/docker/cli/e2e/internal/fixtures"
9+
"gotest.tools/v3/assert"
10+
"gotest.tools/v3/icmd"
11+
)
12+
13+
const privateRegistryPrefix = "privateregistry:5001"
14+
15+
// Regression test for https://github.com/docker/cli/issues/5963
16+
func TestPullPushPrivateRepository(t *testing.T) {
17+
t.Parallel()
18+
19+
dir := fixtures.SetupConfigFile(t)
20+
t.Cleanup(dir.Remove)
21+
emptyConfigDir := t.TempDir()
22+
23+
sourceImage := fixtures.AlpineImage
24+
privateImage := privateRegistryPrefix + "/private/alpine:test-private-pull-push"
25+
26+
runWithPrivateRegistryRetry(t,
27+
icmd.Command("docker", "pull", sourceImage),
28+
).Assert(t, icmd.Success)
29+
t.Cleanup(func() {
30+
icmd.RunCommand("docker", "image", "rm", "-f", privateImage).Assert(t, icmd.Success)
31+
})
32+
33+
icmd.RunCommand("docker", "tag", sourceImage, privateImage).Assert(t, icmd.Success)
34+
35+
pushNoAuth := runWithPrivateRegistryRetry(t,
36+
icmd.Command("docker", "push", privateImage),
37+
fixtures.WithConfig(emptyConfigDir),
38+
)
39+
pushNoAuth.Assert(t, icmd.Expected{ExitCode: 1})
40+
assertAuthDenied(t, pushNoAuth)
41+
42+
pushWithAuth := runWithPrivateRegistryRetry(t,
43+
icmd.Command("docker", "push", privateImage),
44+
fixtures.WithConfig(dir.Path()),
45+
)
46+
pushWithAuth.Assert(t, icmd.Success)
47+
// Docker omits the tag in the "push refers to repository" line; strip it before asserting.
48+
privateRepo := privateImage[:strings.LastIndex(privateImage, ":")]
49+
assert.Check(t, strings.Contains(pushWithAuth.Combined(), "The push refers to repository ["+privateRepo+"]"), pushWithAuth.Combined())
50+
51+
icmd.RunCommand("docker", "image", "rm", "-f", privateImage).Assert(t, icmd.Success)
52+
53+
pullNoAuth := runWithPrivateRegistryRetry(t,
54+
icmd.Command("docker", "pull", privateImage),
55+
fixtures.WithConfig(emptyConfigDir),
56+
)
57+
pullNoAuth.Assert(t, icmd.Expected{ExitCode: 1})
58+
assertAuthDenied(t, pullNoAuth)
59+
60+
pullWithAuth := runWithPrivateRegistryRetry(t,
61+
icmd.Command("docker", "pull", privateImage),
62+
fixtures.WithConfig(dir.Path()),
63+
)
64+
pullWithAuth.Assert(t, icmd.Success)
65+
assert.Check(t, strings.Contains(pullWithAuth.Combined(), privateImage), pullWithAuth.Combined())
66+
}
67+
68+
func assertAuthDenied(t *testing.T, result *icmd.Result) {
69+
t.Helper()
70+
output := result.Combined()
71+
if isPrivateRegistryTransient(output) {
72+
t.Fatalf("private registry unavailable while expecting auth failure: %s", output)
73+
}
74+
75+
assert.Assert(t,
76+
strings.Contains(output, "requested access to the resource is denied") ||
77+
strings.Contains(output, "no basic auth credentials") ||
78+
strings.Contains(output, "unauthorized") ||
79+
strings.Contains(output, "authentication required"),
80+
output,
81+
)
82+
}
83+
84+
func runWithPrivateRegistryRetry(t *testing.T, cmd icmd.Cmd, opts ...icmd.CmdOp) *icmd.Result {
85+
t.Helper()
86+
87+
deadline := time.Now().Add(90 * time.Second)
88+
for {
89+
result := icmd.RunCmd(cmd, opts...)
90+
output := result.Combined()
91+
if isPrivateRegistryTransient(output) {
92+
if time.Now().Before(deadline) {
93+
t.Logf("waiting for private registry availability: %s", output)
94+
time.Sleep(500 * time.Millisecond)
95+
continue
96+
}
97+
}
98+
return result
99+
}
100+
}
101+
102+
func isPrivateRegistryTransient(output string) bool {
103+
return strings.Contains(output, "lookup privateregistry") ||
104+
strings.Contains(output, "lookup registry") ||
105+
strings.Contains(output, "no such host") ||
106+
strings.Contains(output, "server misbehaving") ||
107+
strings.Contains(output, "Temporary failure in name resolution") ||
108+
strings.Contains(output, "connection refused") ||
109+
strings.Contains(output, "i/o timeout") ||
110+
strings.Contains(output, "TLS handshake timeout") ||
111+
strings.Contains(output, "context deadline exceeded") ||
112+
strings.Contains(output, "connection reset by peer") ||
113+
strings.Contains(output, "unexpected EOF")
114+
}

e2e/internal/fixtures/fixtures.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ func SetupConfigFile(t *testing.T) fs.Dir {
2323
"auths": {
2424
"registry:5000": {
2525
"auth": "ZWlhaXM6cGFzc3dvcmQK"
26+
},
27+
"privateregistry:5001": {
28+
"auth": "ZTJlOnBhc3N3b3Jk"
2629
}
2730
}}`), fs.WithDir("trust", fs.WithDir("private")))
2831
return *dir

e2e/testdata/registry/Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FROM registry:3
2+
COPY auth /auth
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
e2e:$2y$05$DxRBsGSy61vZsBgNVxwUh.UtZmlg3wZHMxYcHYAlupY7r1xbIiuoq

0 commit comments

Comments
 (0)