Repository: openshift/assisted-service
Pull request #7656: MGMT-20601: Update pull secret validation
Author: login: CrystalChun
Labels: name: approved
description: Indicates a PR has been approved by an approver from all required OWNERS files.
name: size/L
description: Denotes a PR that changes 100-499 lines, ignoring generated files.
name: lgtm
description: Indicates that a PR is ready to be merged.
name: jira/valid-reference
description: Indicates that this PR references a valid Jira ticket of any type.
Created: 2025-05-14T01:30:02Z | Merged: 2025-05-15T19:12:52Z

<!--
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.

You can refer to [Kubernetes community documentation] on writing good commit messages, which provides good tips and ideas.

Some PRs address specific issues. Please, refer to the [CONTRIBUTING] documentation for more
information on how to link a PR to an existing issue.

It's recommended to take a few extra minutes to provide more information about
how this code was tested. Here are some questions that may be worth answering:

- Should this PR be tested by the reviewer?
- Is this PR relying on CI for an e2e test run?
- Should this PR be tested in a specific environment?
- Any logs, screenshots, etc that can help with the review process?

-->
Previously, the image registries that require authentication were stored as just their base domain. This means that any pull secret the user provides needs to have the base domain of these registries. This is not valid since the credentials for a registry in a pull secret can be scoped to a repository. Matching this way caused false positive errors when validating pull secrets since it looks like the authentication credentials don't exist, when in reality they do.

To better serve this use-case and enhance the validation of pull secrets, the following changes were made:
1. Store the full image registry for all images required 
2. When validating a pull secret:
    - Iteratively truncate the full image registry until a match is found in either the ignore list or the pull secret credentials
   - This ensures we're first checking against the most specific URL (base domain + full repository path), but still allowing a user to provide the credentials for just the base domain of the repository

These changes should still be valid for prior use-cases, but should now include the use case where a user wants to provide repository-scoped registry credentials.

## List all the issues related to this PR

- [ ] New Feature <!-- new functionality -->
- [ ] Enhancement <!-- refactor, code changes, improvement, that won't add new features -->
- [x] Bug fix
- [ ] Tests
- [ ] Documentation
- [ ] CI/CD <!-- Notice that changes for Dockerfiles/Jenkinsfiles aren't tested in CI due to a known bug. -->

## What environments does this code impact?

- [ ] Automation (CI, tools, etc)
- [x] Cloud
- [x] Operator Managed Deployments
- [ ] None

## How was this code tested?

<!-- Please, select one or more if needed: -->

- [ ] assisted-test-infra environment
- [ ] dev-scripts environment
- [ ] Reviewer's test appreciated
- [x] Waiting for CI to do a full test run
- [ ] Manual (Elaborate on how it was tested)
- [ ] No tests needed

## Checklist

- [x] Title and description added to both, commit and PR.
- [x] Relevant issues have been associated (see [CONTRIBUTING] guide)
- [x] This change does not require a documentation update (docstring, `docs`, README, etc)
- [x] Does this change include unit-tests (note that code changes require unit-tests)

## Reviewers Checklist

- Are the title and description (in both PR and commit) meaningful and clear?
- Is there a bug required (and linked) for this change?
- Should this PR be backported?

[Kubernetes community documentation]: https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#commit-message-guidelines
[CONTRIBUTING]: https://github.com/openshift/assisted-service/blob/master/CONTRIBUTING.md




Diff:
diff --git a/go.mod b/go.mod
index 1e0c53d57f33..ff317b2bf9ca 100644
--- a/go.mod
+++ b/go.mod
@@ -15,7 +15,7 @@ require (
 	github.com/coreos/ignition/v2 v2.14.0
 	github.com/coreos/vcontext v0.0.0-20211021162308-f1dbbca7bef4
 	github.com/danielerez/go-dns-client v0.0.0-20200630114514-0b60d1703f0b
-	github.com/docker/distribution v2.8.2-beta.1+incompatible
+	github.com/distribution/reference v0.6.0
 	github.com/dustin/go-humanize v1.0.1
 	github.com/filanov/stateswitch v1.0.1-0.20221122134945-bfa198e3a83a
 	github.com/go-gormigrate/gormigrate/v2 v2.1.2
@@ -75,6 +75,7 @@ require (
 	github.com/thoas/go-funk v0.9.3
 	github.com/vincent-petithory/dataurl v1.0.0
 	golang.org/x/crypto v0.32.0
+	golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
 	golang.org/x/sync v0.10.0
 	golang.org/x/sys v0.29.0
 	gopkg.in/ini.v1 v1.67.0
@@ -103,7 +104,6 @@ require (
 	github.com/containerd/containerd v1.7.12 // indirect
 	github.com/containerd/log v0.1.0 // indirect
 	github.com/cpuguy83/dockercfg v0.3.1 // indirect
-	github.com/distribution/reference v0.5.0 // indirect
 	github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab // indirect
 	github.com/emicklei/go-restful/v3 v3.11.0 // indirect
 	github.com/evanphx/json-patch/v5 v5.8.0 // indirect
@@ -148,7 +148,6 @@ require (
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect
 	go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
-	golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
 	golang.org/x/mod v0.19.0 // indirect
 	golang.org/x/tools v0.23.0 // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect
diff --git a/go.sum b/go.sum
index 6fe815347ff0..b90de2dd38b7 100644
--- a/go.sum
+++ b/go.sum
@@ -251,12 +251,10 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/diskfs/go-diskfs v1.4.0 h1:MAybY6TPD+fmhY+a2qFhmdvMeIKvCqlgh4QIc1uCmBs=
 github.com/diskfs/go-diskfs v1.4.0/go.mod h1:G8cyy+ngM+3yKlqjweMmtqvE+TxsnIo1xumbJX1AeLg=
-github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
-github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
+github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
+github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
 github.com/docker/distribution v0.0.0-20180920194744-16128bbac47f/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
 github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
-github.com/docker/distribution v2.8.2-beta.1+incompatible h1:gILO60VLD2v28ozemv4aAwDb8ds5U2O/vD/sBXbd7Rw=
-github.com/docker/distribution v2.8.2-beta.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
 github.com/docker/docker v1.4.2-0.20191219165747-a9416c67da9f/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker v25.0.6+incompatible h1:5cPwbwriIcsua2REJe8HqQV+6WlWc1byg2QSXzBxBGg=
 github.com/docker/docker v25.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
diff --git a/internal/cluster/validations/pull_secret_validation.go b/internal/cluster/validations/pull_secret_validation.go
index 6d1311c14e4a..e93a340d3f89 100644
--- a/internal/cluster/validations/pull_secret_validation.go
+++ b/internal/cluster/validations/pull_secret_validation.go
@@ -11,6 +11,7 @@ import (
 	"github.com/openshift/assisted-service/pkg/ocm"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
+	"golang.org/x/exp/maps"
 )
 
 const (
@@ -20,7 +21,7 @@ const (
 )
 
 // PullSecretValidator is used run validations on a provided pull secret
-// it verifies the format of the pull secrete and access to required image registries
+// it verifies the format of the pull secret and access to required image registries
 //
 //go:generate mockgen -source=pull_secret_validation.go -package=validations -destination=mock_pull_secret_validation.go
 type PullSecretValidator interface {
@@ -179,38 +180,13 @@ func parseAuthConfig(registry string, authConfig map[string]interface{}) (PullSe
 	return ret, nil
 }
 
-func validateRegistryWithAuth(registry string, credentials map[string]PullSecretCreds) error {
-	// Both "docker.io" and "https://index.docker.io/v1/" are acceptable for DockerHub login
-	if registry == dockerHubRegistry {
-		if _, ok := credentials[dockerHubLegacyAuth]; ok {
-			return nil
-		}
-	}
-
-	// We add auth for stage registry automatically
-	if registry == stageRegistry {
-		return nil
-	}
-
-	if _, ok := credentials[registry]; !ok {
-		return &PullSecretError{Msg: fmt.Sprintf("pull secret must contain auth for %q", registry)}
-	}
-
-	return nil
-}
-
-// ValidatePullSecret validates that a pull secret is well formed and contains all required data
+// ValidatePullSecret validates that a pull secret is well-formed and contains all of the required data
 func (v *registryPullSecretValidator) ValidatePullSecret(additionalPublicRegistries []string, secret string, username string, releaseImageURL string) error {
 	creds, err := ParsePullSecret(secret)
 	if err != nil {
 		return err
 	}
 
-	ignorableRegistries := v.publicRegistries
-	for _, registry := range additionalPublicRegistries {
-		ignorableRegistries[registry] = true
-	}
-
 	// only check for cloud creds if we're authenticating against Red Hat SSO
 	if v.authHandler.AuthType() == auth.TypeRHSSO {
 		r, ok := creds["cloud.openshift.com"]
@@ -229,49 +205,84 @@ func (v *registryPullSecretValidator) ValidatePullSecret(additionalPublicRegistr
 		}
 	}
 
-	for registry := range v.registriesWithAuth {
-		if !ignorableRegistries[registry] {
-			if err = validateRegistryWithAuth(registry, creds); err != nil {
-				return &PullSecretError{
-					Msg: fmt.Sprintf("%s, ignorable registries used for pull secret validation: %+v",
-						err.Error(), ignorableRegistries),
-				}
-			}
-		}
+	ignorableRegistries := maps.Clone(v.publicRegistries)
+	for _, registry := range additionalPublicRegistries {
+		ignorableRegistries[registry] = true
 	}
 
-	registryWithAuth, err := getRegistryAuthStatus(ignorableRegistries, releaseImageURL)
-	if err != nil {
-		return err
+	registries := maps.Keys(v.registriesWithAuth)
+	registries = append(registries, releaseImageURL)
+	return verifyNoMissingAuth(ignorableRegistries, creds, registries...)
+}
+
+// verifyNoMissingAuth verifies that all registries are accounted for in either the ignore list or contain
+// an authentication entry in the pull secret
+func verifyNoMissingAuth(ignorableRegistries map[string]bool, credentials map[string]PullSecretCreds, registries ...string) error {
+	// Combine ignorable registries and the registries listed in the credentials since they effectively
+	// are the same check: if the registry exists in the ignore list or in the credentials list, then the
+	// registry does not require auth
+	for key := range credentials {
+		ignorableRegistries[key] = true
 	}
+	for _, registry := range registries {
+		authStillRequired, err := getRegistryAuthStatus(ignorableRegistries, registry)
+		if err != nil {
+			return err
+		}
 
-	if registryWithAuth != nil {
-		if err := validateRegistryWithAuth(*registryWithAuth, creds); err != nil {
-			return &PullSecretError{
-				Msg: fmt.Sprintf("%s, ignorable registries used for pull secret validation: %+v",
-					err.Error(), ignorableRegistries),
-			}
+		if authStillRequired != nil {
+			return &PullSecretError{Msg: fmt.Sprintf("pull secret must contain auth for %q", registry)}
 		}
 	}
-
 	return nil
 }
 
-// getRegistryAuthStatus takes a release image reference and a set of ignorarble registries,
-// and returns the image's registry if it requires authentication and it is not ignorable
-func getRegistryAuthStatus(ignorableImages map[string]bool, image string) (*string, error) {
+// getRegistryAuthStatus takes a release image reference and a set of ignorable registries,
+// and returns the image's registry if it requires authentication
+func getRegistryAuthStatus(ignorableRegistries map[string]bool, image string) (*string, error) {
 	if image == "" {
 		return nil, nil
 	}
 
-	registry, err := ParseRegistry(image)
+	registry, err := ParseFullRegistry(image)
 	if err != nil {
 		return nil, errors.Wrapf(err, "error occurred while trying to parse the registry out of '%s'", image)
 	}
 
-	if (registry == dockerHubRegistry && ignorableImages[dockerHubLegacyAuth]) || ignorableImages[registry] {
+	if findFullRegistryInSet(registry, ignorableRegistries) {
 		return nil, nil
 	}
-
 	return &registry, nil
 }
+
+// findFullRegistryInSet takes a full image registry (base domain + path) and a set of registries
+// and checks if the image registry exists in the set by first checking if the full registry name
+// is in the set. If it isn't, it truncates the path of the registry and checks the remainder
+// against the set. It continues to do this all the way to the base domain of the registry.
+// E.g. quay.io/test/image first checks for quay.io/test/image, then quay.io/test, and finally quay.io
+// If any of these exist in the set, this function will return true
+func findFullRegistryInSet(registry string, set map[string]bool) bool {
+	if isRegistryInSet(registry, set) {
+		return true
+	}
+	for slash := strings.LastIndex(registry, "/"); slash > 0; slash = strings.LastIndex(registry, "/") {
+		registry = registry[:slash]
+		if isRegistryInSet(registry, set) {
+			return true
+		}
+	}
+
+	return false
+}
+
+func isRegistryInSet(registry string, set map[string]bool) bool {
+	// Both "docker.io" and "https://index.docker.io/v1/" are acceptable for DockerHub login
+	if registry == dockerHubRegistry {
+		if _, ok := set[dockerHubLegacyAuth]; ok {
+			return true
+		}
+	}
+
+	// We add auth for stage registry automatically
+	return registry == stageRegistry || set[registry]
+}
diff --git a/internal/cluster/validations/validation_test.go b/internal/cluster/validations/validation_test.go
index a3d657a6f79e..6ad0ed540c99 100644
--- a/internal/cluster/validations/validation_test.go
+++ b/internal/cluster/validations/validation_test.go
@@ -25,6 +25,7 @@ const (
 	// dXNlcjpwYXNzOndvcmQK <-> user:pass:word
 	validPullSecretWithCIToken = "{\"auths\":{\"cloud.openshift.com\":{\"auth\":\"dXNlcjpwYXNzd29yZAo=\",\"email\":\"r@r.com\"},\"quay.io\":{\"auth\":\"dXNlcjpwYXNzd29yZAo=\",\"email\":\"r@r.com\"},\"registry.connect.redhat.com\":{\"auth\":\"dXNlcjpwYXNzd29yZAo=\",\"email\":\"r@r.com\"},\"registry.redhat.io\":{\"auth\":\"dXNlcjpwYXNzd29yZAo=\",\"email\":\"r@r.com\"}, \"registry.ci\":{\"auth\":\"dXNlcjpwYXNzd29yZAo=\",\"email\":\"r@r.com\"}}}"
 	validSecretFormat          = "{\"auths\":{\"cloud.openshift.com\":{\"auth\":\"dXNlcjpwYXNzd29yZAo=\",\"email\":\"r@r.com\"},\"quay.io\":{\"auth\":\"dXNlcjpwYXNzOndvcmQK\",\"email\":\"r@r.com\"},\"registry.connect.redhat.com\":{\"auth\":\"dXNlcjpwYXNzd29yZAo=\",\"email\":\"r@r.com\"},\"registry.redhat.io\":{\"auth\":\"dXNlcjpwYXNzd29yZAo=\",\"email\":\"r@r.com\"}}}"
+	validSecretWithRepo        = "{\"auths\":{\"cloud.openshift.com\":{\"auth\":\"dXNlcjpwYXNzd29yZAo=\",\"email\":\"r@r.com\"},\"quay.io/testing\":{\"auth\":\"dXNlcjpwYXNzd29yZAo=\",\"email\":\"r@r.com\"},\"registry.connect.redhat.com\":{\"auth\":\"dXNlcjpwYXNzd29yZAo=\",\"email\":\"r@r.com\"},\"registry.redhat.io\":{\"auth\":\"dXNlcjpwYXNzd29yZAo=\",\"email\":\"r@r.com\"}, \"registry.ci\":{\"auth\":\"dXNlcjpwYXNzd29yZAo=\",\"email\":\"r@r.com\"}}}"
 	invalidAuthFormat          = "{\"auths\":{\"cloud.openshift.com\":{\"auth\":\"dXNlcjpwYXNzd29yZAo=\",\"email\":\"r@r.com\"},\"quay.io\":{\"auth\":\"dXNlcjpwYXNzd29yZAo=\",\"email\":\"r@r.com\"},\"registry.connect.redhat.com\":{\"auth\":\"dXNlcjpwYXNzd29yZAo=\",\"email\":\"r@r.com\"},\"registry.redhat.io\":{\"auth\":\"afsdfasf==\",\"email\":\"r@r.com\"}}}"
 	invalidSecretFormat        = "{\"auths\":{\"cloud.openshift.com\":{\"key\":\"abcdef=\",\"email\":\"r@r.com\"},\"quay.io\":{\"auth\":\"adasfsdf=\",\"email\":\"r@r.com\"},\"registry.connect.redhat.com\":{\"auth\":\"tatastata==\",\"email\":\"r@r.com\"},\"registry.redhat.io\":{\"auth\":\"afsdfasf==\",\"email\":\"r@r.com\"}}}"
 	invalidStrSecretFormat     = "{\"auths\":{\"cloud.openshift.com\":{\"auth\":null,\"email\":null},\"quay.io\":{\"auth\":\"adasfsdf=\",\"email\":\"r@r.com\"},\"registry.connect.redhat.com\":{\"auth\":\"tatastata==\",\"email\":\"r@r.com\"},\"registry.redhat.io\":{\"auth\":\"afsdfasf==\",\"email\":\"r@r.com\"}}}"
@@ -152,7 +153,23 @@ var _ = Describe("Pull secret validation", func() {
 			Expect(err).ShouldNot(HaveOccurred())
 		})
 
-		It("pull secret is not accepted when release image is specified bit its registry credentials missing", func() {
+		It("pull secret accepted when release image is specified and the full path for the registry credentials exists", func() {
+			publicRegistries := map[string]bool{}
+			validator, err := NewPullSecretValidator(publicRegistries, authHandlerDisabled, "quay.io/testing/image-name:latest")
+			Expect(err).ShouldNot(HaveOccurred())
+			err = validator.ValidatePullSecret(additionalIgnoredRegistries, validSecretWithRepo, "", "registry.ci/test:latest")
+			Expect(err).ShouldNot(HaveOccurred())
+		})
+
+		It("pull secret is not accepted when release image specified is in a different repo than the registry credentials", func() {
+			publicRegistries := map[string]bool{}
+			validator, err := NewPullSecretValidator(publicRegistries, authHandlerDisabled, "quay.io/not-testing/image-name:latest")
+			Expect(err).ShouldNot(HaveOccurred())
+			err = validator.ValidatePullSecret(additionalIgnoredRegistries, validSecretWithRepo, "", "registry.ci/test:latest")
+			Expect(err).Should(HaveOccurred())
+		})
+
+		It("pull secret is not accepted when release image is specified but its registry credentials missing", func() {
 			publicRegistries := map[string]bool{}
 			validator, err := NewPullSecretValidator(publicRegistries, authHandlerDisabled, "quay.io/testing:latest")
 			Expect(err).ShouldNot(HaveOccurred())
@@ -195,6 +212,7 @@ var _ = Describe("Pull secret validation", func() {
 			Expect(err).Should(HaveOccurred())
 			Expect(err).Should(BeAssignableToTypeOf(&PullSecretError{}))
 		})
+
 	})
 })
 
@@ -406,8 +424,8 @@ var _ = Describe("Get registries", func() {
 		registries, err := getRegistriesWithAuth(ignorableImages, images...)
 		Expect(err).ShouldNot(HaveOccurred())
 		Expect(*registries).Should(HaveLen(2))
-		Expect(*registries).Should(HaveKey("registry.redhat.io"))
-		Expect(*registries).Should(HaveKey("quay.io"))
+		Expect(*registries).Should(HaveKey("registry.redhat.io/fedora"))
+		Expect(*registries).Should(HaveKey("quay.io/example/assisted-service"))
 	})
 
 	It("multiple images with same registry result in one auth entry", func() {
@@ -415,7 +433,7 @@ var _ = Describe("Get registries", func() {
 		registries, err := getRegistriesWithAuth(ignorableImages, images...)
 		Expect(err).ShouldNot(HaveOccurred())
 		Expect(*registries).Should(HaveLen(1))
-		Expect(*registries).Should(HaveKey("quay.io"))
+		Expect(*registries).Should(HaveKey("quay.io/example/assisted-service"))
 	})
 
 	It("port preserved in image registry", func() {
@@ -423,7 +441,7 @@ var _ = Describe("Get registries", func() {
 		registries, err := getRegistriesWithAuth(ignorableImages, images...)
 		Expect(err).ShouldNot(HaveOccurred())
 		Expect(*registries).Should(HaveLen(1))
-		Expect(*registries).Should(HaveKey("localhost:5000"))
+		Expect(*registries).Should(HaveKey("localhost:5000/private/service"))
 	})
 
 	It("empty registry is replaced with official docker registry", func() {
@@ -431,7 +449,7 @@ var _ = Describe("Get registries", func() {
 		registries, err := getRegistriesWithAuth(ignorableImages, images...)
 		Expect(err).ShouldNot(HaveOccurred())
 		Expect(*registries).Should(HaveLen(1))
-		Expect(*registries).Should(HaveKey(dockerHubRegistry))
+		Expect(*registries).Should(HaveKey(fmt.Sprintf("%s/private/service", dockerHubRegistry)))
 	})
 
 	It("registries omitted when in ignore list with comma (,) separator", func() {
@@ -443,7 +461,7 @@ var _ = Describe("Get registries", func() {
 		registries, err := getRegistriesWithAuth(ignorableImages, images...)
 		Expect(err).ShouldNot(HaveOccurred())
 		Expect(*registries).Should(HaveLen(1))
-		Expect(*registries).Should(HaveKey("registry.redhat.io"))
+		Expect(*registries).Should(HaveKey("registry.redhat.io/fedora"))
 	})
 
 	It("all multiple entries from the same registries omitted when in ingore list", func() {
diff --git a/internal/cluster/validations/validations.go b/internal/cluster/validations/validations.go
index 49fa1405377e..b5100e474274 100644
--- a/internal/cluster/validations/validations.go
+++ b/internal/cluster/validations/validations.go
@@ -11,7 +11,7 @@ import (
 	"regexp"
 	"strings"
 
-	"github.com/docker/distribution/reference"
+	"github.com/distribution/reference"
 	"github.com/go-openapi/strfmt"
 	"github.com/go-openapi/swag"
 	"github.com/hashicorp/go-multierror"
@@ -135,6 +135,16 @@ func ParseRegistry(image string) (string, error) {
 	return reference.Domain(parsed), nil
 }
 
+// ParseFullRegistry extracts the registry (base domain) + path from a full image name, or returns
+// the default (docker.io) with the path appended if the name does not start with a registry.
+func ParseFullRegistry(image string) (string, error) {
+	parsed, err := reference.ParseNormalizedNamed(strings.TrimSpace(image))
+	if err != nil {
+		return "", err
+	}
+	return reference.TrimNamed(parsed).Name(), nil
+}
+
 // ParseBaseRegistry extracts the base domain for a registry.
 func ParseBaseRegistry(registry string) string {
 	registryParts := strings.Split(strings.TrimSpace(registry), "/")
diff --git a/internal/common/common.go b/internal/common/common.go
index 105c12bd6073..1f6910724965 100644
--- a/internal/common/common.go
+++ b/internal/common/common.go
@@ -9,7 +9,7 @@ import (
 	"reflect"
 	"strings"
 
-	"github.com/docker/distribution/reference"
+	"github.com/distribution/reference"
 	"github.com/go-openapi/strfmt"
 	"github.com/go-openapi/swag"
 	yamlpatch "github.com/krishicks/yaml-patch"
diff --git a/vendor/github.com/distribution/reference/README.md b/vendor/github.com/distribution/reference/README.md
index e2531e49c4f9..172a02e0b3b5 100644
--- a/vendor/github.com/distribution/reference/README.md
+++ b/vendor/github.com/distribution/reference/README.md
@@ -10,7 +10,7 @@ Go library to handle references to container images.
 [![codecov](https://codecov.io/gh/distribution/reference/branch/main/graph/badge.svg)](https://codecov.io/gh/distribution/reference)
 [![FOSSA Status](https://app.fossa.com/api/projects/custom%2B162%2Fgithub.com%2Fdistribution%2Freference.svg?type=shield)](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Fdistribution%2Freference?ref=badge_shield)
 
-This repository contains a library for handling refrences to container images held in container registries. Please see [godoc](https://pkg.go.dev/github.com/distribution/reference) for details.
+This repository contains a library for handling references to container images held in container registries. Please see [godoc](https://pkg.go.dev/github.com/distribution/reference) for details.
 
 ## Contribution
 
diff --git a/vendor/github.com/distribution/reference/normalize.go b/vendor/github.com/distribution/reference/normalize.go
index a30229d01bb4..f4128314c150 100644
--- a/vendor/github.com/distribution/reference/normalize.go
+++ b/vendor/github.com/distribution/reference/normalize.go
@@ -123,20 +123,51 @@ func ParseDockerRef(ref string) (Named, error) {
 // splitDockerDomain splits a repository name to domain and remote-name.
 // If no valid domain is found, the default domain is used. Repository name
 // needs to be already validated before.
-func splitDockerDomain(name string) (domain, remainder string) {
-	i := strings.IndexRune(name, '/')
-	if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != localhost && strings.ToLower(name[:i]) == name[:i]) {
-		domain, remainder = defaultDomain, name
-	} else {
-		domain, remainder = name[:i], name[i+1:]
-	}
-	if domain == legacyDefaultDomain {
-		domain = defaultDomain
-	}
-	if domain == defaultDomain && !strings.ContainsRune(remainder, '/') {
-		remainder = officialRepoPrefix + remainder
-	}
-	return
+func splitDockerDomain(name string) (domain, remoteName string) {
+	maybeDomain, maybeRemoteName, ok := strings.Cut(name, "/")
+	if !ok {
+		// Fast-path for single element ("familiar" names), such as "ubuntu"
+		// or "ubuntu:latest". Familiar names must be handled separately, to
+		// prevent them from being handled as "hostname:port".
+		//
+		// Canonicalize them as "docker.io/library/name[:tag]"
+
+		// FIXME(thaJeztah): account for bare "localhost" or "example.com" names, which SHOULD be considered a domain.
+		return defaultDomain, officialRepoPrefix + name
+	}
+
+	switch {
+	case maybeDomain == localhost:
+		// localhost is a reserved namespace and always considered a domain.
+		domain, remoteName = maybeDomain, maybeRemoteName
+	case maybeDomain == legacyDefaultDomain:
+		// canonicalize the Docker Hub and legacy "Docker Index" domains.
+		domain, remoteName = defaultDomain, maybeRemoteName
+	case strings.ContainsAny(maybeDomain, ".:"):
+		// Likely a domain or IP-address:
+		//
+		// - contains a "." (e.g., "example.com" or "127.0.0.1")
+		// - contains a ":" (e.g., "example:5000", "::1", or "[::1]:5000")
+		domain, remoteName = maybeDomain, maybeRemoteName
+	case strings.ToLower(maybeDomain) != maybeDomain:
+		// Uppercase namespaces are not allowed, so if the first element
+		// is not lowercase, we assume it to be a domain-name.
+		domain, remoteName = maybeDomain, maybeRemoteName
+	default:
+		// None of the above: it's not a domain, so use the default, and
+		// use the name input the remote-name.
+		domain, remoteName = defaultDomain, name
+	}
+
+	if domain == defaultDomain && !strings.ContainsRune(remoteName, '/') {
+		// Canonicalize "familiar" names, but only on Docker Hub, not
+		// on other domains:
+		//
+		// "docker.io/ubuntu[:tag]" => "docker.io/library/ubuntu[:tag]"
+		remoteName = officialRepoPrefix + remoteName
+	}
+
+	return domain, remoteName
 }
 
 // familiarizeName returns a shortened version of the name familiar
diff --git a/vendor/github.com/distribution/reference/reference.go b/vendor/github.com/distribution/reference/reference.go
index e98c44daa2a8..900398bde7d7 100644
--- a/vendor/github.com/distribution/reference/reference.go
+++ b/vendor/github.com/distribution/reference/reference.go
@@ -35,8 +35,13 @@ import (
 )
 
 const (
+	// RepositoryNameTotalLengthMax is the maximum total number of characters in a repository name.
+	RepositoryNameTotalLengthMax = 255
+
 	// NameTotalLengthMax is the maximum total number of characters in a repository name.
-	NameTotalLengthMax = 255
+	//
+	// Deprecated: use [RepositoryNameTotalLengthMax] instead.
+	NameTotalLengthMax = RepositoryNameTotalLengthMax
 )
 
 var (
@@ -55,8 +60,8 @@ var (
 	// ErrNameEmpty is returned for empty, invalid repository names.
 	ErrNameEmpty = errors.New("repository name must have at least one component")
 
-	// ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
-	ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax)
+	// ErrNameTooLong is returned when a repository name is longer than RepositoryNameTotalLengthMax.
+	ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", RepositoryNameTotalLengthMax)
 
 	// ErrNameNotCanonical is returned when a name is not canonical.
 	ErrNameNotCanonical = errors.New("repository name must be canonical")
@@ -165,6 +170,9 @@ func Path(named Named) (name string) {
 	return path
 }
 
+// splitDomain splits a named reference into a hostname and path string.
+// If no valid hostname is found, the hostname is empty and the full value
+// is returned as name
 func splitDomain(name string) (string, string) {
 	match := anchoredNameRegexp.FindStringSubmatch(name)
 	if len(match) != 3 {
@@ -173,19 +181,6 @@ func splitDomain(name string) (string, string) {
 	return match[1], match[2]
 }
 
-// SplitHostname splits a named reference into a
-// hostname and name string. If no valid hostname is
-// found, the hostname is empty and the full value
-// is returned as name
-//
-// Deprecated: Use [Domain] or [Path].
-func SplitHostname(named Named) (string, string) {
-	if r, ok := named.(namedRepository); ok {
-		return r.Domain(), r.Path()
-	}
-	return splitDomain(named.Name())
-}
-
 // Parse parses s and returns a syntactically valid Reference.
 // If an error was encountered it is returned, along with a nil Reference.
 func Parse(s string) (Reference, error) {
@@ -200,10 +195,6 @@ func Parse(s string) (Reference, error) {
 		return nil, ErrReferenceInvalidFormat
 	}
 
-	if len(matches[1]) > NameTotalLengthMax {
-		return nil, ErrNameTooLong
-	}
-
 	var repo repository
 
 	nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1])
@@ -215,6 +206,10 @@ func Parse(s string) (Reference, error) {
 		repo.path = matches[1]
 	}
 
+	if len(repo.path) > RepositoryNameTotalLengthMax {
+		return nil, ErrNameTooLong
+	}
+
 	ref := reference{
 		namedRepository: repo,
 		tag:             matches[2],
@@ -253,14 +248,15 @@ func ParseNamed(s string) (Named, error) {
 // WithName returns a named object representing the given string. If the input
 // is invalid ErrReferenceInvalidFormat will be returned.
 func WithName(name string) (Named, error) {
-	if len(name) > NameTotalLengthMax {
-		return nil, ErrNameTooLong
-	}
-
 	match := anchoredNameRegexp.FindStringSubmatch(name)
 	if match == nil || len(match) != 3 {
 		return nil, ErrReferenceInvalidFormat
 	}
+
+	if len(match[2]) > RepositoryNameTotalLengthMax {
+		return nil, ErrNameTooLong
+	}
+
 	return repository{
 		domain: match[1],
 		path:   match[2],
diff --git a/vendor/github.com/docker/distribution/LICENSE b/vendor/github.com/docker/distribution/LICENSE
deleted file mode 100644
index e06d2081865a..000000000000
--- a/vendor/github.com/docker/distribution/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
-Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "{}"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright {yyyy} {name of copyright owner}
-
-   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.
-
diff --git a/vendor/github.com/docker/distribution/digestset/set.go b/vendor/github.com/docker/distribution/digestset/set.go
deleted file mode 100644
index 71327dca7209..000000000000
--- a/vendor/github.com/docker/distribution/digestset/set.go
+++ /dev/null
@@ -1,247 +0,0 @@
-package digestset
-
-import (
-	"errors"
-	"sort"
-	"strings"
-	"sync"
-
-	digest "github.com/opencontainers/go-digest"
-)
-
-var (
-	// ErrDigestNotFound is used when a matching digest
-	// could not be found in a set.
-	ErrDigestNotFound = errors.New("digest not found")
-
-	// ErrDigestAmbiguous is used when multiple digests
-	// are found in a set. None of the matching digests
-	// should be considered valid matches.
-	ErrDigestAmbiguous = errors.New("ambiguous digest string")
-)
-
-// Set is used to hold a unique set of digests which
-// may be easily referenced by easily  referenced by a string
-// representation of the digest as well as short representation.
-// The uniqueness of the short representation is based on other
-// digests in the set. If digests are omitted from this set,
-// collisions in a larger set may not be detected, therefore it
-// is important to always do short representation lookups on
-// the complete set of digests. To mitigate collisions, an
-// appropriately long short code should be used.
-type Set struct {
-	mutex   sync.RWMutex
-	entries digestEntries
-}
-
-// NewSet creates an empty set of digests
-// which may have digests added.
-func NewSet() *Set {
-	return &Set{
-		entries: digestEntries{},
-	}
-}
-
-// checkShortMatch checks whether two digests match as either whole
-// values or short values. This function does not test equality,
-// rather whether the second value could match against the first
-// value.
-func checkShortMatch(alg digest.Algorithm, hex, shortAlg, shortHex string) bool {
-	if len(hex) == len(shortHex) {
-		if hex != shortHex {
-			return false
-		}
-		if len(shortAlg) > 0 && string(alg) != shortAlg {
-			return false
-		}
-	} else if !strings.HasPrefix(hex, shortHex) {
-		return false
-	} else if len(shortAlg) > 0 && string(alg) != shortAlg {
-		return false
-	}
-	return true
-}
-
-// Lookup looks for a digest matching the given string representation.
-// If no digests could be found ErrDigestNotFound will be returned
-// with an empty digest value. If multiple matches are found
-// ErrDigestAmbiguous will be returned with an empty digest value.
-func (dst *Set) Lookup(d string) (digest.Digest, error) {
-	dst.mutex.RLock()
-	defer dst.mutex.RUnlock()
-	if len(dst.entries) == 0 {
-		return "", ErrDigestNotFound
-	}
-	var (
-		searchFunc func(int) bool
-		alg        digest.Algorithm
-		hex        string
-	)
-	dgst, err := digest.Parse(d)
-	if err == digest.ErrDigestInvalidFormat {
-		hex = d
-		searchFunc = func(i int) bool {
-			return dst.entries[i].val >= d
-		}
-	} else {
-		hex = dgst.Hex()
-		alg = dgst.Algorithm()
-		searchFunc = func(i int) bool {
-			if dst.entries[i].val == hex {
-				return dst.entries[i].alg >= alg
-			}
-			return dst.entries[i].val >= hex
-		}
-	}
-	idx := sort.Search(len(dst.entries), searchFunc)
-	if idx == len(dst.entries) || !checkShortMatch(dst.entries[idx].alg, dst.entries[idx].val, string(alg), hex) {
-		return "", ErrDigestNotFound
-	}
-	if dst.entries[idx].alg == alg && dst.entries[idx].val == hex {
-		return dst.entries[idx].digest, nil
-	}
-	if idx+1 < len(dst.entries) && checkShortMatch(dst.entries[idx+1].alg, dst.entries[idx+1].val, string(alg), hex) {
-		return "", ErrDigestAmbiguous
-	}
-
-	return dst.entries[idx].digest, nil
-}
-
-// Add adds the given digest to the set. An error will be returned
-// if the given digest is invalid. If the digest already exists in the
-// set, this operation will be a no-op.
-func (dst *Set) Add(d digest.Digest) error {
-	if err := d.Validate(); err != nil {
-		return err
-	}
-	dst.mutex.Lock()
-	defer dst.mutex.Unlock()
-	entry := &digestEntry{alg: d.Algorithm(), val: d.Hex(), digest: d}
-	searchFunc := func(i int) bool {
-		if dst.entries[i].val == entry.val {
-			return dst.entries[i].alg >= entry.alg
-		}
-		return dst.entries[i].val >= entry.val
-	}
-	idx := sort.Search(len(dst.entries), searchFunc)
-	if idx == len(dst.entries) {
-		dst.entries = append(dst.entries, entry)
-		return nil
-	} else if dst.entries[idx].digest == d {
-		return nil
-	}
-
-	entries := append(dst.entries, nil)
-	copy(entries[idx+1:], entries[idx:len(entries)-1])
-	entries[idx] = entry
-	dst.entries = entries
-	return nil
-}
-
-// Remove removes the given digest from the set. An err will be
-// returned if the given digest is invalid. If the digest does
-// not exist in the set, this operation will be a no-op.
-func (dst *Set) Remove(d digest.Digest) error {
-	if err := d.Validate(); err != nil {
-		return err
-	}
-	dst.mutex.Lock()
-	defer dst.mutex.Unlock()
-	entry := &digestEntry{alg: d.Algorithm(), val: d.Hex(), digest: d}
-	searchFunc := func(i int) bool {
-		if dst.entries[i].val == entry.val {
-			return dst.entries[i].alg >= entry.alg
-		}
-		return dst.entries[i].val >= entry.val
-	}
-	idx := sort.Search(len(dst.entries), searchFunc)
-	// Not found if idx is after or value at idx is not digest
-	if idx == len(dst.entries) || dst.entries[idx].digest != d {
-		return nil
-	}
-
-	entries := dst.entries
-	copy(entries[idx:], entries[idx+1:])
-	entries = entries[:len(entries)-1]
-	dst.entries = entries
-
-	return nil
-}
-
-// All returns all the digests in the set
-func (dst *Set) All() []digest.Digest {
-	dst.mutex.RLock()
-	defer dst.mutex.RUnlock()
-	retValues := make([]digest.Digest, len(dst.entries))
-	for i := range dst.entries {
-		retValues[i] = dst.entries[i].digest
-	}
-
-	return retValues
-}
-
-// ShortCodeTable returns a map of Digest to unique short codes. The
-// length represents the minimum value, the maximum length may be the
-// entire value of digest if uniqueness cannot be achieved without the
-// full value. This function will attempt to make short codes as short
-// as possible to be unique.
-func ShortCodeTable(dst *Set, length int) map[digest.Digest]string {
-	dst.mutex.RLock()
-	defer dst.mutex.RUnlock()
-	m := make(map[digest.Digest]string, len(dst.entries))
-	l := length
-	resetIdx := 0
-	for i := 0; i < len(dst.entries); i++ {
-		var short string
-		extended := true
-		for extended {
-			extended = false
-			if len(dst.entries[i].val) <= l {
-				short = dst.entries[i].digest.String()
-			} else {
-				short = dst.entries[i].val[:l]
-				for j := i + 1; j < len(dst.entries); j++ {
-					if checkShortMatch(dst.entries[j].alg, dst.entries[j].val, "", short) {
-						if j > resetIdx {
-							resetIdx = j
-						}
-						extended = true
-					} else {
-						break
-					}
-				}
-				if extended {
-					l++
-				}
-			}
-		}
-		m[dst.entries[i].digest] = short
-		if i >= resetIdx {
-			l = length
-		}
-	}
-	return m
-}
-
-type digestEntry struct {
-	alg    digest.Algorithm
-	val    string
-	digest digest.Digest
-}
-
-type digestEntries []*digestEntry
-
-func (d digestEntries) Len() int {
-	return len(d)
-}
-
-func (d digestEntries) Less(i, j int) bool {
-	if d[i].val != d[j].val {
-		return d[i].val < d[j].val
-	}
-	return d[i].alg < d[j].alg
-}
-
-func (d digestEntries) Swap(i, j int) {
-	d[i], d[j] = d[j], d[i]
-}
diff --git a/vendor/github.com/docker/distribution/reference/helpers.go b/vendor/github.com/docker/distribution/reference/helpers.go
deleted file mode 100644
index 978df7eabbf1..000000000000
--- a/vendor/github.com/docker/distribution/reference/helpers.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package reference
-
-import "path"
-
-// IsNameOnly returns true if reference only contains a repo name.
-func IsNameOnly(ref Named) bool {
-	if _, ok := ref.(NamedTagged); ok {
-		return false
-	}
-	if _, ok := ref.(Canonical); ok {
-		return false
-	}
-	return true
-}
-
-// FamiliarName returns the familiar name string
-// for the given named, familiarizing if needed.
-func FamiliarName(ref Named) string {
-	if nn, ok := ref.(normalizedNamed); ok {
-		return nn.Familiar().Name()
-	}
-	return ref.Name()
-}
-
-// FamiliarString returns the familiar string representation
-// for the given reference, familiarizing if needed.
-func FamiliarString(ref Reference) string {
-	if nn, ok := ref.(normalizedNamed); ok {
-		return nn.Familiar().String()
-	}
-	return ref.String()
-}
-
-// FamiliarMatch reports whether ref matches the specified pattern.
-// See https://godoc.org/path#Match for supported patterns.
-func FamiliarMatch(pattern string, ref Reference) (bool, error) {
-	matched, err := path.Match(pattern, FamiliarString(ref))
-	if namedRef, isNamed := ref.(Named); isNamed && !matched {
-		matched, _ = path.Match(pattern, FamiliarName(namedRef))
-	}
-	return matched, err
-}
diff --git a/vendor/github.com/docker/distribution/reference/normalize.go b/vendor/github.com/docker/distribution/reference/normalize.go
deleted file mode 100644
index b3dfb7a6d7e1..000000000000
--- a/vendor/github.com/docker/distribution/reference/normalize.go
+++ /dev/null
@@ -1,199 +0,0 @@
-package reference
-
-import (
-	"errors"
-	"fmt"
-	"strings"
-
-	"github.com/docker/distribution/digestset"
-	"github.com/opencontainers/go-digest"
-)
-
-var (
-	legacyDefaultDomain = "index.docker.io"
-	defaultDomain       = "docker.io"
-	officialRepoName    = "library"
-	defaultTag          = "latest"
-)
-
-// normalizedNamed represents a name which has been
-// normalized and has a familiar form. A familiar name
-// is what is used in Docker UI. An example normalized
-// name is "docker.io/library/ubuntu" and corresponding
-// familiar name of "ubuntu".
-type normalizedNamed interface {
-	Named
-	Familiar() Named
-}
-
-// ParseNormalizedNamed parses a string into a named reference
-// transforming a familiar name from Docker UI to a fully
-// qualified reference. If the value may be an identifier
-// use ParseAnyReference.
-func ParseNormalizedNamed(s string) (Named, error) {
-	if ok := anchoredIdentifierRegexp.MatchString(s); ok {
-		return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s)
-	}
-	domain, remainder := splitDockerDomain(s)
-	var remoteName string
-	if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 {
-		remoteName = remainder[:tagSep]
-	} else {
-		remoteName = remainder
-	}
-	if strings.ToLower(remoteName) != remoteName {
-		return nil, errors.New("invalid reference format: repository name must be lowercase")
-	}
-
-	ref, err := Parse(domain + "/" + remainder)
-	if err != nil {
-		return nil, err
-	}
-	named, isNamed := ref.(Named)
-	if !isNamed {
-		return nil, fmt.Errorf("reference %s has no name", ref.String())
-	}
-	return named, nil
-}
-
-// ParseDockerRef normalizes the image reference following the docker convention. This is added
-// mainly for backward compatibility.
-// The reference returned can only be either tagged or digested. For reference contains both tag
-// and digest, the function returns digested reference, e.g. docker.io/library/busybox:latest@
-// sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa will be returned as
-// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa.
-func ParseDockerRef(ref string) (Named, error) {
-	named, err := ParseNormalizedNamed(ref)
-	if err != nil {
-		return nil, err
-	}
-	if _, ok := named.(NamedTagged); ok {
-		if canonical, ok := named.(Canonical); ok {
-			// The reference is both tagged and digested, only
-			// return digested.
-			newNamed, err := WithName(canonical.Name())
-			if err != nil {
-				return nil, err
-			}
-			newCanonical, err := WithDigest(newNamed, canonical.Digest())
-			if err != nil {
-				return nil, err
-			}
-			return newCanonical, nil
-		}
-	}
-	return TagNameOnly(named), nil
-}
-
-// splitDockerDomain splits a repository name to domain and remotename string.
-// If no valid domain is found, the default domain is used. Repository name
-// needs to be already validated before.
-func splitDockerDomain(name string) (domain, remainder string) {
-	i := strings.IndexRune(name, '/')
-	if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") {
-		domain, remainder = defaultDomain, name
-	} else {
-		domain, remainder = name[:i], name[i+1:]
-	}
-	if domain == legacyDefaultDomain {
-		domain = defaultDomain
-	}
-	if domain == defaultDomain && !strings.ContainsRune(remainder, '/') {
-		remainder = officialRepoName + "/" + remainder
-	}
-	return
-}
-
-// familiarizeName returns a shortened version of the name familiar
-// to to the Docker UI. Familiar names have the default domain
-// "docker.io" and "library/" repository prefix removed.
-// For example, "docker.io/library/redis" will have the familiar
-// name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp".
-// Returns a familiarized named only reference.
-func familiarizeName(named namedRepository) repository {
-	repo := repository{
-		domain: named.Domain(),
-		path:   named.Path(),
-	}
-
-	if repo.domain == defaultDomain {
-		repo.domain = ""
-		// Handle official repositories which have the pattern "library/<official repo name>"
-		if split := strings.Split(repo.path, "/"); len(split) == 2 && split[0] == officialRepoName {
-			repo.path = split[1]
-		}
-	}
-	return repo
-}
-
-func (r reference) Familiar() Named {
-	return reference{
-		namedRepository: familiarizeName(r.namedRepository),
-		tag:             r.tag,
-		digest:          r.digest,
-	}
-}
-
-func (r repository) Familiar() Named {
-	return familiarizeName(r)
-}
-
-func (t taggedReference) Familiar() Named {
-	return taggedReference{
-		namedRepository: familiarizeName(t.namedRepository),
-		tag:             t.tag,
-	}
-}
-
-func (c canonicalReference) Familiar() Named {
-	return canonicalReference{
-		namedRepository: familiarizeName(c.namedRepository),
-		digest:          c.digest,
-	}
-}
-
-// TagNameOnly adds the default tag "latest" to a reference if it only has
-// a repo name.
-func TagNameOnly(ref Named) Named {
-	if IsNameOnly(ref) {
-		namedTagged, err := WithTag(ref, defaultTag)
-		if err != nil {
-			// Default tag must be valid, to create a NamedTagged
-			// type with non-validated input the WithTag function
-			// should be used instead
-			panic(err)
-		}
-		return namedTagged
-	}
-	return ref
-}
-
-// ParseAnyReference parses a reference string as a possible identifier,
-// full digest, or familiar name.
-func ParseAnyReference(ref string) (Reference, error) {
-	if ok := anchoredIdentifierRegexp.MatchString(ref); ok {
-		return digestReference("sha256:" + ref), nil
-	}
-	if dgst, err := digest.Parse(ref); err == nil {
-		return digestReference(dgst), nil
-	}
-
-	return ParseNormalizedNamed(ref)
-}
-
-// ParseAnyReferenceWithSet parses a reference string as a possible short
-// identifier to be matched in a digest set, a full digest, or familiar name.
-func ParseAnyReferenceWithSet(ref string, ds *digestset.Set) (Reference, error) {
-	if ok := anchoredShortIdentifierRegexp.MatchString(ref); ok {
-		dgst, err := ds.Lookup(ref)
-		if err == nil {
-			return digestReference(dgst), nil
-		}
-	} else {
-		if dgst, err := digest.Parse(ref); err == nil {
-			return digestReference(dgst), nil
-		}
-	}
-
-	return ParseNormalizedNamed(ref)
-}
diff --git a/vendor/github.com/docker/distribution/reference/reference.go b/vendor/github.com/docker/distribution/reference/reference.go
deleted file mode 100644
index b7cd00b0d68e..000000000000
--- a/vendor/github.com/docker/distribution/reference/reference.go
+++ /dev/null
@@ -1,433 +0,0 @@
-// Package reference provides a general type to represent any way of referencing images within the registry.
-// Its main purpose is to abstract tags and digests (content-addressable hash).
-//
-// Grammar
-//
-//	reference                       := name [ ":" tag ] [ "@" digest ]
-//	name                            := [domain '/'] path-component ['/' path-component]*
-//	domain                          := domain-component ['.' domain-component]* [':' port-number]
-//	domain-component                := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
-//	port-number                     := /[0-9]+/
-//	path-component                  := alpha-numeric [separator alpha-numeric]*
-//	alpha-numeric                   := /[a-z0-9]+/
-//	separator                       := /[_.]|__|[-]*/
-//
-//	tag                             := /[\w][\w.-]{0,127}/
-//
-//	digest                          := digest-algorithm ":" digest-hex
-//	digest-algorithm                := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]*
-//	digest-algorithm-separator      := /[+.-_]/
-//	digest-algorithm-component      := /[A-Za-z][A-Za-z0-9]*/
-//	digest-hex                      := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value
-//
-//	identifier                      := /[a-f0-9]{64}/
-//	short-identifier                := /[a-f0-9]{6,64}/
-package reference
-
-import (
-	"errors"
-	"fmt"
-	"strings"
-
-	"github.com/opencontainers/go-digest"
-)
-
-const (
-	// NameTotalLengthMax is the maximum total number of characters in a repository name.
-	NameTotalLengthMax = 255
-)
-
-var (
-	// ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference.
-	ErrReferenceInvalidFormat = errors.New("invalid reference format")
-
-	// ErrTagInvalidFormat represents an error while trying to parse a string as a tag.
-	ErrTagInvalidFormat = errors.New("invalid tag format")
-
-	// ErrDigestInvalidFormat represents an error while trying to parse a string as a tag.
-	ErrDigestInvalidFormat = errors.New("invalid digest format")
-
-	// ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters.
-	ErrNameContainsUppercase = errors.New("repository name must be lowercase")
-
-	// ErrNameEmpty is returned for empty, invalid repository names.
-	ErrNameEmpty = errors.New("repository name must have at least one component")
-
-	// ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
-	ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax)
-
-	// ErrNameNotCanonical is returned when a name is not canonical.
-	ErrNameNotCanonical = errors.New("repository name must be canonical")
-)
-
-// Reference is an opaque object reference identifier that may include
-// modifiers such as a hostname, name, tag, and digest.
-type Reference interface {
-	// String returns the full reference
-	String() string
-}
-
-// Field provides a wrapper type for resolving correct reference types when
-// working with encoding.
-type Field struct {
-	reference Reference
-}
-
-// AsField wraps a reference in a Field for encoding.
-func AsField(reference Reference) Field {
-	return Field{reference}
-}
-
-// Reference unwraps the reference type from the field to
-// return the Reference object. This object should be
-// of the appropriate type to further check for different
-// reference types.
-func (f Field) Reference() Reference {
-	return f.reference
-}
-
-// MarshalText serializes the field to byte text which
-// is the string of the reference.
-func (f Field) MarshalText() (p []byte, err error) {
-	return []byte(f.reference.String()), nil
-}
-
-// UnmarshalText parses text bytes by invoking the
-// reference parser to ensure the appropriately
-// typed reference object is wrapped by field.
-func (f *Field) UnmarshalText(p []byte) error {
-	r, err := Parse(string(p))
-	if err != nil {
-		return err
-	}
-
-	f.reference = r
-	return nil
-}
-
-// Named is an object with a full name
-type Named interface {
-	Reference
-	Name() string
-}
-
-// Tagged is an object which has a tag
-type Tagged interface {
-	Reference
-	Tag() string
-}
-
-// NamedTagged is an object including a name and tag.
-type NamedTagged interface {
-	Named
-	Tag() string
-}
-
-// Digested is an object which has a digest
-// in which it can be referenced by
-type Digested interface {
-	Reference
-	Digest() digest.Digest
-}
-
-// Canonical reference is an object with a fully unique
-// name including a name with domain and digest
-type Canonical interface {
-	Named
-	Digest() digest.Digest
-}
-
-// namedRepository is a reference to a repository with a name.
-// A namedRepository has both domain and path components.
-type namedRepository interface {
-	Named
-	Domain() string
-	Path() string
-}
-
-// Domain returns the domain part of the Named reference
-func Domain(named Named) string {
-	if r, ok := named.(namedRepository); ok {
-		return r.Domain()
-	}
-	domain, _ := splitDomain(named.Name())
-	return domain
-}
-
-// Path returns the name without the domain part of the Named reference
-func Path(named Named) (name string) {
-	if r, ok := named.(namedRepository); ok {
-		return r.Path()
-	}
-	_, path := splitDomain(named.Name())
-	return path
-}
-
-func splitDomain(name string) (string, string) {
-	match := anchoredNameRegexp.FindStringSubmatch(name)
-	if len(match) != 3 {
-		return "", name
-	}
-	return match[1], match[2]
-}
-
-// SplitHostname splits a named reference into a
-// hostname and name string. If no valid hostname is
-// found, the hostname is empty and the full value
-// is returned as name
-// DEPRECATED: Use Domain or Path
-func SplitHostname(named Named) (string, string) {
-	if r, ok := named.(namedRepository); ok {
-		return r.Domain(), r.Path()
-	}
-	return splitDomain(named.Name())
-}
-
-// Parse parses s and returns a syntactically valid Reference.
-// If an error was encountered it is returned, along with a nil Reference.
-// NOTE: Parse will not handle short digests.
-func Parse(s string) (Reference, error) {
-	matches := ReferenceRegexp.FindStringSubmatch(s)
-	if matches == nil {
-		if s == "" {
-			return nil, ErrNameEmpty
-		}
-		if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil {
-			return nil, ErrNameContainsUppercase
-		}
-		return nil, ErrReferenceInvalidFormat
-	}
-
-	if len(matches[1]) > NameTotalLengthMax {
-		return nil, ErrNameTooLong
-	}
-
-	var repo repository
-
-	nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1])
-	if len(nameMatch) == 3 {
-		repo.domain = nameMatch[1]
-		repo.path = nameMatch[2]
-	} else {
-		repo.domain = ""
-		repo.path = matches[1]
-	}
-
-	ref := reference{
-		namedRepository: repo,
-		tag:             matches[2],
-	}
-	if matches[3] != "" {
-		var err error
-		ref.digest, err = digest.Parse(matches[3])
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	r := getBestReferenceType(ref)
-	if r == nil {
-		return nil, ErrNameEmpty
-	}
-
-	return r, nil
-}
-
-// ParseNamed parses s and returns a syntactically valid reference implementing
-// the Named interface. The reference must have a name and be in the canonical
-// form, otherwise an error is returned.
-// If an error was encountered it is returned, along with a nil Reference.
-// NOTE: ParseNamed will not handle short digests.
-func ParseNamed(s string) (Named, error) {
-	named, err := ParseNormalizedNamed(s)
-	if err != nil {
-		return nil, err
-	}
-	if named.String() != s {
-		return nil, ErrNameNotCanonical
-	}
-	return named, nil
-}
-
-// WithName returns a named object representing the given string. If the input
-// is invalid ErrReferenceInvalidFormat will be returned.
-func WithName(name string) (Named, error) {
-	if len(name) > NameTotalLengthMax {
-		return nil, ErrNameTooLong
-	}
-
-	match := anchoredNameRegexp.FindStringSubmatch(name)
-	if match == nil || len(match) != 3 {
-		return nil, ErrReferenceInvalidFormat
-	}
-	return repository{
-		domain: match[1],
-		path:   match[2],
-	}, nil
-}
-
-// WithTag combines the name from "name" and the tag from "tag" to form a
-// reference incorporating both the name and the tag.
-func WithTag(name Named, tag string) (NamedTagged, error) {
-	if !anchoredTagRegexp.MatchString(tag) {
-		return nil, ErrTagInvalidFormat
-	}
-	var repo repository
-	if r, ok := name.(namedRepository); ok {
-		repo.domain = r.Domain()
-		repo.path = r.Path()
-	} else {
-		repo.path = name.Name()
-	}
-	if canonical, ok := name.(Canonical); ok {
-		return reference{
-			namedRepository: repo,
-			tag:             tag,
-			digest:          canonical.Digest(),
-		}, nil
-	}
-	return taggedReference{
-		namedRepository: repo,
-		tag:             tag,
-	}, nil
-}
-
-// WithDigest combines the name from "name" and the digest from "digest" to form
-// a reference incorporating both the name and the digest.
-func WithDigest(name Named, digest digest.Digest) (Canonical, error) {
-	if !anchoredDigestRegexp.MatchString(digest.String()) {
-		return nil, ErrDigestInvalidFormat
-	}
-	var repo repository
-	if r, ok := name.(namedRepository); ok {
-		repo.domain = r.Domain()
-		repo.path = r.Path()
-	} else {
-		repo.path = name.Name()
-	}
-	if tagged, ok := name.(Tagged); ok {
-		return reference{
-			namedRepository: repo,
-			tag:             tagged.Tag(),
-			digest:          digest,
-		}, nil
-	}
-	return canonicalReference{
-		namedRepository: repo,
-		digest:          digest,
-	}, nil
-}
-
-// TrimNamed removes any tag or digest from the named reference.
-func TrimNamed(ref Named) Named {
-	domain, path := SplitHostname(ref)
-	return repository{
-		domain: domain,
-		path:   path,
-	}
-}
-
-func getBestReferenceType(ref reference) Reference {
-	if ref.Name() == "" {
-		// Allow digest only references
-		if ref.digest != "" {
-			return digestReference(ref.digest)
-		}
-		return nil
-	}
-	if ref.tag == "" {
-		if ref.digest != "" {
-			return canonicalReference{
-				namedRepository: ref.namedRepository,
-				digest:          ref.digest,
-			}
-		}
-		return ref.namedRepository
-	}
-	if ref.digest == "" {
-		return taggedReference{
-			namedRepository: ref.namedRepository,
-			tag:             ref.tag,
-		}
-	}
-
-	return ref
-}
-
-type reference struct {
-	namedRepository
-	tag    string
-	digest digest.Digest
-}
-
-func (r reference) String() string {
-	return r.Name() + ":" + r.tag + "@" + r.digest.String()
-}
-
-func (r reference) Tag() string {
-	return r.tag
-}
-
-func (r reference) Digest() digest.Digest {
-	return r.digest
-}
-
-type repository struct {
-	domain string
-	path   string
-}
-
-func (r repository) String() string {
-	return r.Name()
-}
-
-func (r repository) Name() string {
-	if r.domain == "" {
-		return r.path
-	}
-	return r.domain + "/" + r.path
-}
-
-func (r repository) Domain() string {
-	return r.domain
-}
-
-func (r repository) Path() string {
-	return r.path
-}
-
-type digestReference digest.Digest
-
-func (d digestReference) String() string {
-	return digest.Digest(d).String()
-}
-
-func (d digestReference) Digest() digest.Digest {
-	return digest.Digest(d)
-}
-
-type taggedReference struct {
-	namedRepository
-	tag string
-}
-
-func (t taggedReference) String() string {
-	return t.Name() + ":" + t.tag
-}
-
-func (t taggedReference) Tag() string {
-	return t.tag
-}
-
-type canonicalReference struct {
-	namedRepository
-	digest digest.Digest
-}
-
-func (c canonicalReference) String() string {
-	return c.Name() + "@" + c.digest.String()
-}
-
-func (c canonicalReference) Digest() digest.Digest {
-	return c.digest
-}
diff --git a/vendor/github.com/docker/distribution/reference/regexp.go b/vendor/github.com/docker/distribution/reference/regexp.go
deleted file mode 100644
index 78603493203f..000000000000
--- a/vendor/github.com/docker/distribution/reference/regexp.go
+++ /dev/null
@@ -1,143 +0,0 @@
-package reference
-
-import "regexp"
-
-var (
-	// alphaNumericRegexp defines the alpha numeric atom, typically a
-	// component of names. This only allows lower case characters and digits.
-	alphaNumericRegexp = match(`[a-z0-9]+`)
-
-	// separatorRegexp defines the separators allowed to be embedded in name
-	// components. This allow one period, one or two underscore and multiple
-	// dashes.
-	separatorRegexp = match(`(?:[._]|__|[-]*)`)
-
-	// nameComponentRegexp restricts registry path component names to start
-	// with at least one letter or number, with following parts able to be
-	// separated by one period, one or two underscore and multiple dashes.
-	nameComponentRegexp = expression(
-		alphaNumericRegexp,
-		optional(repeated(separatorRegexp, alphaNumericRegexp)))
-
-	// domainComponentRegexp restricts the registry domain component of a
-	// repository name to start with a component as defined by DomainRegexp
-	// and followed by an optional port.
-	domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
-
-	// DomainRegexp defines the structure of potential domain components
-	// that may be part of image names. This is purposely a subset of what is
-	// allowed by DNS to ensure backwards compatibility with Docker image
-	// names.
-	DomainRegexp = expression(
-		domainComponentRegexp,
-		optional(repeated(literal(`.`), domainComponentRegexp)),
-		optional(literal(`:`), match(`[0-9]+`)))
-
-	// TagRegexp matches valid tag names. From docker/docker:graph/tags.go.
-	TagRegexp = match(`[\w][\w.-]{0,127}`)
-
-	// anchoredTagRegexp matches valid tag names, anchored at the start and
-	// end of the matched string.
-	anchoredTagRegexp = anchored(TagRegexp)
-
-	// DigestRegexp matches valid digests.
-	DigestRegexp = match(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`)
-
-	// anchoredDigestRegexp matches valid digests, anchored at the start and
-	// end of the matched string.
-	anchoredDigestRegexp = anchored(DigestRegexp)
-
-	// NameRegexp is the format for the name component of references. The
-	// regexp has capturing groups for the domain and name part omitting
-	// the separating forward slash from either.
-	NameRegexp = expression(
-		optional(DomainRegexp, literal(`/`)),
-		nameComponentRegexp,
-		optional(repeated(literal(`/`), nameComponentRegexp)))
-
-	// anchoredNameRegexp is used to parse a name value, capturing the
-	// domain and trailing components.
-	anchoredNameRegexp = anchored(
-		optional(capture(DomainRegexp), literal(`/`)),
-		capture(nameComponentRegexp,
-			optional(repeated(literal(`/`), nameComponentRegexp))))
-
-	// ReferenceRegexp is the full supported format of a reference. The regexp
-	// is anchored and has capturing groups for name, tag, and digest
-	// components.
-	ReferenceRegexp = anchored(capture(NameRegexp),
-		optional(literal(":"), capture(TagRegexp)),
-		optional(literal("@"), capture(DigestRegexp)))
-
-	// IdentifierRegexp is the format for string identifier used as a
-	// content addressable identifier using sha256. These identifiers
-	// are like digests without the algorithm, since sha256 is used.
-	IdentifierRegexp = match(`([a-f0-9]{64})`)
-
-	// ShortIdentifierRegexp is the format used to represent a prefix
-	// of an identifier. A prefix may be used to match a sha256 identifier
-	// within a list of trusted identifiers.
-	ShortIdentifierRegexp = match(`([a-f0-9]{6,64})`)
-
-	// anchoredIdentifierRegexp is used to check or match an
-	// identifier value, anchored at start and end of string.
-	anchoredIdentifierRegexp = anchored(IdentifierRegexp)
-
-	// anchoredShortIdentifierRegexp is used to check if a value
-	// is a possible identifier prefix, anchored at start and end
-	// of string.
-	anchoredShortIdentifierRegexp = anchored(ShortIdentifierRegexp)
-)
-
-// match compiles the string to a regular expression.
-var match = regexp.MustCompile
-
-// literal compiles s into a literal regular expression, escaping any regexp
-// reserved characters.
-func literal(s string) *regexp.Regexp {
-	re := match(regexp.QuoteMeta(s))
-
-	if _, complete := re.LiteralPrefix(); !complete {
-		panic("must be a literal")
-	}
-
-	return re
-}
-
-// expression defines a full expression, where each regular expression must
-// follow the previous.
-func expression(res ...*regexp.Regexp) *regexp.Regexp {
-	var s string
-	for _, re := range res {
-		s += re.String()
-	}
-
-	return match(s)
-}
-
-// optional wraps the expression in a non-capturing group and makes the
-// production optional.
-func optional(res ...*regexp.Regexp) *regexp.Regexp {
-	return match(group(expression(res...)).String() + `?`)
-}
-
-// repeated wraps the regexp in a non-capturing group to get one or more
-// matches.
-func repeated(res ...*regexp.Regexp) *regexp.Regexp {
-	return match(group(expression(res...)).String() + `+`)
-}
-
-// group wraps the regexp in a non-capturing group.
-func group(res ...*regexp.Regexp) *regexp.Regexp {
-	return match(`(?:` + expression(res...).String() + `)`)
-}
-
-// capture wraps the expression in a capturing group.
-func capture(res ...*regexp.Regexp) *regexp.Regexp {
-	return match(`(` + expression(res...).String() + `)`)
-}
-
-// anchored anchors the regular expression by adding start and end delimiters.
-func anchored(res ...*regexp.Regexp) *regexp.Regexp {
-	return match(`^` + expression(res...).String() + `$`)
-}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 68d9cfd7c057..6eb7763dda19 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -195,13 +195,9 @@ github.com/diskfs/go-diskfs/partition/gpt
 github.com/diskfs/go-diskfs/partition/mbr
 github.com/diskfs/go-diskfs/partition/part
 github.com/diskfs/go-diskfs/util
-# github.com/distribution/reference v0.5.0
+# github.com/distribution/reference v0.6.0
 ## explicit; go 1.20
 github.com/distribution/reference
-# github.com/docker/distribution v2.8.2-beta.1+incompatible
-## explicit
-github.com/docker/distribution/digestset
-github.com/docker/distribution/reference
 # github.com/docker/docker v25.0.6+incompatible
 ## explicit
 github.com/docker/docker/api


Reviews:
user: login: omer-vishlitzky
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T22:49:41Z
user: login: omer-vishlitzky
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T22:58:45Z
user: login: CrystalChun
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T13:27:43Z
user: login: CrystalChun
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T13:27:59Z

Review comments:
diff_hunk: @@ -229,49 +205,84 @@ func (v *registryPullSecretValidator) ValidatePullSecret(additionalPublicRegistr
 		}
 	}
 
-	for registry := range v.registriesWithAuth {
-		if !ignorableRegistries[registry] {
-			if err = validateRegistryWithAuth(registry, creds); err != nil {
-				return &PullSecretError{
-					Msg: fmt.Sprintf("%s, ignorable registries used for pull secret validation: %+v",
-						err.Error(), ignorableRegistries),
-				}
-			}
-		}
+	ignorableRegistries := maps.Clone(v.publicRegistries)
+	for _, registry := range additionalPublicRegistries {
+		ignorableRegistries[registry] = true
 	}
 
-	registryWithAuth, err := getRegistryAuthStatus(ignorableRegistries, releaseImageURL)
-	if err != nil {
-		return err
+	registries := maps.Keys(v.registriesWithAuth)
+	registries = append(registries, releaseImageURL)
+	return verifyNoMissingAuth(ignorableRegistries, creds, registries...)
+}
+
+// verifyNoMissingAuth verifies that all registries are accounted for in either the ignore list or contain
+// an authentication entry in the pull secret
+func verifyNoMissingAuth(ignorableRegistries map[string]bool, credentials map[string]PullSecretCreds, registries ...string) error {
+	// Combine ignorable registries and the registries listed in the credentials since they effectively
+	// are the same check: if the registry exists in the ignore list or in the credentials list, then the
+	// registry does not still require auth
path: internal/cluster/validations/pull_secret_validation.go
user: login: omer-vishlitzky
body: nit: `registry does not still require auth` -> `registry still does not require auth`
created_at: 2025-05-14T22:49:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -152,7 +153,30 @@ var _ = Describe("Pull secret validation", func() {
 			Expect(err).ShouldNot(HaveOccurred())
 		})
 
-		It("pull secret is not accepted when release image is specified bit its registry credentials missing", func() {
+		It("pull secret accepted when release image is specified and the full path for the registry credentials exists", func() {
+			publicRegistries := map[string]bool{}
+			validator, err := NewPullSecretValidator(publicRegistries, authHandlerDisabled, "quay.io/testing/image-name:latest")
+			Expect(err).ShouldNot(HaveOccurred())
+			err = validator.ValidatePullSecret(additionalIgnoredRegistries, validSecretWithRepo, "", "registry.ci/test:latest")
+			Expect(err).ShouldNot(HaveOccurred())
+		})
+		It("pull secret accepted when release image is specified and the full path for the registry credentials exists", func() {
+			publicRegistries := map[string]bool{}
+			validator, err := NewPullSecretValidator(publicRegistries, authHandlerDisabled, "quay.io/testing/image-name:latest")
+			Expect(err).ShouldNot(HaveOccurred())
+			err = validator.ValidatePullSecret(additionalIgnoredRegistries, validSecretWithRepo, "", "registry.ci/test:latest")
+			Expect(err).ShouldNot(HaveOccurred())
+		})
path: internal/cluster/validations/validation_test.go
user: login: omer-vishlitzky
body: aren't these identical?
created_at: 2025-05-14T22:58:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -229,49 +205,84 @@ func (v *registryPullSecretValidator) ValidatePullSecret(additionalPublicRegistr
 		}
 	}
 
-	for registry := range v.registriesWithAuth {
-		if !ignorableRegistries[registry] {
-			if err = validateRegistryWithAuth(registry, creds); err != nil {
-				return &PullSecretError{
-					Msg: fmt.Sprintf("%s, ignorable registries used for pull secret validation: %+v",
-						err.Error(), ignorableRegistries),
-				}
-			}
-		}
+	ignorableRegistries := maps.Clone(v.publicRegistries)
+	for _, registry := range additionalPublicRegistries {
+		ignorableRegistries[registry] = true
 	}
 
-	registryWithAuth, err := getRegistryAuthStatus(ignorableRegistries, releaseImageURL)
-	if err != nil {
-		return err
+	registries := maps.Keys(v.registriesWithAuth)
+	registries = append(registries, releaseImageURL)
+	return verifyNoMissingAuth(ignorableRegistries, creds, registries...)
+}
+
+// verifyNoMissingAuth verifies that all registries are accounted for in either the ignore list or contain
+// an authentication entry in the pull secret
+func verifyNoMissingAuth(ignorableRegistries map[string]bool, credentials map[string]PullSecretCreds, registries ...string) error {
+	// Combine ignorable registries and the registries listed in the credentials since they effectively
+	// are the same check: if the registry exists in the ignore list or in the credentials list, then the
+	// registry does not still require auth
path: internal/cluster/validations/pull_secret_validation.go
user: login: CrystalChun
body: I removed the still 😆 how does it sound?
created_at: 2025-05-15T13:27:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -152,7 +153,30 @@ var _ = Describe("Pull secret validation", func() {
 			Expect(err).ShouldNot(HaveOccurred())
 		})
 
-		It("pull secret is not accepted when release image is specified bit its registry credentials missing", func() {
+		It("pull secret accepted when release image is specified and the full path for the registry credentials exists", func() {
+			publicRegistries := map[string]bool{}
+			validator, err := NewPullSecretValidator(publicRegistries, authHandlerDisabled, "quay.io/testing/image-name:latest")
+			Expect(err).ShouldNot(HaveOccurred())
+			err = validator.ValidatePullSecret(additionalIgnoredRegistries, validSecretWithRepo, "", "registry.ci/test:latest")
+			Expect(err).ShouldNot(HaveOccurred())
+		})
+		It("pull secret accepted when release image is specified and the full path for the registry credentials exists", func() {
+			publicRegistries := map[string]bool{}
+			validator, err := NewPullSecretValidator(publicRegistries, authHandlerDisabled, "quay.io/testing/image-name:latest")
+			Expect(err).ShouldNot(HaveOccurred())
+			err = validator.ValidatePullSecret(additionalIgnoredRegistries, validSecretWithRepo, "", "registry.ci/test:latest")
+			Expect(err).ShouldNot(HaveOccurred())
+		})
path: internal/cluster/validations/validation_test.go
user: login: CrystalChun
body: Yes thank you! I forgot to remove the duplicate
created_at: 2025-05-15T13:27:59Z
author_association: CONTRIBUTOR

PR comments:
user: login: openshift-ci-robot
created_at: 2025-05-14T01:30:05Z
body: @CrystalChun: This pull request references [MGMT-20601](https://issues.redhat.com//browse/MGMT-20601) which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the bug to target the "4.20.0" version, but no target version was set.

<details>

In response to [this](https://github.com/openshift/assisted-service/pull/7656):

><!--
>Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
>
>You can refer to [Kubernetes community documentation] on writing good commit messages, which provides good tips and ideas.
>
>Some PRs address specific issues. Please, refer to the [CONTRIBUTING] documentation for more
>information on how to link a PR to an existing issue.
>
>It's recommended to take a few extra minutes to provide more information about
>how this code was tested. Here are some questions that may be worth answering:
>
>- Should this PR be tested by the reviewer?
>- Is this PR relying on CI for an e2e test run?
>- Should this PR be tested in a specific environment?
>- Any logs, screenshots, etc that can help with the review process?
>
>-->
>Previously, the image registries that require authentication were stored as just their base domain. This means that any pull secret the user provides needs to have the base domain of these registries. This is not valid since the credentials for a registry in a pull secret can be scoped to a repository. Matching this way caused false positive errors when validating pull secrets since it looks like the authentication credentials don't exist, when in reality they do.
>
>To better serve this use-case and enhance the validation of pull secrets, the following changes were made:
>1. Store the full image registry for all images required 
>2. When validating a pull secret:
>    - Iteratively truncate the full image registry until a match is found in either the ignore list or the pull secret credentials
>   - This ensures we're first checking against the most specific URL (base domain + full repository path), but still allowing a user to provide the credentials for just the base domain of the repository
>
>These changes should still be valid for prior use-cases, but should now include the use case where a user wants to provide repository-scoped registry credentials.
>
>## List all the issues related to this PR
>
>- [ ] New Feature <!-- new functionality -->
>- [ ] Enhancement <!-- refactor, code changes, improvement, that won't add new features -->
>- [x] Bug fix
>- [ ] Tests
>- [ ] Documentation
>- [ ] CI/CD <!-- Notice that changes for Dockerfiles/Jenkinsfiles aren't tested in CI due to a known bug. -->
>
>## What environments does this code impact?
>
>- [ ] Automation (CI, tools, etc)
>- [x] Cloud
>- [x] Operator Managed Deployments
>- [ ] None
>
>## How was this code tested?
>
><!-- Please, select one or more if needed: -->
>
>- [ ] assisted-test-infra environment
>- [ ] dev-scripts environment
>- [ ] Reviewer's test appreciated
>- [x] Waiting for CI to do a full test run
>- [ ] Manual (Elaborate on how it was tested)
>- [ ] No tests needed
>
>## Checklist
>
>- [x] Title and description added to both, commit and PR.
>- [x] Relevant issues have been associated (see [CONTRIBUTING] guide)
>- [x] This change does not require a documentation update (docstring, `docs`, README, etc)
>- [x] Does this change include unit-tests (note that code changes require unit-tests)
>
>## Reviewers Checklist
>
>- Are the title and description (in both PR and commit) meaningful and clear?
>- Is there a bug required (and linked) for this change?
>- Should this PR be backported?
>
>[Kubernetes community documentation]: https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#commit-message-guidelines
>[CONTRIBUTING]: https://github.com/openshift/assisted-service/blob/master/CONTRIBUTING.md
>
>


Instructions for interacting with me using PR comments are available [here](https://prow.ci.openshift.org/command-help?repo=openshift%2Fassisted-service).  If you have questions or suggestions related to my behavior, please file an issue against the [openshift-eng/jira-lifecycle-plugin](https://github.com/openshift-eng/jira-lifecycle-plugin/issues/new) repository.
</details>
author_association: NONE
user: login: codecov[bot]
created_at: 2025-05-14T03:04:30Z
body: ## [Codecov](https://app.codecov.io/gh/openshift/assisted-service/pull/7656?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openshift) Report
Attention: Patch coverage is `88.88889%` with `4 lines` in your changes missing coverage. Please review.
> Project coverage is 67.26%. Comparing base [(`6ef17dd`)](https://app.codecov.io/gh/openshift/assisted-service/commit/6ef17dd3ff8304682d3b122ad769bd69c5182ccf?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openshift) to head [(`e4b3146`)](https://app.codecov.io/gh/openshift/assisted-service/commit/e4b314613cf02558430194afd39800a88e2b6a93?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openshift).
> Report is 1 commits behind head on master.

| [Files with missing lines](https://app.codecov.io/gh/openshift/assisted-service/pull/7656?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openshift) | Patch % | Lines |
|---|---|---|
| [...rnal/cluster/validations/pull\_secret\_validation.go](https://app.codecov.io/gh/openshift/assisted-service/pull/7656?src=pr&el=tree&filepath=internal%2Fcluster%2Fvalidations%2Fpull_secret_validation.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openshift#diff-aW50ZXJuYWwvY2x1c3Rlci92YWxpZGF0aW9ucy9wdWxsX3NlY3JldF92YWxpZGF0aW9uLmdv) | 87.09% | [2 Missing and 2 partials :warning: ](https://app.codecov.io/gh/openshift/assisted-service/pull/7656?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openshift) |

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/openshift/assisted-service/pull/7656/graphs/tree.svg?width=650&height=150&src=pr&token=YOR4NSSOXQ&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openshift)](https://app.codecov.io/gh/openshift/assisted-service/pull/7656?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openshift)

```diff
@@            Coverage Diff             @@
##           master    #7656      +/-   ##
==========================================
- Coverage   67.28%   67.26%   -0.02%     
==========================================
  Files         334      334              
  Lines       42606    42613       +7     
==========================================
- Hits        28667    28665       -2     
- Misses      11348    11356       +8     
- Partials     2591     2592       +1     
```

| [Files with missing lines](https://app.codecov.io/gh/openshift/assisted-service/pull/7656?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openshift) | Coverage Δ | |
|---|---|---|
| [internal/cluster/validations/validations.go](https://app.codecov.io/gh/openshift/assisted-service/pull/7656?src=pr&el=tree&filepath=internal%2Fcluster%2Fvalidations%2Fvalidations.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openshift#diff-aW50ZXJuYWwvY2x1c3Rlci92YWxpZGF0aW9ucy92YWxpZGF0aW9ucy5nbw==) | `13.66% <100.00%> (-0.16%)` | :arrow_down: |
| [internal/common/common.go](https://app.codecov.io/gh/openshift/assisted-service/pull/7656?src=pr&el=tree&filepath=internal%2Fcommon%2Fcommon.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openshift#diff-aW50ZXJuYWwvY29tbW9uL2NvbW1vbi5nbw==) | `32.02% <ø> (ø)` | |
| [...rnal/cluster/validations/pull\_secret\_validation.go](https://app.codecov.io/gh/openshift/assisted-service/pull/7656?src=pr&el=tree&filepath=internal%2Fcluster%2Fvalidations%2Fpull_secret_validation.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openshift#diff-aW50ZXJuYWwvY2x1c3Rlci92YWxpZGF0aW9ucy9wdWxsX3NlY3JldF92YWxpZGF0aW9uLmdv) | `75.78% <87.09%> (-2.80%)` | :arrow_down: |

</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: omer-vishlitzky
created_at: 2025-05-15T13:30:11Z
body: /lgtm
/approve

author_association: CONTRIBUTOR
user: login: openshift-ci-robot
created_at: 2025-05-15T13:30:47Z
body: /test remaining-required

Scheduling tests matching the `pipeline_run_if_changed` parameter:
/test edge-e2e-ai-operator-ztp
/test edge-e2e-metal-assisted-4-19

Overriding unmatched contexts:
/override  ci/prow/edge-e2e-ai-operator-disconnected-capi ci/prow/edge-e2e-ai-operator-ztp-capi ci/prow/edge-e2e-ai-operator-ztp-disconnected ci/prow/edge-e2e-metal-assisted-ai-amd-4-18 ci/prow/edge-e2e-metal-assisted-ai-nvidia-4-18 ci/prow/edge-e2e-metal-assisted-external-4-19 ci/prow/edge-e2e-metal-assisted-lvm-4-19 ci/prow/edge-e2e-metal-assisted-none-4-19 ci/prow/edge-e2e-metal-assisted-osc-4-19 ci/prow/edge-e2e-metal-assisted-virtualization-4-18 ci/prow/edge-e2e-nutanix-assisted-4-19 ci/prow/edge-e2e-vsphere-assisted-4-19
author_association: NONE
user: login: openshift-ci[bot]
created_at: 2025-05-15T13:31:25Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/openshift/assisted-service/pull/7656#" title="Author self-approved">CrystalChun</a>*, *<a href="https://github.com/openshift/assisted-service/pull/7656#issuecomment-2883827902" title="Approved">omer-vishlitzky</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=openshift%2Fassisted-service).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/openshift/assisted-service/blob/master/OWNERS)~~ [CrystalChun]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: NONE
user: login: openshift-ci[bot]
created_at: 2025-05-15T13:31:42Z
body: @openshift-ci-robot: Overrode contexts on behalf of openshift-ci-robot: ci/prow/edge-e2e-ai-operator-disconnected-capi, ci/prow/edge-e2e-ai-operator-ztp-capi, ci/prow/edge-e2e-ai-operator-ztp-disconnected, ci/prow/edge-e2e-metal-assisted-ai-amd-4-18, ci/prow/edge-e2e-metal-assisted-ai-nvidia-4-18, ci/prow/edge-e2e-metal-assisted-external-4-19, ci/prow/edge-e2e-metal-assisted-lvm-4-19, ci/prow/edge-e2e-metal-assisted-none-4-19, ci/prow/edge-e2e-metal-assisted-osc-4-19, ci/prow/edge-e2e-metal-assisted-virtualization-4-18, ci/prow/edge-e2e-nutanix-assisted-4-19, ci/prow/edge-e2e-vsphere-assisted-4-19

<details>

In response to [this](https://github.com/openshift/assisted-service/pull/7656#issuecomment-2883829655):

>/test remaining-required
>
>Scheduling tests matching the `pipeline_run_if_changed` parameter:
>/test edge-e2e-ai-operator-ztp
>/test edge-e2e-metal-assisted-4-19
>
>Overriding unmatched contexts:
>/override  ci/prow/edge-e2e-ai-operator-disconnected-capi ci/prow/edge-e2e-ai-operator-ztp-capi ci/prow/edge-e2e-ai-operator-ztp-disconnected ci/prow/edge-e2e-metal-assisted-ai-amd-4-18 ci/prow/edge-e2e-metal-assisted-ai-nvidia-4-18 ci/prow/edge-e2e-metal-assisted-external-4-19 ci/prow/edge-e2e-metal-assisted-lvm-4-19 ci/prow/edge-e2e-metal-assisted-none-4-19 ci/prow/edge-e2e-metal-assisted-osc-4-19 ci/prow/edge-e2e-metal-assisted-virtualization-4-18 ci/prow/edge-e2e-nutanix-assisted-4-19 ci/prow/edge-e2e-vsphere-assisted-4-19


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
author_association: NONE
user: login: openshift-ci[bot]
created_at: 2025-05-15T18:28:58Z
body: @CrystalChun: all tests **passed!**


[Full PR test history](https://prow.ci.openshift.org/pr-history?org=openshift&repo=assisted-service&pr=7656). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:CrystalChun).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
author_association: NONE
user: login: CrystalChun
created_at: 2025-05-15T18:56:23Z
body: /retest
author_association: CONTRIBUTOR
user: login: openshift-bot
created_at: 2025-05-15T21:13:44Z
body: **[ART PR BUILD NOTIFIER]**

Distgit: ose-agent-installer-api-server
This PR has been included in build [ose-agent-installer-api-server-container-v4.20.0-202505152017.p0.gc412679.assembly.stream.el9](https://brewweb.engineering.redhat.com/brew/buildinfo?buildID=3649819).
All builds following this will include this PR.
author_association: CONTRIBUTOR
user: login: CrystalChun
created_at: 2025-06-03T16:47:13Z
body: /cherry-pick release-ocm-2.13 release-ocm-2.12 release-ocm-2.11
author_association: CONTRIBUTOR
user: login: openshift-cherrypick-robot
created_at: 2025-06-03T16:48:12Z
body: @CrystalChun: #7656 failed to apply on top of branch "release-ocm-2.13":
```
Applying: MGMT-20601: Replace dependency docker/reference with distribution/reference
Applying: MGMT-20601: Add ParseFullRegistry method
Applying: MGMT-20601: Check full registry during pull secret validation
Using index info to reconstruct a base tree...
M	go.mod
Falling back to patching base and 3-way merge...
Auto-merging go.mod
CONFLICT (content): Merge conflict in go.mod
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
hint: When you have resolved this problem, run "git am --continue".
hint: If you prefer to skip this patch, run "git am --skip" instead.
hint: To restore the original branch and stop patching, run "git am --abort".
hint: Disable this message with "git config advice.mergeConflict false"
Patch failed at 0003 MGMT-20601: Check full registry during pull secret validation

```

<details>

In response to [this](https://github.com/openshift/assisted-service/pull/7656#issuecomment-2936280677):

>/cherry-pick release-ocm-2.13 release-ocm-2.12 release-ocm-2.11


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
author_association: NONE