chore: run SSH daemon in test dind container to create e2e test for docker-pussh

This commit is contained in:
Pasha Sviderski
2025-09-02 12:09:54 +10:00
parent e5d47430fc
commit f98b390a9a
8 changed files with 109 additions and 70 deletions

View File

@@ -6,6 +6,7 @@
!internal/
!scripts/
!go.*
!test/e2e/ssh/test_key.pub
# Ignore unnecessary files inside allowed directories.
**/.DS_Store

View File

@@ -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"]

View File

@@ -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

View File

@@ -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.
"$@" &

View File

@@ -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())

7
test/e2e/ssh/test_key Normal file
View File

@@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBS9CsZhkOgawNzvNPRSIOJiKhz9xEqUkPGeMGXZV2UmQAAAJBT8W4dU/Fu
HQAAAAtzc2gtZWQyNTUxOQAAACBS9CsZhkOgawNzvNPRSIOJiKhz9xEqUkPGeMGXZV2UmQ
AAAECiXqC92PMhl7Xe1TtjWTVtd9r+PORzw4iGLmdzmbwE9FL0KxmGQ6BrA3O809FIg4mI
qHP3ESpSQ8Z4wZdlXZSZAAAACGUyZUB0ZXN0AQIDBAU=
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFL0KxmGQ6BrA3O809FIg4mIqHP3ESpSQ8Z4wZdlXZSZ e2e@test

87
test/e2e/unregistry.go Normal file
View File

@@ -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()
}