From a5871072ee341fc16ba732c0595ee16acc6fbf4a Mon Sep 17 00:00:00 2001 From: Oz Date: Fri, 12 Jun 2026 17:54:47 +0000 Subject: [PATCH] Fix Docker sidecar volume names for registry refs with ports The docker backend derives sidecar volume names from the image repo name, but only replaced '/' characters. Image refs that include a registry host with a port (e.g. localhost:5000/warp-agent:tag) kept the ':' and failed volume creation with: "localhost:5000-warp-agent-" includes invalid characters for a local volume name, only "[a-zA-Z0-9][a-zA-Z0-9_.-]" are allowed Replace every character outside [a-zA-Z0-9_.-] instead, and add unit tests covering port-qualified registry refs. Found while validating warp-agent-docker#120 against a local docker-backend oz stack using a localhost:5000 registry for locally built sidecar images. Co-Authored-By: Oz --- internal/worker/docker.go | 10 ++++- internal/worker/docker_volume_name_test.go | 46 ++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 internal/worker/docker_volume_name_test.go diff --git a/internal/worker/docker.go b/internal/worker/docker.go index b3e9873..086ecb9 100644 --- a/internal/worker/docker.go +++ b/internal/worker/docker.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "regexp" "strings" "time" @@ -22,6 +23,9 @@ import ( const dockerHubAuthConfigKey = "https://index.docker.io/v1/" +// invalidVolumeNameChars matches characters not allowed in Docker volume names. +var invalidVolumeNameChars = regexp.MustCompile(`[^a-zA-Z0-9_.-]`) + // DockerBackendConfig holds configuration specific to the Docker backend. type DockerBackendConfig struct { NoCleanup bool @@ -514,8 +518,10 @@ func sanitizeVolumeName(imageName, digest string) string { repoName = imageName } - // Sanitize the repository name for use in volume name - baseName := strings.ReplaceAll(repoName, "/", "-") + // Sanitize the repository name for use in volume name. Docker volume names + // only allow [a-zA-Z0-9_.-], so registry hosts with ports (e.g. + // localhost:5000/warp-agent) must have their colons replaced too. + baseName := invalidVolumeNameChars.ReplaceAllString(repoName, "-") // digest format is typically "sha256:abc123..." parts := strings.Split(digest, ":") diff --git a/internal/worker/docker_volume_name_test.go b/internal/worker/docker_volume_name_test.go new file mode 100644 index 0000000..74eb23c --- /dev/null +++ b/internal/worker/docker_volume_name_test.go @@ -0,0 +1,46 @@ +package worker + +import "testing" + +func TestSanitizeVolumeName(t *testing.T) { + tests := []struct { + name string + imageName string + digest string + want string + }{ + { + name: "docker hub image", + imageName: "warpdotdev/warp-agent:latest-dev", + digest: "sha256:cdd2970ebf33dea820d96c39246d0db275f1e33965e9b65381e778eb77a7a1af", + want: "warpdotdev-warp-agent-cdd2970ebf33", + }, + { + name: "registry host with port", + imageName: "localhost:5000/warp-agent:pr120", + digest: "sha256:cdd2970ebf33dea820d96c39246d0db275f1e33965e9b65381e778eb77a7a1af", + want: "localhost-5000-warp-agent-cdd2970ebf33", + }, + { + name: "registry host with port and nested repo", + imageName: "registry.example.com:8443/team/agent:v1", + digest: "sha256:abcdef0123456789", + want: "registry.example.com-8443-team-agent-abcdef012345", + }, + { + name: "malformed digest falls back to base name plus digest", + imageName: "warpdotdev/warp-agent", + digest: "not-a-digest", + want: "warpdotdev-warp-agent-not-a-digest", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := sanitizeVolumeName(tt.imageName, tt.digest) + if got != tt.want { + t.Errorf("sanitizeVolumeName(%q, %q) = %q, want %q", tt.imageName, tt.digest, got, tt.want) + } + }) + } +}