diff --git a/e2e/compose-env.yaml b/e2e/compose-env.yaml index 651d5d145aee..78f7a8d42c97 100644 --- a/e2e/compose-env.yaml +++ b/e2e/compose-env.yaml @@ -3,9 +3,19 @@ services: registry: image: 'registry:3' + private-registry: + image: 'registry:3' + environment: + - REGISTRY_HTTP_ADDR=0.0.0.0:5001 + - REGISTRY_AUTH=htpasswd + - REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm + - REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd + volumes: + - ./e2e/testdata/registry/auth:/auth:ro + engine: image: 'docker:${ENGINE_VERSION:-29}-dind' privileged: true - command: ['--insecure-registry=registry:5000', '--experimental'] + command: ['--insecure-registry=registry:5000', '--insecure-registry=private-registry:5001', '--experimental'] environment: - DOCKER_TLS_CERTDIR= diff --git a/e2e/image/private_test.go b/e2e/image/private_test.go new file mode 100644 index 000000000000..c4c3df84afe9 --- /dev/null +++ b/e2e/image/private_test.go @@ -0,0 +1,95 @@ +package image + +import ( + "strings" + "testing" + "time" + + "github.com/docker/cli/e2e/internal/fixtures" + "gotest.tools/v3/assert" + "gotest.tools/v3/icmd" +) + +const privateRegistryPrefix = "private-registry:5001" + +// Regression test for https://github.com/docker/cli/issues/5963 +func TestPullPushPrivateRepository(t *testing.T) { + t.Parallel() + + dir := fixtures.SetupConfigFile(t) + t.Cleanup(dir.Remove) + emptyConfigDir := t.TempDir() + + sourceImage := fixtures.AlpineImage + privateImage := privateRegistryPrefix + "/private/alpine:test-private-pull-push" + + icmd.RunCommand("docker", "pull", sourceImage).Assert(t, icmd.Success) + t.Cleanup(func() { + icmd.RunCommand("docker", "image", "rm", "-f", privateImage).Assert(t, icmd.Success) + }) + + icmd.RunCommand("docker", "tag", sourceImage, privateImage).Assert(t, icmd.Success) + + pushNoAuth := runWithPrivateRegistryRetry(t, + icmd.Command("docker", "push", privateImage), + fixtures.WithConfig(emptyConfigDir), + ) + pushNoAuth.Assert(t, icmd.Expected{ExitCode: 1}) + assertAuthDenied(t, pushNoAuth) + + pushWithAuth := runWithPrivateRegistryRetry(t, + icmd.Command("docker", "push", privateImage), + fixtures.WithConfig(dir.Path()), + ) + pushWithAuth.Assert(t, icmd.Success) + assert.Check(t, strings.Contains(pushWithAuth.Combined(), "The push refers to repository ["+privateImage+"]"), pushWithAuth.Combined()) + + icmd.RunCommand("docker", "image", "rm", "-f", privateImage).Assert(t, icmd.Success) + + pullNoAuth := runWithPrivateRegistryRetry(t, + icmd.Command("docker", "pull", privateImage), + fixtures.WithConfig(emptyConfigDir), + ) + pullNoAuth.Assert(t, icmd.Expected{ExitCode: 1}) + assertAuthDenied(t, pullNoAuth) + + pullWithAuth := runWithPrivateRegistryRetry(t, + icmd.Command("docker", "pull", privateImage), + fixtures.WithConfig(dir.Path()), + ) + pullWithAuth.Assert(t, icmd.Success) + assert.Check(t, strings.Contains(pullWithAuth.Combined(), privateImage), pullWithAuth.Combined()) +} + +func assertAuthDenied(t *testing.T, result *icmd.Result) { + t.Helper() + output := result.Combined() + + assert.Check(t, + strings.Contains(output, "requested access to the resource is denied") || + strings.Contains(output, "no basic auth credentials") || + strings.Contains(output, "unauthorized") || + strings.Contains(output, "authentication required"), + output, + ) +} + +func runWithPrivateRegistryRetry(t *testing.T, cmd *icmd.Cmd, opts ...func(*icmd.Cmd)) *icmd.Result { + t.Helper() + + deadline := time.Now().Add(30 * time.Second) + for { + result := icmd.RunCmd(cmd, opts...) + output := result.Combined() + if strings.Contains(output, "lookup private-registry") || + strings.Contains(output, "no such host") || + strings.Contains(output, "server misbehaving") { + if time.Now().Before(deadline) { + t.Logf("waiting for private registry DNS to become available: %s", output) + time.Sleep(500 * time.Millisecond) + continue + } + } + return result + } +} diff --git a/e2e/internal/fixtures/fixtures.go b/e2e/internal/fixtures/fixtures.go index 256e14f17612..2978e2dc6684 100644 --- a/e2e/internal/fixtures/fixtures.go +++ b/e2e/internal/fixtures/fixtures.go @@ -23,6 +23,9 @@ func SetupConfigFile(t *testing.T) fs.Dir { "auths": { "registry:5000": { "auth": "ZWlhaXM6cGFzc3dvcmQK" + }, + "private-registry:5001": { + "auth": "ZTJlOnBhc3N3b3Jk" } }}`), fs.WithDir("trust", fs.WithDir("private"))) return *dir diff --git a/e2e/testdata/registry/auth/htpasswd b/e2e/testdata/registry/auth/htpasswd new file mode 100644 index 000000000000..1715b8934ce7 --- /dev/null +++ b/e2e/testdata/registry/auth/htpasswd @@ -0,0 +1 @@ +e2e:$2y$05$UozlY7.SA2NMcojF.qocv.W9Q4rsr75uLMW.mVEsAPx90BVeMgveC