Skip to content

Commit febb222

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 Fix private-registry flakiness by moving the registry debug listener off port 5001 (to avoid conflicting listeners) and fail fast during e2e setup if supporting services are not running. 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 febb222

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)