• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

opendefensecloud / solution-arsenal / 24822429227

23 Apr 2026 07:21AM UTC coverage: 72.505% (-0.5%) from 73.044%
24822429227

Pull #436

github

web-flow
Merge 4275ff704 into 7e082458b
Pull Request #436: Registry credentials PoC

190 of 212 new or added lines in 6 files covered. (89.62%)

41 existing lines in 2 files now uncovered.

2165 of 2986 relevant lines covered (72.51%)

34.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.2
/pkg/controller/resolve.go
1
// Copyright 2026 BWI GmbH and Solution Arsenal contributors
2
// SPDX-License-Identifier: Apache-2.0
3

4
package controller
5

6
import (
7
        "fmt"
8
        "strings"
9

10
        solarv1alpha1 "go.opendefense.cloud/solar/api/solar/v1alpha1"
11
)
12

13
// registryBindingInfo combines a RegistryBinding with its resolved Registry.
14
type registryBindingInfo struct {
15
        binding  *solarv1alpha1.RegistryBinding
16
        registry *solarv1alpha1.Registry
17
}
18

19
// extractHost returns the host (with optional port) from a repository string.
20
// It handles both "host/path" and "host:port/path" formats.
21
func extractHost(repository string) string {
61✔
22
        // Strip oci:// prefix if present
61✔
23
        repo := strings.TrimPrefix(repository, "oci://")
61✔
24

61✔
25
        // Split on first slash to get host portion
61✔
26
        if before, _, ok := strings.Cut(repo, "/"); ok {
121✔
27
                return before
60✔
28
        }
60✔
29

30
        return repo
1✔
31
}
32

33
// rewriteRepository replaces the host in a repository string with the bound
34
// registry's hostname and optionally prepends a repository prefix.
35
func rewriteRepository(repository string, targetHostname string, prefix string) string {
9✔
36
        // Strip oci:// prefix if present, we'll work without it
9✔
37
        repo := strings.TrimPrefix(repository, "oci://")
9✔
38

9✔
39
        // Split host from path
9✔
40
        path := ""
9✔
41
        if _, after, ok := strings.Cut(repo, "/"); ok {
17✔
42
                path = after
8✔
43
        }
8✔
44

45
        // Build new repository: hostname / prefix / path
46
        parts := []string{targetHostname}
9✔
47
        if prefix != "" {
13✔
48
                parts = append(parts, strings.Trim(prefix, "/"))
4✔
49
        }
4✔
50
        if path != "" {
17✔
51
                parts = append(parts, path)
8✔
52
        }
8✔
53

54
        return strings.Join(parts, "/")
9✔
55
}
56

57
// resolveResources rewrites resource repositories and attaches pull secret names
58
// based on RegistryBindings for the target.
59
//
60
// Algorithm (per ADR 010):
61
//  1. For each resource, match its repository host against rewrite.sourceEndpoint
62
//     on each RegistryBinding. If matched, rewrite to the bound Registry's endpoint.
63
//  2. If no rewrite match, look for a RegistryBinding whose Registry hostname
64
//     matches the resource host (identity binding). Use its pullSecretName.
65
//  3. If no matching binding is found, return an error.
66
func resolveResources(resources map[string]solarv1alpha1.ResourceAccess, bindings []registryBindingInfo) (map[string]solarv1alpha1.ResourceAccess, error) {
38✔
67
        resolved := make(map[string]solarv1alpha1.ResourceAccess, len(resources))
38✔
68

38✔
69
        for name, res := range resources {
76✔
70
                host := extractHost(res.Repository)
38✔
71

38✔
72
                var matched bool
38✔
73

38✔
74
                // First pass: check rewrite bindings
38✔
75
                for _, bi := range bindings {
105✔
76
                        if bi.binding.Spec.Rewrite == nil || bi.binding.Spec.Rewrite.SourceEndpoint == "" {
129✔
77
                                continue
62✔
78
                        }
79

80
                        if bi.binding.Spec.Rewrite.SourceEndpoint != host {
6✔
81
                                continue
1✔
82
                        }
83

84
                        // Rewrite match found
85
                        resolved[name] = solarv1alpha1.ResourceAccess{
4✔
86
                                Repository:     rewriteRepository(res.Repository, bi.registry.Spec.Hostname, bi.binding.Spec.Rewrite.RepositoryPrefix),
4✔
87
                                Insecure:       bi.registry.Spec.PlainHTTP,
4✔
88
                                Tag:            res.Tag,
4✔
89
                                PullSecretName: bi.registry.Spec.TargetPullSecretName,
4✔
90
                        }
4✔
91

4✔
92
                        matched = true
4✔
93

4✔
94
                        break
4✔
95
                }
96

97
                if matched {
42✔
98
                        continue
4✔
99
                }
100

101
                // Second pass: identity binding (registry hostname matches resource host)
102
                for _, bi := range bindings {
71✔
103
                        registryHost := bi.registry.Spec.Hostname
37✔
104

37✔
105
                        // Normalize: compare just hostnames, ignoring schemes
37✔
106
                        registryHost = strings.TrimPrefix(registryHost, "https://")
37✔
107
                        registryHost = strings.TrimPrefix(registryHost, "http://")
37✔
108
                        registryHost, _, _ = strings.Cut(registryHost, "/")
37✔
109

37✔
110
                        if registryHost != host {
41✔
111
                                continue
4✔
112
                        }
113

114
                        resolved[name] = solarv1alpha1.ResourceAccess{
33✔
115
                                Repository:     res.Repository,
33✔
116
                                Insecure:       res.Insecure,
33✔
117
                                Tag:            res.Tag,
33✔
118
                                PullSecretName: bi.registry.Spec.TargetPullSecretName,
33✔
119
                        }
33✔
120

33✔
121
                        matched = true
33✔
122

33✔
123
                        break
33✔
124
                }
125

126
                if !matched {
35✔
127
                        return nil, fmt.Errorf("resource %q (host %q) has no matching RegistryBinding", name, host)
1✔
128
                }
1✔
129
        }
130

131
        return resolved, nil
37✔
132
}
133

134
// resolveBootstrapReleases rewrites resolved release chart URLs using the
135
// RegistryBindings for the target. Bootstrap releases point at the render
136
// registry, so we match against its hostname.
137
func resolveBootstrapReleases(releases map[string]solarv1alpha1.ResourceAccess, bindings []registryBindingInfo) map[string]solarv1alpha1.ResourceAccess {
12✔
138
        resolved := make(map[string]solarv1alpha1.ResourceAccess, len(releases))
12✔
139

12✔
140
        for name, res := range releases {
31✔
141
                host := extractHost(res.Repository)
19✔
142

19✔
143
                matched := false
19✔
144

19✔
145
                for _, bi := range bindings {
52✔
146
                        registryHost := bi.registry.Spec.Hostname
33✔
147
                        registryHost = strings.TrimPrefix(registryHost, "https://")
33✔
148
                        registryHost = strings.TrimPrefix(registryHost, "http://")
33✔
149
                        registryHost, _, _ = strings.Cut(registryHost, "/")
33✔
150

33✔
151
                        if registryHost != host {
47✔
152
                                continue
14✔
153
                        }
154

155
                        resolved[name] = solarv1alpha1.ResourceAccess{
19✔
156
                                Repository:     res.Repository,
19✔
157
                                Insecure:       res.Insecure,
19✔
158
                                Tag:            res.Tag,
19✔
159
                                PullSecretName: bi.registry.Spec.TargetPullSecretName,
19✔
160
                        }
19✔
161

19✔
162
                        matched = true
19✔
163

19✔
164
                        break
19✔
165
                }
166

167
                if !matched {
19✔
NEW
168
                        // No binding — pass through without pullSecretName
×
NEW
169
                        resolved[name] = res
×
NEW
170
                }
×
171
        }
172

173
        return resolved
12✔
174
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc