diff --git a/api/v1alpha1/addon_types.go b/api/v1alpha1/addon_types.go index 6f524159d..9a6595f21 100644 --- a/api/v1alpha1/addon_types.go +++ b/api/v1alpha1/addon_types.go @@ -311,12 +311,34 @@ func (CSI) VariableSchema() clusterv1.VariableSchema { } // CCM tells us to enable or disable the cloud provider interface. -type CCM struct{} +type CCM struct { + // A reference to the Secret for credential information for the target Prism Central instance + // +optional + Credentials *corev1.LocalObjectReference `json:"credentials"` +} func (CCM) VariableSchema() clusterv1.VariableSchema { + // TODO Validate credentials is set. + // This CCM is shared across all providers. + // Some of these providers may require credentials to be set, but we don't want to require it for all providers. + // The Nutanix CCM handler will fail in at runtime if credentials are not set. return clusterv1.VariableSchema{ OpenAPIV3Schema: clusterv1.JSONSchemaProps{ Type: "object", + Properties: map[string]clusterv1.JSONSchemaProps{ + "credentials": { + Description: "A reference to the Secret for credential information" + + "for the target Prism Central instance", + Type: "object", + Properties: map[string]clusterv1.JSONSchemaProps{ + "name": { + Description: "The name of the Secret", + Type: "string", + }, + }, + Required: []string{"name"}, + }, + }, }, } } diff --git a/api/v1alpha1/clusterconfig_types.go b/api/v1alpha1/clusterconfig_types.go index ba7e012b0..5cee9a2fb 100644 --- a/api/v1alpha1/clusterconfig_types.go +++ b/api/v1alpha1/clusterconfig_types.go @@ -25,7 +25,8 @@ const ( CSIProviderAWSEBS = "aws-ebs" CSIProviderNutanix = "nutanix" - CCMProviderAWS = "aws" + CCMProviderAWS = "aws" + CCMProviderNutanix = "nutanix" ) // +kubebuilder:object:root=true diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index a98f87d08..d8dffcecf 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -202,7 +202,7 @@ func (in *Addons) DeepCopyInto(out *Addons) { if in.CCM != nil { in, out := &in.CCM, &out.CCM *out = new(CCM) - **out = **in + (*in).DeepCopyInto(*out) } if in.CSIProviders != nil { in, out := &in.CSIProviders, &out.CSIProviders @@ -224,6 +224,11 @@ func (in *Addons) DeepCopy() *Addons { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CCM) DeepCopyInto(out *CCM) { *out = *in + if in.Credentials != nil { + in, out := &in.Credentials, &out.Credentials + *out = new(v1.LocalObjectReference) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CCM. diff --git a/pkg/handlers/generic/lifecycle/ccm/aws/handler.go b/pkg/handlers/generic/lifecycle/ccm/aws/handler.go index fa37fd1db..626de46fb 100644 --- a/pkg/handlers/generic/lifecycle/ccm/aws/handler.go +++ b/pkg/handlers/generic/lifecycle/ccm/aws/handler.go @@ -15,6 +15,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/api/v1alpha1" "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/common/pkg/k8s/client" lifecycleutils "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/lifecycle/utils" "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/options" @@ -56,6 +57,7 @@ func New( func (a *AWSCCM) Apply( ctx context.Context, cluster *clusterv1.Cluster, + _ *v1alpha1.ClusterConfigSpec, ) error { log := ctrl.LoggerFrom(ctx).WithValues( "cluster", diff --git a/pkg/handlers/generic/lifecycle/ccm/handler.go b/pkg/handlers/generic/lifecycle/ccm/handler.go index 08d8372f0..fb8e764d5 100644 --- a/pkg/handlers/generic/lifecycle/ccm/handler.go +++ b/pkg/handlers/generic/lifecycle/ccm/handler.go @@ -25,7 +25,7 @@ const ( ) type CCMProvider interface { - Apply(context.Context, *clusterv1.Cluster) error + Apply(context.Context, *clusterv1.Cluster, *v1alpha1.ClusterConfigSpec) error } type CCMHandler struct { @@ -78,7 +78,7 @@ func (c *CCMHandler) AfterControlPlaneInitialized( ) resp.SetStatus(runtimehooksv1.ResponseStatusFailure) resp.SetMessage( - fmt.Sprintf("failed to read CCM provider from cluster definition: %v", + fmt.Sprintf("failed to read CCM from cluster definition: %v", err, ), ) @@ -88,17 +88,36 @@ func (c *CCMHandler) AfterControlPlaneInitialized( log.V(4).Info("Skipping CCM handler.") return } + + clusterConfigVar, found, err := variables.Get[v1alpha1.ClusterConfigSpec](varMap, clusterconfig.MetaVariableName) + if err != nil { + log.Error( + err, + "failed to read clusterConfig variable from cluster definition", + ) + resp.SetStatus(runtimehooksv1.ResponseStatusFailure) + resp.SetMessage( + fmt.Sprintf("ffailed to read clusterConfig variable from cluster definition: %v", + err, + ), + ) + return + } + infraKind := req.Cluster.Spec.InfrastructureRef.Kind log.Info(fmt.Sprintf("finding CCM handler for %s", infraKind)) var handler CCMProvider switch { case strings.Contains(strings.ToLower(infraKind), v1alpha1.CCMProviderAWS): handler = c.ProviderHandler[v1alpha1.CCMProviderAWS] + case strings.Contains(strings.ToLower(infraKind), v1alpha1.CCMProviderNutanix): + handler = c.ProviderHandler[v1alpha1.CCMProviderNutanix] default: log.Info(fmt.Sprintf("No CCM handler provided for infra kind %s", infraKind)) return } - err = handler.Apply(ctx, &req.Cluster) + + err = handler.Apply(ctx, &req.Cluster, &clusterConfigVar) if err != nil { log.Error( err, diff --git a/pkg/handlers/generic/lifecycle/ccm/nutanix/handler.go b/pkg/handlers/generic/lifecycle/ccm/nutanix/handler.go new file mode 100644 index 000000000..84489d8c5 --- /dev/null +++ b/pkg/handlers/generic/lifecycle/ccm/nutanix/handler.go @@ -0,0 +1,176 @@ +// Copyright 2023 D2iQ, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package nutanix + +import ( + "bytes" + "context" + "errors" + "fmt" + "text/template" + + "github.com/spf13/pflag" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + caaphv1 "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/api/external/sigs.k8s.io/cluster-api-addon-provider-helm/api/v1alpha1" + "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/common/pkg/k8s/client" + lifecycleutils "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/lifecycle/utils" + "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/options" +) + +const ( + defaultHelmRepositoryURL = "https://nutanix.github.io/helm/" + defaultHelmChartVersion = "0.3.3" + defaultHelmChartName = "nutanix-cloud-provider" + defaultHelmReleaseName = "nutanix-ccm" + defaultHelmReleaseNamespace = "kube-system" + + // This is the name of the Secret on the remote cluster that should match what is defined in Helm values. + defaultCredentialsSecretName = "nutanix-ccm-credentials" +) + +//nolint:gosec // Does not contain hard coded credentials. +var ErrMissingCredentials = errors.New("name of the Secret containing PC credentials must be set") + +type Config struct { + *options.GlobalOptions + + defaultValuesTemplateConfigMapName string +} + +func (c *Config) AddFlags(prefix string, flags *pflag.FlagSet) { + flags.StringVar( + &c.defaultValuesTemplateConfigMapName, + prefix+".default-values-template-configmap-name", + "default-nutanix-ccm-helm-values-template", + "default values ConfigMap name", + ) +} + +type provider struct { + client ctrlclient.Client + config *Config +} + +func New( + c ctrlclient.Client, + cfg *Config, +) *provider { + return &provider{ + client: c, + config: cfg, + } +} + +func (p *provider) Apply( + ctx context.Context, + cluster *clusterv1.Cluster, + clusterConfig *v1alpha1.ClusterConfigSpec, +) error { + // No need to check for nil values in the struct, this function will only be called if CCM is not nil + if clusterConfig.Addons.CCM.Credentials == nil { + return ErrMissingCredentials + } + + valuesTemplateConfigMap, err := lifecycleutils.RetrieveValuesTemplateConfigMap( + ctx, + p.client, + p.config.defaultValuesTemplateConfigMapName, + p.config.DefaultsNamespace(), + ) + if err != nil { + return fmt.Errorf( + "failed to retrieve Nutanix CCM installation values template ConfigMap for cluster: %w", + err, + ) + } + + // It's possible to have the credentials Secret be created by the Helm chart. + // However, that would leave the credentials visible in the HelmChartProxy. + // Instead, we'll create the Secret on the remote cluster and reference it in the Helm values. + if clusterConfig.Addons.CCM.Credentials != nil { + key := ctrlclient.ObjectKey{ + Name: defaultCredentialsSecretName, + Namespace: defaultHelmReleaseNamespace, + } + err = lifecycleutils.CopySecretToRemoteCluster( + ctx, + p.client, + clusterConfig.Addons.CCM.Credentials.Name, + key, + cluster, + ) + if err != nil { + return fmt.Errorf("error creating Nutanix CCM Credentials Secret on the remote cluster: %w", err) + } + } + + values := valuesTemplateConfigMap.Data["values.yaml"] + // The configMap will contain the Helm values, but templated with fields that need to be filled in. + values, err = templateValues(clusterConfig, values) + if err != nil { + return fmt.Errorf("failed to template Helm values read from ConfigMap: %w", err) + } + + hcp := &caaphv1.HelmChartProxy{ + TypeMeta: metav1.TypeMeta{ + APIVersion: caaphv1.GroupVersion.String(), + Kind: "HelmChartProxy", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: cluster.Namespace, + Name: "nutanix-ccm-" + cluster.Name, + }, + Spec: caaphv1.HelmChartProxySpec{ + RepoURL: defaultHelmRepositoryURL, + ChartName: defaultHelmChartName, + ClusterSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{clusterv1.ClusterNameLabel: cluster.Name}, + }, + ReleaseNamespace: defaultHelmReleaseNamespace, + ReleaseName: defaultHelmReleaseName, + Version: defaultHelmChartVersion, + ValuesTemplate: values, + }, + } + + if err = controllerutil.SetOwnerReference(cluster, hcp, p.client.Scheme()); err != nil { + return fmt.Errorf( + "failed to set owner reference on nutanix-ccm installation HelmChartProxy: %w", + err, + ) + } + + if err = client.ServerSideApply(ctx, p.client, hcp); err != nil { + return fmt.Errorf("failed to apply nutanix-ccm installation HelmChartProxy: %w", err) + } + + return nil +} + +func templateValues(clusterConfig *v1alpha1.ClusterConfigSpec, text string) (string, error) { + helmValuesTemplate, err := template.New("").Parse(text) + if err != nil { + return "", fmt.Errorf("failed to parse Helm values template: %w", err) + } + + type input struct { + PrismCentralEndpoint v1alpha1.NutanixPrismCentralEndpointSpec + } + templateInput := input{ + PrismCentralEndpoint: clusterConfig.Nutanix.PrismCentralEndpoint, + } + + var b bytes.Buffer + err = helmValuesTemplate.Execute(&b, templateInput) + if err != nil { + return "", fmt.Errorf("failed setting PrismCentral configuration in template: %w", err) + } + + return b.String(), nil +} diff --git a/pkg/handlers/generic/lifecycle/ccm/nutanix/handler_test.go b/pkg/handlers/generic/lifecycle/ccm/nutanix/handler_test.go new file mode 100644 index 000000000..7bb91a486 --- /dev/null +++ b/pkg/handlers/generic/lifecycle/ccm/nutanix/handler_test.go @@ -0,0 +1,121 @@ +// Copyright 2023 D2iQ, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package nutanix + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" + + "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/api/v1alpha1" +) + +const ( + in = `--- +prismCentralEndPoint: {{ .PrismCentralEndpoint.Host }} +prismCentralPort: {{ .PrismCentralEndpoint.Port }} +prismCentralInsecure: {{ .PrismCentralEndpoint.Insecure }} +prismCentralAdditionalTrustBundle: "{{ or .PrismCentralEndpoint.AdditionalTrustBundle "" }}" + +# The Secret containing the credentials will be created by the handler. +createSecret: false +secretName: nutanix-ccm-credentials +` + //nolint:lll // just a long string + testCertBundle = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVjekNDQTF1Z0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRUUZBRC4uQWtHQTFVRUJoTUNSMEl4CkV6QVJCZ05WQkFnVENsTnZiV1V0VTNSaGRHVXhGREFTQmdOVkJBb1RDMC4uMEVnVEhSa01UY3dOUVlEClZRUUxFeTVEYkdGemN5QXhJRkIxWW14cFl5QlFjbWx0WVhKNUlFTmxjbi4uWFJwYjI0Z1FYVjBhRzl5CmFYUjVNUlF3RWdZRFZRUURFd3RDWlhOMElFTkJJRXgwWkRBZUZ3MHdNRC4uVFV3TVRaYUZ3MHdNVEF5Ck1EUXhPVFV3TVRaYU1JR0hNUXN3Q1FZRFZRUUdFd0pIUWpFVE1CRUdBMS4uMjl0WlMxVGRHRjBaVEVVCk1CSUdBMVVFQ2hNTFFtVnpkQ0JEUVNCTWRHUXhOekExQmdOVkJBc1RMay4uREVnVUhWaWJHbGpJRkJ5CmFXMWhjbmtnUTJWeWRHbG1hV05oZEdsdmJpQkJkWFJvYjNKcGRIa3hGRC4uQU1UQzBKbGMzUWdRMEVnClRIUmtNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZy4uVHoybXI3U1ppQU1mUXl1CnZCak05T2lKalJhelhCWjFCalA1Q0UvV20vUnI1MDBQUksrTGg5eDVlSi4uL0FOQkUwc1RLMFpzREdNCmFrMm0xZzdvcnVJM2RZM1ZIcUl4RlR6MFRhMWQrTkFqd25MZTRuT2I3Ly4uazA1U2hoQnJKR0JLS3hiCjhuMTA0by81cDhIQXNaUGR6YkZNSXlOakp6Qk0ybzV5NUExM3dpTGl0RS4uZnlZa1F6YXhDdzBBd3psCmtWSGlJeUN1YUY0d2o1NzFwU3prdjZzdis0SURNYlQvWHBDbzhMNndUYS4uc2grZXRMRDZGdFRqWWJiCnJ2WjhSUU0xdGxLZG9NSGcycXhyYUFWKytITkJZbU5XczBkdUVkalViSi4uWEk5VHRuUzRvMUNrajdQCk9mbGppUUlEQVFBQm80SG5NSUhrTUIwR0ExVWREZ1FXQkJROHVyTUNSTC4uNUFrSXA5TkpISnc1VENCCnRBWURWUjBqQklHc01JR3BnQlE4dXJNQ1JMWVlNSFVLVTVBa0lwOU5KSC4uYVNCaWpDQmh6RUxNQWtHCkExVUVCaE1DUjBJeEV6QVJCZ05WQkFnVENsTnZiV1V0VTNSaGRHVXhGRC4uQW9UQzBKbGMzUWdRMEVnClRIUmtNVGN3TlFZRFZRUUxFeTVEYkdGemN5QXhJRkIxWW14cFl5QlFjbS4uRU5sY25ScFptbGpZWFJwCmIyNGdRWFYwYUc5eWFYUjVNUlF3RWdZRFZRUURFd3RDWlhOMElFTkJJRS4uREFNQmdOVkhSTUVCVEFECkFRSC9NQTBHQ1NxR1NJYjNEUUVCQkFVQUE0SUJBUUMxdVlCY3NTbmN3QS4uRENzUWVyNzcyQzJ1Y3BYCnhRVUUvQzBwV1dtNmdEa3dkNUQwRFNNREpScVYvd2VvWjR3QzZCNzNmNS4uYkxoR1lIYVhKZVNENktyClhjb093TGRTYUdtSllzbExLWkIzWklERXAwd1lUR2hndGViNkpGaVR0bi4uc2YyeGRyWWZQQ2lJQjdnCkJNQVY3R3pkYzRWc3BTNmxqckFoYmlpYXdkQmlRbFFtc0JlRno5SmtGNC4uYjNsOEJvR04rcU1hNTZZCkl0OHVuYTJnWTRsMk8vL29uODhyNUlXSmxtMUwwb0E4ZTRmUjJ5ckJIWC4uYWRzR2VGS2t5TnJ3R2kvCjd2UU1mWGRHc1JyWE5HUkduWCt2V0RaMy96V0kwam9EdENrTm5xRXBWbi4uSG9YCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=" +) + +const ( + //nolint:lll // just a long string + expectedWithAdditionalTrustBundle = `--- +prismCentralEndPoint: prism-central.nutanix.com +prismCentralPort: 9440 +prismCentralInsecure: false +prismCentralAdditionalTrustBundle: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVjekNDQTF1Z0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRUUZBRC4uQWtHQTFVRUJoTUNSMEl4CkV6QVJCZ05WQkFnVENsTnZiV1V0VTNSaGRHVXhGREFTQmdOVkJBb1RDMC4uMEVnVEhSa01UY3dOUVlEClZRUUxFeTVEYkdGemN5QXhJRkIxWW14cFl5QlFjbWx0WVhKNUlFTmxjbi4uWFJwYjI0Z1FYVjBhRzl5CmFYUjVNUlF3RWdZRFZRUURFd3RDWlhOMElFTkJJRXgwWkRBZUZ3MHdNRC4uVFV3TVRaYUZ3MHdNVEF5Ck1EUXhPVFV3TVRaYU1JR0hNUXN3Q1FZRFZRUUdFd0pIUWpFVE1CRUdBMS4uMjl0WlMxVGRHRjBaVEVVCk1CSUdBMVVFQ2hNTFFtVnpkQ0JEUVNCTWRHUXhOekExQmdOVkJBc1RMay4uREVnVUhWaWJHbGpJRkJ5CmFXMWhjbmtnUTJWeWRHbG1hV05oZEdsdmJpQkJkWFJvYjNKcGRIa3hGRC4uQU1UQzBKbGMzUWdRMEVnClRIUmtNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZy4uVHoybXI3U1ppQU1mUXl1CnZCak05T2lKalJhelhCWjFCalA1Q0UvV20vUnI1MDBQUksrTGg5eDVlSi4uL0FOQkUwc1RLMFpzREdNCmFrMm0xZzdvcnVJM2RZM1ZIcUl4RlR6MFRhMWQrTkFqd25MZTRuT2I3Ly4uazA1U2hoQnJKR0JLS3hiCjhuMTA0by81cDhIQXNaUGR6YkZNSXlOakp6Qk0ybzV5NUExM3dpTGl0RS4uZnlZa1F6YXhDdzBBd3psCmtWSGlJeUN1YUY0d2o1NzFwU3prdjZzdis0SURNYlQvWHBDbzhMNndUYS4uc2grZXRMRDZGdFRqWWJiCnJ2WjhSUU0xdGxLZG9NSGcycXhyYUFWKytITkJZbU5XczBkdUVkalViSi4uWEk5VHRuUzRvMUNrajdQCk9mbGppUUlEQVFBQm80SG5NSUhrTUIwR0ExVWREZ1FXQkJROHVyTUNSTC4uNUFrSXA5TkpISnc1VENCCnRBWURWUjBqQklHc01JR3BnQlE4dXJNQ1JMWVlNSFVLVTVBa0lwOU5KSC4uYVNCaWpDQmh6RUxNQWtHCkExVUVCaE1DUjBJeEV6QVJCZ05WQkFnVENsTnZiV1V0VTNSaGRHVXhGRC4uQW9UQzBKbGMzUWdRMEVnClRIUmtNVGN3TlFZRFZRUUxFeTVEYkdGemN5QXhJRkIxWW14cFl5QlFjbS4uRU5sY25ScFptbGpZWFJwCmIyNGdRWFYwYUc5eWFYUjVNUlF3RWdZRFZRUURFd3RDWlhOMElFTkJJRS4uREFNQmdOVkhSTUVCVEFECkFRSC9NQTBHQ1NxR1NJYjNEUUVCQkFVQUE0SUJBUUMxdVlCY3NTbmN3QS4uRENzUWVyNzcyQzJ1Y3BYCnhRVUUvQzBwV1dtNmdEa3dkNUQwRFNNREpScVYvd2VvWjR3QzZCNzNmNS4uYkxoR1lIYVhKZVNENktyClhjb093TGRTYUdtSllzbExLWkIzWklERXAwd1lUR2hndGViNkpGaVR0bi4uc2YyeGRyWWZQQ2lJQjdnCkJNQVY3R3pkYzRWc3BTNmxqckFoYmlpYXdkQmlRbFFtc0JlRno5SmtGNC4uYjNsOEJvR04rcU1hNTZZCkl0OHVuYTJnWTRsMk8vL29uODhyNUlXSmxtMUwwb0E4ZTRmUjJ5ckJIWC4uYWRzR2VGS2t5TnJ3R2kvCjd2UU1mWGRHc1JyWE5HUkduWCt2V0RaMy96V0kwam9EdENrTm5xRXBWbi4uSG9YCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=" + +# The Secret containing the credentials will be created by the handler. +createSecret: false +secretName: nutanix-ccm-credentials +` + + expectedWithoutAdditionalTrustBundle = `--- +prismCentralEndPoint: prism-central.nutanix.com +prismCentralPort: 9440 +prismCentralInsecure: true +prismCentralAdditionalTrustBundle: "" + +# The Secret containing the credentials will be created by the handler. +createSecret: false +secretName: nutanix-ccm-credentials +` +) + +func Test_templateValues(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + clusterConfig *v1alpha1.ClusterConfigSpec + in string + expected string + }{ + { + name: "With AdditionalTrustBundle set", + clusterConfig: &v1alpha1.ClusterConfigSpec{ + GenericClusterConfig: v1alpha1.GenericClusterConfig{ + Addons: &v1alpha1.Addons{ + CCM: &v1alpha1.CCM{ + Credentials: &corev1.LocalObjectReference{ + Name: "creds", + }, + }, + }, + }, + Nutanix: &v1alpha1.NutanixSpec{ + PrismCentralEndpoint: v1alpha1.NutanixPrismCentralEndpointSpec{ + Host: "prism-central.nutanix.com", + Port: v1alpha1.PrismCentralPort, + AdditionalTrustBundle: ptr.To(testCertBundle), + }, + }, + }, + in: in, + expected: expectedWithAdditionalTrustBundle, + }, + { + name: "Without an AdditionalTrustBundle set", + clusterConfig: &v1alpha1.ClusterConfigSpec{ + GenericClusterConfig: v1alpha1.GenericClusterConfig{ + Addons: &v1alpha1.Addons{ + CCM: &v1alpha1.CCM{ + Credentials: &corev1.LocalObjectReference{ + Name: "creds", + }, + }, + }, + }, + Nutanix: &v1alpha1.NutanixSpec{ + PrismCentralEndpoint: v1alpha1.NutanixPrismCentralEndpointSpec{ + Host: "prism-central.nutanix.com", + Port: v1alpha1.PrismCentralPort, + Insecure: true, + }, + }, + }, + in: in, + expected: expectedWithoutAdditionalTrustBundle, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + out, err := templateValues(test.clusterConfig, test.in) + require.NoError(t, err) + assert.Equal(t, test.expected, out) + }) + } +} diff --git a/pkg/handlers/generic/lifecycle/clusterautoscaler/strategy_helmaddon.go b/pkg/handlers/generic/lifecycle/clusterautoscaler/strategy_helmaddon.go index a22e69d2f..b16313841 100644 --- a/pkg/handlers/generic/lifecycle/clusterautoscaler/strategy_helmaddon.go +++ b/pkg/handlers/generic/lifecycle/clusterautoscaler/strategy_helmaddon.go @@ -57,7 +57,8 @@ func (s helmAddonStrategy) apply( ctx, s.client, s.config.defaultValuesTemplateConfigMapName, - defaultsNamespace) + defaultsNamespace, + ) if err != nil { return fmt.Errorf( "failed to retrieve cluster-autoscaler installation values template ConfigMap for cluster: %w", diff --git a/pkg/handlers/generic/lifecycle/handlers.go b/pkg/handlers/generic/lifecycle/handlers.go index b3b0d22fb..116130b15 100644 --- a/pkg/handlers/generic/lifecycle/handlers.go +++ b/pkg/handlers/generic/lifecycle/handlers.go @@ -11,6 +11,7 @@ import ( "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers" "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/lifecycle/ccm" awsccm "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/lifecycle/ccm/aws" + nutanixccm "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/lifecycle/ccm/nutanix" "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/lifecycle/clusterautoscaler" "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/lifecycle/cni/calico" "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/lifecycle/cni/cilium" @@ -30,6 +31,7 @@ type Handlers struct { ebsConfig *awsebs.AWSEBSConfig nutnaixCSIConfig *nutanixcsi.NutanixCSIConfig awsccmConfig *awsccm.AWSCCMConfig + nutanixCCMConfig *nutanixccm.Config } func New(globalOptions *options.GlobalOptions) *Handlers { @@ -41,6 +43,7 @@ func New(globalOptions *options.GlobalOptions) *Handlers { ebsConfig: &awsebs.AWSEBSConfig{GlobalOptions: globalOptions}, awsccmConfig: &awsccm.AWSCCMConfig{GlobalOptions: globalOptions}, nutnaixCSIConfig: &nutanixcsi.NutanixCSIConfig{GlobalOptions: globalOptions}, + nutanixCCMConfig: &nutanixccm.Config{GlobalOptions: globalOptions}, } } @@ -50,7 +53,8 @@ func (h *Handlers) AllHandlers(mgr manager.Manager) []handlers.Named { v1alpha1.CSIProviderNutanix: nutanixcsi.New(mgr.GetClient(), h.nutnaixCSIConfig), } ccmHandlers := map[string]ccm.CCMProvider{ - v1alpha1.CCMProviderAWS: awsccm.New(mgr.GetClient(), h.awsccmConfig), + v1alpha1.CCMProviderAWS: awsccm.New(mgr.GetClient(), h.awsccmConfig), + v1alpha1.CCMProviderNutanix: nutanixccm.New(mgr.GetClient(), h.nutanixCCMConfig), } return []handlers.Named{ @@ -72,4 +76,5 @@ func (h *Handlers) AddFlags(flagSet *pflag.FlagSet) { h.ebsConfig.AddFlags("awsebs", pflag.CommandLine) h.awsccmConfig.AddFlags("awsccm", pflag.CommandLine) h.nutnaixCSIConfig.AddFlags("nutanixcsi", flagSet) + h.nutanixCCMConfig.AddFlags("nutanixccm", flagSet) }