Merge pull request #134453 from stlaz/node-conformance-e2e

Fix node conformance tests with fake registry
This commit is contained in:
Kubernetes Prow Robot
2025-10-30 07:48:06 -07:00
committed by GitHub
8 changed files with 390 additions and 64 deletions

View File

@@ -43,6 +43,7 @@ type ConformanceContainer struct {
RestartPolicy v1.RestartPolicy
Volumes []v1.Volume
ImagePullSecrets []string
NodeName string
PodClient *e2epod.PodClient
podName string
@@ -65,6 +66,7 @@ func (cc *ConformanceContainer) Create(ctx context.Context) {
Containers: []v1.Container{
cc.Container,
},
NodeName: cc.NodeName,
SecurityContext: cc.PodSecurityContext,
Volumes: cc.Volumes,
ImagePullSecrets: imagePullSecrets,

View File

@@ -19,13 +19,17 @@ package node
import (
"context"
"fmt"
"os"
"path"
"time"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/kubernetes/pkg/kubelet/images"
"k8s.io/kubernetes/test/e2e/framework"
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
e2eregistry "k8s.io/kubernetes/test/e2e/framework/registry"
imageutils "k8s.io/kubernetes/test/utils/image"
admissionapi "k8s.io/pod-security-admission/api"
@@ -36,7 +40,7 @@ import (
var _ = SIGDescribe("Container Runtime", func() {
f := framework.NewDefaultFramework("container-runtime")
f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged // custom registry pods need HostPorts
ginkgo.Describe("blackbox test", func() {
ginkgo.Context("when starting a container that exits", func() {
@@ -254,28 +258,47 @@ while true; do sleep 1; done
})
})
ginkgo.Context("when running a container with a new image", func() {
framework.Context("when running a container with a new image", framework.WithSerial(), func() {
var registryAddress string
ginkgo.BeforeEach(func(ctx context.Context) {
var err error
registryAddress, _, err = e2eregistry.SetupRegistry(ctx, f, false)
framework.ExpectNoError(err)
// we need to wait for the registry to be removed and so we need to delete the whole NS ourselves
ginkgo.DeferCleanup(func(ctx context.Context) {
f.DeleteNamespace(ctx, f.Namespace.Name)
})
})
// Images used for ConformanceContainer are not added into NodePrePullImageList, because this test is
// testing image pulling, these images don't need to be prepulled. The ImagePullPolicy
// is v1.PullAlways, so it won't be blocked by framework image pre-pull list check.
imagePullTest := func(ctx context.Context, image string, expectedPhase v1.PodPhase, expectedPullStatus bool, windowsImage bool) {
command := []string{"/bin/sh", "-c", "while true; do sleep 1; done"}
if windowsImage {
// -t: Ping the specified host until stopped.
command = []string{"ping", "-t", "localhost"}
}
imagePullTest := func(ctx context.Context, image string, hasSecret bool, expectedPhase v1.PodPhase, expectedPullStatus bool) {
container := ConformanceContainer{
PodClient: e2epod.NewPodClient(f),
Container: v1.Container{
Name: "image-pull-test",
Image: image,
Command: command,
ImagePullPolicy: v1.PullAlways,
},
RestartPolicy: v1.RestartPolicyNever,
}
if hasSecret {
secret := e2eregistry.User1DockerSecret(registryAddress)
secret.Name = "image-pull-secret-" + string(uuid.NewUUID())
// we might be told to use a different docker config JSON.
if framework.TestContext.DockerConfigFile != "" {
contents, err := os.ReadFile(framework.TestContext.DockerConfigFile)
framework.ExpectNoError(err)
secret.Data[v1.DockerConfigJsonKey] = contents
}
ginkgo.By("create image pull secret")
_, err := f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, secret, metav1.CreateOptions{})
framework.ExpectNoError(err)
ginkgo.DeferCleanup(framework.IgnoreNotFound(f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Delete), secret.Name, metav1.DeleteOptions{})
container.ImagePullSecrets = []string{secret.Name}
}
// checkContainerStatus checks whether the container status matches expectation.
checkContainerStatus := func(ctx context.Context) error {
status, err := container.GetStatus(ctx)
@@ -293,6 +316,7 @@ while true; do sleep 1; done
}
if expectedPullStatus {
if status.State.Waiting == nil {
gomega.Expect(status.State.Running).To(gomega.BeNil())
return fmt.Errorf("expected container state: Waiting, got: %q",
GetContainerState(status.State))
}
@@ -340,24 +364,23 @@ while true; do sleep 1; done
f.It("should not be able to pull image from invalid registry", f.WithNodeConformance(), func(ctx context.Context) {
image := imageutils.GetE2EImage(imageutils.InvalidRegistryImage)
imagePullTest(ctx, image, v1.PodPending, true, false)
imagePullTest(ctx, image, false, v1.PodPending, true)
})
f.It("should be able to pull image", f.WithNodeConformance(), func(ctx context.Context) {
// NOTE(claudiub): The agnhost image is supposed to work on both Linux and Windows.
image := imageutils.GetE2EImage(imageutils.Agnhost)
imagePullTest(ctx, image, v1.PodRunning, false, false)
imagePullTest(ctx, image, false, v1.PodRunning, false)
})
// TODO: https://github.com/kubernetes/kubernetes/issues/130271
// Switch this to use a locally hosted private image and not depend on this host
f.It("should not be able to pull from private registry without secret", f.WithNodeConformance(), func(ctx context.Context) {
image := imageutils.GetE2EImage(imageutils.AuthenticatedAlpine)
imagePullTest(ctx, image, v1.PodPending, true, false)
image := registryAddress + "/pause:testing"
imagePullTest(ctx, image, false, v1.PodPending, true)
})
// TODO: https://github.com/kubernetes/kubernetes/issues/130271
// Add a sustainable test for pulling with a private registry secret
f.It("should be able to pull from private registry with secret", f.WithNodeConformance(), func(ctx context.Context) {
image := registryAddress + "/pause:testing"
imagePullTest(ctx, image, true, v1.PodRunning, false)
})
})
})
})

View File

@@ -0,0 +1,14 @@
rules:
# Using k8s packages, other framework helpers and the public API should be good enough.
#
# public API
- selectorRegexp: ^k8s[.]io/(api|apimachinery|client-go|component-base|klog|pod-security-admission|utils)
allowedPrefixes: [ "" ]
# test helpers
- selectorRegexp: ^k8s[.]io/kubernetes/test
allowedPrefixes: [ "" ]
# stdlib
- selectorRegexp: ^[a-z]+(/|$)
allowedPrefixes: [ "" ]

View File

@@ -0,0 +1,143 @@
/*
Copyright 2025 The Kubernetes 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.
*/
package registry
import (
"context"
"fmt"
"time"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/pod-security-admission/api"
"k8s.io/pod-security-admission/test"
"k8s.io/utils/ptr"
"k8s.io/kubernetes/test/e2e/framework"
e2edaemonset "k8s.io/kubernetes/test/e2e/framework/daemonset"
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
imageutils "k8s.io/kubernetes/test/utils/image"
)
const (
dockerCredsFmt = `{
"auths": {
"%s": {
"auth": "%s"
}
}
}`
user1creds = "dXNlcjpwYXNzd29yZA==" // user:password
)
// SetupRegistry runs the `fake-registry-server --private` from the agnhost image.
// The registry is run with HostPort 5000 exposed in order to allow locally-scheduled
// pods to query the registry via kubelet.
// The registry only runs in HTTP (no TLS) mode, and so this hack-path is used as
// localhost is typically allowed by CRIs and so no CRI-specific configuration is needed
//
// By default, the function runs the registry as a DaemonSet on all nodes, but it supports running
// it in just a `pod` for cases where kube-controller-manager is not running (like in
// the Node Conformance test suite).
//
// This function returns:
// - the node-local address of the registry
// - set of node names that the registry runs on, mostly useful only in the podOnly case
// - an error
//
// TODO: once https://github.com/kubernetes/kubernetes/issues/132955 is
// addressed, we might be able to proxy a single endpoint from the cluster to each
// node's localhost port instead of using DaemonSets.
func SetupRegistry(ctx context.Context, f *framework.Framework, podOnly bool) (string, []string, error) {
podTestLabel := "test-registry-pod-" + f.UniqueName
pod, err := podManifest(podTestLabel)
if err != nil {
return "", nil, err
}
if podOnly {
podClient := e2epod.NewPodClient(f)
pod = podClient.Create(ctx, pod)
framework.ExpectNoError(e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod))
} else {
labels := map[string]string{"kube-e2e": podTestLabel}
daemonset := e2edaemonset.NewDaemonSet("", "", labels, nil, nil, nil)
daemonset.GenerateName = "test-registry-"
daemonset.Spec.Template.Spec = pod.Spec
daemonset, err = f.ClientSet.AppsV1().DaemonSets(f.Namespace.Name).Create(ctx, daemonset, metav1.CreateOptions{})
if err != nil {
return "", nil, err
}
err = wait.PollUntilContextTimeout(ctx, 1*time.Second, 120*time.Second, true, func(ctx context.Context) (done bool, err error) {
return e2edaemonset.CheckRunningOnAllNodes(ctx, f, daemonset)
})
if err != nil {
return "", nil, err
}
}
pods, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).List(ctx, metav1.ListOptions{LabelSelector: "kube-e2e=" + podTestLabel})
if err != nil {
return "", nil, err
}
podNodes := make([]string, 0, len(pods.Items))
for _, pod := range pods.Items {
podNodes = append(podNodes, pod.Spec.NodeName)
}
return "localhost:5000", podNodes, nil
}
func podManifest(podTestLabel string) (*v1.Pod, error) {
pod, err := test.GetMinimalValidPod(api.LevelRestricted, api.MajorMinorVersion(1, 25))
if err != nil {
return nil, err
}
pod.ObjectMeta.GenerateName = "test-registry-"
pod.ObjectMeta.Labels = map[string]string{"kube-e2e": podTestLabel}
pod.Spec.InitContainers = nil
pod.Spec.Containers[0].Name = "registry"
pod.Spec.Containers[0].Image = imageutils.GetE2EImage(imageutils.Agnhost)
pod.Spec.Containers[0].ImagePullPolicy = v1.PullIfNotPresent
pod.Spec.Containers[0].Args = []string{"fake-registry-server", "--private"}
pod.Spec.Containers[0].Ports = []v1.ContainerPort{
{
Name: "http",
ContainerPort: 5000,
HostPort: 5000,
},
}
pod.Spec.Containers[0].SecurityContext.RunAsUser = ptr.To[int64](5123)
return pod, nil
}
// User1DockerSecret creates a secret containing the docker credentials for pulling from
// the agnhost fake-registry-server.
func User1DockerSecret(registryAddress string) *v1.Secret {
return &v1.Secret{
Type: v1.SecretTypeDockerConfigJson,
Data: map[string][]byte{
v1.DockerConfigJsonKey: fmt.Appendf(nil, dockerCredsFmt, "http://"+registryAddress, user1creds),
},
}
}

View File

@@ -0,0 +1,163 @@
/*
Copyright 2016 The Kubernetes 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.
*/
package e2enode
import (
"context"
"fmt"
"os"
"path/filepath"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kubernetes/pkg/kubelet/images"
"k8s.io/kubernetes/test/e2e/common/node"
"k8s.io/kubernetes/test/e2e/framework"
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
e2eregistry "k8s.io/kubernetes/test/e2e/framework/registry"
"k8s.io/kubernetes/test/e2e_node/services"
admissionapi "k8s.io/pod-security-admission/api"
"github.com/onsi/ginkgo/v2"
)
var _ = SIGDescribe("Container Runtime Conformance Test", func() {
f := framework.NewDefaultFramework("runtime-conformance")
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged // custom registry pods need HostPorts
ginkgo.Describe("container runtime conformance blackbox test", func() {
ginkgo.Context("when running a container with a new image", func() {
// The following images are not added into NodePrePullImageList, because this test is
// testing image pulling, these images don't need to be prepulled. The ImagePullPolicy
// is v1.PullAlways, so it won't be blocked by framework image pre-pull list check.
for _, testCase := range []struct {
description string
image string
setupRegisty bool
phase v1.PodPhase
waiting bool
}{
{
description: "should be able to pull from private registry with credential provider",
image: "pause:testing",
setupRegisty: true,
phase: v1.PodRunning,
waiting: false,
},
} {
var registryAddress string
var podNodes []string
ginkgo.BeforeEach(func(ctx context.Context) {
var err error
if !testCase.setupRegisty {
return
}
registryAddress, podNodes, err = e2eregistry.SetupRegistry(ctx, f, true)
framework.ExpectNoError(err)
// we need to wait for the registry to be removed and so we need to delete the whole NS ourselves
ginkgo.DeferCleanup(func(ctx context.Context) {
f.DeleteNamespace(ctx, f.Namespace.Name)
})
})
f.It(testCase.description+"", f.WithNodeConformance(), func(ctx context.Context) {
name := "image-pull-test"
container := node.ConformanceContainer{
PodClient: e2epod.NewPodClient(f),
Container: v1.Container{
Name: name,
Image: registryAddress + "/" + testCase.image,
// PullAlways makes sure that the image will always be pulled even if it is present before the test.
ImagePullPolicy: v1.PullAlways,
},
RestartPolicy: v1.RestartPolicyNever,
}
if testCase.setupRegisty {
container.NodeName = podNodes[0]
}
auth := e2eregistry.User1DockerSecret(registryAddress).Data[v1.DockerConfigJsonKey]
configFile := filepath.Join(services.KubeletRootDirectory, "config.json")
err := os.WriteFile(configFile, []byte(auth), 0644)
framework.ExpectNoError(err)
ginkgo.DeferCleanup(func() { framework.ExpectNoError(os.Remove(configFile)) })
// checkContainerStatus checks whether the container status matches expectation.
checkContainerStatus := func(ctx context.Context) error {
status, err := container.GetStatus(ctx)
if err != nil {
return fmt.Errorf("failed to get container status: %w", err)
}
// We need to check container state first. The default pod status is pending, If we check
// pod phase first, and the expected pod phase is Pending, the container status may not
// even show up when we check it.
// Check container state
if !testCase.waiting {
if status.State.Running == nil {
return fmt.Errorf("expected container state: Running, got: %q",
node.GetContainerState(status.State))
}
}
if testCase.waiting {
if status.State.Waiting == nil {
return fmt.Errorf("expected container state: Waiting, got: %q",
node.GetContainerState(status.State))
}
reason := status.State.Waiting.Reason
if reason != images.ErrImagePull.Error() &&
reason != images.ErrImagePullBackOff.Error() {
return fmt.Errorf("unexpected waiting reason: %q", reason)
}
}
// Check pod phase
phase, err := container.GetPhase(ctx)
if err != nil {
return fmt.Errorf("failed to get pod phase: %w", err)
}
if phase != testCase.phase {
return fmt.Errorf("expected pod phase: %q, got: %q", testCase.phase, phase)
}
return nil
}
ginkgo.By("create the container")
container.Create(ctx)
ginkgo.DeferCleanup(func(ctx context.Context) {
ginkgo.By("delete the conformance container")
if err := container.Delete(ctx); err != nil {
framework.Logf("error deleting a conformance container: %v", err)
}
})
ginkgo.By("check the container status")
var latestErr error
err = wait.PollUntilContextCancel(ctx, node.ContainerStatusPollInterval, true, func(ctx context.Context) (bool, error) {
if latestErr = checkContainerStatus(ctx); latestErr != nil {
return false, nil
}
return true, nil
})
if err != nil {
framework.Failf("Failed to read container status: %v; last observed error from wait loop: %v", err, latestErr)
}
})
}
})
})
})

View File

@@ -3,7 +3,10 @@
# We are aiming to consolidate on: registry.k8s.io/e2e-test-images/agnhost
# The sources for which are in test/images/agnhost.
# If agnhost is missing functionality for your tests, please reach out to SIG Testing.
gcr.io/authenticated-image-pulling/alpine
#
# TODO: remove gcr.io/k8s-authenticated-test/agnhost and set up a cluster-local
# private registry via test/e2e/framework/registry.SetupRegistry() to test
# private image pulls
gcr.io/k8s-authenticated-test/agnhost
invalid.registry.k8s.io/invalid/alpine
registry.k8s.io/build-image/distroless-iptables

View File

@@ -33,7 +33,6 @@ import (
// RegistryList holds public and private image registries
type RegistryList struct {
GcAuthenticatedRegistry string `yaml:"gcAuthenticatedRegistry"`
PromoterE2eRegistry string `yaml:"promoterE2eRegistry"`
BuildImageRegistry string `yaml:"buildImageRegistry"`
InvalidRegistry string `yaml:"invalidRegistry"`
@@ -129,17 +128,14 @@ func readFromURL(url string, writer io.Writer) error {
var (
initRegistry = RegistryList{
// TODO: https://github.com/kubernetes/kubernetes/issues/130271
// Eliminate GcAuthenticatedRegistry.
GcAuthenticatedRegistry: "gcr.io/authenticated-image-pulling",
PromoterE2eRegistry: "registry.k8s.io/e2e-test-images",
BuildImageRegistry: "registry.k8s.io/build-image",
InvalidRegistry: "invalid.registry.k8s.io/invalid",
GcEtcdRegistry: "registry.k8s.io",
GcRegistry: "registry.k8s.io",
SigStorageRegistry: "registry.k8s.io/sig-storage",
// TODO: https://github.com/kubernetes/kubernetes/issues/130271
// Eliminate PrivateRegistry.
PromoterE2eRegistry: "registry.k8s.io/e2e-test-images",
BuildImageRegistry: "registry.k8s.io/build-image",
InvalidRegistry: "invalid.registry.k8s.io/invalid",
GcEtcdRegistry: "registry.k8s.io",
GcRegistry: "registry.k8s.io",
SigStorageRegistry: "registry.k8s.io/sig-storage",
PrivateRegistry: "gcr.io/k8s-authenticated-test",
DockerLibraryRegistry: "docker.io/library",
CloudProviderGcpRegistry: "registry.k8s.io/cloud-provider-gcp",
@@ -158,17 +154,11 @@ const (
// Agnhost image
Agnhost
// AgnhostPrivate image
// TODO: https://github.com/kubernetes/kubernetes/issues/130271
// Eliminate this.
AgnhostPrivate
// APIServer image
APIServer
// AppArmorLoader image
AppArmorLoader
// AuthenticatedAlpine image
// TODO: https://github.com/kubernetes/kubernetes/issues/130271
// Eliminate this.
AuthenticatedAlpine
// BusyBox image
BusyBox
// DistrolessIptables Image
@@ -219,9 +209,8 @@ const (
func initImageConfigs(list RegistryList) (map[ImageID]Config, map[ImageID]Config) {
configs := map[ImageID]Config{}
configs[AgnhostPrev] = Config{list.PromoterE2eRegistry, "agnhost", "2.55"}
configs[Agnhost] = Config{list.PromoterE2eRegistry, "agnhost", "2.56"}
configs[Agnhost] = Config{list.PromoterE2eRegistry, "agnhost", "2.57"}
configs[AgnhostPrivate] = Config{list.PrivateRegistry, "agnhost", "2.6"}
configs[AuthenticatedAlpine] = Config{list.GcAuthenticatedRegistry, "alpine", "3.7"}
configs[APIServer] = Config{list.PromoterE2eRegistry, "sample-apiserver", "1.29.2"}
configs[AppArmorLoader] = Config{list.PromoterE2eRegistry, "apparmor-loader", "1.4"}
configs[BusyBox] = Config{list.PromoterE2eRegistry, "busybox", "1.37.0-1"}
@@ -268,8 +257,7 @@ func GetMappedImageConfigs(originalImageConfigs map[ImageID]Config, repo string)
configs := make(map[ImageID]Config)
for i, config := range originalImageConfigs {
switch i {
case InvalidRegistryImage, AuthenticatedAlpine,
AgnhostPrivate:
case InvalidRegistryImage, AgnhostPrivate:
// These images are special and can't be run out of the cloud - some because they
// are authenticated, and others because they are not real images. Tests that depend
// on these images can't be run without access to the public internet.
@@ -400,8 +388,6 @@ func replaceRegistryInImageURLWithList(imageURL string, reg RegistryList) (strin
registryAndUser = reg.PromoterE2eRegistry
case initRegistry.BuildImageRegistry:
registryAndUser = reg.BuildImageRegistry
case initRegistry.GcAuthenticatedRegistry:
registryAndUser = reg.GcAuthenticatedRegistry
case initRegistry.DockerLibraryRegistry:
registryAndUser = reg.DockerLibraryRegistry
case initRegistry.CloudProviderGcpRegistry:

View File

@@ -56,20 +56,16 @@ func BenchmarkReplaceRegistryInImageURL(b *testing.B) {
}, {
in: "registry.k8s.io/build-image/test:latest",
out: "test.io/build/test:latest",
}, {
in: "gcr.io/authenticated-image-pulling/test:latest",
out: "test.io/gcAuth/test:latest",
},
}
reg := RegistryList{
DockerLibraryRegistry: "test.io/library",
GcRegistry: "test.io",
PrivateRegistry: "test.io/k8s-authenticated-test",
SigStorageRegistry: "test.io/sig-storage",
InvalidRegistry: "test.io/invalid",
PromoterE2eRegistry: "test.io/promoter",
BuildImageRegistry: "test.io/build",
GcAuthenticatedRegistry: "test.io/gcAuth",
DockerLibraryRegistry: "test.io/library",
GcRegistry: "test.io",
PrivateRegistry: "test.io/k8s-authenticated-test",
SigStorageRegistry: "test.io/sig-storage",
InvalidRegistry: "test.io/invalid",
PromoterE2eRegistry: "test.io/promoter",
BuildImageRegistry: "test.io/build",
}
for i := 0; i < b.N; i++ {
tt := registryTests[i%len(registryTests)]
@@ -113,9 +109,6 @@ func TestReplaceRegistryInImageURL(t *testing.T) {
}, {
in: "registry.k8s.io/build-image/test:latest",
out: "test.io/build/test:latest",
}, {
in: "gcr.io/authenticated-image-pulling/test:latest",
out: "test.io/gcAuth/test:latest",
}, {
in: "unknwon.io/google-samples/test:latest",
expectErr: fmt.Errorf("Registry: unknwon.io/google-samples is missing in test/utils/image/manifest.go, please add the registry, otherwise the test will fail on air-gapped clusters"),
@@ -124,14 +117,13 @@ func TestReplaceRegistryInImageURL(t *testing.T) {
// Set custom registries
reg := RegistryList{
DockerLibraryRegistry: "test.io/library",
GcRegistry: "test.io",
PrivateRegistry: "test.io/k8s-authenticated-test",
SigStorageRegistry: "test.io/sig-storage",
InvalidRegistry: "test.io/invalid",
PromoterE2eRegistry: "test.io/promoter",
BuildImageRegistry: "test.io/build",
GcAuthenticatedRegistry: "test.io/gcAuth",
DockerLibraryRegistry: "test.io/library",
GcRegistry: "test.io",
PrivateRegistry: "test.io/k8s-authenticated-test",
SigStorageRegistry: "test.io/sig-storage",
InvalidRegistry: "test.io/invalid",
PromoterE2eRegistry: "test.io/promoter",
BuildImageRegistry: "test.io/build",
}
for _, tt := range registryTests {