Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions support/util/registryoverride/registryoverride.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ import "strings"
//
// Matching is strict: an override applies only when the image reference is
// exactly equal to the source key or starts with the source key followed by a
// "/" separator. This prevents accidental substring matches (e.g. an override
// for "quay.io" must not match "quay.io.example.com/foo").
// valid image-reference separator ("/" for path components, "@" for digests,
// or ":" for tags). This prevents accidental substring matches (e.g. an
// override for "quay.io" must not match "quay.io.example.com/foo") while
// correctly handling repository-level overrides against digest references
// (e.g. "quay.io/org/repo" must match "quay.io/org/repo@sha256:abc").
//
// When several override keys match the same image, the longest key wins. This
// makes the result deterministic regardless of map iteration order and lets
Expand All @@ -31,7 +34,7 @@ func Replace(image string, overrides map[string]string) string {
if source == "" {
continue
}
if image != source && !strings.HasPrefix(image, source+"/") {
if !matchesPrefix(image, source) {
continue
}
if len(source) > len(bestSource) {
Expand All @@ -43,3 +46,23 @@ func Replace(image string, overrides map[string]string) string {
}
return bestTarget + image[len(bestSource):]
}

// matchesPrefix reports whether image is exactly source, or source is a prefix
// of image followed by a valid separator. Valid separators are "/" (path) and
// "@" (digest), always. ":" is accepted only when source contains a "/" (i.e.
// includes a path component), where it denotes a tag boundary. Without a "/"
// the source is a bare hostname and ":" would be a port separator, which must
// not match (e.g. override "quay.io" must not match "quay.io:5000/foo").
func matchesPrefix(image, source string) bool {
if image == source {
return true
}
if !strings.HasPrefix(image, source) {
return false
}
sep := image[len(source)]
if sep == '/' || sep == '@' {
return true
}
return sep == ':' && strings.ContainsRune(source, '/')
}
39 changes: 39 additions & 0 deletions support/util/registryoverride/registryoverride_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,45 @@ func TestReplace(t *testing.T) {
overrides: map[string]string{"quay.io": "mirror.example.com"},
want: "mirror.example.com/foo/bar:v1.2.3",
},
{
name: "When source matches full repository with digest separator it should replace prefix",
image: "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:abc123",
overrides: map[string]string{"quay.io/openshift-release-dev/ocp-v4.0-art-dev": "mirror.example.com/art-dev"},
want: "mirror.example.com/art-dev@sha256:abc123",
},
{
name: "When source matches full repository with tag separator it should replace prefix",
image: "quay.io/openshift-release-dev/ocp-v4.0-art-dev:latest",
overrides: map[string]string{"quay.io/openshift-release-dev/ocp-v4.0-art-dev": "mirror.example.com/art-dev"},
want: "mirror.example.com/art-dev:latest",
},
{
name: "When multiple overrides match with digest it should pick longest prefix",
image: "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:abc123",
overrides: map[string]string{
"quay.io": "broad.example.com",
"quay.io/openshift-release-dev/ocp-v4.0-art-dev": "narrow.example.com/art-dev",
},
want: "narrow.example.com/art-dev@sha256:abc123",
},
{
name: "When source has trailing dash it should not match similar prefix (no false positive)",
image: "quay.io/openshift-release-dev/ocp-v4.0-art-dev-extra@sha256:abc",
overrides: map[string]string{"quay.io/openshift-release-dev/ocp-v4.0-art-dev": "mirror/art-dev"},
want: "quay.io/openshift-release-dev/ocp-v4.0-art-dev-extra@sha256:abc",
},
{
name: "When host-only source matches host:port image it should not match (port is not a tag)",
image: "quay.io:5000/org/repo@sha256:abc",
overrides: map[string]string{"quay.io": "mirror.example.com"},
want: "quay.io:5000/org/repo@sha256:abc",
},
{
name: "When host:port source matches host:port image it should match via slash",
image: "myregistry:5000/org/repo@sha256:abc",
overrides: map[string]string{"myregistry:5000": "mirror.example.com"},
want: "mirror.example.com/org/repo@sha256:abc",
},
{
name: "empty image returns empty",
image: "",
Expand Down