diff --git a/docs/content/developers/release-management.md b/docs/content/developers/release-management.md index 6280204b68a..9c34def3b88 100644 --- a/docs/content/developers/release-management.md +++ b/docs/content/developers/release-management.md @@ -118,6 +118,14 @@ This approval only applies to fork PRs from a contributor without push access When testing this on `ddev-test/ddev`, do the same steps there first, and confirm `vars.DOCKER_ORG` on that repository points at the DockerHub org used for testing. +That org is not the one `versionconstants.go` names, so a binary built from `ddev-test/ddev` asks for `ddev/ddev-webserver:vX.Y.Z` while its images were published to `ddevhq`. Set `DDEV_DOCKER_ORG` to pull them from where they actually landed: + +```bash +DDEV_DOCKER_ORG=ddevhq ddev start +``` + +It replaces the org on the web, db, router, ssh-agent, and xhgui images, leaving upstream images like `postgres` alone, and `ddev version` shows what it resolved to. Unset, it changes nothing. + Since a job referencing an environment that doesn't exist yet gets auto-created with no protection rules (silently *not* gating), verify the environment actually has a `required_reviewers` rule before relying on it, e.g. `gh api repos///environments/image-push`. ## Pushing Docker Images with the GitHub Actions Workflow diff --git a/pkg/docker/images.go b/pkg/docker/images.go index 5e566de1635..d6318165ea7 100644 --- a/pkg/docker/images.go +++ b/pkg/docker/images.go @@ -2,7 +2,9 @@ package docker import ( "fmt" + "os" "regexp" + "strings" "github.com/ddev/ddev/pkg/globalconfig" "github.com/ddev/ddev/pkg/nodeps" @@ -14,6 +16,25 @@ import ( // which of its image generations a pinned webimage/dbimage came from. const DdevImageTagLabel = "com.ddev.image-tag" +// dockerOrgEnvVar overrides the organization the repositories in +// versionconstants.go live under. A ddev-test/ddev build publishes its images +// to `ddevhq` rather than `ddev`, so without this its own release cannot be +// pull-tested. Unset, which is every normal build, nothing changes. +// See ddev/ddev#8753. +const dockerOrgEnvVar = "DDEV_DOCKER_ORG" + +// imageRepo returns an image reference with its organization replaced by +// dockerOrgEnvVar, when that is set. A reference with no organization (the +// upstream `postgres`) has nothing to replace and is returned unchanged. +func imageRepo(image string) string { + org := strings.Trim(strings.TrimSpace(os.Getenv(dockerOrgEnvVar)), "/") + i := strings.LastIndex(image, "/") + if org == "" || i < 0 { + return image + } + return org + image[i:] +} + // releaseTagPattern matches vX.Y.Z, or a prerelease like v1.25.4-rc1. Naming // the kinds keeps `git describe`'s v1.25.4-15-gabcdef1 out. Also in // release-prep.sh and release-marker.sh. @@ -33,7 +54,7 @@ func resolveImageTag(tag, branch string) string { // GetWebImage returns the correctly formatted web image:tag reference func GetWebImage() string { - fullWebImg := versionconstants.WebImg + fullWebImg := imageRepo(versionconstants.WebImg) if globalconfig.DdevGlobalConfig.UseHardenedImages { fullWebImg = fullWebImg + "-prod" } @@ -57,21 +78,21 @@ func GetDBImage(dbType string, dbVersion string) string { case nodeps.MariaDB: fallthrough default: - return fmt.Sprintf("%s-%s-%s:%s", versionconstants.DBImg, dbType, v, resolveImageTag(versionconstants.BaseDBTag, versionconstants.BaseDBTagBranch)) + return fmt.Sprintf("%s-%s-%s:%s", imageRepo(versionconstants.DBImg), dbType, v, resolveImageTag(versionconstants.BaseDBTag, versionconstants.BaseDBTagBranch)) } } // GetSSHAuthImage returns the correctly formatted sshauth image:tag reference func GetSSHAuthImage() string { - return fmt.Sprintf("%s:%s", versionconstants.SSHAuthImage, resolveImageTag(versionconstants.SSHAuthTag, versionconstants.SSHAuthTagBranch)) + return fmt.Sprintf("%s:%s", imageRepo(versionconstants.SSHAuthImage), resolveImageTag(versionconstants.SSHAuthTag, versionconstants.SSHAuthTagBranch)) } // GetRouterImage returns the router image:tag reference func GetRouterImage() string { - return fmt.Sprintf("%s:%s", versionconstants.TraefikRouterImage, resolveImageTag(versionconstants.TraefikRouterTag, versionconstants.TraefikRouterTagBranch)) + return fmt.Sprintf("%s:%s", imageRepo(versionconstants.TraefikRouterImage), resolveImageTag(versionconstants.TraefikRouterTag, versionconstants.TraefikRouterTagBranch)) } // GetXhguiImage returns the xhgui image:tag reference func GetXhguiImage() string { - return fmt.Sprintf("%s:%s", versionconstants.XhguiImage, resolveImageTag(versionconstants.XhguiTag, versionconstants.XhguiTagBranch)) + return fmt.Sprintf("%s:%s", imageRepo(versionconstants.XhguiImage), resolveImageTag(versionconstants.XhguiTag, versionconstants.XhguiTagBranch)) } diff --git a/pkg/docker/images_test.go b/pkg/docker/images_test.go index 2fe9af65a2b..cdecf28dd13 100644 --- a/pkg/docker/images_test.go +++ b/pkg/docker/images_test.go @@ -3,6 +3,7 @@ package docker import ( "testing" + "github.com/ddev/ddev/pkg/nodeps" "github.com/stretchr/testify/require" ) @@ -18,3 +19,21 @@ func TestResolveImageTag(t *testing.T) { require.Equal(t, "c202e92108", resolveImageTag("c202e92108", "v1.25.4-dirty")) require.Equal(t, "c202e92108", resolveImageTag("c202e92108", "v1.25.4-preview1")) } + +func TestImageRepoDockerOrg(t *testing.T) { + require.Equal(t, "ddev/ddev-webserver", imageRepo("ddev/ddev-webserver")) + + t.Setenv(dockerOrgEnvVar, "ddevhq") + require.Equal(t, "ddevhq/ddev-webserver", imageRepo("ddev/ddev-webserver")) + require.Equal(t, "postgres", imageRepo("postgres")) + + require.Regexp(t, `^ddevhq/ddev-webserver:`, GetWebImage()) + require.Regexp(t, `^ddevhq/ddev-dbserver-mariadb-11\.8:`, GetDBImage(nodeps.MariaDB, nodeps.MariaDB118)) + require.Regexp(t, `^ddevhq/ddev-ssh-agent:`, GetSSHAuthImage()) + require.Regexp(t, `^ddevhq/ddev-traefik-router:`, GetRouterImage()) + require.Regexp(t, `^ddevhq/ddev-xhgui:`, GetXhguiImage()) + require.Equal(t, "postgres:17", GetDBImage(nodeps.Postgres, nodeps.Postgres17)) + + t.Setenv(dockerOrgEnvVar, "") + require.Equal(t, "ddev/ddev-webserver", imageRepo("ddev/ddev-webserver")) +}