Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: improve error handling for Prism Client #354

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 55 additions & 19 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
"context"
"encoding/json"
"fmt"
"net"
"net/url"
"os"
"path/filepath"

Expand All @@ -43,6 +45,9 @@
capxNamespaceKey = "POD_NAMESPACE"
)

var ErrPrismAddressNotSet = fmt.Errorf("cannot get credentials if Prism Address is not set")
var ErrPrismPortNotSet = fmt.Errorf("cannot get credentials if Prism Port is not set")

type NutanixClientHelper struct {
secretInformer coreinformers.SecretInformer
configMapInformer coreinformers.ConfigMapInformer
Expand All @@ -63,15 +68,18 @@
// If PrismCentral is set, add the required env provider
prismCentralInfo := nutanixCluster.Spec.PrismCentral
if prismCentralInfo != nil {
if prismCentralInfo.Address == "" {
return nil, fmt.Errorf("cannot get credentials if Prism Address is not set")
address, err := validateAndSanitizePrismCentralInfoAddress(prismCentralInfo.Address)
if err != nil {
return nil, err

Check warning on line 73 in pkg/client/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/client/client.go#L71-L73

Added lines #L71 - L73 were not covered by tests
}
prismCentralInfo.Address = address

Check warning on line 75 in pkg/client/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/client/client.go#L75

Added line #L75 was not covered by tests
if prismCentralInfo.Port == 0 {
return nil, fmt.Errorf("cannot get credentials if Prism Port is not set")
return nil, ErrPrismPortNotSet

Check warning on line 77 in pkg/client/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/client/client.go#L77

Added line #L77 was not covered by tests
}
credentialRef := prismCentralInfo.CredentialRef
if credentialRef == nil {
return nil, fmt.Errorf("credentialRef must be set on prismCentral attribute for cluster %s in namespace %s", nutanixCluster.Name, nutanixCluster.Namespace)
credentialRef, err := GetCredentialRefForCluster(nutanixCluster)
faiq marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {

Check warning on line 80 in pkg/client/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/client/client.go#L79-L80

Added lines #L79 - L80 were not covered by tests
//nolint:wrapcheck // error is alredy wrapped
faiq marked this conversation as resolved.
Show resolved Hide resolved
return nil, err

Check warning on line 82 in pkg/client/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/client/client.go#L82

Added line #L82 was not covered by tests
}
// If namespace is empty, use the cluster namespace
if credentialRef.Namespace == "" {
Expand All @@ -94,7 +102,7 @@
// Add env provider for CAPX manager
npe, err := n.getManagerNutanixPrismEndpoint()
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to create prism endpoint %w", err)

Check warning on line 105 in pkg/client/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/client/client.go#L105

Added line #L105 was not covered by tests
faiq marked this conversation as resolved.
Show resolved Hide resolved
}
// If namespaces is not set, set it to the namespace of the CAPX manager
if npe.CredentialRef.Namespace == "" {
Expand All @@ -115,15 +123,14 @@
*npe,
n.secretInformer,
n.configMapInformer))

// init env with providers
env := environment.NewEnvironment(
providers...,
)
// fetch endpoint details
me, err := env.GetManagementEndpoint(envTypes.Topology{})
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to get management endpoint object %w", err)

Check warning on line 133 in pkg/client/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/client/client.go#L133

Added line #L133 was not covered by tests
}
creds := prismgoclient.Credentials{
URL: me.Address.Host,
Expand Down Expand Up @@ -155,26 +162,25 @@
}
cli, err := nutanixClientV3.NewV3Client(cred, clientOpts...)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to create new nutanix client %w", err)

Check warning on line 165 in pkg/client/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/client/client.go#L165

Added line #L165 was not covered by tests
}

// Check if the client is working
_, err = cli.V3.GetCurrentLoggedInUser(context.Background())
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to get current logged in user with client %w", err)

Check warning on line 171 in pkg/client/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/client/client.go#L171

Added line #L171 was not covered by tests
}

return cli, nil
}

func (n *NutanixClientHelper) getManagerNutanixPrismEndpoint() (*credentialTypes.NutanixPrismEndpoint, error) {
npe := &credentialTypes.NutanixPrismEndpoint{}
config, err := n.readEndpointConfig()
if err != nil {
return npe, err
return nil, fmt.Errorf("failed to read config %w", err)

Check warning on line 180 in pkg/client/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/client/client.go#L180

Added line #L180 was not covered by tests
}
if err = json.Unmarshal(config, npe); err != nil {
return npe, err
return nil, fmt.Errorf("failed to unmarshal config %w", err)

Check warning on line 183 in pkg/client/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/client/client.go#L183

Added line #L183 was not covered by tests
}
if npe.CredentialRef == nil {
return nil, fmt.Errorf("credentialRef must be set on CAPX manager")
Expand All @@ -196,16 +202,46 @@
if nutanixCluster == nil {
return nil, fmt.Errorf("cannot get credential reference if nutanix cluster object is nil")
}
prismCentralinfo := nutanixCluster.Spec.PrismCentral
if prismCentralinfo == nil {
prismCentralInfo := nutanixCluster.Spec.PrismCentral
if prismCentralInfo == nil {

Check warning on line 206 in pkg/client/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/client/client.go#L205-L206

Added lines #L205 - L206 were not covered by tests
return nil, nil
}
if prismCentralinfo.CredentialRef == nil {
if prismCentralInfo.CredentialRef == nil {

Check warning on line 209 in pkg/client/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/client/client.go#L209

Added line #L209 was not covered by tests
return nil, fmt.Errorf("credentialRef must be set on prismCentral attribute for cluster %s in namespace %s", nutanixCluster.Name, nutanixCluster.Namespace)
}
if prismCentralinfo.CredentialRef.Kind != credentialTypes.SecretKind {
if prismCentralInfo.CredentialRef.Kind != credentialTypes.SecretKind {

Check warning on line 212 in pkg/client/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/client/client.go#L212

Added line #L212 was not covered by tests
return nil, nil
}

return prismCentralinfo.CredentialRef, nil
return prismCentralInfo.CredentialRef, nil

Check warning on line 216 in pkg/client/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/client/client.go#L216

Added line #L216 was not covered by tests
}

func validateAndSanitizePrismCentralInfoAddress(address string) (string, error) {
faiq marked this conversation as resolved.
Show resolved Hide resolved
if address == "" {
return "", ErrPrismAddressNotSet
}
u, urlParseErr := url.Parse(address)
if urlParseErr != nil {
ipHost, ipErr := parseIP(address)
if ipErr != nil {
return "", fmt.Errorf("failed to resolve %s as url or ip addr %w", address, ipErr)

Check warning on line 227 in pkg/client/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/client/client.go#L227

Added line #L227 was not covered by tests
}
return ipHost, nil
}
if u.Scheme != "" || u.Port() != "" {
return u.Hostname(), nil
}
return address, nil
}

func parseIP(s string) (string, error) {
ipStr, _, err := net.SplitHostPort(s)
if err == nil {
return ipStr, nil
}
ip := net.ParseIP(s)
if ip == nil {
return "", fmt.Errorf("invalid IP %s", ip)

Check warning on line 244 in pkg/client/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/client/client.go#L242-L244

Added lines #L242 - L244 were not covered by tests
}
return ip.String(), nil

Check warning on line 246 in pkg/client/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/client/client.go#L246

Added line #L246 was not covered by tests
}
76 changes: 76 additions & 0 deletions pkg/client/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package client

import (
"errors"
"testing"
)

func Test_validateAndSanitizePrismCentralInfoAddress(t *testing.T) {
tests := []struct {
name string
providedAddress string
expectedAddress string
expectedErr error
}{
{
"with scheme",
"https://prism-bowser.ntnxsherlock.com",
"prism-bowser.ntnxsherlock.com",
faiq marked this conversation as resolved.
Show resolved Hide resolved
nil,
},
{
"with scheme and port",
"https://prism-bowser.ntnxsherlock.com:9440",
"prism-bowser.ntnxsherlock.com",
nil,
},
{
"as expected from the user",
"prism-bowser.ntnxsherlock.com",
"prism-bowser.ntnxsherlock.com",
nil,
},
{
"not set",
"",
"",
ErrPrismAddressNotSet,
},
{
"using a IP with scheme and port",
"https://1.2.3.4:9440",
"1.2.3.4",
nil,
},
{
"using a IP with scheme",
"https://1.2.3.4",
"1.2.3.4",
nil,
},
{
"using a IP with port",
"1.2.3.4:9440",
"1.2.3.4",
nil,
},
{
"just an IP",
"1.2.3.4",
"1.2.3.4",
nil,
},
}

for _, test := range tests {
s, err := validateAndSanitizePrismCentralInfoAddress(test.providedAddress)
if err != nil {
if test.expectedErr == nil || !errors.Is(err, test.expectedErr) {
t.Errorf("validateAndSanitizePrismCentralInfoAddress() error = %v, wantErr = %v", err, test.expectedErr)
}
}
if s != test.expectedAddress {
t.Errorf("validateAndSanitizePrismCentralInfoAddress() got = %v, want = %v", s, test.expectedAddress)
}
}
}
Loading