Skip to content

Commit 9435dce

Browse files
committed
feat: adds a generic checker package with registry checks
1 parent 3ef53dc commit 9435dce

File tree

5 files changed

+228
-0
lines changed

5 files changed

+228
-0
lines changed

cmd/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import (
4242
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/options"
4343
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/webhook/cluster"
4444
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/webhook/preflight"
45+
preflightgeneric "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/webhook/preflight/generic"
4546
preflightnutanix "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/webhook/preflight/nutanix"
4647
)
4748

@@ -241,6 +242,7 @@ func main() {
241242
Handler: preflight.New(mgr.GetClient(), admission.NewDecoder(mgr.GetScheme()),
242243
[]preflight.Checker{
243244
// Add your preflight checkers here.
245+
preflightgeneric.Checker,
244246
preflightnutanix.Checker,
245247
}...,
246248
),

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ require (
2828
github.com/onsi/ginkgo/v2 v2.23.4
2929
github.com/onsi/gomega v1.37.0
3030
github.com/pkg/errors v0.9.1
31+
github.com/regclient/regclient v0.8.3
3132
github.com/samber/lo v1.51.0
3233
github.com/spf13/pflag v1.0.6
3334
github.com/stretchr/testify v1.10.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,8 @@ github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G
270270
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
271271
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
272272
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
273+
github.com/regclient/regclient v0.8.3 h1:AFAPu/vmOYGyY22AIgzdBUKbzH+83lEpRioRYJ/reCs=
274+
github.com/regclient/regclient v0.8.3/go.mod h1:gjQh5uBVZoo/CngchghtQh9Hx81HOMKRRDd5WPcPkbk=
273275
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
274276
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
275277
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright 2025 Nutanix. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package generic
4+
5+
import (
6+
"context"
7+
8+
"github.com/go-logr/logr"
9+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
10+
ctrl "sigs.k8s.io/controller-runtime"
11+
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
12+
13+
carenv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
14+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/webhook/preflight"
15+
)
16+
17+
var Checker = &genericChecker{
18+
registryCheckFactory: newRegistryCheck,
19+
}
20+
21+
type genericChecker struct {
22+
registryCheckFactory func(
23+
cd *checkDependencies,
24+
) []preflight.Check
25+
}
26+
27+
type checkDependencies struct {
28+
kclient ctrlclient.Client
29+
cluster *clusterv1.Cluster
30+
genericClusterConfigSpec *carenv1.GenericClusterConfigSpec
31+
log logr.Logger
32+
}
33+
34+
func (g *genericChecker) Init(
35+
ctx context.Context,
36+
kclient ctrlclient.Client,
37+
cluster *clusterv1.Cluster,
38+
) []preflight.Check {
39+
cd := &checkDependencies{
40+
kclient: kclient,
41+
cluster: cluster,
42+
log: ctrl.LoggerFrom(ctx).WithName("preflight/generic"),
43+
}
44+
checks := []preflight.Check{}
45+
checks = append(checks, g.registryCheckFactory(cd)...)
46+
return checks
47+
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// Copyright 2025 Nutanix. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package generic
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"regexp"
10+
11+
"github.com/regclient/regclient"
12+
"github.com/regclient/regclient/config"
13+
"github.com/regclient/regclient/types/ref"
14+
corev1 "k8s.io/api/core/v1"
15+
"k8s.io/apimachinery/pkg/types"
16+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
17+
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
18+
19+
carenv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
20+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/webhook/preflight"
21+
)
22+
23+
var (
24+
registryMirrorVarPath = "cluster.spec.topology.variables[.name=clusterConfig].value.globalImageRegistryMirror"
25+
mirrorURLValidationRegex = regexp.MustCompile(`^https?://`) // in order to use regclient we need to pass just a hostname
26+
// this regex allows us to strip it so we can verify connectivity for this test.
27+
)
28+
29+
type registyCheck struct {
30+
registryMirror *carenv1.GlobalImageRegistryMirror
31+
imageRegistry *carenv1.ImageRegistry
32+
field string
33+
kclient ctrlclient.Client
34+
cluster *clusterv1.Cluster
35+
}
36+
37+
func (r *registyCheck) Name() string {
38+
return "RegistryCredentials"
39+
}
40+
41+
func (r *registyCheck) Run(ctx context.Context) preflight.CheckResult {
42+
if r.registryMirror != nil {
43+
return r.checkRegistry(
44+
ctx,
45+
mirrorURLValidationRegex.ReplaceAllString(r.registryMirror.URL, ""),
46+
r.registryMirror.Credentials,
47+
)
48+
}
49+
if r.imageRegistry != nil {
50+
return r.checkRegistry(
51+
ctx,
52+
mirrorURLValidationRegex.ReplaceAllString(r.imageRegistry.URL, ""),
53+
r.imageRegistry.Credentials,
54+
)
55+
}
56+
return preflight.CheckResult{
57+
Allowed: true,
58+
}
59+
}
60+
61+
func (r *registyCheck) checkRegistry(ctx context.Context, registryURL string, credentials *carenv1.RegistryCredentials) preflight.CheckResult {
62+
result := preflight.CheckResult{
63+
Allowed: false,
64+
}
65+
mirrorHost := config.Host{
66+
Name: registryURL,
67+
}
68+
if credentials != nil && credentials.SecretRef != nil {
69+
mirrorCredentialsSecret := &corev1.Secret{}
70+
err := r.kclient.Get(
71+
ctx,
72+
types.NamespacedName{
73+
Namespace: r.cluster.Namespace,
74+
Name: r.registryMirror.Credentials.SecretRef.Name,
75+
},
76+
mirrorCredentialsSecret,
77+
)
78+
if err != nil {
79+
result.Allowed = false
80+
result.Error = true
81+
result.Causes = append(result.Causes,
82+
preflight.Cause{
83+
Message: fmt.Sprintf("failed to get Registry credentials Secret: %s", err),
84+
Field: fmt.Sprintf("%s.credentials.secretRef", registryMirrorVarPath),
85+
},
86+
)
87+
return result
88+
}
89+
username, ok := mirrorCredentialsSecret.Data["username"]
90+
if !ok {
91+
result.Allowed = false
92+
result.Error = true
93+
result.Causes = append(result.Causes,
94+
preflight.Cause{
95+
Message: "failed to get username from Registry credentials Secret. secret must have field username.",
96+
Field: fmt.Sprintf("%s.credentials.secretRef", registryMirrorVarPath),
97+
},
98+
)
99+
return result
100+
}
101+
password, ok := mirrorCredentialsSecret.Data["password"]
102+
if !ok {
103+
result.Allowed = false
104+
result.Error = true
105+
result.Causes = append(result.Causes,
106+
preflight.Cause{
107+
Message: "failed to get username from Registry credentials Secret. secret must have field password.",
108+
Field: fmt.Sprintf("%s.credentials.secretRef", registryMirrorVarPath),
109+
},
110+
)
111+
return result
112+
}
113+
mirrorHost.User = string(username)
114+
mirrorHost.Pass = string(password)
115+
if caCert, ok := mirrorCredentialsSecret.Data["ca.crt"]; ok {
116+
mirrorHost.RegCert = string(caCert)
117+
}
118+
}
119+
rc := regclient.New(
120+
regclient.WithConfigHost(mirrorHost),
121+
regclient.WithUserAgent("regclient/example"),
122+
)
123+
mirrorRef, err := ref.NewHost(mirrorHost.Hostname)
124+
if err != nil {
125+
result.Allowed = false
126+
result.Error = true
127+
result.Causes = append(result.Causes,
128+
preflight.Cause{
129+
Message: "failed to create a client to verify registry configuration",
130+
Field: registryMirrorVarPath,
131+
},
132+
)
133+
return result
134+
}
135+
_, err = rc.Ping(ctx, mirrorRef) // ping will return an error for anything that's not 200
136+
if err != nil {
137+
result.Allowed = false
138+
result.Error = true
139+
result.Causes = append(result.Causes,
140+
preflight.Cause{
141+
Message: fmt.Sprintf("failed to ping registry %s with err: %s", mirrorHost.Hostname, err.Error()),
142+
Field: registryMirrorVarPath,
143+
},
144+
)
145+
return result
146+
}
147+
result.Allowed = true
148+
result.Error = false
149+
return result
150+
}
151+
152+
func newRegistryCheck(
153+
cd *checkDependencies,
154+
) []preflight.Check {
155+
checks := []preflight.Check{}
156+
if cd.genericClusterConfigSpec != nil &&
157+
cd.genericClusterConfigSpec.GlobalImageRegistryMirror != nil {
158+
checks = append(checks, &registyCheck{
159+
field: "cluster.spec.topology.variables[.name=clusterConfig].value.globalImageRegistryMirror",
160+
kclient: cd.kclient,
161+
registryMirror: cd.genericClusterConfigSpec.GlobalImageRegistryMirror,
162+
cluster: cd.cluster,
163+
})
164+
}
165+
if cd.genericClusterConfigSpec != nil && len(cd.genericClusterConfigSpec.ImageRegistries) > 0 {
166+
for i, registry := range cd.genericClusterConfigSpec.ImageRegistries {
167+
checks = append(checks, &registyCheck{
168+
field: fmt.Sprintf("cluster.spec.topology.variables[.name=clusterConfig].value.imageRegistries[%d]", i),
169+
kclient: cd.kclient,
170+
imageRegistry: &registry,
171+
cluster: cd.cluster,
172+
})
173+
}
174+
}
175+
return checks
176+
}

0 commit comments

Comments
 (0)