diff --git a/.dockerignore b/.dockerignore index b1d380e..9d447df 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,6 +6,7 @@ !internal/ !scripts/ !go.* +!test/e2e/ssh/test_key.pub # Ignore unnecessary files inside allowed directories. **/.DS_Store diff --git a/Dockerfile b/Dockerfile index 62544bb..41d70d7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,4 +24,3 @@ EXPOSE 5000 # Run as root user by default to allow access to the containerd socket. This in unfortunate as running as non-root user # requires changing the containerd socket permissions which still can be manually done by advanced users. ENTRYPOINT ["unregistry"] - diff --git a/Dockerfile.test b/Dockerfile.test index 7414900..7f56788 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -21,7 +21,10 @@ FROM docker:28.3.3-dind AS unregistry-dind ENV UNREGISTRY_CONTAINERD_SOCK="/run/docker/containerd/containerd.sock" +RUN apk add --no-cache openssh && ssh-keygen -A && mkdir /root/.ssh + COPY scripts/dind-entrypoint.sh /usr/local/bin/entrypoint.sh +COPY test/e2e/ssh/test_key.pub /root/.ssh/authorized_keys COPY --from=builder /build/unregistry /usr/local/bin/ EXPOSE 5000 diff --git a/scripts/dind-entrypoint.sh b/scripts/dind-entrypoint.sh index c7e409f..beba1f9 100755 --- a/scripts/dind-entrypoint.sh +++ b/scripts/dind-entrypoint.sh @@ -15,6 +15,11 @@ cleanup() { kill "$(cat /run/docker.pid)" 2>/dev/null || true fi + # Terminate SSH daemon if PID file exists. + if [ -f /run/sshd.pid ]; then + kill "$(cat /run/sshd.pid)" 2>/dev/null || true + fi + # Wait for processes to terminate. wait } @@ -28,7 +33,8 @@ else echo "Using the default Docker image store." fi -dind dockerd --host=tcp://0.0.0.0:2375 --tls=false & +dind dockerd --host unix:///run/docker.sock --host=tcp://0.0.0.0:2375 --tls=false & +/usr/sbin/sshd -o AllowTcpForwarding=yes # Execute the passed command and wait for it while maintaining signal handling. "$@" & diff --git a/test/e2e/registry_test.go b/test/e2e/registry_test.go index 04845f5..ca8046a 100644 --- a/test/e2e/registry_test.go +++ b/test/e2e/registry_test.go @@ -11,9 +11,7 @@ import ( "slices" "strings" "testing" - "time" - "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/pkg/jsonmessage" ocispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -26,83 +24,20 @@ import ( "github.com/docker/docker/api/types/image" "github.com/docker/docker/client" - "github.com/testcontainers/testcontainers-go" - "github.com/testcontainers/testcontainers-go/wait" ) func TestRegistryPushPull(t *testing.T) { ctx := context.Background() - - // Start unregistry in a Docker-in-Docker container with Docker using containerd image store. - req := testcontainers.GenericContainerRequest{ - ContainerRequest: testcontainers.ContainerRequest{ - FromDockerfile: testcontainers.FromDockerfile{ - Context: filepath.Join("..", ".."), - Dockerfile: "Dockerfile.test", - BuildOptionsModifier: func(buildOptions *types.ImageBuildOptions) { - buildOptions.Target = "unregistry-dind" - }, - }, - Env: map[string]string{ - "UNREGISTRY_LOG_LEVEL": "debug", - }, - Privileged: true, - // Explicitly specify the host port for the registry because if not specified, 'docker push' from Docker - // Desktop is unable to reach the automatically mapped one for some reason. - ExposedPorts: []string{"2375", "50000:5000"}, - WaitingFor: wait.ForAll( - wait.ForListeningPort("2375"), - wait.ForListeningPort("5000"), - ).WithStartupTimeoutDefault(15 * time.Second), - }, - Started: true, - } - unregistryContainer, err := testcontainers.GenericContainer(ctx, req) - require.NoError(t, err) - - t.Cleanup(func() { - // Print last 20 lines of unregistry container logs. - logs, err := unregistryContainer.Logs(ctx) - assert.NoError(t, err, "Failed to get logs from unregistry container.") - if err == nil { - defer logs.Close() - logsContent, err := io.ReadAll(logs) - assert.NoError(t, err, "Failed to read logs from unregistry container.") - if err == nil { - - lines := strings.Split(string(logsContent), "\n") - start := len(lines) - 20 - if start < 0 { - start = 0 - } - - t.Log("=== Last 20 lines of unregistry container logs ===") - for i := start; i < len(lines); i++ { - if lines[i] != "" { - t.Log(lines[i]) - } - } - t.Log("=== End of unregistry container logs ===") - } - } - - // Ensure the container is terminated after the test. - assert.NoError(t, unregistryContainer.Terminate(ctx)) - }) - - mappedDockerPort, err := unregistryContainer.MappedPort(ctx, "2375") - require.NoError(t, err) - mappedRegistryPort, err := unregistryContainer.MappedPort(ctx, "5000") - require.NoError(t, err) + mappedDockerPort, mappedRegistryPort := runUnregistryDinD(t, true) remoteCli, err := client.NewClientWithOpts( - client.WithHost("tcp://localhost:"+mappedDockerPort.Port()), + client.WithHost("tcp://localhost:"+mappedDockerPort), client.WithAPIVersionNegotiation(), ) require.NoError(t, err) defer remoteCli.Close() - registryAddr := "localhost:" + mappedRegistryPort.Port() + registryAddr := "localhost:" + mappedRegistryPort t.Logf("Unregistry started at %s", registryAddr) localCli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) diff --git a/test/e2e/ssh/test_key b/test/e2e/ssh/test_key new file mode 100644 index 0000000..c6c5114 --- /dev/null +++ b/test/e2e/ssh/test_key @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACBS9CsZhkOgawNzvNPRSIOJiKhz9xEqUkPGeMGXZV2UmQAAAJBT8W4dU/Fu +HQAAAAtzc2gtZWQyNTUxOQAAACBS9CsZhkOgawNzvNPRSIOJiKhz9xEqUkPGeMGXZV2UmQ +AAAECiXqC92PMhl7Xe1TtjWTVtd9r+PORzw4iGLmdzmbwE9FL0KxmGQ6BrA3O809FIg4mI +qHP3ESpSQ8Z4wZdlXZSZAAAACGUyZUB0ZXN0AQIDBAU= +-----END OPENSSH PRIVATE KEY----- diff --git a/test/e2e/ssh/test_key.pub b/test/e2e/ssh/test_key.pub new file mode 100644 index 0000000..ab59777 --- /dev/null +++ b/test/e2e/ssh/test_key.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFL0KxmGQ6BrA3O809FIg4mIqHP3ESpSQ8Z4wZdlXZSZ e2e@test diff --git a/test/e2e/unregistry.go b/test/e2e/unregistry.go new file mode 100644 index 0000000..62b5c12 --- /dev/null +++ b/test/e2e/unregistry.go @@ -0,0 +1,87 @@ +package e2e + +import ( + "context" + "fmt" + "io" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/docker/docker/api/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +// runUnregistryDinD starts unregistry in a Docker-in-Docker container. It returns the mapped Docker +// port and the mapped unregistry port. The containerdStore parameter specifies whether to use containerd image store. +func runUnregistryDinD(t *testing.T, containerdStore bool) (string, string) { + ctx := context.Background() + // Start unregistry in a Docker-in-Docker container with Docker using containerd image store. + req := testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + FromDockerfile: testcontainers.FromDockerfile{ + Context: filepath.Join("..", ".."), + Dockerfile: "Dockerfile.test", + BuildOptionsModifier: func(buildOptions *types.ImageBuildOptions) { + buildOptions.Target = "unregistry-dind" + }, + }, + Env: map[string]string{ + "DOCKER_CONTAINERD_STORE": fmt.Sprintf("%t", containerdStore), + "UNREGISTRY_LOG_LEVEL": "debug", + }, + Privileged: true, + // Explicitly specify the host port for the registry because if not specified, 'docker push' from Docker + // Desktop is unable to reach the automatically mapped one for some reason. + ExposedPorts: []string{"2375", "50000:5000"}, + WaitingFor: wait.ForAll( + wait.ForListeningPort("2375"), + wait.ForListeningPort("5000"), + ).WithStartupTimeoutDefault(15 * time.Second), + }, + Started: true, + } + ctr, err := testcontainers.GenericContainer(ctx, req) + require.NoError(t, err) + + t.Cleanup(func() { + // Print last 20 lines of unregistry container logs. + logs, err := ctr.Logs(ctx) + assert.NoError(t, err, "Failed to get logs from unregistry container.") + if err == nil { + defer logs.Close() + logsContent, err := io.ReadAll(logs) + assert.NoError(t, err, "Failed to read logs from unregistry container.") + if err == nil { + + lines := strings.Split(string(logsContent), "\n") + start := len(lines) - 20 + if start < 0 { + start = 0 + } + + t.Log("=== Last 20 lines of unregistry container logs ===") + for i := start; i < len(lines); i++ { + if lines[i] != "" { + t.Log(lines[i]) + } + } + t.Log("=== End of unregistry container logs ===") + } + } + + // Ensure the container is terminated after the test. + assert.NoError(t, ctr.Terminate(ctx)) + }) + + mappedDockerPort, err := ctr.MappedPort(ctx, "2375") + require.NoError(t, err) + mappedRegistryPort, err := ctr.MappedPort(ctx, "5000") + require.NoError(t, err) + + return mappedDockerPort.Port(), mappedRegistryPort.Port() +}