Skip to content

Commit fc06a6c

Browse files
committed
Implement single VM image check for control plane and workers
1 parent 317ab8c commit fc06a6c

File tree

4 files changed

+162
-93
lines changed

4 files changed

+162
-93
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ require (
2424
github.com/nutanix/ntnx-api-golang-clients/clustermgmt-go-client/v4 v4.0.1-beta.2
2525
github.com/nutanix/ntnx-api-golang-clients/networking-go-client/v4 v4.0.2-beta.1
2626
github.com/nutanix/ntnx-api-golang-clients/prism-go-client/v4 v4.0.1-beta.1
27+
github.com/nutanix/ntnx-api-golang-clients/vmm-go-client/v4 v4.0.1-beta.1
2728
github.com/onsi/ginkgo/v2 v2.23.4
2829
github.com/onsi/gomega v1.37.0
2930
github.com/pkg/errors v0.9.1
@@ -111,7 +112,6 @@ require (
111112
github.com/modern-go/reflect2 v1.0.2 // indirect
112113
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
113114
github.com/nutanix/ntnx-api-golang-clients/storage-go-client/v4 v4.0.2-alpha.3 // indirect
114-
github.com/nutanix/ntnx-api-golang-clients/vmm-go-client/v4 v4.0.1-beta.1 // indirect
115115
github.com/nutanix/ntnx-api-golang-clients/volumes-go-client/v4 v4.0.1-beta.1 // indirect
116116
github.com/opencontainers/go-digest v1.0.0 // indirect
117117
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect

pkg/webhook/preflight/nutanix/checker.go

Lines changed: 10 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5,85 +5,34 @@ package nutanix
55

66
import (
77
"context"
8-
"fmt"
8+
"sync"
99

10+
prismv4 "github.com/nutanix-cloud-native/prism-go-client/v4"
1011
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
1112
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
1213

13-
prismv4 "github.com/nutanix-cloud-native/prism-go-client/v4"
14-
15-
carenv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
16-
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/variables"
1714
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/webhook/preflight"
1815
)
1916

2017
type Checker struct {
2118
client ctrlclient.Client
2219
nutanixClient *prismv4.Client
20+
cluster *clusterv1.Cluster
21+
22+
clientMutex sync.Mutex
2323
}
2424

2525
func (n *Checker) Init(
2626
ctx context.Context,
2727
client ctrlclient.Client,
2828
cluster *clusterv1.Cluster,
29-
) ([]preflight.Check, error) {
29+
) []preflight.Check {
3030
n.client = client
31+
n.cluster = cluster
3132

32-
clusterConfig, err := variables.UnmarshalClusterConfigVariable(cluster.Spec.Topology.Variables)
33-
if err != nil {
34-
return nil, fmt.Errorf("failed to unmarshal topology variable %q: %w", carenv1.ClusterConfigVariableName, err)
35-
}
36-
37-
if clusterConfig.Nutanix == nil {
38-
return nil, fmt.Errorf("missing Nutanix configuration in cluster topology")
39-
}
40-
41-
// Initialize Nutanix client from the credentials referenced by the cluster configuration.
42-
n.nutanixClient, err = newV4Client(ctx, client, cluster.Namespace, clusterConfig)
43-
if err != nil {
44-
return nil, fmt.Errorf("failed to create Nutanix client: %w", err)
45-
}
46-
47-
checks := []preflight.Check{}
48-
if clusterConfig.ControlPlane != nil && clusterConfig.ControlPlane.Nutanix != nil {
49-
checks = append(
50-
checks,
51-
n.VMImageCheck(
52-
&clusterConfig.ControlPlane.Nutanix.MachineDetails,
53-
"controlPlane.nutanix.machineDetails",
54-
),
55-
)
56-
}
57-
58-
if cluster.Spec.Topology.Workers != nil {
59-
for i, md := range cluster.Spec.Topology.Workers.MachineDeployments {
60-
if md.Variables == nil {
61-
continue
62-
}
63-
64-
workerConfig, err := variables.UnmarshalWorkerConfigVariable(md.Variables.Overrides)
65-
if err != nil {
66-
return nil, fmt.Errorf(
67-
"failed to unmarshal topology variable %q %d: %w",
68-
carenv1.WorkerConfigVariableName,
69-
i,
70-
err,
71-
)
72-
}
73-
74-
if workerConfig.Nutanix == nil {
75-
continue
76-
}
77-
78-
n.VMImageCheck(
79-
&workerConfig.Nutanix.MachineDetails,
80-
fmt.Sprintf(
81-
"workers.machineDeployments[.name=%s].variables.overrides[.name=workerConfig].value.nutanix.machineDetails",
82-
md.Name,
83-
),
84-
)
85-
}
33+
checks := []preflight.Check{
34+
n.VMImageCheck,
8635
}
8736

88-
return checks, nil
37+
return checks
8938
}

pkg/webhook/preflight/nutanix/client.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,40 @@ import (
44
"context"
55
"fmt"
66

7-
corev1 "k8s.io/api/core/v1"
8-
"k8s.io/apimachinery/pkg/types"
9-
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
10-
117
prism "github.com/nutanix-cloud-native/prism-go-client"
128
prismcredentials "github.com/nutanix-cloud-native/prism-go-client/environment/credentials"
139
prismv4 "github.com/nutanix-cloud-native/prism-go-client/v4"
10+
corev1 "k8s.io/api/core/v1"
11+
"k8s.io/apimachinery/pkg/types"
12+
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
1413

15-
carenvariables "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/variables"
14+
carenv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
15+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/variables"
1616
)
1717

18-
func newV4Client(
18+
func (n *Checker) v4client(
1919
ctx context.Context,
2020
client ctrlclient.Client,
2121
clusterNamespace string,
22-
clusterConfig *carenvariables.ClusterConfigSpec,
2322
) (
2423
*prismv4.Client,
2524
error,
2625
) {
26+
n.clientMutex.Lock()
27+
defer n.clientMutex.Unlock()
28+
if n.nutanixClient != nil {
29+
return n.nutanixClient, nil
30+
}
31+
32+
clusterConfig, err := variables.UnmarshalClusterConfigVariable(n.cluster.Spec.Topology.Variables)
33+
if err != nil {
34+
return nil, fmt.Errorf("failed to unmarshal cluster variable %q: %w", carenv1.ClusterConfigVariableName, err)
35+
}
36+
37+
if clusterConfig.Nutanix == nil {
38+
return nil, fmt.Errorf("missing Nutanix configuration in cluster topology")
39+
}
40+
2741
if clusterConfig.Nutanix.PrismCentralEndpoint.Credentials.SecretRef.Name == "" {
2842
return nil, fmt.Errorf("prism Central credentials reference SecretRef.Name has no value")
2943
}

pkg/webhook/preflight/nutanix/image.go

Lines changed: 130 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,151 @@ import (
44
"context"
55
"fmt"
66

7-
vmmv4 "github.com/nutanix/ntnx-api-golang-clients/vmm-go-client/v4/models/vmm/v4/content"
8-
97
prismv4 "github.com/nutanix-cloud-native/prism-go-client/v4"
8+
vmmv4 "github.com/nutanix/ntnx-api-golang-clients/vmm-go-client/v4/models/vmm/v4/content"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1010

1111
capxv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1"
1212
carenv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
13+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/variables"
1314
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/webhook/preflight"
1415
)
1516

16-
func (n *Checker) VMImageCheck(details *carenv1.NutanixMachineDetails, field string) preflight.Check {
17-
return func(ctx context.Context) preflight.CheckResult {
18-
result := preflight.CheckResult{
19-
Allowed: true,
20-
Field: field,
17+
func (n *Checker) VMImageCheck(ctx context.Context) preflight.CheckResult {
18+
result := preflight.CheckResult{
19+
Allowed: true,
20+
}
21+
22+
// Check control plane VM image.
23+
clusterConfig, err := variables.UnmarshalClusterConfigVariable(n.cluster.Spec.Topology.Variables)
24+
if err != nil {
25+
result.Error = true
26+
result.Causes = append(result.Causes, metav1.StatusCause{
27+
Type: "VMImageCheck",
28+
Message: fmt.Sprintf(
29+
"failed to unmarshal topology variable %q: %s",
30+
carenv1.ClusterConfigVariableName,
31+
err,
32+
),
33+
Field: "cluster.spec.topology.variables",
34+
})
35+
} else if clusterConfig != nil {
36+
if clusterConfig.ControlPlane == nil || clusterConfig.ControlPlane.Nutanix == nil {
37+
result.Causes = append(result.Causes, metav1.StatusCause{
38+
Type: "VMImageCheck",
39+
Message: "missing Nutanix configuration in cluster topology",
40+
Field: "cluster.spec.topology.controlPlane.nutanix",
41+
})
2142
}
2243

23-
if details.ImageLookup != nil {
24-
result.Allowed = false
25-
result.Message = "ImageLookup is not yet supported"
26-
return result
44+
n.vmImageCheckForMachineDetails(
45+
ctx,
46+
&clusterConfig.ControlPlane.Nutanix.MachineDetails,
47+
"controlPlane.nutanix.machineDetails",
48+
&result,
49+
)
50+
}
51+
52+
// Check worker VM images.
53+
if n.cluster.Spec.Topology.Workers == nil {
54+
return result
55+
}
56+
57+
for _, md := range n.cluster.Spec.Topology.Workers.MachineDeployments {
58+
if md.Variables == nil {
59+
continue
2760
}
2861

29-
if details.Image != nil {
30-
images, err := getVMImages(n.nutanixClient, details.Image)
31-
if err != nil {
32-
result.Allowed = false
33-
result.Error = true
34-
result.Message = fmt.Sprintf("failed to count matching VM Images: %s", err)
35-
return result
62+
workerConfig, err := variables.UnmarshalWorkerConfigVariable(md.Variables.Overrides)
63+
if err != nil {
64+
result.Error = true
65+
result.Causes = append(result.Causes, metav1.StatusCause{
66+
Type: "VMImageCheck",
67+
Message: fmt.Sprintf(
68+
"failed to unmarshal topology variable %q: %s",
69+
carenv1.WorkerConfigVariableName,
70+
err,
71+
),
72+
Field: fmt.Sprintf(
73+
"cluster.spec.topology.workers.machineDeployments[.name=%s].variables.overrides",
74+
md.Name,
75+
),
76+
})
77+
} else if workerConfig != nil {
78+
if workerConfig.Nutanix == nil {
79+
result.Causes = append(result.Causes, metav1.StatusCause{
80+
Type: "VMImageCheck",
81+
Message: "missing Nutanix configuration in worker machine deployment",
82+
Field: fmt.Sprintf("cluster.spec.topology.workers.machineDeployments[.name=%s]"+
83+
".variables.overrides[.name=workerConfig].value.nutanix", md.Name),
84+
})
85+
} else {
86+
n.vmImageCheckForMachineDetails(
87+
ctx,
88+
&workerConfig.Nutanix.MachineDetails,
89+
fmt.Sprintf(
90+
"workers.machineDeployments[.name=%s].variables.overrides[.name=workerConfig].value.nutanix.machineDetails",
91+
md.Name,
92+
),
93+
&result,
94+
)
3695
}
96+
}
97+
}
3798

38-
if len(images) != 1 {
39-
result.Allowed = false
40-
result.Message = fmt.Sprintf("expected to find 1 VM Image, found %d", len(images))
41-
return result
42-
}
99+
return result
100+
}
101+
102+
func (n *Checker) vmImageCheckForMachineDetails(
103+
ctx context.Context,
104+
details *carenv1.NutanixMachineDetails,
105+
field string,
106+
result *preflight.CheckResult,
107+
) {
108+
if details.ImageLookup != nil {
109+
result.Allowed = false
110+
result.Error = true
111+
result.Causes = append(result.Causes, metav1.StatusCause{
112+
Type: "VMImageCheck",
113+
Message: "ImageLookup is not yet supported",
114+
Field: field,
115+
})
116+
return
117+
}
118+
119+
if details.Image != nil {
120+
client, err := n.v4client(ctx, n.client, n.cluster.Namespace)
121+
if err != nil {
122+
result.Allowed = false
123+
result.Error = true
124+
result.Causes = append(result.Causes, metav1.StatusCause{
125+
Type: "VMImageCheck",
126+
Message: fmt.Sprintf("failed to get Nutanix client: %s", err),
127+
Field: field,
128+
})
129+
return
43130
}
44131

45-
return result
132+
images, err := getVMImages(client, details.Image)
133+
if err != nil {
134+
result.Allowed = false
135+
result.Error = true
136+
result.Causes = append(result.Causes, metav1.StatusCause{
137+
Type: "VMImageCheck",
138+
Message: fmt.Sprintf("failed to count matching VM Images: %s", err),
139+
Field: field,
140+
})
141+
return
142+
}
143+
144+
if len(images) != 1 {
145+
result.Allowed = false
146+
result.Causes = append(result.Causes, metav1.StatusCause{
147+
Type: "VMImageCheck",
148+
Message: fmt.Sprintf("expected to find 1 VM Image, found %d", len(images)),
149+
Field: field,
150+
})
151+
}
46152
}
47153
}
48154

0 commit comments

Comments
 (0)