1package domain
2
3import "strings"
4
5// KnownGordonRegistryDomains returns the normalized set of Gordon-managed
6// registry domains, with the current domain first followed by legacy domains.
7// Empty values are ignored, trailing slashes are removed, and duplicates are
8// removed while preserving first-seen order.
9func KnownGordonRegistryDomains(current string, legacy []string) []string {
10 domains := make([]string, 0, 1+len(legacy))
11 seen := make(map[string]struct{}, 1+len(legacy))
12
13 add := func(domain string) {
14 normalized := normalizeRegistryDomain(domain)
15 if normalized == "" {
16 return
17 }
18
19 key := strings.ToLower(normalized)
20 if _, ok := seen[key]; ok {
21 return
22 }
23
24 seen[key] = struct{}{}
25 domains = append(domains, normalized)
26 }
27
28 add(current)
29 for _, domain := range legacy {
30 add(domain)
31 }
32
33 return domains
34}
35
36// StripKnownGordonRegistry removes the Gordon-managed registry host prefix from
37// an image reference when it matches the current or a legacy Gordon registry
38// domain. Bare references and external registries are returned unchanged.
39func StripKnownGordonRegistry(imageRef string, current string, legacy []string) string {
40 host, remainder, ok := splitRegistryImageRef(imageRef)
41 if !ok {
42 return imageRef
43 }
44 if !isKnownGordonRegistryDomain(host, current, legacy) {
45 return imageRef
46 }
47 return remainder
48}
49
50// ExtractGordonRepoName strips a known Gordon registry host, then removes any
51// tag or digest suffix, returning the repository name portion of the image
52// reference.
53func ExtractGordonRepoName(imageRef string, current string, legacy []string) string {
54 repo := StripKnownGordonRegistry(imageRef, current, legacy)
55
56 if idx := strings.Index(repo, "@"); idx != -1 {
57 repo = repo[:idx]
58 }
59 if idx := strings.LastIndex(repo, ":"); idx != -1 {
60 slashIdx := strings.LastIndex(repo, "/")
61 if idx > slashIdx {
62 repo = repo[:idx]
63 }
64 }
65
66 return repo
67}
68
69// CanonicalizeGordonImageRef rewrites Gordon-managed image references to use
70// the current Gordon registry domain. External registries and bare references
71// are returned unchanged. If the current domain is empty, the original image
72// reference is returned unchanged.
73func CanonicalizeGordonImageRef(imageRef, current string, legacy []string) string {
74 currentDomain := normalizeRegistryDomain(current)
75 if currentDomain == "" {
76 return imageRef
77 }
78
79 host, remainder, ok := splitRegistryImageRef(imageRef)
80 if !ok {
81 return imageRef
82 }
83 if !isKnownGordonRegistryDomain(host, currentDomain, legacy) {
84 return imageRef
85 }
86
87 return currentDomain + "/" + remainder
88}
89
90// IsGordonRegistryImageRef reports whether an image reference is explicitly
91// qualified with the current or a legacy Gordon registry domain.
92func IsGordonRegistryImageRef(imageRef, current string, legacy []string) bool {
93 host, _, ok := splitRegistryImageRef(imageRef)
94 if !ok {
95 return false
96 }
97 return isKnownGordonRegistryDomain(host, current, legacy)
98}
99
100func normalizeRegistryDomain(domain string) string {
101 domain = strings.TrimSpace(domain)
102 domain = strings.TrimRight(domain, "/")
103 return domain
104}
105
106func isKnownGordonRegistryDomain(host, current string, legacy []string) bool {
107 host = normalizeRegistryDomain(host)
108 if host == "" {
109 return false
110 }
111
112 for _, domain := range KnownGordonRegistryDomains(current, legacy) {
113 if strings.EqualFold(domain, host) {
114 return true
115 }
116 }
117 return false
118}
119
120func splitRegistryImageRef(imageRef string) (host string, remainder string, ok bool) {
121 host, remainder, ok = strings.Cut(imageRef, "/")
122 if !ok || host == "" || remainder == "" {
123 return "", "", false
124 }
125 if !looksLikeRegistryHost(host) {
126 return "", "", false
127 }
128 return host, remainder, true
129}
130
131func looksLikeRegistryHost(host string) bool {
132 return strings.Contains(host, ".") || strings.Contains(host, ":") || strings.EqualFold(host, "localhost")
133}