diff --git a/api/v1beta1/nutanixcluster_types.go b/api/v1beta1/nutanixcluster_types.go index fe92422ff2..03ebfc1ab5 100644 --- a/api/v1beta1/nutanixcluster_types.go +++ b/api/v1beta1/nutanixcluster_types.go @@ -17,6 +17,8 @@ limitations under the License. package v1beta1 import ( + "fmt" + credentialTypes "github.com/nutanix-cloud-native/prism-go-client/environment/credentials" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" capiv1 "sigs.k8s.io/cluster-api/api/v1beta1" @@ -75,12 +77,12 @@ type NutanixClusterStatus struct { FailureMessage *string `json:"failureMessage,omitempty"` } -//+kubebuilder:object:root=true -//+kubebuilder:resource:path=nutanixclusters,shortName=ncl,scope=Namespaced,categories=cluster-api -//+kubebuilder:subresource:status -//+kubebuilder:storageversion -//+kubebuilder:printcolumn:name="ControlplaneEndpoint",type="string",JSONPath=".spec.controlPlaneEndpoint.host",description="ControlplaneEndpoint" -//+kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.ready",description="in ready status" +// +kubebuilder:object:root=true +// +kubebuilder:resource:path=nutanixclusters,shortName=ncl,scope=Namespaced,categories=cluster-api +// +kubebuilder:subresource:status +// +kubebuilder:storageversion +// +kubebuilder:printcolumn:name="ControlplaneEndpoint",type="string",JSONPath=".spec.controlPlaneEndpoint.host",description="ControlplaneEndpoint" +// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.ready",description="in ready status" // NutanixCluster is the Schema for the nutanixclusters API type NutanixCluster struct { @@ -101,7 +103,22 @@ func (ncl *NutanixCluster) SetConditions(conditions capiv1.Conditions) { ncl.Status.Conditions = conditions } -//+kubebuilder:object:root=true +func (ncl *NutanixCluster) GetPrismCentralCredentialRef() (*credentialTypes.NutanixCredentialReference, error) { + prismCentralInfo := ncl.Spec.PrismCentral + if prismCentralInfo == nil { + return nil, nil + } + if prismCentralInfo.CredentialRef == nil { + return nil, fmt.Errorf("credentialRef must be set on prismCentral attribute for cluster %s in namespace %s", ncl.Name, ncl.Namespace) + } + if prismCentralInfo.CredentialRef.Kind != credentialTypes.SecretKind { + return nil, nil + } + + return prismCentralInfo.CredentialRef, nil +} + +// +kubebuilder:object:root=true // NutanixClusterList contains a list of NutanixCluster type NutanixClusterList struct { diff --git a/api/v1beta1/nutanixcluster_types_test.go b/api/v1beta1/nutanixcluster_types_test.go new file mode 100644 index 0000000000..59a74b3298 --- /dev/null +++ b/api/v1beta1/nutanixcluster_types_test.go @@ -0,0 +1,114 @@ +/* +Copyright 2024 Nutanix + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/nutanix-cloud-native/prism-go-client/environment/credentials" +) + +func TestGetCredentialRefForCluster(t *testing.T) { + t.Parallel() + tests := []struct { + name string + nutanixCluster *NutanixCluster + expectedCredentialsRef *credentials.NutanixCredentialReference + expectedErr error + }{ + { + name: "all info is set", + nutanixCluster: &NutanixCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: corev1.NamespaceDefault, + }, + Spec: NutanixClusterSpec{ + PrismCentral: &credentials.NutanixPrismEndpoint{ + Address: "address", + Port: 9440, + CredentialRef: &credentials.NutanixCredentialReference{ + Kind: credentials.SecretKind, + Name: "creds", + Namespace: corev1.NamespaceDefault, + }, + }, + }, + }, + expectedCredentialsRef: &credentials.NutanixCredentialReference{ + Kind: credentials.SecretKind, + Name: "creds", + Namespace: corev1.NamespaceDefault, + }, + }, + { + name: "prismCentralInfo is nil, should not fail", + nutanixCluster: &NutanixCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: corev1.NamespaceDefault, + }, + Spec: NutanixClusterSpec{}, + }, + }, + { + name: "CredentialRef kind is not kind Secret, should not fail", + nutanixCluster: &NutanixCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: corev1.NamespaceDefault, + }, + Spec: NutanixClusterSpec{ + PrismCentral: &credentials.NutanixPrismEndpoint{ + CredentialRef: &credentials.NutanixCredentialReference{ + Kind: "unknown", + }, + }, + }, + }, + }, + { + name: "prismCentralInfo is not nil but CredentialRef is nil, should fail", + nutanixCluster: &NutanixCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: corev1.NamespaceDefault, + }, + Spec: NutanixClusterSpec{ + PrismCentral: &credentials.NutanixPrismEndpoint{ + Address: "address", + }, + }, + }, + expectedErr: fmt.Errorf("credentialRef must be set on prismCentral attribute for cluster test in namespace default"), + }, + } + for _, tt := range tests { + tt := tt // Capture range variable. + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ref, err := tt.nutanixCluster.GetPrismCentralCredentialRef() + assert.Equal(t, tt.expectedCredentialsRef, ref) + assert.Equal(t, tt.expectedErr, err) + }) + } +} diff --git a/controllers/helpers.go b/controllers/helpers.go index b6574cbd69..ff46e6ce83 100644 --- a/controllers/helpers.go +++ b/controllers/helpers.go @@ -23,13 +23,14 @@ import ( "strings" "github.com/google/uuid" - infrav1 "github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1" - nutanixClientHelper "github.com/nutanix-cloud-native/cluster-api-provider-nutanix/pkg/client" "github.com/nutanix-cloud-native/prism-go-client/utils" nutanixClientV3 "github.com/nutanix-cloud-native/prism-go-client/v3" "k8s.io/apimachinery/pkg/api/resource" coreinformers "k8s.io/client-go/informers/core/v1" ctrl "sigs.k8s.io/controller-runtime" + + infrav1 "github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1" + nutanixClient "github.com/nutanix-cloud-native/cluster-api-provider-nutanix/pkg/client" ) const ( @@ -47,12 +48,8 @@ const ( func CreateNutanixClient(ctx context.Context, secretInformer coreinformers.SecretInformer, cmInformer coreinformers.ConfigMapInformer, nutanixCluster *infrav1.NutanixCluster) (*nutanixClientV3.Client, error) { log := ctrl.LoggerFrom(ctx) log.V(1).Info("creating nutanix client") - helper, err := nutanixClientHelper.NewNutanixClientHelper(secretInformer, cmInformer) - if err != nil { - log.Error(err, "error creating nutanix client helper") - return nil, err - } - return helper.GetClientFromEnvironment(ctx, nutanixCluster) + helper := nutanixClient.NewHelper(secretInformer, cmInformer) + return helper.BuildClientForNutanixClusterWithFallback(ctx, nutanixCluster) } // DeleteVM deletes a VM and is invoked by the NutanixMachineReconciler @@ -343,7 +340,7 @@ func GetImageUUID(ctx context.Context, client *nutanixClientV3.Client, imageName // HasTaskInProgress returns true if the given task is in progress func HasTaskInProgress(ctx context.Context, client *nutanixClientV3.Client, taskUUID string) (bool, error) { log := ctrl.LoggerFrom(ctx) - taskStatus, err := nutanixClientHelper.GetTaskStatus(ctx, client, taskUUID) + taskStatus, err := nutanixClient.GetTaskStatus(ctx, client, taskUUID) if err != nil { return false, err } diff --git a/controllers/nutanixcluster_controller.go b/controllers/nutanixcluster_controller.go index 171dfe83d5..4490631f00 100644 --- a/controllers/nutanixcluster_controller.go +++ b/controllers/nutanixcluster_controller.go @@ -21,6 +21,7 @@ import ( "fmt" "time" + credentialTypes "github.com/nutanix-cloud-native/prism-go-client/environment/credentials" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -43,7 +44,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/source" infrav1 "github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1" - nutanixClient "github.com/nutanix-cloud-native/cluster-api-provider-nutanix/pkg/client" nctx "github.com/nutanix-cloud-native/cluster-api-provider-nutanix/pkg/context" ) @@ -101,11 +101,11 @@ func (r *NutanixClusterReconciler) SetupWithManager(ctx context.Context, mgr ctr return nil } -//+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;update;delete -//+kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters;clusters/status,verbs=get;list;watch -//+kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=nutanixclusters,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=nutanixclusters/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=nutanixclusters/finalizers,verbs=update +// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;update;delete +// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters;clusters/status,verbs=get;list;watch +// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=nutanixclusters,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=nutanixclusters/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=nutanixclusters/finalizers,verbs=update // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -306,7 +306,7 @@ func (r *NutanixClusterReconciler) reconcileCategoriesDelete(rctx *nctx.ClusterC func (r *NutanixClusterReconciler) reconcileCredentialRefDelete(ctx context.Context, nutanixCluster *infrav1.NutanixCluster) error { log := ctrl.LoggerFrom(ctx) - credentialRef, err := nutanixClient.GetCredentialRefForCluster(nutanixCluster) + credentialRef, err := getPrismCentralCredentialRefForCluster(nutanixCluster) if err != nil { return err } @@ -337,7 +337,7 @@ func (r *NutanixClusterReconciler) reconcileCredentialRefDelete(ctx context.Cont func (r *NutanixClusterReconciler) reconcileCredentialRef(ctx context.Context, nutanixCluster *infrav1.NutanixCluster) error { log := ctrl.LoggerFrom(ctx) - credentialRef, err := nutanixClient.GetCredentialRefForCluster(nutanixCluster) + credentialRef, err := getPrismCentralCredentialRefForCluster(nutanixCluster) if err != nil { return err } @@ -378,3 +378,12 @@ func (r *NutanixClusterReconciler) reconcileCredentialRef(ctx context.Context, n } return nil } + +// getPrismCentralCredentialRefForCluster calls nutanixCluster.GetPrismCentralCredentialRef() function +// and returns an error if nutanixCluster is nil +func getPrismCentralCredentialRefForCluster(nutanixCluster *infrav1.NutanixCluster) (*credentialTypes.NutanixCredentialReference, error) { + if nutanixCluster == nil { + return nil, fmt.Errorf("cannot get credential reference if nutanix cluster object is nil") + } + return nutanixCluster.GetPrismCentralCredentialRef() +} diff --git a/pkg/client/client.go b/pkg/client/client.go index 35dc3bb017..aee99414ee 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -21,11 +21,10 @@ import ( "encoding/json" "fmt" "os" - "path/filepath" prismgoclient "github.com/nutanix-cloud-native/prism-go-client" "github.com/nutanix-cloud-native/prism-go-client/environment" - credentialTypes "github.com/nutanix-cloud-native/prism-go-client/environment/credentials" + "github.com/nutanix-cloud-native/prism-go-client/environment/credentials" kubernetesEnv "github.com/nutanix-cloud-native/prism-go-client/environment/providers/kubernetes" envTypes "github.com/nutanix-cloud-native/prism-go-client/environment/types" nutanixClientV3 "github.com/nutanix-cloud-native/prism-go-client/v3" @@ -37,66 +36,138 @@ import ( const ( defaultEndpointPort = "9440" - ProviderName = "nutanix" - configPath = "/etc/nutanix/config" - endpointKey = "prismCentral" - capxNamespaceKey = "POD_NAMESPACE" + + capxNamespaceKey = "POD_NAMESPACE" +) + +const ( + configPath = "/etc/nutanix/config/prismCentral" ) -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") +var ( + ErrPrismAddressNotSet = fmt.Errorf("cannot get credentials if Prism Address is not set") + ErrPrismPortNotSet = fmt.Errorf("cannot get credentials if Prism Port is not set") + + ErrPrismIUsernameNotSet = fmt.Errorf("could not create client because username was not set") + ErrPrismIPasswordNotSet = fmt.Errorf("could not create client because password was not set") + + ErrCredentialRefNotSet = fmt.Errorf("credentialRef must be set on CAPX manager") +) type NutanixClientHelper struct { secretInformer coreinformers.SecretInformer configMapInformer coreinformers.ConfigMapInformer + + managerNutanixPrismEndpointReader func() (*credentials.NutanixPrismEndpoint, error) } -func NewNutanixClientHelper(secretInformer coreinformers.SecretInformer, cmInformer coreinformers.ConfigMapInformer) (*NutanixClientHelper, error) { +func NewHelper(secretInformer coreinformers.SecretInformer, cmInformer coreinformers.ConfigMapInformer) *NutanixClientHelper { return &NutanixClientHelper{ - secretInformer: secretInformer, - configMapInformer: cmInformer, - }, nil + secretInformer: secretInformer, + configMapInformer: cmInformer, + managerNutanixPrismEndpointReader: readManagerNutanixPrismEndpointFromDefaultFile, + } +} + +func (n *NutanixClientHelper) withCustomNutanixPrismEndpointReader(getter func() (*credentials.NutanixPrismEndpoint, error)) *NutanixClientHelper { + n.managerNutanixPrismEndpointReader = getter + return n +} + +// BuildClientForNutanixClusterWithFallback builds a Nutanix Client from the information provided in nutanixCluster. +func (n *NutanixClientHelper) BuildClientForNutanixClusterWithFallback(ctx context.Context, nutanixCluster *infrav1.NutanixCluster) (*nutanixClientV3.Client, error) { + me, err := n.buildManagementEndpoint(ctx, nutanixCluster) + if err != nil { + return nil, err + } + creds := prismgoclient.Credentials{ + URL: me.Address.Host, + Endpoint: me.Address.Host, + Insecure: me.Insecure, + Username: me.ApiCredentials.Username, + Password: me.ApiCredentials.Password, + } + return Build(creds, me.AdditionalTrustBundle) } -func (n *NutanixClientHelper) GetClientFromEnvironment(ctx context.Context, nutanixCluster *infrav1.NutanixCluster) (*nutanixClientV3.Client, error) { +// buildManagementEndpoint takes in a NutanixCluster and constructs a ManagementEndpoint with all the information provided. +// If required information is not set, it will fallback to using information from /etc/nutanix/config/prismCentral, +// which is expected to be mounted in the Pod. +func (n *NutanixClientHelper) buildManagementEndpoint(ctx context.Context, nutanixCluster *infrav1.NutanixCluster) (*envTypes.ManagementEndpoint, error) { log := ctrl.LoggerFrom(ctx) - // Create a list of env providers + + // Create an empty list of env providers providers := make([]envTypes.Provider, 0) - // If PrismCentral is set, add the required env provider - prismCentralInfo := nutanixCluster.Spec.PrismCentral - if prismCentralInfo != nil { - if prismCentralInfo.Address == "" { - return nil, ErrPrismAddressNotSet - } - if prismCentralInfo.Port == 0 { - return nil, ErrPrismPortNotSet - } - credentialRef, err := GetCredentialRefForCluster(nutanixCluster) - if err != nil { - //nolint:wrapcheck // error is alredy wrapped - return nil, err - } - // If namespace is empty, use the cluster namespace - if credentialRef.Namespace == "" { - credentialRef.Namespace = nutanixCluster.Namespace - } - additionalTrustBundleRef := prismCentralInfo.AdditionalTrustBundle - if additionalTrustBundleRef != nil && - additionalTrustBundleRef.Kind == credentialTypes.NutanixTrustBundleKindConfigMap && - additionalTrustBundleRef.Namespace == "" { - additionalTrustBundleRef.Namespace = nutanixCluster.Namespace - } - providers = append(providers, kubernetesEnv.NewProvider( - *nutanixCluster.Spec.PrismCentral, - n.secretInformer, - n.configMapInformer)) + // Attempt to build a provider from the NutanixCluster object + providerForNutanixCluster, err := n.buildProviderFromNutanixCluster(nutanixCluster) + if err != nil { + return nil, fmt.Errorf("error building an environment provider from NutanixCluster: %w", err) + } + if providerForNutanixCluster != nil { + providers = append(providers, providerForNutanixCluster) } else { log.Info(fmt.Sprintf("[WARNING] prismCentral attribute was not set on NutanixCluster %s in namespace %s. Defaulting to CAPX manager credentials", nutanixCluster.Name, nutanixCluster.Namespace)) } - // Add env provider for CAPX manager - npe, err := n.getManagerNutanixPrismEndpoint() + // Fallback to building a provider using the global CAPX manager credentials + providerForLocalFile, err := n.buildProviderFromFile() + if err != nil { + return nil, fmt.Errorf("error building an environment provider from file: %w", err) + } + if providerForLocalFile != nil { + providers = append(providers, providerForLocalFile) + } + + // Initialize environment with providers + env := environment.NewEnvironment(providers...) + // GetManagementEndpoint will return the first valid endpoint from the list of providers + me, err := env.GetManagementEndpoint(envTypes.Topology{}) + if err != nil { + return nil, fmt.Errorf("failed to get management endpoint object: %w", err) + } + return me, nil +} + +// buildProviderFromNutanixCluster will return an envTypes.Provider with info from the provided NutanixCluster. +// It will return nil if nutanixCluster.Spec.PrismCentral is nil. +// It will return an error if required information is missing. +func (n *NutanixClientHelper) buildProviderFromNutanixCluster(nutanixCluster *infrav1.NutanixCluster) (envTypes.Provider, error) { + prismCentralInfo := nutanixCluster.Spec.PrismCentral + if prismCentralInfo == nil { + return nil, nil + } + + // PrismCentral is set, build a provider and fixup missing information + if prismCentralInfo.Address == "" { + return nil, ErrPrismAddressNotSet + } + if prismCentralInfo.Port == 0 { + return nil, ErrPrismPortNotSet + } + credentialRef, err := nutanixCluster.GetPrismCentralCredentialRef() + if err != nil { + //nolint:wrapcheck // error is already wrapped + return nil, err + } + // If namespace is empty, use the cluster namespace + if credentialRef.Namespace == "" { + credentialRef.Namespace = nutanixCluster.Namespace + } + additionalTrustBundleRef := prismCentralInfo.AdditionalTrustBundle + if additionalTrustBundleRef != nil && + additionalTrustBundleRef.Kind == credentials.NutanixTrustBundleKindConfigMap && + additionalTrustBundleRef.Namespace == "" { + additionalTrustBundleRef.Namespace = nutanixCluster.Namespace + } + + return kubernetesEnv.NewProvider(*prismCentralInfo, n.secretInformer, n.configMapInformer), nil +} + +// buildProviderFromFile will return an envTypes.Provider with info from the provided file. +// It will return an error if required information is missing. +func (n *NutanixClientHelper) buildProviderFromFile() (envTypes.Provider, error) { + npe, err := n.managerNutanixPrismEndpointReader() if err != nil { return nil, fmt.Errorf("failed to create prism endpoint: %w", err) } @@ -115,99 +186,70 @@ func (n *NutanixClientHelper) GetClientFromEnvironment(ctx context.Context, nuta } npe.AdditionalTrustBundle.Namespace = capxNamespace } - providers = append(providers, kubernetesEnv.NewProvider( - *npe, - n.secretInformer, - n.configMapInformer)) - // init env with providers - env := environment.NewEnvironment( - providers..., - ) - // fetch endpoint details - me, err := env.GetManagementEndpoint(envTypes.Topology{}) + + return kubernetesEnv.NewProvider(*npe, n.secretInformer, n.configMapInformer), nil +} + +func Build(creds prismgoclient.Credentials, additionalTrustBundle string) (*nutanixClientV3.Client, error) { + cli, err := buildClientFromCredentials(creds, additionalTrustBundle) if err != nil { - return nil, fmt.Errorf("failed to get management endpoint object: %w", err) + return nil, err } - creds := prismgoclient.Credentials{ - URL: me.Address.Host, - Endpoint: me.Address.Host, - Insecure: me.Insecure, - Username: me.ApiCredentials.Username, - Password: me.ApiCredentials.Password, + // Check if the client is working + _, err = cli.V3.GetCurrentLoggedInUser(context.Background()) + if err != nil { + return nil, fmt.Errorf("failed to get current logged in user with client: %w", err) } - - return n.GetClient(creds, me.AdditionalTrustBundle) + return cli, nil } -func (n *NutanixClientHelper) GetClient(cred prismgoclient.Credentials, additionalTrustBundle string) (*nutanixClientV3.Client, error) { - if cred.Username == "" { - return nil, fmt.Errorf("could not create client because username was not set") +func buildClientFromCredentials(creds prismgoclient.Credentials, additionalTrustBundle string) (*nutanixClientV3.Client, error) { + if creds.Username == "" { + return nil, ErrPrismIUsernameNotSet } - if cred.Password == "" { - return nil, fmt.Errorf("could not create client because password was not set") + if creds.Password == "" { + return nil, ErrPrismIPasswordNotSet } - if cred.Port == "" { - cred.Port = defaultEndpointPort + if creds.Port == "" { + creds.Port = defaultEndpointPort } - if cred.URL == "" { - cred.URL = fmt.Sprintf("%s:%s", cred.Endpoint, cred.Port) + if creds.URL == "" { + creds.URL = fmt.Sprintf("%s:%s", creds.Endpoint, creds.Port) } + clientOpts := make([]nutanixClientV3.ClientOption, 0) if additionalTrustBundle != "" { clientOpts = append(clientOpts, nutanixClientV3.WithPEMEncodedCertBundle([]byte(additionalTrustBundle))) } - cli, err := nutanixClientV3.NewV3Client(cred, clientOpts...) + // Build the client with the creds and possibly an additional TrustBundle + cli, err := nutanixClientV3.NewV3Client(creds, clientOpts...) if err != nil { return nil, fmt.Errorf("failed to create new nutanix client: %w", err) } - // Check if the client is working - _, err = cli.V3.GetCurrentLoggedInUser(context.Background()) - if err != nil { - return nil, fmt.Errorf("failed to get current logged in user with client: %w", err) - } return cli, nil } -func (n *NutanixClientHelper) getManagerNutanixPrismEndpoint() (*credentialTypes.NutanixPrismEndpoint, error) { - npe := &credentialTypes.NutanixPrismEndpoint{} - config, err := n.readEndpointConfig() +// readManagerNutanixPrismEndpoint reads the default config file and unmarshalls it into NutanixPrismEndpoint. +// Returns an error if the file does not exist and other read or unmarshalling errors. +func readManagerNutanixPrismEndpointFromDefaultFile() (*credentials.NutanixPrismEndpoint, error) { + return readManagerNutanixPrismEndpointFromFile(configPath) +} + +// this function is primarily here to make writing unit tests simpler +// readManagerNutanixPrismEndpointFromDefaultFile should be used outside of tests +func readManagerNutanixPrismEndpointFromFile(configFile string) (*credentials.NutanixPrismEndpoint, error) { + // fail on all errors including NotExist error + config, err := os.ReadFile(configFile) if err != nil { - return nil, fmt.Errorf("failed to read config: %w", err) + return nil, fmt.Errorf("failed to read prism config in manager: %w", err) } + npe := &credentials.NutanixPrismEndpoint{} if err = json.Unmarshal(config, npe); err != nil { return nil, fmt.Errorf("failed to unmarshal config: %w", err) } if npe.CredentialRef == nil { - return nil, fmt.Errorf("credentialRef must be set on CAPX manager") + return nil, ErrCredentialRefNotSet } return npe, nil } - -func (n *NutanixClientHelper) readEndpointConfig() ([]byte, error) { - if b, err := os.ReadFile(filepath.Join(configPath, endpointKey)); err == nil { - return b, err - } else if os.IsNotExist(err) { - return []byte{}, nil - } else { - return []byte{}, err - } -} - -func GetCredentialRefForCluster(nutanixCluster *infrav1.NutanixCluster) (*credentialTypes.NutanixCredentialReference, error) { - if nutanixCluster == nil { - return nil, fmt.Errorf("cannot get credential reference if nutanix cluster object is nil") - } - prismCentralInfo := nutanixCluster.Spec.PrismCentral - if prismCentralInfo == nil { - return nil, nil - } - if prismCentralInfo.CredentialRef == nil { - 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 { - return nil, nil - } - - return prismCentralInfo.CredentialRef, nil -} diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go new file mode 100644 index 0000000000..21755b0179 --- /dev/null +++ b/pkg/client/client_test.go @@ -0,0 +1,701 @@ +/* +Copyright 2024 Nutanix + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "context" + _ "embed" + "fmt" + "net/url" + "os" + "testing" + "time" + + prismgoclient "github.com/nutanix-cloud-native/prism-go-client" + "github.com/nutanix-cloud-native/prism-go-client/environment/credentials" + envTypes "github.com/nutanix-cloud-native/prism-go-client/environment/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/tools/cache" + ctrl "sigs.k8s.io/controller-runtime" + + infrav1 "github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1" +) + +var ( + //go:embed testdata/validTestCredentials.json + validTestCredentials string + //go:embed testdata/validTestManagerCredentials.json + validTestManagerCredentials string + + certBundleKey = "ca.crt" + //go:embed testdata/validTestCA.pem + validTestCA string + //go:embed testdata/validTestManagerCA.pem + validTestManagerCA string + + //go:embed testdata/validTestConfig.json + validTestConfig string + //go:embed testdata/invalidTestConfigMissingCredentialRef.json + invalidTestConfigMissingCredentialRef string +) + +var ( + testSecrets = []corev1.Secret{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "creds", + Namespace: "test", + }, + Data: map[string][]byte{ + credentials.KeyName: []byte(validTestCredentials), + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "capx-nutanix-creds", + Namespace: "capx-system", + }, + Data: map[string][]byte{ + credentials.KeyName: []byte(validTestManagerCredentials), + }, + }, + } + testConfigMaps = []corev1.ConfigMap{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cm", + Namespace: "test", + }, + BinaryData: map[string][]byte{ + certBundleKey: []byte(validTestCA), + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cm", + Namespace: "capx-system", + }, + BinaryData: map[string][]byte{ + certBundleKey: []byte(validTestManagerCA), + }, + }, + } +) + +// setup a single controller context to be shared in unit tests +// otherwise it gets prematurely closed +var controllerCtx = ctrl.SetupSignalHandler() + +func Test_buildManagementEndpoint(t *testing.T) { + t.Parallel() + tests := []struct { + name string + helper *NutanixClientHelper + nutanixCluster *infrav1.NutanixCluster + expectedManagementEndpoint *envTypes.ManagementEndpoint + expectedErr error + }{ + { + name: "all information set in NutanixCluster", + helper: testHelperWithFakedInformers(testSecrets, testConfigMaps).withCustomNutanixPrismEndpointReader( + func() (*credentials.NutanixPrismEndpoint, error) { + return &credentials.NutanixPrismEndpoint{ + Address: "manager-endpoint", + Port: 9440, + CredentialRef: &credentials.NutanixCredentialReference{ + Kind: credentials.SecretKind, + Name: "capx-nutanix-creds", + Namespace: "capx-system", + }, + AdditionalTrustBundle: &credentials.NutanixTrustBundleReference{ + Kind: credentials.NutanixTrustBundleKindConfigMap, + Name: "cm", + Namespace: "capx-system", + }, + }, nil + }, + ), + nutanixCluster: &infrav1.NutanixCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: corev1.NamespaceDefault, + }, + Spec: infrav1.NutanixClusterSpec{ + PrismCentral: &credentials.NutanixPrismEndpoint{ + Address: "cluster-endpoint", + Port: 9440, + CredentialRef: &credentials.NutanixCredentialReference{ + Kind: credentials.SecretKind, + Name: "creds", + Namespace: "test", + }, + AdditionalTrustBundle: &credentials.NutanixTrustBundleReference{ + Kind: credentials.NutanixTrustBundleKindConfigMap, + Name: "cm", + Namespace: "test", + }, + }, + }, + }, + expectedManagementEndpoint: &envTypes.ManagementEndpoint{ + ApiCredentials: envTypes.ApiCredentials{ + Username: "user", + Password: "password", + }, + Address: &url.URL{ + Scheme: "https", + Host: "cluster-endpoint:9440", + }, + AdditionalTrustBundle: validTestCA, + }, + }, + { + name: "information missing in NutanixCluster, fallback to management", + helper: testHelperWithFakedInformers(testSecrets, testConfigMaps).withCustomNutanixPrismEndpointReader( + func() (*credentials.NutanixPrismEndpoint, error) { + return &credentials.NutanixPrismEndpoint{ + Address: "manager-endpoint", + Port: 9440, + CredentialRef: &credentials.NutanixCredentialReference{ + Kind: credentials.SecretKind, + Name: "capx-nutanix-creds", + Namespace: "capx-system", + }, + AdditionalTrustBundle: &credentials.NutanixTrustBundleReference{ + Kind: credentials.NutanixTrustBundleKindConfigMap, + Name: "cm", + Namespace: "capx-system", + }, + }, nil + }, + ), + nutanixCluster: &infrav1.NutanixCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: corev1.NamespaceDefault, + }, + Spec: infrav1.NutanixClusterSpec{}, + }, + expectedManagementEndpoint: &envTypes.ManagementEndpoint{ + ApiCredentials: envTypes.ApiCredentials{ + Username: "admin", + Password: "adminpassword", + }, + Address: &url.URL{ + Scheme: "https", + Host: "manager-endpoint:9440", + }, + AdditionalTrustBundle: validTestManagerCA, + }, + }, + { + name: "NutanixCluster missing required information, should fail", + helper: testHelperWithFakedInformers(testSecrets, testConfigMaps).withCustomNutanixPrismEndpointReader( + func() (*credentials.NutanixPrismEndpoint, error) { + return &credentials.NutanixPrismEndpoint{ + Address: "manager-endpoint", + Port: 9440, + CredentialRef: &credentials.NutanixCredentialReference{ + Kind: credentials.SecretKind, + Name: "capx-nutanix-creds", + Namespace: "capx-system", + }, + AdditionalTrustBundle: &credentials.NutanixTrustBundleReference{ + Kind: credentials.NutanixTrustBundleKindConfigMap, + Name: "cm", + Namespace: "capx-system", + }, + }, nil + }, + ), + nutanixCluster: &infrav1.NutanixCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: corev1.NamespaceDefault, + }, + Spec: infrav1.NutanixClusterSpec{ + PrismCentral: &credentials.NutanixPrismEndpoint{ + Port: 9440, + CredentialRef: &credentials.NutanixCredentialReference{ + Kind: credentials.SecretKind, + Name: "creds", + Namespace: "test", + }, + AdditionalTrustBundle: &credentials.NutanixTrustBundleReference{ + Kind: credentials.NutanixTrustBundleKindConfigMap, + Name: "cm", + Namespace: "test", + }, + }, + }, + }, + expectedErr: fmt.Errorf("error building an environment provider from NutanixCluster: %w", ErrPrismAddressNotSet), + }, + { + name: "could not read management configuration, should fail", + helper: testHelperWithFakedInformers(testSecrets, testConfigMaps).withCustomNutanixPrismEndpointReader( + func() (*credentials.NutanixPrismEndpoint, error) { + return nil, fmt.Errorf("could not read config") + }, + ), + nutanixCluster: &infrav1.NutanixCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: corev1.NamespaceDefault, + }, + Spec: infrav1.NutanixClusterSpec{}, + }, + expectedErr: fmt.Errorf("error building an environment provider from file: %w", fmt.Errorf("failed to create prism endpoint: %w", fmt.Errorf("could not read config"))), + }, + } + for _, tt := range tests { + tt := tt // Capture range variable. + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + me, err := tt.helper.buildManagementEndpoint(context.TODO(), tt.nutanixCluster) + assert.Equal(t, tt.expectedManagementEndpoint, me) + assert.Equal(t, tt.expectedErr, err) + }) + } +} + +func Test_buildClientFromCredentials(t *testing.T) { + t.Parallel() + tests := []struct { + name string + creds prismgoclient.Credentials + additionalTrustBundle string + expectClientToBeNil bool + expectedErr error + }{ + { + name: "all information set", + creds: prismgoclient.Credentials{ + Endpoint: "cluster-endpoint", + Port: "9440", + Username: "user", + Password: "password", + }, + additionalTrustBundle: validTestCA, + }, + { + name: "some information set, expect defaults", + creds: prismgoclient.Credentials{ + Endpoint: "cluster-endpoint", + Username: "user", + Password: "password", + }, + }, + { + name: "missing username", + creds: prismgoclient.Credentials{ + Endpoint: "cluster-endpoint", + Port: "9440", + Password: "password", + }, + additionalTrustBundle: validTestCA, + expectClientToBeNil: true, + expectedErr: ErrPrismIUsernameNotSet, + }, + { + name: "missing password", + creds: prismgoclient.Credentials{ + Endpoint: "cluster-endpoint", + Port: "9440", + Username: "user", + }, + additionalTrustBundle: validTestCA, + expectClientToBeNil: true, + expectedErr: ErrPrismIPasswordNotSet, + }, + } + for _, tt := range tests { + tt := tt // Capture range variable. + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + client, err := buildClientFromCredentials(tt.creds, tt.additionalTrustBundle) + if tt.expectClientToBeNil { + assert.Nil(t, client) + } else { + assert.NotNil(t, client) + } + assert.Equal(t, tt.expectedErr, err) + }) + } +} + +func Test_buildProviderFromNutanixCluster(t *testing.T) { + t.Parallel() + tests := []struct { + name string + helper *NutanixClientHelper + nutanixCluster *infrav1.NutanixCluster + expectProviderToBeNil bool + expectedErr error + }{ + { + name: "all information set", + helper: testHelper(), + nutanixCluster: &infrav1.NutanixCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: corev1.NamespaceDefault, + }, + Spec: infrav1.NutanixClusterSpec{ + PrismCentral: &credentials.NutanixPrismEndpoint{ + Address: "cluster-endpoint", + Port: 9440, + CredentialRef: &credentials.NutanixCredentialReference{ + Kind: credentials.SecretKind, + Name: "creds", + Namespace: "test", + }, + AdditionalTrustBundle: &credentials.NutanixTrustBundleReference{ + Kind: credentials.NutanixTrustBundleKindConfigMap, + Name: "cm", + Namespace: "test", + }, + }, + }, + }, + expectProviderToBeNil: false, + }, + { + name: "namespace not set, should not fail", + helper: testHelper(), + nutanixCluster: &infrav1.NutanixCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: corev1.NamespaceDefault, + }, + Spec: infrav1.NutanixClusterSpec{ + PrismCentral: &credentials.NutanixPrismEndpoint{ + Address: "cluster-endpoint", + Port: 9440, + CredentialRef: &credentials.NutanixCredentialReference{ + Kind: credentials.SecretKind, + Name: "creds", + }, + AdditionalTrustBundle: &credentials.NutanixTrustBundleReference{ + Kind: credentials.NutanixTrustBundleKindConfigMap, + Name: "cm", + }, + }, + }, + }, + expectProviderToBeNil: false, + }, + { + name: "address is empty, should fail", + helper: testHelper(), + nutanixCluster: &infrav1.NutanixCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: corev1.NamespaceDefault, + }, + Spec: infrav1.NutanixClusterSpec{ + PrismCentral: &credentials.NutanixPrismEndpoint{ + Address: "", + Port: 9440, + CredentialRef: &credentials.NutanixCredentialReference{ + Kind: credentials.SecretKind, + Name: "creds", + Namespace: "test", + }, + AdditionalTrustBundle: &credentials.NutanixTrustBundleReference{ + Kind: credentials.NutanixTrustBundleKindConfigMap, + Name: "cm", + Namespace: "test", + }, + }, + }, + }, + expectProviderToBeNil: true, + expectedErr: ErrPrismAddressNotSet, + }, + { + name: "port is not set, should fail", + helper: testHelper(), + nutanixCluster: &infrav1.NutanixCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: corev1.NamespaceDefault, + }, + Spec: infrav1.NutanixClusterSpec{ + PrismCentral: &credentials.NutanixPrismEndpoint{ + Address: "cluster-endpoint", + CredentialRef: &credentials.NutanixCredentialReference{ + Kind: credentials.SecretKind, + Name: "creds", + Namespace: "test", + }, + AdditionalTrustBundle: &credentials.NutanixTrustBundleReference{ + Kind: credentials.NutanixTrustBundleKindConfigMap, + Name: "cm", + Namespace: "test", + }, + }, + }, + }, + expectProviderToBeNil: true, + expectedErr: ErrPrismPortNotSet, + }, + { + name: "CredentialRef is not set, should fail", + helper: testHelper(), + nutanixCluster: &infrav1.NutanixCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: corev1.NamespaceDefault, + }, + Spec: infrav1.NutanixClusterSpec{ + PrismCentral: &credentials.NutanixPrismEndpoint{ + Address: "cluster-endpoint", + Port: 9440, + AdditionalTrustBundle: &credentials.NutanixTrustBundleReference{ + Kind: credentials.NutanixTrustBundleKindConfigMap, + Name: "cm", + Namespace: "test", + }, + }, + }, + }, + expectProviderToBeNil: true, + expectedErr: fmt.Errorf("credentialRef must be set on prismCentral attribute for cluster %s in namespace %s", "test", corev1.NamespaceDefault), + }, + } + for _, tt := range tests { + tt := tt // Capture range variable. + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + provider, err := tt.helper.buildProviderFromNutanixCluster(tt.nutanixCluster) + if tt.expectProviderToBeNil { + assert.Nil(t, provider) + } else { + assert.NotNil(t, provider) + } + assert.Equal(t, tt.expectedErr, err) + }) + } +} + +func Test_buildProviderFromFile(t *testing.T) { + tests := []struct { + name string + helper *NutanixClientHelper + expectProviderToBeNil bool + envs map[string]string + expectedErr error + }{ + { + name: "all information set", + helper: testHelper().withCustomNutanixPrismEndpointReader( + func() (*credentials.NutanixPrismEndpoint, error) { + return &credentials.NutanixPrismEndpoint{ + Address: "manager-endpoint", + Port: 9440, + CredentialRef: &credentials.NutanixCredentialReference{ + Kind: credentials.SecretKind, + Name: "capx-nutanix-creds", + Namespace: "capx-system", + }, + AdditionalTrustBundle: &credentials.NutanixTrustBundleReference{ + Kind: credentials.NutanixTrustBundleKindConfigMap, + Name: "cm", + Namespace: "capx-system", + }, + }, nil + }, + ), + expectProviderToBeNil: false, + }, + { + name: "namespace not set with env, should not fail", + helper: testHelper().withCustomNutanixPrismEndpointReader( + func() (*credentials.NutanixPrismEndpoint, error) { + return &credentials.NutanixPrismEndpoint{ + Address: "manager-endpoint", + Port: 9440, + CredentialRef: &credentials.NutanixCredentialReference{ + Kind: credentials.SecretKind, + Name: "capx-nutanix-creds", + }, + AdditionalTrustBundle: &credentials.NutanixTrustBundleReference{ + Kind: credentials.NutanixTrustBundleKindConfigMap, + Name: "cm", + }, + }, nil + }, + ), + envs: map[string]string{capxNamespaceKey: "test"}, + expectProviderToBeNil: false, + }, + { + name: "credentialRef namespace not set, should fail", + helper: testHelper().withCustomNutanixPrismEndpointReader( + func() (*credentials.NutanixPrismEndpoint, error) { + return &credentials.NutanixPrismEndpoint{ + Address: "cluster-endpoint", + Port: 9440, + CredentialRef: &credentials.NutanixCredentialReference{ + Kind: credentials.SecretKind, + Name: "capx-nutanix-creds", + }, + AdditionalTrustBundle: &credentials.NutanixTrustBundleReference{ + Kind: credentials.NutanixTrustBundleKindConfigMap, + Name: "cm", + Namespace: "capx-system", + }, + }, nil + }, + ), + expectProviderToBeNil: true, + expectedErr: fmt.Errorf("failed to retrieve capx-namespace. Make sure %s env variable is set", capxNamespaceKey), + }, + { + name: "additionalTrustBundle namespace not set, should fail", + helper: testHelper().withCustomNutanixPrismEndpointReader( + func() (*credentials.NutanixPrismEndpoint, error) { + return &credentials.NutanixPrismEndpoint{ + Address: "cluster-endpoint", + Port: 9440, + CredentialRef: &credentials.NutanixCredentialReference{ + Kind: credentials.SecretKind, + Name: "creds", + Namespace: "capx-system", + }, + AdditionalTrustBundle: &credentials.NutanixTrustBundleReference{ + Kind: credentials.NutanixTrustBundleKindConfigMap, + Name: "cm", + }, + }, nil + }, + ), + expectProviderToBeNil: true, + expectedErr: fmt.Errorf("failed to retrieve capx-namespace. Make sure %s env variable is set", capxNamespaceKey), + }, + { + name: "reader returns an error, should fail", + helper: testHelper().withCustomNutanixPrismEndpointReader( + func() (*credentials.NutanixPrismEndpoint, error) { + return nil, fmt.Errorf("could not read config") + }, + ), + expectProviderToBeNil: true, + expectedErr: fmt.Errorf("failed to create prism endpoint: %w", fmt.Errorf("could not read config")), + }, + } + for _, tt := range tests { + tt := tt // Capture range variable. + t.Run(tt.name, func(t *testing.T) { + for k, v := range tt.envs { + t.Setenv(k, v) + } + provider, err := tt.helper.buildProviderFromFile() + if tt.expectProviderToBeNil { + assert.Nil(t, provider) + } else { + assert.NotNil(t, provider) + } + assert.Equal(t, tt.expectedErr, err) + }) + } +} + +func Test_readManagerNutanixPrismEndpointFromFile(t *testing.T) { + t.Parallel() + tests := []struct { + name string + config []byte + expectedEndpoint *credentials.NutanixPrismEndpoint + expectedErrString string + }{ + { + name: "valid config", + config: []byte(validTestConfig), + expectedEndpoint: &credentials.NutanixPrismEndpoint{ + Address: "cluster-endpoint", + Port: 9440, + CredentialRef: &credentials.NutanixCredentialReference{ + Kind: credentials.SecretKind, + Name: "creds", + Namespace: "test", + }, + }, + }, + { + name: "invalid config, expect an error", + config: []byte("{{}"), + expectedErrString: "failed to unmarshal config: invalid character '{' looking for beginning of object key string", + }, + { + name: "missing CredentialRef, expect an error", + config: []byte(invalidTestConfigMissingCredentialRef), + expectedErrString: "credentialRef must be set on CAPX manager", + }, + } + for _, tt := range tests { + tt := tt // Capture range variable. + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + f, err := os.CreateTemp(t.TempDir(), "config-*.json") + require.NoError(t, err, "error creating temp file") + _, err = f.Write(tt.config) + require.NoError(t, err, "error writing temp file") + + endpoint, err := readManagerNutanixPrismEndpointFromFile(f.Name()) + if err != nil { + assert.ErrorContains(t, err, tt.expectedErrString) + } + assert.Equal(t, tt.expectedEndpoint, endpoint) + }) + } +} + +func Test_readManagerNutanixPrismEndpointFromFile_IsNotExist(t *testing.T) { + _, err := readManagerNutanixPrismEndpointFromFile("filedoesnotexist.json") + assert.ErrorIs(t, err, os.ErrNotExist) +} + +func testHelper() *NutanixClientHelper { + helper := NutanixClientHelper{} + return &helper +} + +func testHelperWithFakedInformers(secrets []corev1.Secret, configMaps []corev1.ConfigMap) *NutanixClientHelper { + objects := make([]runtime.Object, 0) + for i := range secrets { + secret := secrets[i] + objects = append(objects, &secret) + } + for i := range configMaps { + cm := configMaps[i] + objects = append(objects, &cm) + } + + fakeClient := fake.NewSimpleClientset(objects...) + informerFactory := informers.NewSharedInformerFactory(fakeClient, time.Minute) + secretInformer := informerFactory.Core().V1().Secrets() + informer := secretInformer.Informer() + go informer.Run(controllerCtx.Done()) + cache.WaitForCacheSync(controllerCtx.Done(), informer.HasSynced) + + configMapInformer := informerFactory.Core().V1().ConfigMaps() + informer = configMapInformer.Informer() + go informer.Run(controllerCtx.Done()) + cache.WaitForCacheSync(controllerCtx.Done(), informer.HasSynced) + + return NewHelper(secretInformer, configMapInformer) +} diff --git a/pkg/client/testdata/invalidTestConfigMissingCredentialRef.json b/pkg/client/testdata/invalidTestConfigMissingCredentialRef.json new file mode 100644 index 0000000000..ae1a540481 --- /dev/null +++ b/pkg/client/testdata/invalidTestConfigMissingCredentialRef.json @@ -0,0 +1,4 @@ +{ + "address": "cluster-endpoint", + "port": 9440 +} \ No newline at end of file diff --git a/pkg/client/testdata/validTestCA.pem b/pkg/client/testdata/validTestCA.pem new file mode 100644 index 0000000000..471f3c553b --- /dev/null +++ b/pkg/client/testdata/validTestCA.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx +GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds +b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD +VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa +DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc +THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb +Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP +c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX +gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF +AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj +Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG +j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH +hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC +X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/pkg/client/testdata/validTestConfig.json b/pkg/client/testdata/validTestConfig.json new file mode 100644 index 0000000000..f61e1b0a73 --- /dev/null +++ b/pkg/client/testdata/validTestConfig.json @@ -0,0 +1,9 @@ +{ + "address": "cluster-endpoint", + "port": 9440, + "credentialRef": { + "kind": "Secret", + "name": "creds", + "namespace": "test" + } +} \ No newline at end of file diff --git a/pkg/client/testdata/validTestCredentials.json b/pkg/client/testdata/validTestCredentials.json new file mode 100644 index 0000000000..ea5c8fb8cc --- /dev/null +++ b/pkg/client/testdata/validTestCredentials.json @@ -0,0 +1,11 @@ +[ + { + "type": "basic_auth", + "data": { + "prismCentral":{ + "username": "user", + "password": "password" + } + } + } +] \ No newline at end of file diff --git a/pkg/client/testdata/validTestManagerCA.pem b/pkg/client/testdata/validTestManagerCA.pem new file mode 100644 index 0000000000..4fdfa0b60a --- /dev/null +++ b/pkg/client/testdata/validTestManagerCA.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u +ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp +bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV +BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx +NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3 +d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl +MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u +ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL +Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr +hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW +nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi +VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ +KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy +T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT +J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e +nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/pkg/client/testdata/validTestManagerCredentials.json b/pkg/client/testdata/validTestManagerCredentials.json new file mode 100644 index 0000000000..e9a28975cc --- /dev/null +++ b/pkg/client/testdata/validTestManagerCredentials.json @@ -0,0 +1,11 @@ +[ + { + "type": "basic_auth", + "data": { + "prismCentral":{ + "username": "admin", + "password": "adminpassword" + } + } + } +] \ No newline at end of file diff --git a/test/e2e/nutanix_client.go b/test/e2e/nutanix_client.go index 855284e692..511b30bf8b 100644 --- a/test/e2e/nutanix_client.go +++ b/test/e2e/nutanix_client.go @@ -31,7 +31,7 @@ import ( . "github.com/onsi/gomega" "sigs.k8s.io/cluster-api/test/framework/clusterctl" - nutanixClientHelper "github.com/nutanix-cloud-native/cluster-api-provider-nutanix/pkg/client" + nutanixClient "github.com/nutanix-cloud-native/cluster-api-provider-nutanix/pkg/client" ) const ( @@ -128,11 +128,10 @@ func initNutanixClient(e2eConfig clusterctl.E2EConfig) (*prismGoClientV3.Client, } trustBundle = string(decodedCert) } - nch := nutanixClientHelper.NutanixClientHelper{} - nutanixClient, err := nch.GetClient(*creds, trustBundle) + client, err := nutanixClient.Build(*creds, trustBundle) if err != nil { return nil, err } - return nutanixClient, nil + return client, nil }