Skip to content

Commit 92e78ad

Browse files
authored
feat: adds a generic checker package with registry and mirror checks (#1186)
**What problem does this PR solve?**: https://jira.nutanix.com/browse/NCN-107573 https://jira.nutanix.com/browse/NCN-105405 **Which issue(s) this PR fixes**: Fixes # **How Has This Been Tested?**: <!-- Please describe the tests that you ran to verify your changes. Provide output from the tests and any manual steps needed to replicate the tests. --> **Special notes for your reviewer**: <!-- Use this to provide any additional information to the reviewers. This may include: - Best way to review the PR. - Where the author wants the most review attention on. - etc. -->
1 parent 08aa66e commit 92e78ad

File tree

8 files changed

+765
-0
lines changed

8 files changed

+765
-0
lines changed

cmd/main.go

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

@@ -246,6 +247,7 @@ func main() {
246247
Handler: preflight.New(mgr.GetClient(), admission.NewDecoder(mgr.GetScheme()),
247248
[]preflight.Checker{
248249
// Add your preflight checkers here.
250+
preflightgeneric.Checker,
249251
preflightnutanix.Checker,
250252
}...,
251253
),

go.mod

Lines changed: 4 additions & 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
@@ -73,6 +74,7 @@ require (
7374
github.com/docker/docker v28.0.2+incompatible // indirect
7475
github.com/docker/go-connections v0.5.0 // indirect
7576
github.com/docker/go-units v0.5.0 // indirect
77+
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
7678
github.com/drone/envsubst/v2 v2.0.0-20210730161058-179042472c46 // indirect
7779
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
7880
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
@@ -111,6 +113,7 @@ require (
111113
github.com/jmespath/go-jmespath v0.4.0 // indirect
112114
github.com/josharian/intern v1.0.0 // indirect
113115
github.com/json-iterator/go v1.1.12 // indirect
116+
github.com/klauspost/compress v1.18.0 // indirect
114117
github.com/mailru/easyjson v0.7.7 // indirect
115118
github.com/mattn/go-colorable v0.1.13 // indirect
116119
github.com/mattn/go-isatty v0.0.20 // indirect
@@ -145,6 +148,7 @@ require (
145148
github.com/spf13/viper v1.20.0 // indirect
146149
github.com/stoewer/go-strcase v1.3.0 // indirect
147150
github.com/subosito/gotenv v1.6.0 // indirect
151+
github.com/ulikunitz/xz v0.5.12 // indirect
148152
github.com/valyala/fastjson v1.6.4 // indirect
149153
github.com/x448/float16 v0.8.4 // indirect
150154
go.mongodb.org/mongo-driver v1.14.0 // indirect

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj
7373
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
7474
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
7575
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
76+
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
77+
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
7678
github.com/drone/envsubst/v2 v2.0.0-20210730161058-179042472c46 h1:7QPwrLT79GlD5sizHf27aoY2RTvw62mO6x7mxkScNk0=
7779
github.com/drone/envsubst/v2 v2.0.0-20210730161058-179042472c46/go.mod h1:esf2rsHFNlZlxsqsZDojNBcnNs5REqIvRrWRHqX0vEU=
7880
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
@@ -192,6 +194,8 @@ github.com/keploy/go-sdk v0.9.0 h1:kpSNcCTDdELsa1gWyhoD9oV57SgSMbG/wq6Cjp4y7cY=
192194
github.com/keploy/go-sdk v0.9.0/go.mod h1:vNKXoFd2MaK+Gly/K6XeP1Hs9dP834C74szH+vtBPwg=
193195
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
194196
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
197+
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
198+
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
195199
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
196200
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
197201
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -241,6 +245,8 @@ github.com/nutanix/ntnx-api-golang-clients/volumes-go-client/v4 v4.0.1-beta.1 h1
241245
github.com/nutanix/ntnx-api-golang-clients/volumes-go-client/v4 v4.0.1-beta.1/go.mod h1:Z+RKLwsHYxAcFbZPy2ft3QAK9kBPt9bQdqXSp7eYWkY=
242246
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
243247
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
248+
github.com/olareg/olareg v0.1.2 h1:75G8X6E9FUlzL/CSjgFcYfMgNzlc7CxULpUUNsZBIvI=
249+
github.com/olareg/olareg v0.1.2/go.mod h1:TWs+N6pO1S4bdB6eerzUm/ITRQ6kw91mVf9ZYeGtw+Y=
244250
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
245251
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
246252
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
@@ -270,6 +276,8 @@ github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G
270276
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
271277
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
272278
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
279+
github.com/regclient/regclient v0.8.3 h1:AFAPu/vmOYGyY22AIgzdBUKbzH+83lEpRioRYJ/reCs=
280+
github.com/regclient/regclient v0.8.3/go.mod h1:gjQh5uBVZoo/CngchghtQh9Hx81HOMKRRDd5WPcPkbk=
273281
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
274282
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
275283
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@@ -310,6 +318,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
310318
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
311319
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
312320
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
321+
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
322+
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
313323
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
314324
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
315325
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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+
configurationCheckFactory: newConfigurationCheck,
20+
}
21+
22+
type genericChecker struct {
23+
configurationCheckFactory func(
24+
cd *checkDependencies,
25+
) preflight.Check
26+
registryCheckFactory func(
27+
cd *checkDependencies,
28+
) []preflight.Check
29+
}
30+
31+
type checkDependencies struct {
32+
kclient ctrlclient.Client
33+
cluster *clusterv1.Cluster
34+
genericClusterConfigSpec *carenv1.GenericClusterConfigSpec
35+
log logr.Logger
36+
}
37+
38+
func (g *genericChecker) Init(
39+
ctx context.Context,
40+
kclient ctrlclient.Client,
41+
cluster *clusterv1.Cluster,
42+
) []preflight.Check {
43+
cd := &checkDependencies{
44+
kclient: kclient,
45+
cluster: cluster,
46+
log: ctrl.LoggerFrom(ctx).WithName("preflight/generic"),
47+
}
48+
checks := []preflight.Check{
49+
// The configuration check must run first, because it initializes data used by all other checks.
50+
g.configurationCheckFactory(cd),
51+
}
52+
checks = append(checks, g.registryCheckFactory(cd)...)
53+
return checks
54+
}
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
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+
"net/url"
10+
11+
"github.com/go-logr/logr"
12+
"github.com/regclient/regclient"
13+
"github.com/regclient/regclient/config"
14+
"github.com/regclient/regclient/types/ping"
15+
"github.com/regclient/regclient/types/ref"
16+
corev1 "k8s.io/api/core/v1"
17+
"k8s.io/apimachinery/pkg/types"
18+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
19+
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
20+
21+
carenv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
22+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/webhook/preflight"
23+
)
24+
25+
var registryMirrorVarPath = "cluster.spec.topology.variables[.name=clusterConfig].value.globalImageRegistryMirror"
26+
27+
type registryCheck struct {
28+
field string
29+
kclient ctrlclient.Client
30+
cluster *clusterv1.Cluster
31+
regClientPingerGetter regClientPingerFactory
32+
log logr.Logger
33+
34+
registryURL string
35+
credentials *carenv1.RegistryCredentials
36+
}
37+
38+
func (r *registryCheck) Name() string {
39+
return "RegistryCredentials"
40+
}
41+
42+
func (r *registryCheck) Run(ctx context.Context) preflight.CheckResult {
43+
return r.checkRegistry(
44+
ctx,
45+
r.registryURL,
46+
r.credentials,
47+
r.regClientPingerGetter,
48+
)
49+
}
50+
51+
type regClientPinger interface {
52+
Ping(context.Context, ref.Ref) (ping.Result, error)
53+
}
54+
55+
type regClientPingerFactory func(...regclient.Opt) regClientPinger
56+
57+
func defaultRegClientGetter(opts ...regclient.Opt) regClientPinger {
58+
return regclient.New(opts...)
59+
}
60+
61+
func pingFailedReasonString(registryURL string, err error) string {
62+
return fmt.Sprintf("failed to ping registry %s with err: %s", registryURL, err.Error())
63+
}
64+
65+
func (r *registryCheck) checkRegistry(
66+
ctx context.Context,
67+
registryURL string,
68+
credentials *carenv1.RegistryCredentials,
69+
regClientGetter regClientPingerFactory,
70+
) preflight.CheckResult {
71+
result := preflight.CheckResult{
72+
Allowed: false,
73+
}
74+
if r.registryURL == "" {
75+
result.Allowed = true
76+
return result
77+
}
78+
registryURLParsed, err := url.ParseRequestURI(registryURL)
79+
if err != nil {
80+
result.Allowed = false
81+
result.Error = true
82+
result.Causes = append(result.Causes,
83+
preflight.Cause{
84+
Message: fmt.Sprintf("failed to parse registry url %s with error : %s", registryURL, err),
85+
Field: registryMirrorVarPath,
86+
},
87+
)
88+
return result
89+
}
90+
mirrorHost := config.Host{
91+
Name: registryURLParsed.Host,
92+
}
93+
if credentials != nil && credentials.SecretRef != nil {
94+
mirrorCredentialsSecret := &corev1.Secret{}
95+
err := r.kclient.Get(
96+
ctx,
97+
types.NamespacedName{
98+
Namespace: r.cluster.Namespace,
99+
Name: credentials.SecretRef.Name,
100+
},
101+
mirrorCredentialsSecret,
102+
)
103+
if err != nil {
104+
result.Allowed = false
105+
result.Error = true
106+
result.Causes = append(result.Causes,
107+
preflight.Cause{
108+
Message: fmt.Sprintf("failed to get Registry credentials Secret: %s", err),
109+
Field: fmt.Sprintf("%s.credentials.secretRef", registryMirrorVarPath),
110+
},
111+
)
112+
return result
113+
}
114+
username, ok := mirrorCredentialsSecret.Data["username"]
115+
if ok {
116+
mirrorHost.User = string(username)
117+
}
118+
password, ok := mirrorCredentialsSecret.Data["password"]
119+
if ok {
120+
mirrorHost.Pass = string(password)
121+
}
122+
if caCert, ok := mirrorCredentialsSecret.Data["ca.crt"]; ok {
123+
mirrorHost.RegCert = string(caCert)
124+
}
125+
}
126+
rc := regClientGetter(
127+
regclient.WithConfigHost(mirrorHost),
128+
regclient.WithUserAgent("regclient/example"),
129+
)
130+
mirrorRef, err := ref.NewHost(registryURLParsed.Host)
131+
if err != nil {
132+
result.Allowed = false
133+
result.Error = true
134+
result.Causes = append(result.Causes,
135+
preflight.Cause{
136+
Message: fmt.Sprintf("failed to create a client to verify registry configuration %s", err.Error()),
137+
Field: registryMirrorVarPath,
138+
},
139+
)
140+
return result
141+
}
142+
_, err = rc.Ping(ctx, mirrorRef) // ping will return an error for anything that's not 200
143+
if err != nil {
144+
result.Allowed = false
145+
result.Causes = append(result.Causes,
146+
preflight.Cause{
147+
Message: pingFailedReasonString(registryURL, err),
148+
Field: registryMirrorVarPath,
149+
},
150+
)
151+
return result
152+
}
153+
result.Allowed = true
154+
result.Error = false
155+
return result
156+
}
157+
158+
func newRegistryCheck(
159+
cd *checkDependencies,
160+
) []preflight.Check {
161+
checks := []preflight.Check{}
162+
if cd.genericClusterConfigSpec != nil &&
163+
cd.genericClusterConfigSpec.GlobalImageRegistryMirror != nil {
164+
checks = append(checks, &registryCheck{
165+
field: "cluster.spec.topology.variables[.name=clusterConfig].value.globalImageRegistryMirror",
166+
kclient: cd.kclient,
167+
cluster: cd.cluster,
168+
regClientPingerGetter: defaultRegClientGetter,
169+
log: cd.log,
170+
registryURL: cd.genericClusterConfigSpec.GlobalImageRegistryMirror.DeepCopy().URL,
171+
credentials: cd.genericClusterConfigSpec.GlobalImageRegistryMirror.DeepCopy().Credentials,
172+
})
173+
}
174+
if cd.genericClusterConfigSpec != nil && len(cd.genericClusterConfigSpec.ImageRegistries) > 0 {
175+
for i := range cd.genericClusterConfigSpec.ImageRegistries {
176+
registry := cd.genericClusterConfigSpec.ImageRegistries[i]
177+
checks = append(checks, &registryCheck{
178+
field: fmt.Sprintf(
179+
"cluster.spec.topology.variables[.name=clusterConfig].value.imageRegistries[%d]",
180+
i,
181+
),
182+
kclient: cd.kclient,
183+
cluster: cd.cluster,
184+
regClientPingerGetter: defaultRegClientGetter,
185+
log: cd.log,
186+
registryURL: registry.DeepCopy().URL,
187+
credentials: registry.DeepCopy().Credentials,
188+
})
189+
}
190+
}
191+
return checks
192+
}

0 commit comments

Comments
 (0)