Skip to content

Commit 47322d0

Browse files
committed
Refactor error handling to report relevant fields
1 parent 0185a91 commit 47322d0

File tree

5 files changed

+208
-155
lines changed

5 files changed

+208
-155
lines changed

pkg/webhook/preflight/nutanix/checker.go

Lines changed: 50 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -12,135 +12,83 @@ import (
1212

1313
prismv4 "github.com/nutanix-cloud-native/prism-go-client/v4"
1414

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"
1715
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/webhook/preflight"
1816
)
1917

20-
type Checker struct {
21-
client ctrlclient.Client
22-
cluster *clusterv1.Cluster
23-
nutanixSpec *carenv1.NutanixSpec
24-
nutanixControlPlaneNodeSpec *carenv1.NutanixNodeSpec
25-
nutanixNodeSpecByMachineDeploymentName map[string]*carenv1.NutanixNodeSpec
26-
v4client *prismv4.Client
27-
}
18+
type Checker struct{}
2819

2920
func (n *Checker) Init(
3021
ctx context.Context,
31-
client ctrlclient.Client,
22+
kclient ctrlclient.Client,
3223
cluster *clusterv1.Cluster,
3324
) []preflight.Check {
34-
n.client = client
35-
n.cluster = cluster
36-
37-
var err error
25+
prismCentralEndpointSpec,
26+
controlPlaneNutanixNodeSpec,
27+
nutanixNodeSpecByMachineDeploymentName,
28+
errCauses := readNutanixSpecs(cluster)
29+
if len(errCauses) > 0 {
30+
return initErrorCheck(errCauses...)
31+
}
3832

39-
n.nutanixSpec, n.nutanixControlPlaneNodeSpec, n.nutanixNodeSpecByMachineDeploymentName, err = getData(cluster)
40-
if err != nil {
41-
return []preflight.Check{
42-
errorCheck("TBD", "TBD"),
43-
}
33+
if controlPlaneNutanixNodeSpec == nil && len(nutanixNodeSpecByMachineDeploymentName) == 0 {
34+
// No Nutanix specs found, no checks to run.
35+
return nil
4436
}
4537

46-
credentials, err := getCredentials(ctx, client, cluster, n.nutanixSpec)
47-
if err != nil {
48-
return []preflight.Check{
49-
errorCheck(
50-
fmt.Sprintf("failed to get Nutanix credentials: %s", err),
51-
"cluster.spec.topology.variables[.name=clusterConfig].nutanix.prismCentralEndpoint.credentials",
52-
),
53-
}
38+
// At least one Nutanix spec is defined. Get credentials and create a client,
39+
// because all checks require them.
40+
credentials, errCauses := getCredentials(ctx, kclient, cluster, prismCentralEndpointSpec)
41+
if len(errCauses) > 0 {
42+
return initErrorCheck(errCauses...)
5443
}
5544

56-
n.v4client, err = v4client(credentials, n.nutanixSpec)
45+
nv4client, err := prismv4.NewV4Client(*credentials)
5746
if err != nil {
58-
return []preflight.Check{
59-
errorCheck(
60-
fmt.Sprintf("failed to initialize Nutanix v4 client: %s", err),
61-
"cluster.spec.topology.variables[.name=clusterConfig].nutanix",
62-
),
63-
}
47+
return initErrorCheck(
48+
preflight.Cause{
49+
Message: fmt.Sprintf("failed to initialize Nutanix v4 client: %s", err),
50+
Field: "",
51+
})
6452
}
6553

6654
// Initialize checks.
6755
checks := []preflight.Check{}
68-
if n.nutanixControlPlaneNodeSpec != nil {
56+
if controlPlaneNutanixNodeSpec != nil {
6957
checks = append(checks,
70-
func(ctx context.Context) preflight.CheckResult {
71-
return n.vmImageCheckForMachineDetails(
72-
&n.nutanixControlPlaneNodeSpec.MachineDetails,
73-
"cluster.spec.topology[.name=clusterConfig].value.controlPlane.nutanix.machineDetails",
74-
)
75-
},
58+
vmImageCheck(
59+
nv4client,
60+
controlPlaneNutanixNodeSpec,
61+
"cluster.spec.topology[.name=clusterConfig].value.controlPlane.nutanix",
62+
),
7663
)
7764
}
78-
for mdName, nodeSpec := range n.nutanixNodeSpecByMachineDeploymentName {
65+
for _, md := range cluster.Spec.Topology.Workers.MachineDeployments {
66+
if nutanixNodeSpecByMachineDeploymentName[md.Name] == nil {
67+
continue
68+
}
7969
checks = append(checks,
80-
func(ctx context.Context) preflight.CheckResult {
81-
return n.vmImageCheckForMachineDetails(
82-
&nodeSpec.MachineDetails,
83-
fmt.Sprintf(
84-
"cluster.spec.topology.workers.machineDeployments[.name=%s]"+
85-
".overrides[.name=workerConfig].value.nutanix.machineDetails",
86-
mdName,
87-
),
88-
)
89-
},
70+
vmImageCheck(
71+
nv4client,
72+
nutanixNodeSpecByMachineDeploymentName[md.Name],
73+
fmt.Sprintf(
74+
"cluster.spec.topology.workers.machineDeployments[.name=%s].variables[.name=workerConfig].value.nutanix",
75+
md.Name,
76+
),
77+
),
9078
)
9179
}
9280
return checks
9381
}
9482

95-
func errorCheck(msg, field string) preflight.Check {
96-
return func(ctx context.Context) preflight.CheckResult {
97-
return preflight.CheckResult{
98-
Name: "Nutanix",
99-
Allowed: false,
100-
Error: true,
101-
Causes: []preflight.Cause{
102-
{
103-
Message: msg,
104-
Field: field,
105-
},
106-
},
107-
}
108-
}
109-
}
110-
111-
func getData(
112-
cluster *clusterv1.Cluster,
113-
) (*carenv1.NutanixSpec, *carenv1.NutanixNodeSpec, map[string]*carenv1.NutanixNodeSpec, error) {
114-
nutanixSpec := &carenv1.NutanixSpec{}
115-
controlPlaneNutanixNodeSpec := &carenv1.NutanixNodeSpec{}
116-
nutanixNodeSpecByMachineDeploymentName := make(map[string]*carenv1.NutanixNodeSpec)
117-
118-
clusterConfig, err := variables.UnmarshalClusterConfigVariable(cluster.Spec.Topology.Variables)
119-
if err != nil {
120-
return nil, nil, nil, fmt.Errorf("failed to unmarshal .variables[.name=clusterConfig]: %w", err)
121-
}
122-
if clusterConfig != nil && clusterConfig.Nutanix != nil {
123-
nutanixSpec = clusterConfig.Nutanix
124-
}
125-
126-
if clusterConfig.ControlPlane != nil && clusterConfig.ControlPlane.Nutanix != nil {
127-
controlPlaneNutanixNodeSpec = clusterConfig.ControlPlane.Nutanix
128-
}
129-
130-
if cluster.Spec.Topology.Workers != nil {
131-
for _, md := range cluster.Spec.Topology.Workers.MachineDeployments {
132-
workerConfig, err := variables.UnmarshalWorkerConfigVariable(md.Variables.Overrides)
133-
if err != nil {
134-
return nil, nil, nil, fmt.Errorf(
135-
"failed to unmarshal .variables[.name=workerConfig] for machine deployment %s: %w",
136-
md.Name, err,
137-
)
83+
func initErrorCheck(causes ...preflight.Cause) []preflight.Check {
84+
return []preflight.Check{
85+
func(ctx context.Context) preflight.CheckResult {
86+
return preflight.CheckResult{
87+
Name: "Nutanix",
88+
Allowed: false,
89+
Error: true,
90+
Causes: causes,
13891
}
139-
if workerConfig != nil && workerConfig.Nutanix != nil {
140-
nutanixNodeSpecByMachineDeploymentName[md.Name] = workerConfig.Nutanix
141-
}
142-
}
92+
},
14393
}
144-
145-
return nutanixSpec, controlPlaneNutanixNodeSpec, nutanixNodeSpecByMachineDeploymentName, nil
14694
}

pkg/webhook/preflight/nutanix/client.go

Lines changed: 0 additions & 31 deletions
This file was deleted.

pkg/webhook/preflight/nutanix/credentials.go

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import (
99
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
1010
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
1111

12+
prismgoclient "github.com/nutanix-cloud-native/prism-go-client"
1213
prismcredentials "github.com/nutanix-cloud-native/prism-go-client/environment/credentials"
13-
prismtypes "github.com/nutanix-cloud-native/prism-go-client/environment/types"
1414

1515
carenv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
16+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/webhook/preflight"
1617
)
1718

1819
const credentialsSecretDataKey = "credentials"
@@ -21,41 +22,86 @@ func getCredentials(
2122
ctx context.Context,
2223
client ctrlclient.Client,
2324
cluster *clusterv1.Cluster,
24-
nutanixSpec *carenv1.NutanixSpec,
25-
) (*prismtypes.ApiCredentials, error) {
26-
if nutanixSpec.PrismCentralEndpoint.Credentials.SecretRef.Name == "" {
27-
return nil, fmt.Errorf("secretRef.name has no value")
25+
prismCentralEndpointSpec *carenv1.NutanixPrismCentralEndpointSpec,
26+
) (*prismgoclient.Credentials, []preflight.Cause) {
27+
if prismCentralEndpointSpec == nil {
28+
return nil, []preflight.Cause{
29+
{
30+
Message: "Prism Central endpoint specification is missing",
31+
Field: "cluster.spec.topology.variables[.name=clusterConfig].nutanix.prismCentralEndpoint",
32+
},
33+
}
34+
}
35+
36+
if prismCentralEndpointSpec.Credentials.SecretRef.Name == "" {
37+
return nil, []preflight.Cause{
38+
{
39+
Message: "Prism Central credentials secret reference is missing the name",
40+
Field: "cluster.spec.topology.variables[.name=clusterConfig].nutanix.prismCentralEndpoint.credentials.secretRef.name",
41+
},
42+
}
2843
}
2944

3045
credentialsSecret := &corev1.Secret{}
3146
if err := client.Get(
3247
ctx,
3348
types.NamespacedName{
3449
Namespace: cluster.Namespace,
35-
Name: nutanixSpec.PrismCentralEndpoint.Credentials.SecretRef.Name,
50+
Name: prismCentralEndpointSpec.Credentials.SecretRef.Name,
3651
},
3752
credentialsSecret,
3853
); err != nil {
39-
return nil, fmt.Errorf("failed to get Secret: %w", err)
54+
return nil, []preflight.Cause{
55+
{
56+
Message: fmt.Sprintf("failed to get the credentials Secret: %s", err),
57+
Field: "cluster.spec.topology.variables[.name=clusterConfig].nutanix.prismCentralEndpoint.credentials.secretRef",
58+
},
59+
}
4060
}
4161

4262
if len(credentialsSecret.Data) == 0 {
43-
return nil, fmt.Errorf(
44-
"the Secret %s/%s has no data",
45-
cluster.Namespace,
46-
nutanixSpec.PrismCentralEndpoint.Credentials.SecretRef.Name,
47-
)
63+
return nil, []preflight.Cause{
64+
{
65+
Message: fmt.Sprintf("credentials Secret has no data"),
66+
Field: "cluster.spec.topology.variables[.name=clusterConfig].nutanix.prismCentralEndpoint.credentials.secretRef",
67+
},
68+
}
4869
}
4970

5071
data, ok := credentialsSecret.Data[credentialsSecretDataKey]
5172
if !ok {
52-
return nil, fmt.Errorf(
53-
"the Secret %s/%s has no data for key %s",
54-
cluster.Namespace,
55-
nutanixSpec.PrismCentralEndpoint.Credentials.SecretRef.Name,
56-
credentialsSecretDataKey,
57-
)
73+
return nil, []preflight.Cause{
74+
{
75+
Message: fmt.Sprintf("credentials Secret data is missing the key '%s'", credentialsSecretDataKey),
76+
Field: "cluster.spec.topology.variables[.name=clusterConfig].nutanix.prismCentralEndpoint.credentials.secretRef",
77+
},
78+
}
79+
}
80+
81+
usernamePassword, err := prismcredentials.ParseCredentials(data)
82+
if err != nil {
83+
return nil, []preflight.Cause{
84+
{
85+
Message: fmt.Sprintf("failed to parse credentials from Secret: %s", err),
86+
Field: "cluster.spec.topology.variables[.name=clusterConfig].nutanix.prismCentralEndpoint.credentials.secretRef",
87+
},
88+
}
5889
}
59-
// Get username and password
60-
return prismcredentials.ParseCredentials(data)
90+
91+
host, port, err := prismCentralEndpointSpec.ParseURL()
92+
if err != nil {
93+
return nil, []preflight.Cause{
94+
{
95+
Message: fmt.Sprintf("failed to parse Prism Central URL: %s", err),
96+
Field: "cluster.spec.topology.variables[.name=clusterConfig].nutanix.prismCentralEndpoint.url",
97+
},
98+
}
99+
}
100+
101+
return &prismgoclient.Credentials{
102+
Endpoint: fmt.Sprintf("%s:%d", host, port),
103+
Username: usernamePassword.Username,
104+
Password: usernamePassword.Password,
105+
Insecure: prismCentralEndpointSpec.Insecure,
106+
}, nil
61107
}

pkg/webhook/preflight/nutanix/image.go

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package nutanix
22

33
import (
4+
"context"
45
"fmt"
56

67
vmmv4 "github.com/nutanix/ntnx-api-golang-clients/vmm-go-client/v4/models/vmm/v4/content"
@@ -12,7 +13,38 @@ import (
1213
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/webhook/preflight"
1314
)
1415

15-
func (n *Checker) vmImageCheckForMachineDetails(
16+
func vmImageCheck(
17+
client *prismv4.Client,
18+
nutanixNodeSpec *carenv1.NutanixNodeSpec,
19+
nutanixNodeSpecField string,
20+
) preflight.Check {
21+
if nutanixNodeSpec == nil {
22+
return func(ctx context.Context) preflight.CheckResult {
23+
return preflight.CheckResult{
24+
Name: "VMImage",
25+
Allowed: false,
26+
Error: true,
27+
Causes: []preflight.Cause{
28+
{
29+
Message: fmt.Sprintf("NutanixNodeSpec is missing"),
30+
Field: nutanixNodeSpecField,
31+
},
32+
},
33+
}
34+
}
35+
}
36+
37+
return func(ctx context.Context) preflight.CheckResult {
38+
return vmImageCheckForMachineDetails(
39+
client,
40+
&nutanixNodeSpec.MachineDetails,
41+
fmt.Sprintf("%s.machineDetails", nutanixNodeSpecField),
42+
)
43+
}
44+
}
45+
46+
func vmImageCheckForMachineDetails(
47+
client *prismv4.Client,
1648
details *carenv1.NutanixMachineDetails,
1749
field string,
1850
) preflight.CheckResult {
@@ -36,7 +68,7 @@ func (n *Checker) vmImageCheckForMachineDetails(
3668
errCh := make(chan error)
3769
defer close(errCh)
3870

39-
images, err := getVMImages(n.v4client, details.Image)
71+
images, err := getVMImages(client, details.Image)
4072
if err != nil {
4173
result.Allowed = false
4274
result.Error = true

0 commit comments

Comments
 (0)