From 6e34bcf15325e6b3afd7e6ee449575664145c8bc Mon Sep 17 00:00:00 2001 From: Fabian Buchenberger <37747351+ffabss@users.noreply.github.com> Date: Tue, 27 Jan 2026 22:14:23 +0100 Subject: [PATCH] feat: allow namespace override per image (#4725) --- internal/utils/docker.go | 48 ++++++++++++++++++++++++++++++----- internal/utils/docker_test.go | 46 +++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 7 deletions(-) diff --git a/internal/utils/docker.go b/internal/utils/docker.go index 6da0d5aa6..3cfa5780d 100644 --- a/internal/utils/docker.go +++ b/internal/utils/docker.go @@ -194,15 +194,49 @@ func GetRegistry() string { return strings.ToLower(registry) } -func GetRegistryImageUrl(imageName string) string { +// GetRegistryImageUrl builds a fully-qualified image reference. +// +// Expected input format: +// +// NAMESPACE/IMAGE:TAG +// +// Behavior: +// - Uses the configured registry (via GetRegistry()). +// - Allows per-image namespace overrides via +// INTERNAL_IMAGE_NAMESPACE_"IMAGE_NAME". +// - Falls back to the "supabase" namespace for non-docker.io registries +// when no override is configured. +func GetRegistryImageUrl(imageRef string) string { registry := GetRegistry() - if registry == "docker.io" { - return imageName + + // Split path segments (supports nested namespaces) + parts := strings.Split(imageRef, "/") + if len(parts) < 2 { + return imageRef // invalid format + } + + imageWithTag := parts[len(parts)-1] + namespace := strings.Join(parts[:len(parts)-1], "/") + + // Split image and tag + imageParts := strings.SplitN(imageWithTag, ":", 2) + if len(imageParts) != 2 { + return imageRef // invalid format } - // Configure mirror registry - parts := strings.Split(imageName, "/") - imageName = parts[len(parts)-1] - return registry + "/supabase/" + imageName + + imageName := imageParts[0] + imageTag := imageParts[1] + + // Check for per-image namespace override + overrideKey := "INTERNAL_IMAGE_NAMESPACE_" + strings.ToUpper(imageName) + if override := viper.GetString(overrideKey); override != "" { + namespace = override + } else if registry != "docker.io" { + // Default namespace for non-docker.io registries + namespace = "supabase" + } + + return fmt.Sprintf("%s/%s/%s:%s", registry, namespace, imageName, imageTag) } func DockerImagePull(ctx context.Context, imageTag string, w io.Writer) error { diff --git a/internal/utils/docker_test.go b/internal/utils/docker_test.go index 069204559..da7c3948b 100644 --- a/internal/utils/docker_test.go +++ b/internal/utils/docker_test.go @@ -306,3 +306,49 @@ func TestExecOnce(t *testing.T) { // TODO: mock tcp hijack } + +func TestGetRegistryImageUrl(t *testing.T) { + t.Run("docker.io keeps provided namespace (library)", func(t *testing.T) { + viper.Set("INTERNAL_IMAGE_REGISTRY", "docker.io") + + got := GetRegistryImageUrl("library/kong:2.8.1") + assert.Equal(t, "docker.io/library/kong:2.8.1", got) + }) + + t.Run("non-docker.io defaults namespace to supabase when no override", func(t *testing.T) { + viper.Set("INTERNAL_IMAGE_REGISTRY", "ghcr.io") + + got := GetRegistryImageUrl("library/kong:2.8.1") + assert.Equal(t, "ghcr.io/supabase/kong:2.8.1", got) + }) + + t.Run("supabase namespace remains supabase on non-docker.io registry", func(t *testing.T) { + viper.Set("INTERNAL_IMAGE_REGISTRY", "ghcr.io") + + got := GetRegistryImageUrl("supabase/postgres:17.6.1.074") + assert.Equal(t, "ghcr.io/supabase/postgres:17.6.1.074", got) + }) + + t.Run("namespace override gets applied", func(t *testing.T) { + viper.Set("INTERNAL_IMAGE_REGISTRY", "ghcr.io") + viper.Set("INTERNAL_IMAGE_NAMESPACE_POSTGREST", "custom") + + got := GetRegistryImageUrl("postgrest/postgrest:v14.3") + assert.Equal(t, "ghcr.io/custom/postgrest:v14.3", got) + }) + + t.Run("invalid image format returns as-is", func(t *testing.T) { + viper.Set("INTERNAL_IMAGE_REGISTRY", "docker.io") + + got := GetRegistryImageUrl("postgrest") + assert.Equal(t, "postgrest", got) + }) + + t.Run("overrides kong namespace to docker/library on public.ecr.aws", func(t *testing.T) { + viper.Set("INTERNAL_IMAGE_REGISTRY", "public.ecr.aws") + viper.Set("INTERNAL_IMAGE_NAMESPACE_KONG", "docker/library") + + got := GetRegistryImageUrl("library/kong:2.8.1") + assert.Equal(t, "public.ecr.aws/docker/library/kong:2.8.1", got) + }) +}