diff --git a/.github/workflows/pr-workflow.yaml b/.github/workflows/pr-workflow.yaml index 4584f1057b..82026a858c 100644 --- a/.github/workflows/pr-workflow.yaml +++ b/.github/workflows/pr-workflow.yaml @@ -51,6 +51,8 @@ jobs: uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: 'go.mod' + - name: Use the Docker Hub mirror + run: hack/ci-docker-registry-mirror.sh # we only test on amd64, make sure everything still builds for arm64 - name: build for arm64 run: GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build ./... diff --git a/cmd/ateapi/internal/authz/authz_test.go b/cmd/ateapi/internal/authz/authz_test.go index c391ffb48d..58cd685199 100644 --- a/cmd/ateapi/internal/authz/authz_test.go +++ b/cmd/ateapi/internal/authz/authz_test.go @@ -18,9 +18,6 @@ import ( "context" "errors" "os" - "os/exec" - "runtime" - "strings" "testing" "time" @@ -38,29 +35,6 @@ import ( "go.ate.dev/substrate/internal/principal" ) -func configureDockerEnv(ctx context.Context) error { - if os.Getenv("DOCKER_HOST") != "" { - return nil - } - output, err := exec.CommandContext(ctx, "docker", "context", "inspect", "--format", "{{.Endpoints.docker.Host}}").Output() - if err != nil { - return err - } - host := strings.TrimSpace(string(output)) - if host == "" { - return nil - } - _ = os.Setenv("DOCKER_HOST", host) - if os.Getenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE") == "" { - socket := host - if runtime.GOOS == "darwin" { - socket = "/var/run/docker.sock" - } - _ = os.Setenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", socket) - } - return nil -} - func startPostgres(t *testing.T) *pgxpool.Pool { t.Helper() ctx := context.Background() @@ -70,12 +44,12 @@ func startPostgres(t *testing.T) *pgxpool.Pool { if dockerenv.Required() { unavailable = t.Fatalf } - if err := configureDockerEnv(ctx); err != nil { + if err := dockerenv.Configure(ctx); err != nil { unavailable("docker is unavailable: %v", err) } pgContainer, err := postgres.Run(ctx, - "postgres:18-alpine", + dockerenv.PostgresImage, postgres.WithDatabase("testdb"), postgres.WithUsername("postgres"), postgres.WithPassword("postgres"), diff --git a/cmd/ateapi/internal/store/atepg/cert_auth_test.go b/cmd/ateapi/internal/store/atepg/cert_auth_test.go index d20f819f54..4fdbe9e51f 100644 --- a/cmd/ateapi/internal/store/atepg/cert_auth_test.go +++ b/cmd/ateapi/internal/store/atepg/cert_auth_test.go @@ -37,6 +37,7 @@ import ( "github.com/testcontainers/testcontainers-go" tcexec "github.com/testcontainers/testcontainers-go/exec" "github.com/testcontainers/testcontainers-go/modules/postgres" + "go.ate.dev/substrate/cmd/ateapi/internal/store/dockerenv" "go.ate.dev/substrate/internal/localca" "go.ate.dev/substrate/pkg/postgressetup" "sigs.k8s.io/yaml" @@ -88,7 +89,7 @@ func TestBundledPostgresCertificateAuthentication(t *testing.T) { ).Replace(cm.Data["postgresql.conf"]) configPath := filepath.Join(dir, "postgresql.conf") writeCertTestFile(t, configPath, []byte(conf)) - container, err := postgres.Run(t.Context(), "postgres:18-alpine", + container, err := postgres.Run(t.Context(), dockerenv.PostgresImage, postgres.WithDatabase("atepg"), postgres.WithUsername("postgres"), postgres.WithPassword("unused"), postgres.WithSSLCert(clientRoot, serverCert, serverKey), postgres.WithConfigFile(configPath), testcontainers.WithFiles(testcontainers.ContainerFile{Reader: strings.NewReader(cm.Data["pg_hba.conf"]), ContainerFilePath: "/tmp/testcontainers-go/postgres/pg_hba.conf", FileMode: 0644}), diff --git a/cmd/ateapi/internal/store/atepg/main_test.go b/cmd/ateapi/internal/store/atepg/main_test.go index a0ca3b1bf8..d703c9aa7b 100644 --- a/cmd/ateapi/internal/store/atepg/main_test.go +++ b/cmd/ateapi/internal/store/atepg/main_test.go @@ -63,7 +63,7 @@ func requirePool(t *testing.T) *pgxpool.Pool { containerErr = err return } - pgContainer, err := postgres.Run(ctx, "postgres:18-alpine", + pgContainer, err := postgres.Run(ctx, dockerenv.PostgresImage, postgres.WithDatabase("atepg"), postgres.WithUsername("atepg"), postgres.WithPassword("atepg"), diff --git a/cmd/ateapi/internal/store/dockerenv/dockerenv.go b/cmd/ateapi/internal/store/dockerenv/dockerenv.go index b1c5d61dae..83ecae5108 100644 --- a/cmd/ateapi/internal/store/dockerenv/dockerenv.go +++ b/cmd/ateapi/internal/store/dockerenv/dockerenv.go @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package dockerenv points testcontainers at the active Docker context. +// Package dockerenv points testcontainers at the active Docker context and +// names the images they start. // // It lives apart from storetest because storetest imports atepg, so atepg // cannot import storetest back, and both need this before starting a @@ -29,6 +30,10 @@ import ( "strings" ) +// PostgresImage is the PostgreSQL testcontainer image, shared by every test +// that starts one. It is digest-pinned and matches the install manifest. +const PostgresImage = "postgres:18-alpine@sha256:9a8afca54e7861fd90fab5fdf4c42477a6b1cb7d293595148e674e0a3181de15" + // Configure sets the environment testcontainers reads to find Docker, unless // it is already set. testcontainers-go resolves the daemon from DOCKER_HOST // and its own probe list; it never consults the Docker CLI context, so a diff --git a/cmd/ateapi/internal/store/dockerenv/dockerenv_test.go b/cmd/ateapi/internal/store/dockerenv/dockerenv_test.go index 96a5c6dc01..b44854b7de 100644 --- a/cmd/ateapi/internal/store/dockerenv/dockerenv_test.go +++ b/cmd/ateapi/internal/store/dockerenv/dockerenv_test.go @@ -19,9 +19,20 @@ import ( "os" "path/filepath" "runtime" + "strings" "testing" ) +func TestPostgresImageMatchesManifest(t *testing.T) { + manifest, err := os.ReadFile("../../../../../manifests/ate-install/postgres/postgres.yaml") + if err != nil { + t.Fatal(err) + } + if !strings.Contains(string(manifest), "image: "+PostgresImage) { + t.Errorf("postgres.yaml does not use image %s", PostgresImage) + } +} + func TestConfigureFromContext(t *testing.T) { t.Setenv("DOCKER_HOST", "") t.Setenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", "") diff --git a/cmd/ateapi/internal/store/storetest/storetest.go b/cmd/ateapi/internal/store/storetest/storetest.go index 7bc219b3d2..0c84532ac7 100644 --- a/cmd/ateapi/internal/store/storetest/storetest.go +++ b/cmd/ateapi/internal/store/storetest/storetest.go @@ -180,7 +180,7 @@ func requireAdminPool(t *testing.T) *pgxpool.Pool { containerErr = err return } - containerPG, containerErr = postgres.Run(ctx, "postgres:18-alpine", + containerPG, containerErr = postgres.Run(ctx, dockerenv.PostgresImage, postgres.WithDatabase("postgres"), postgres.WithUsername("postgres"), postgres.WithPassword("postgres"), diff --git a/hack/ci-docker-registry-mirror.sh b/hack/ci-docker-registry-mirror.sh new file mode 100755 index 0000000000..6c440f0f5a --- /dev/null +++ b/hack/ci-docker-registry-mirror.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# Copyright 2026 The Agent Substrate Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# Points the Docker daemon at mirror.gcr.io. It rewrites /etc/docker/daemon.json +# and restarts dockerd, so it is for CI runners only. +# +# Anonymous Docker Hub pulls are rate limited per runner IP. mirror.gcr.io is +# meant to be used as a daemon mirror; images it lacks still come from Docker Hub. + +set -o errexit -o nounset -o pipefail + +cfg=/etc/docker/daemon.json +[[ -s "${cfg}" ]] || echo '{}' | sudo tee "${cfg}" >/dev/null +sudo jq '. + {"registry-mirrors": ["https://mirror.gcr.io"]}' "${cfg}" | sudo tee "${cfg}.new" >/dev/null +sudo mv "${cfg}.new" "${cfg}" +sudo systemctl restart docker