Skip to content

Commit f99c5e9

Browse files
committed
fixup! feat: Nutanix VM image preflight check
Merge separate v3 and v4 clients into one
1 parent a1a08d3 commit f99c5e9

File tree

7 files changed

+138
-176
lines changed

7 files changed

+138
-176
lines changed

pkg/webhook/preflight/nutanix/checker.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ func New(kclient ctrlclient.Client, cluster *clusterv1.Cluster) preflight.Checke
2222
kclient: kclient,
2323
cluster: cluster,
2424

25-
v3clientFactory: newV3Client,
26-
v4clientFactory: newV4Client,
25+
nclientFactory: newClient,
2726

2827
vmImageCheckFunc: vmImageCheck,
2928
initNutanixConfigurationFunc: initNutanixConfiguration,
@@ -39,11 +38,8 @@ type nutanixChecker struct {
3938
nutanixClusterConfigSpec *carenv1.NutanixClusterConfigSpec
4039
nutanixWorkerNodeConfigSpecByMachineDeploymentName map[string]*carenv1.NutanixWorkerNodeConfigSpec
4140

42-
v3client v3client
43-
v3clientFactory func(prismgoclient.Credentials) (v3client, error)
44-
45-
v4client v4client
46-
v4clientFactory func(prismgoclient.Credentials) (v4client, error)
41+
nclient client
42+
nclientFactory func(prismgoclient.Credentials) (client, error)
4743

4844
vmImageCheckFunc func(
4945
n *nutanixChecker,

pkg/webhook/preflight/nutanix/clients.go

Lines changed: 35 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package nutanix
55

66
import (
77
"context"
8+
"fmt"
89

910
vmmv4 "github.com/nutanix/ntnx-api-golang-clients/vmm-go-client/v4/models/vmm/v4/content"
1011

@@ -13,29 +14,10 @@ import (
1314
prismv4 "github.com/nutanix-cloud-native/prism-go-client/v4"
1415
)
1516

16-
type v3client interface {
17+
// client contains methods to interact with Nutanix Prism v3 and v4 APIs.
18+
type client interface {
1719
GetCurrentLoggedInUser(ctx context.Context) (*prismv3.UserIntentResponse, error)
18-
}
19-
20-
type v3clientWrapper struct {
21-
prismv3.Service
22-
}
23-
24-
var _ = v3client(&v3clientWrapper{})
2520

26-
func newV3Client(
27-
credentials prismgoclient.Credentials, //nolint:gocritic // hugeParam is fine
28-
) (v3client, error) {
29-
client, err := prismv3.NewV3Client(credentials)
30-
if err != nil {
31-
return nil, err
32-
}
33-
return &v3clientWrapper{
34-
client.V3,
35-
}, nil
36-
}
37-
38-
type v4client interface {
3921
GetImageById(id *string) (*vmmv4.GetImageApiResponse, error)
4022
ListImages(page_ *int,
4123
limit_ *int,
@@ -49,28 +31,53 @@ type v4client interface {
4931
)
5032
}
5133

52-
type v4clientWrapper struct {
53-
client *prismv4.Client
34+
// clientWrapper implements the client interface and wraps both v3 and v4 clients.
35+
type clientWrapper struct {
36+
v3client *prismv3.Client
37+
v4client *prismv4.Client
38+
}
39+
40+
var _ = client(&clientWrapper{})
41+
42+
func newClient(
43+
credentials prismgoclient.Credentials, //nolint:gocritic // hugeParam is fine
44+
) (client, error) {
45+
v3c, err := prismv3.NewV3Client(credentials)
46+
if err != nil {
47+
return nil, fmt.Errorf("failed to create v3 client: %w", err)
48+
}
49+
50+
v4c, err := prismv4.NewV4Client(credentials)
51+
if err != nil {
52+
return nil, fmt.Errorf("failed to create v4 client: %w", err)
53+
}
54+
55+
return &clientWrapper{
56+
v3client: v3c,
57+
v4client: v4c,
58+
}, nil
5459
}
5560

56-
var _ = v4client(&v4clientWrapper{})
61+
func (c *clientWrapper) GetCurrentLoggedInUser(ctx context.Context) (*prismv3.UserIntentResponse, error) {
62+
return c.v3client.V3.GetCurrentLoggedInUser(ctx)
63+
}
5764

58-
func (c *v4clientWrapper) GetImageById(id *string) (*vmmv4.GetImageApiResponse, error) {
59-
resp, err := c.client.ImagesApiInstance.GetImageById(id)
65+
func (c *clientWrapper) GetImageById(id *string) (*vmmv4.GetImageApiResponse, error) {
66+
resp, err := c.v4client.ImagesApiInstance.GetImageById(id)
6067
if err != nil {
6168
return nil, err
6269
}
6370
return resp, nil
6471
}
6572

66-
func (c *v4clientWrapper) ListImages(page_ *int,
73+
func (c *clientWrapper) ListImages(page_ *int,
6774
limit_ *int,
6875
filter_ *string,
6976
orderby_ *string,
7077
select_ *string,
7178
args ...map[string]interface{},
7279
) (*vmmv4.ListImagesApiResponse, error) {
73-
resp, err := c.client.ImagesApiInstance.ListImages(
80+
resp, err := c.v4client.ImagesApiInstance.ListImages(
7481
page_,
7582
limit_,
7683
filter_,
@@ -83,15 +90,3 @@ func (c *v4clientWrapper) ListImages(page_ *int,
8390
}
8491
return resp, nil
8592
}
86-
87-
func newV4Client(
88-
credentials prismgoclient.Credentials, //nolint:gocritic // hugeParam is fine
89-
) (v4client, error) {
90-
client, err := prismv4.NewV4Client(credentials)
91-
if err != nil {
92-
return nil, err
93-
}
94-
return &v4clientWrapper{
95-
client: client,
96-
}, nil
97-
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2024 Nutanix. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package nutanix
5+
6+
import (
7+
"context"
8+
9+
vmmv4 "github.com/nutanix/ntnx-api-golang-clients/vmm-go-client/v4/models/vmm/v4/content"
10+
11+
prismv3 "github.com/nutanix-cloud-native/prism-go-client/v3"
12+
)
13+
14+
var _ = client(&mocknclient{})
15+
16+
// mocknclient is a mock implementation of the client interface for testing purposes.
17+
type mocknclient struct {
18+
user *prismv3.UserIntentResponse
19+
err error
20+
21+
getImageByIdFunc func(
22+
uuid *string,
23+
) (
24+
*vmmv4.GetImageApiResponse, error,
25+
)
26+
27+
listImagesFunc func(
28+
page,
29+
limit *int,
30+
filter,
31+
orderby,
32+
select_ *string,
33+
args ...map[string]interface{},
34+
) (
35+
*vmmv4.ListImagesApiResponse,
36+
error,
37+
)
38+
}
39+
40+
func (m *mocknclient) GetCurrentLoggedInUser(ctx context.Context) (*prismv3.UserIntentResponse, error) {
41+
return m.user, m.err
42+
}
43+
44+
func (m *mocknclient) GetImageById(uuid *string) (*vmmv4.GetImageApiResponse, error) {
45+
return m.getImageByIdFunc(uuid)
46+
}
47+
48+
func (m *mocknclient) ListImages(
49+
page, limit *int,
50+
filter, orderby, select_ *string,
51+
args ...map[string]interface{},
52+
) (*vmmv4.ListImagesApiResponse, error) {
53+
return m.listImagesFunc(page, limit, filter, orderby, select_)
54+
}

pkg/webhook/preflight/nutanix/credentials.go

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -154,39 +154,24 @@ func initCredentialsCheck(
154154
Insecure: prismCentralEndpointSpec.Insecure,
155155
}
156156

157-
// Initialize the clients.
158-
v4client, err := n.v4clientFactory(credentials)
157+
// Initialize the Nutanix client.
158+
nclient, err := n.nclientFactory(credentials)
159159
if err != nil {
160160
result.Allowed = false
161161
result.Error = true
162162
result.Causes = append(result.Causes,
163163
preflight.Cause{
164-
Message: fmt.Sprintf("failed to initialize Nutanix v4 client: %s", err),
164+
Message: fmt.Sprintf("Failed to initialize Nutanix client: %s", err),
165165
Field: "cluster.spec.topology.variables[.name=clusterConfig].nutanix.prismCentralEndpoint.credentials",
166166
},
167167
)
168-
}
169-
170-
v3client, err := n.v3clientFactory(credentials)
171-
if err != nil {
172-
result.Allowed = false
173-
result.Error = true
174-
result.Causes = append(result.Causes,
175-
preflight.Cause{
176-
Message: fmt.Sprintf("failed to initialize Nutanix v3 client: %s", err),
177-
Field: "cluster.spec.topology.variables[.name=clusterConfig].nutanix.prismCentralEndpoint.credentials",
178-
},
179-
)
180-
}
181-
182-
if v3client == nil || v4client == nil {
183168
return func(ctx context.Context) preflight.CheckResult {
184169
return result
185170
}
186171
}
187172

188173
// Validate the credentials using an API call.
189-
_, err = v3client.GetCurrentLoggedInUser(ctx)
174+
_, err = nclient.GetCurrentLoggedInUser(ctx)
190175
if err != nil {
191176
result.Allowed = false
192177
result.Error = true
@@ -203,8 +188,7 @@ func initCredentialsCheck(
203188
}
204189

205190
// We initialized both clients, and verified the credentials using the v3 client.
206-
n.v3client = v3client
207-
n.v4client = v4client
191+
n.nclient = nclient
208192

209193
return func(ctx context.Context) preflight.CheckResult {
210194
return result

pkg/webhook/preflight/nutanix/credentials_test.go

Lines changed: 9 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,13 @@ import (
77
"context"
88
"testing"
99

10-
vmmv4 "github.com/nutanix/ntnx-api-golang-clients/vmm-go-client/v4/models/vmm/v4/content"
1110
"github.com/stretchr/testify/assert"
1211
corev1 "k8s.io/api/core/v1"
1312
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1413
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
1514
"sigs.k8s.io/controller-runtime/pkg/client/fake"
1615

1716
prismgoclient "github.com/nutanix-cloud-native/prism-go-client"
18-
prismv3 "github.com/nutanix-cloud-native/prism-go-client/v3"
1917

2018
carenv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
2119
)
@@ -129,47 +127,27 @@ func TestInitCredentialsCheck_InvalidCredentialsFormat(t *testing.T) {
129127
assert.Contains(t, result.Causes[0].Message, "failed to parse Prism Central credentials")
130128
}
131129

132-
func TestInitCredentialsCheck_FailedToCreateV3Client(t *testing.T) {
133-
// Simulate a failure in creating the v3 client
134-
nc := validNutanixChecker()
135-
nc.v3clientFactory = func(_ prismgoclient.Credentials) (v3client, error) {
136-
return nil, assert.AnError
137-
}
138-
nc.v4clientFactory = func(_ prismgoclient.Credentials) (v4client, error) {
139-
return &mockv4client{}, nil
140-
}
141-
check := nc.initCredentialsCheckFunc(context.Background(), nc)
142-
result := check(context.Background())
143-
assert.False(t, result.Allowed)
144-
assert.True(t, result.Error)
145-
assert.Contains(t, result.Causes[0].Message, "failed to initialize Nutanix v3 client")
146-
}
147-
148-
func TestInitCredentialsCheck_FailedToCreateV4Client(t *testing.T) {
130+
func TestInitCredentialsCheck_FailedToCreateClient(t *testing.T) {
149131
// Simulate a failure in creating the v4 client
150132
nc := validNutanixChecker()
151-
nc.v3clientFactory = func(_ prismgoclient.Credentials) (v3client, error) {
152-
return &mockv3client{}, nil
153-
}
154-
nc.v4clientFactory = func(_ prismgoclient.Credentials) (v4client, error) {
133+
nc.nclientFactory = func(_ prismgoclient.Credentials) (client, error) {
155134
return nil, assert.AnError
156135
}
136+
157137
check := nc.initCredentialsCheckFunc(context.Background(), nc)
158138
result := check(context.Background())
159139
assert.False(t, result.Allowed)
160140
assert.True(t, result.Error)
161-
assert.Contains(t, result.Causes[0].Message, "failed to initialize Nutanix v4 client")
141+
assert.Contains(t, result.Causes[0].Message, "Failed to initialize Nutanix client")
162142
}
163143

164144
func TestInitCredentialsCheck_FailedToGetCurrentLoggedInUser(t *testing.T) {
165145
// Simulate a failure in getting the current logged-in user
166146
nc := validNutanixChecker()
167-
nc.v3clientFactory = func(_ prismgoclient.Credentials) (v3client, error) {
168-
return &mockv3client{err: assert.AnError}, nil
169-
}
170-
nc.v4clientFactory = func(_ prismgoclient.Credentials) (v4client, error) {
171-
return &mockv4client{}, nil
147+
nc.nclientFactory = func(_ prismgoclient.Credentials) (client, error) {
148+
return &mocknclient{err: assert.AnError}, nil
172149
}
150+
173151
check := nc.initCredentialsCheckFunc(context.Background(), nc)
174152
result := check(context.Background())
175153
assert.False(t, result.Allowed)
@@ -207,11 +185,8 @@ func validNutanixChecker() *nutanixChecker {
207185
},
208186
},
209187

210-
v3clientFactory: func(_ prismgoclient.Credentials) (v3client, error) {
211-
return &mockv3client{}, nil
212-
},
213-
v4clientFactory: func(_ prismgoclient.Credentials) (v4client, error) {
214-
return &mockv4client{}, nil
188+
nclientFactory: func(_ prismgoclient.Credentials) (client, error) {
189+
return &mocknclient{}, nil
215190
},
216191

217192
vmImageCheckFunc: vmImageCheck,
@@ -233,45 +208,3 @@ func validNutanixChecker() *nutanixChecker {
233208
nutanixWorkerNodeConfigSpecByMachineDeploymentName: map[string]*carenv1.NutanixWorkerNodeConfigSpec{},
234209
}
235210
}
236-
237-
type mockv3client struct {
238-
user *prismv3.UserIntentResponse
239-
err error
240-
}
241-
242-
func (m *mockv3client) GetCurrentLoggedInUser(ctx context.Context) (*prismv3.UserIntentResponse, error) {
243-
return m.user, m.err
244-
}
245-
246-
// mockv4client is a mock implementation of the v4client interface for testing.
247-
type mockv4client struct {
248-
getImageByIdFunc func(
249-
uuid *string,
250-
) (
251-
*vmmv4.GetImageApiResponse, error,
252-
)
253-
254-
listImagesFunc func(
255-
page,
256-
limit *int,
257-
filter,
258-
orderby,
259-
select_ *string,
260-
args ...map[string]interface{},
261-
) (
262-
*vmmv4.ListImagesApiResponse,
263-
error,
264-
)
265-
}
266-
267-
func (m *mockv4client) GetImageById(uuid *string) (*vmmv4.GetImageApiResponse, error) {
268-
return m.getImageByIdFunc(uuid)
269-
}
270-
271-
func (m *mockv4client) ListImages(
272-
page, limit *int,
273-
filter, orderby, select_ *string,
274-
args ...map[string]interface{},
275-
) (*vmmv4.ListImagesApiResponse, error) {
276-
return m.listImagesFunc(page, limit, filter, orderby, select_)
277-
}

0 commit comments

Comments
 (0)