Skip to content
This repository was archived by the owner on Apr 11, 2024. It is now read-only.

Commit 681e4f5

Browse files
committed
refactor: combine PC host and port into a single url var
This makes it simpler for clients to provide a single input field and not have to do any parsing to split the hostname and port. It also allows us to use API validation for bad input.
1 parent 184e7d6 commit 681e4f5

File tree

5 files changed

+165
-37
lines changed

5 files changed

+165
-37
lines changed

api/v1alpha1/nutanix_clusterconfig_types.go

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,10 @@ import (
77
corev1 "k8s.io/api/core/v1"
88
"k8s.io/utils/ptr"
99
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
10-
11-
"github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/api/variables"
1210
)
1311

1412
const (
15-
PrismCentralPort = 9440
13+
DefaultPrismCentralPort = 9440
1614
)
1715

1816
// NutanixSpec defines the desired state of NutanixCluster.
@@ -39,11 +37,8 @@ func (NutanixSpec) VariableSchema() clusterv1.VariableSchema {
3937
}
4038

4139
type NutanixPrismCentralEndpointSpec struct {
42-
// host is the DNS name or IP address of the Nutanix Prism Central
43-
Host string `json:"host"`
44-
45-
// port is the port number to access the Nutanix Prism Central
46-
Port int32 `json:"port"`
40+
// The URL of Nutanix Prism Central, can be DNS name or an IP address
41+
URL string `json:"url"`
4742

4843
// use insecure connection to Prism Central endpoint
4944
// +optional
@@ -65,17 +60,12 @@ func (NutanixPrismCentralEndpointSpec) VariableSchema() clusterv1.VariableSchema
6560
Description: "Nutanix Prism Central endpoint configuration",
6661
Type: "object",
6762
Properties: map[string]clusterv1.JSONSchemaProps{
68-
"host": {
69-
Description: "the DNS name or IP address of the Nutanix Prism Central",
63+
"url": {
64+
Description: "The URL of Nutanix Prism Central, can be DNS name or an IP address",
7065
Type: "string",
7166
MinLength: ptr.To[int64](1),
72-
},
73-
"port": {
74-
Description: "The port number to access the Nutanix Prism Central",
75-
Type: "integer",
76-
Default: variables.MustMarshal(PrismCentralPort),
77-
Minimum: ptr.To[int64](1),
78-
Maximum: ptr.To[int64](65535),
67+
Format: "uri",
68+
Pattern: "^https://",
7969
},
8070
"insecure": {
8171
Description: "Use insecure connection to Prism Central endpoint",
@@ -103,7 +93,7 @@ func (NutanixPrismCentralEndpointSpec) VariableSchema() clusterv1.VariableSchema
10393
Required: []string{"name"},
10494
},
10595
},
106-
Required: []string{"host", "port", "credentials"},
96+
Required: []string{"url", "credentials"},
10797
},
10898
}
10999
}

pkg/handlers/nutanix/mutation/controlplaneendpoint/variables_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package controlplaneendpoint
55

66
import (
7+
"fmt"
78
"testing"
89

910
corev1 "k8s.io/api/core/v1"
@@ -16,6 +17,8 @@ import (
1617
nutanixclusterconfig "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/nutanix/clusterconfig"
1718
)
1819

20+
var testPrismCentralURL = fmt.Sprintf("https://prism-central.nutanix.com:%d", v1alpha1.DefaultPrismCentralPort)
21+
1922
func TestVariableValidation(t *testing.T) {
2023
capitest.ValidateDiscoverVariables(
2124
t,
@@ -33,8 +36,7 @@ func TestVariableValidation(t *testing.T) {
3336
},
3437
// PrismCentralEndpoint is a required field and must always be set
3538
PrismCentralEndpoint: v1alpha1.NutanixPrismCentralEndpointSpec{
36-
Host: "prism-central.nutanix.com",
37-
Port: v1alpha1.PrismCentralPort,
39+
URL: testPrismCentralURL,
3840
Credentials: corev1.LocalObjectReference{
3941
Name: "credentials",
4042
},
@@ -52,8 +54,7 @@ func TestVariableValidation(t *testing.T) {
5254
},
5355
// PrismCentralEndpoint is a required field and must always be set
5456
PrismCentralEndpoint: v1alpha1.NutanixPrismCentralEndpointSpec{
55-
Host: "prism-central.nutanix.com",
56-
Port: v1alpha1.PrismCentralPort,
57+
URL: testPrismCentralURL,
5758
Credentials: corev1.LocalObjectReference{
5859
Name: "credentials",
5960
},
@@ -72,8 +73,7 @@ func TestVariableValidation(t *testing.T) {
7273
},
7374
// PrismCentralEndpoint is a required field and must always be set
7475
PrismCentralEndpoint: v1alpha1.NutanixPrismCentralEndpointSpec{
75-
Host: "prism-central.nutanix.com",
76-
Port: v1alpha1.PrismCentralPort,
76+
URL: testPrismCentralURL,
7777
Credentials: corev1.LocalObjectReference{
7878
Name: "credentials",
7979
},

pkg/handlers/nutanix/mutation/prismcentralendpoint/inject.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"context"
88
"encoding/base64"
99
"fmt"
10+
"net/url"
11+
"strconv"
1012

1113
"github.com/nutanix-cloud-native/prism-go-client/environment/credentials"
1214
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -97,9 +99,15 @@ func (h *nutanixPrismCentralEndpoint) Mutate(
9799
"patchedObjectName", client.ObjectKeyFromObject(obj),
98100
).Info("setting prismCentralEndpoint in NutanixCluster spec")
99101

102+
var address string
103+
var port int32
104+
address, port, err = parsePrismCentralURL(prismCentralEndpointVar.URL)
105+
if err != nil {
106+
return err
107+
}
100108
prismCentral := &credentials.NutanixPrismEndpoint{
101-
Address: prismCentralEndpointVar.Host,
102-
Port: prismCentralEndpointVar.Port,
109+
Address: address,
110+
Port: port,
103111
Insecure: prismCentralEndpointVar.Insecure,
104112
CredentialRef: &credentials.NutanixCredentialReference{
105113
Kind: credentials.SecretKind,
@@ -135,3 +143,25 @@ func (h *nutanixPrismCentralEndpoint) Mutate(
135143
},
136144
)
137145
}
146+
147+
func parsePrismCentralURL(in string) (string, int32, error) {
148+
var prismCentralURL *url.URL
149+
prismCentralURL, err := url.Parse(in)
150+
if err != nil {
151+
return "", -1, fmt.Errorf("error parsing Prism Central URL: %w", err)
152+
}
153+
154+
hostname := prismCentralURL.Hostname()
155+
156+
// return early with the default port if no port is specified
157+
if prismCentralURL.Port() == "" {
158+
return hostname, v1alpha1.DefaultPrismCentralPort, nil
159+
}
160+
161+
port, err := strconv.Atoi(prismCentralURL.Port())
162+
if err != nil {
163+
return "", -1, fmt.Errorf("error converting port to int: %w", err)
164+
}
165+
166+
return hostname, int32(port), nil
167+
}

pkg/handlers/nutanix/mutation/prismcentralendpoint/inject_test.go

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ var _ = Describe("Generate Nutanix Prism Central Endpoint patches", func() {
4444
capitest.VariableWithValue(
4545
clusterconfig.MetaVariableName,
4646
v1alpha1.NutanixPrismCentralEndpointSpec{
47-
Host: "prism-central.nutanix.com",
48-
Port: 9441,
47+
URL: "https://prism-central.nutanix.com:9441",
4948
Insecure: true,
5049
Credentials: corev1.LocalObjectReference{
5150
Name: "credentials",
@@ -73,14 +72,47 @@ var _ = Describe("Generate Nutanix Prism Central Endpoint patches", func() {
7372
},
7473
},
7574
},
75+
{
76+
Name: "all required fields set without port",
77+
Vars: []runtimehooksv1.Variable{
78+
capitest.VariableWithValue(
79+
clusterconfig.MetaVariableName,
80+
v1alpha1.NutanixPrismCentralEndpointSpec{
81+
URL: "https://prism-central.nutanix.com",
82+
Insecure: true,
83+
Credentials: corev1.LocalObjectReference{
84+
Name: "credentials",
85+
},
86+
},
87+
nutanixclusterconfig.NutanixVariableName,
88+
VariableName,
89+
),
90+
},
91+
RequestItem: request.NewNutanixClusterTemplateRequestItem(""),
92+
ExpectedPatchMatchers: []capitest.JSONPatchMatcher{
93+
{
94+
Operation: "replace",
95+
Path: "/spec/template/spec/prismCentral",
96+
ValueMatcher: gomega.SatisfyAll(
97+
gomega.HaveKeyWithValue(
98+
"address",
99+
gomega.BeEquivalentTo("prism-central.nutanix.com"),
100+
),
101+
gomega.HaveKeyWithValue("port", gomega.BeEquivalentTo(v1alpha1.DefaultPrismCentralPort)),
102+
gomega.HaveKeyWithValue("insecure", true),
103+
gomega.HaveKey("credentialRef"),
104+
gomega.Not(gomega.HaveKey("additionalTrustBundle")),
105+
),
106+
},
107+
},
108+
},
76109
{
77110
Name: "additional trust bundle is set",
78111
Vars: []runtimehooksv1.Variable{
79112
capitest.VariableWithValue(
80113
clusterconfig.MetaVariableName,
81114
v1alpha1.NutanixPrismCentralEndpointSpec{
82-
Host: "prism-central.nutanix.com",
83-
Port: 9441,
115+
URL: "https://prism-central.nutanix.com:9441",
84116
Insecure: true,
85117
Credentials: corev1.LocalObjectReference{
86118
Name: "credentials",

pkg/handlers/nutanix/mutation/prismcentralendpoint/variables_test.go

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package prismcentralendpoint
55

66
import (
7+
"fmt"
78
"testing"
89

910
corev1 "k8s.io/api/core/v1"
@@ -24,12 +25,11 @@ func TestVariableValidation(t *testing.T) {
2425
true,
2526
nutanixclusterconfig.NewVariable,
2627
capitest.VariableTestDef{
27-
Name: "valid PC address and port",
28+
Name: "valid PC URL",
2829
Vals: v1alpha1.ClusterConfigSpec{
2930
Nutanix: &v1alpha1.NutanixSpec{
3031
PrismCentralEndpoint: v1alpha1.NutanixPrismCentralEndpointSpec{
31-
Host: "prism-central.nutanix.com",
32-
Port: v1alpha1.PrismCentralPort,
32+
URL: fmt.Sprintf("https://prism-central.nutanix.com:%d", v1alpha1.DefaultPrismCentralPort),
3333
Insecure: false,
3434
Credentials: corev1.LocalObjectReference{
3535
Name: "credentials",
@@ -44,11 +44,88 @@ func TestVariableValidation(t *testing.T) {
4444
},
4545
},
4646
capitest.VariableTestDef{
47-
Name: "empty PC address",
47+
Name: "valid PC URL as an IP",
4848
Vals: v1alpha1.ClusterConfigSpec{
4949
Nutanix: &v1alpha1.NutanixSpec{
5050
PrismCentralEndpoint: v1alpha1.NutanixPrismCentralEndpointSpec{
51-
Port: v1alpha1.PrismCentralPort,
51+
URL: fmt.Sprintf("https://10.0.0.1:%d", v1alpha1.DefaultPrismCentralPort),
52+
Insecure: false,
53+
Credentials: corev1.LocalObjectReference{
54+
Name: "credentials",
55+
},
56+
},
57+
// ControlPlaneEndpoint is a required field and must always be set
58+
ControlPlaneEndpoint: clusterv1.APIEndpoint{
59+
Host: "10.20.100.10",
60+
Port: 6443,
61+
},
62+
},
63+
},
64+
},
65+
capitest.VariableTestDef{
66+
Name: "valid PC URL without a port",
67+
Vals: v1alpha1.ClusterConfigSpec{
68+
Nutanix: &v1alpha1.NutanixSpec{
69+
PrismCentralEndpoint: v1alpha1.NutanixPrismCentralEndpointSpec{
70+
URL: "https://prism-central.nutanix.com",
71+
Insecure: false,
72+
Credentials: corev1.LocalObjectReference{
73+
Name: "credentials",
74+
},
75+
},
76+
// ControlPlaneEndpoint is a required field and must always be set
77+
ControlPlaneEndpoint: clusterv1.APIEndpoint{
78+
Host: "10.20.100.10",
79+
Port: 6443,
80+
},
81+
},
82+
},
83+
},
84+
capitest.VariableTestDef{
85+
Name: "empty PC URL",
86+
Vals: v1alpha1.ClusterConfigSpec{
87+
Nutanix: &v1alpha1.NutanixSpec{
88+
PrismCentralEndpoint: v1alpha1.NutanixPrismCentralEndpointSpec{
89+
Insecure: false,
90+
Credentials: corev1.LocalObjectReference{
91+
Name: "credentials",
92+
},
93+
},
94+
// ControlPlaneEndpoint is a required field and must always be set
95+
ControlPlaneEndpoint: clusterv1.APIEndpoint{
96+
Host: "10.20.100.10",
97+
Port: 6443,
98+
},
99+
},
100+
},
101+
ExpectError: true,
102+
},
103+
capitest.VariableTestDef{
104+
Name: "http is not a valid PC URL",
105+
Vals: v1alpha1.ClusterConfigSpec{
106+
Nutanix: &v1alpha1.NutanixSpec{
107+
PrismCentralEndpoint: v1alpha1.NutanixPrismCentralEndpointSpec{
108+
URL: "http://prism-central.nutanix.com",
109+
Insecure: false,
110+
Credentials: corev1.LocalObjectReference{
111+
Name: "credentials",
112+
},
113+
},
114+
// ControlPlaneEndpoint is a required field and must always be set
115+
ControlPlaneEndpoint: clusterv1.APIEndpoint{
116+
Host: "10.20.100.10",
117+
Port: 6443,
118+
},
119+
},
120+
},
121+
ExpectError: true,
122+
},
123+
capitest.VariableTestDef{
124+
Name: "not a valid PC URL",
125+
Vals: v1alpha1.ClusterConfigSpec{
126+
Nutanix: &v1alpha1.NutanixSpec{
127+
PrismCentralEndpoint: v1alpha1.NutanixPrismCentralEndpointSpec{
128+
URL: "not-a-valid-url",
52129
Insecure: false,
53130
Credentials: corev1.LocalObjectReference{
54131
Name: "credentials",
@@ -68,8 +145,7 @@ func TestVariableValidation(t *testing.T) {
68145
Vals: v1alpha1.ClusterConfigSpec{
69146
Nutanix: &v1alpha1.NutanixSpec{
70147
PrismCentralEndpoint: v1alpha1.NutanixPrismCentralEndpointSpec{
71-
Host: "prism-central.nutanix.com",
72-
Port: v1alpha1.PrismCentralPort,
148+
URL: fmt.Sprintf("https://prism-central.nutanix.com:%d", v1alpha1.DefaultPrismCentralPort),
73149
Insecure: false,
74150
},
75151
// ControlPlaneEndpoint is a required field and must always be set

0 commit comments

Comments
 (0)