diff --git a/README.md b/README.md index cd04a4aff..27a85eba4 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,14 @@ When adding a new handler, or modifying an existing one, pay close attention to when a new version of this extension is deployed in the management cluster, and avoid rollouts of Machines in those existing clusters. +If a change to a handler is unavoidable, you must: + +1. Update the version of the handlers, e.g. from `v4` to `v5`. + This is done by updating the `..ClusterV4ConfigPatch` in the handler files in `pkg/handlers/../mutation/`. + And `..clusterv4configpatch` in `hack/examples/overlays/clusterclasses/../kustomization.yaml.tmpl` +2. Update the version of the handler in the `pkg/handlers/v3` package, e.g. from `v3` to `v4`. +3. Copy the existing implementation of the handler to `pkg/handlers/../v4/`. + During CAPI provider upgrades, and periodically, all managed clusters are reconciled and mutation handler patches are applied. Any new handlers that return a new set of patches, or updated handlers that return a different set of patches, diff --git a/charts/cluster-api-runtime-extensions-nutanix/defaultclusterclasses/aws-cluster-class.yaml b/charts/cluster-api-runtime-extensions-nutanix/defaultclusterclasses/aws-cluster-class.yaml index 004ffab64..9e3dcdb0c 100644 --- a/charts/cluster-api-runtime-extensions-nutanix/defaultclusterclasses/aws-cluster-class.yaml +++ b/charts/cluster-api-runtime-extensions-nutanix/defaultclusterclasses/aws-cluster-class.yaml @@ -23,11 +23,11 @@ spec: patches: - external: discoverVariablesExtension: awsclusterconfigvars-dv.cluster-api-runtime-extensions-nutanix - generateExtension: awsclusterv3configpatch-gp.cluster-api-runtime-extensions-nutanix + generateExtension: awsclusterv4configpatch-gp.cluster-api-runtime-extensions-nutanix name: cluster-config - external: discoverVariablesExtension: awsworkerconfigvars-dv.cluster-api-runtime-extensions-nutanix - generateExtension: awsworkerv3configpatch-gp.cluster-api-runtime-extensions-nutanix + generateExtension: awsworkerv4configpatch-gp.cluster-api-runtime-extensions-nutanix name: worker-config - definitions: - jsonPatches: diff --git a/charts/cluster-api-runtime-extensions-nutanix/defaultclusterclasses/docker-cluster-class.yaml b/charts/cluster-api-runtime-extensions-nutanix/defaultclusterclasses/docker-cluster-class.yaml index 3f95c1da7..2fd587a81 100644 --- a/charts/cluster-api-runtime-extensions-nutanix/defaultclusterclasses/docker-cluster-class.yaml +++ b/charts/cluster-api-runtime-extensions-nutanix/defaultclusterclasses/docker-cluster-class.yaml @@ -31,11 +31,11 @@ spec: patches: - external: discoverVariablesExtension: dockerclusterconfigvars-dv.cluster-api-runtime-extensions-nutanix - generateExtension: dockerclusterv3configpatch-gp.cluster-api-runtime-extensions-nutanix + generateExtension: dockerclusterv4configpatch-gp.cluster-api-runtime-extensions-nutanix name: cluster-config - external: discoverVariablesExtension: dockerworkerconfigvars-dv.cluster-api-runtime-extensions-nutanix - generateExtension: dockerworkerv3configpatch-gp.cluster-api-runtime-extensions-nutanix + generateExtension: dockerworkerv4configpatch-gp.cluster-api-runtime-extensions-nutanix name: worker-config workers: machineDeployments: diff --git a/charts/cluster-api-runtime-extensions-nutanix/defaultclusterclasses/nutanix-cluster-class.yaml b/charts/cluster-api-runtime-extensions-nutanix/defaultclusterclasses/nutanix-cluster-class.yaml index 549c623a0..980c216f1 100644 --- a/charts/cluster-api-runtime-extensions-nutanix/defaultclusterclasses/nutanix-cluster-class.yaml +++ b/charts/cluster-api-runtime-extensions-nutanix/defaultclusterclasses/nutanix-cluster-class.yaml @@ -74,11 +74,11 @@ spec: patches: - external: discoverVariablesExtension: nutanixclusterconfigvars-dv.cluster-api-runtime-extensions-nutanix - generateExtension: nutanixclusterv3configpatch-gp.cluster-api-runtime-extensions-nutanix + generateExtension: nutanixclusterv4configpatch-gp.cluster-api-runtime-extensions-nutanix name: cluster-config - external: discoverVariablesExtension: nutanixworkerconfigvars-dv.cluster-api-runtime-extensions-nutanix - generateExtension: nutanixworkerv3configpatch-gp.cluster-api-runtime-extensions-nutanix + generateExtension: nutanixworkerv4configpatch-gp.cluster-api-runtime-extensions-nutanix name: worker-config workers: machineDeployments: diff --git a/docs/content/getting-started/integrating-with-your-clusterclass/_index.md b/docs/content/getting-started/integrating-with-your-clusterclass/_index.md index 8957bc615..559359039 100644 --- a/docs/content/getting-started/integrating-with-your-clusterclass/_index.md +++ b/docs/content/getting-started/integrating-with-your-clusterclass/_index.md @@ -21,11 +21,11 @@ The required values are shown below per provider. patches: - external: discoverVariablesExtension: awsclusterconfigvars.cluster-api-runtime-extensions-nutanix - generateExtension: awsclusterv3configpatch.cluster-api-runtime-extensions-nutanix + generateExtension: awsclusterv4configpatch.cluster-api-runtime-extensions-nutanix name: cluster-config - external: discoverVariablesExtension: awsworkerconfigvars.cluster-api-runtime-extensions-nutanix - generateExtension: awsworkerv3configpatch.cluster-api-runtime-extensions-nutanix + generateExtension: awsworkerv4configpatch.cluster-api-runtime-extensions-nutanix name: worker-config ``` @@ -35,11 +35,11 @@ The required values are shown below per provider. patches: - external: discoverVariablesExtension: nutanixclusterconfigvars.cluster-api-runtime-extensions-nutanix - generateExtension: nutanixclusterv3configpatch.cluster-api-runtime-extensions-nutanix + generateExtension: nutanixclusterv4configpatch.cluster-api-runtime-extensions-nutanix name: cluster-config - external: discoverVariablesExtension: nutanixworkerconfigvars.cluster-api-runtime-extensions-nutanix - generateExtension: nutanixworkerv3configpatch.cluster-api-runtime-extensions-nutanix + generateExtension: nutanixworkerv4configpatch.cluster-api-runtime-extensions-nutanix name: worker-config ``` @@ -49,11 +49,11 @@ The required values are shown below per provider. patches: - external: discoverVariablesExtension: dockerclusterconfigvars.cluster-api-runtime-extensions-nutanix - generateExtension: dockerclusterv3configpatch.cluster-api-runtime-extensions-nutanix + generateExtension: dockerclusterv4configpatch.cluster-api-runtime-extensions-nutanix name: cluster-config - external: discoverVariablesExtension: dockerworkerconfigvars.cluster-api-runtime-extensions-nutanix - generateExtension: dockerworkerv3configpatch.cluster-api-runtime-extensions-nutanix + generateExtension: dockerworkerv4configpatch.cluster-api-runtime-extensions-nutanix name: worker-config ``` @@ -63,7 +63,7 @@ The required values are shown below per provider. patches: - external: discoverVariablesExtension: genericclusterconfigvars.cluster-api-runtime-extensions-nutanix - generateExtension: genericclusterv3configpatch.cluster-api-runtime-extensions-nutanix + generateExtension: genericclusterv4configpatch.cluster-api-runtime-extensions-nutanix name: cluster-config ``` diff --git a/hack/examples/overlays/clusterclasses/aws/kustomization.yaml.tmpl b/hack/examples/overlays/clusterclasses/aws/kustomization.yaml.tmpl index 3d8f4f008..ca79f3628 100644 --- a/hack/examples/overlays/clusterclasses/aws/kustomization.yaml.tmpl +++ b/hack/examples/overlays/clusterclasses/aws/kustomization.yaml.tmpl @@ -19,11 +19,11 @@ patches: value: - name: "cluster-config" external: - generateExtension: "awsclusterv3configpatch-gp.cluster-api-runtime-extensions-nutanix" + generateExtension: "awsclusterv4configpatch-gp.cluster-api-runtime-extensions-nutanix" discoverVariablesExtension: "awsclusterconfigvars-dv.cluster-api-runtime-extensions-nutanix" - name: "worker-config" external: - generateExtension: "awsworkerv3configpatch-gp.cluster-api-runtime-extensions-nutanix" + generateExtension: "awsworkerv4configpatch-gp.cluster-api-runtime-extensions-nutanix" discoverVariablesExtension: "awsworkerconfigvars-dv.cluster-api-runtime-extensions-nutanix" - name: identityRef definitions: diff --git a/hack/examples/overlays/clusterclasses/docker/kustomization.yaml.tmpl b/hack/examples/overlays/clusterclasses/docker/kustomization.yaml.tmpl index f8cfb46e0..42e323b61 100644 --- a/hack/examples/overlays/clusterclasses/docker/kustomization.yaml.tmpl +++ b/hack/examples/overlays/clusterclasses/docker/kustomization.yaml.tmpl @@ -19,11 +19,11 @@ patches: value: - name: "cluster-config" external: - generateExtension: "dockerclusterv3configpatch-gp.cluster-api-runtime-extensions-nutanix" + generateExtension: "dockerclusterv4configpatch-gp.cluster-api-runtime-extensions-nutanix" discoverVariablesExtension: "dockerclusterconfigvars-dv.cluster-api-runtime-extensions-nutanix" - name: "worker-config" external: - generateExtension: "dockerworkerv3configpatch-gp.cluster-api-runtime-extensions-nutanix" + generateExtension: "dockerworkerv4configpatch-gp.cluster-api-runtime-extensions-nutanix" discoverVariablesExtension: "dockerworkerconfigvars-dv.cluster-api-runtime-extensions-nutanix" # BEGIN CIS patches diff --git a/hack/examples/overlays/clusterclasses/nutanix/kustomization.yaml.tmpl b/hack/examples/overlays/clusterclasses/nutanix/kustomization.yaml.tmpl index ca16d8749..751829f48 100644 --- a/hack/examples/overlays/clusterclasses/nutanix/kustomization.yaml.tmpl +++ b/hack/examples/overlays/clusterclasses/nutanix/kustomization.yaml.tmpl @@ -19,11 +19,11 @@ patches: value: - name: "cluster-config" external: - generateExtension: "nutanixclusterv3configpatch-gp.cluster-api-runtime-extensions-nutanix" + generateExtension: "nutanixclusterv4configpatch-gp.cluster-api-runtime-extensions-nutanix" discoverVariablesExtension: "nutanixclusterconfigvars-dv.cluster-api-runtime-extensions-nutanix" - name: "worker-config" external: - generateExtension: "nutanixworkerv3configpatch-gp.cluster-api-runtime-extensions-nutanix" + generateExtension: "nutanixworkerv4configpatch-gp.cluster-api-runtime-extensions-nutanix" discoverVariablesExtension: "nutanixworkerconfigvars-dv.cluster-api-runtime-extensions-nutanix" # BEGIN CIS patches diff --git a/pkg/handlers/aws/handlers.go b/pkg/handlers/aws/handlers.go index b52e72eb7..140f6c581 100644 --- a/pkg/handlers/aws/handlers.go +++ b/pkg/handlers/aws/handlers.go @@ -12,7 +12,7 @@ import ( awsmutation "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/mutation" awsworkerconfig "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/workerconfig" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/options" - v2awsmutation "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/v2/aws/mutation" + awsmutationvprev "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/v3/aws/mutation" ) type Handlers struct{} @@ -28,9 +28,9 @@ func (h *Handlers) AllHandlers(mgr manager.Manager) []handlers.Named { awsclusterconfig.NewVariable(), awsworkerconfig.NewVariable(), awsmutation.MetaPatchHandler(mgr), - v2awsmutation.MetaPatchHandler(mgr), + awsmutationvprev.MetaPatchHandler(mgr), awsmutation.MetaWorkerPatchHandler(mgr), - v2awsmutation.MetaWorkerPatchHandler(mgr), + awsmutationvprev.MetaWorkerPatchHandler(mgr), } } diff --git a/pkg/handlers/aws/mutation/metapatch_handler.go b/pkg/handlers/aws/mutation/metapatch_handler.go index 0ab9f2c8c..19654318f 100644 --- a/pkg/handlers/aws/mutation/metapatch_handler.go +++ b/pkg/handlers/aws/mutation/metapatch_handler.go @@ -35,7 +35,7 @@ func MetaPatchHandler(mgr manager.Manager) handlers.Named { patchHandlers = append(patchHandlers, genericmutation.ControlPlaneMetaMutators()...) return mutation.NewMetaGeneratePatchesHandler( - "awsClusterV3ConfigPatch", + "awsClusterV4ConfigPatch", mgr.GetClient(), patchHandlers..., ) @@ -52,7 +52,7 @@ func MetaWorkerPatchHandler(mgr manager.Manager) handlers.Named { patchHandlers = append(patchHandlers, genericmutation.WorkerMetaMutators()...) return mutation.NewMetaGeneratePatchesHandler( - "awsWorkerv3ConfigPatch", + "awsWorkerV4ConfigPatch", mgr.GetClient(), patchHandlers..., ) diff --git a/pkg/handlers/docker/handlers.go b/pkg/handlers/docker/handlers.go index 624ede013..437071be8 100644 --- a/pkg/handlers/docker/handlers.go +++ b/pkg/handlers/docker/handlers.go @@ -12,7 +12,7 @@ import ( dockermutation "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/docker/mutation" dockerworkerconfig "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/docker/workerconfig" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/options" - v2dockermutation "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/v2/docker/mutation" + dockermutationvprev "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/v3/docker/mutation" ) type Handlers struct{} @@ -28,9 +28,9 @@ func (h *Handlers) AllHandlers(mgr manager.Manager) []handlers.Named { dockerclusterconfig.NewVariable(), dockerworkerconfig.NewVariable(), dockermutation.MetaPatchHandler(mgr), - v2dockermutation.MetaPatchHandler(mgr), + dockermutationvprev.MetaPatchHandler(mgr), dockermutation.MetaWorkerPatchHandler(mgr), - v2dockermutation.MetaWorkerPatchHandler(mgr), + dockermutationvprev.MetaWorkerPatchHandler(mgr), } } diff --git a/pkg/handlers/docker/mutation/metapatch_handler.go b/pkg/handlers/docker/mutation/metapatch_handler.go index 436b16e26..821bbc0ad 100644 --- a/pkg/handlers/docker/mutation/metapatch_handler.go +++ b/pkg/handlers/docker/mutation/metapatch_handler.go @@ -21,7 +21,7 @@ func MetaPatchHandler(mgr manager.Manager) handlers.Named { patchHandlers = append(patchHandlers, genericmutation.ControlPlaneMetaMutators()...) return mutation.NewMetaGeneratePatchesHandler( - "dockerClusterV3ConfigPatch", + "dockerClusterV4ConfigPatch", mgr.GetClient(), patchHandlers..., ) @@ -35,7 +35,7 @@ func MetaWorkerPatchHandler(mgr manager.Manager) handlers.Named { patchHandlers = append(patchHandlers, genericmutation.WorkerMetaMutators()...) return mutation.NewMetaGeneratePatchesHandler( - "dockerWorkerV3ConfigPatch", + "dockerWorkerV4ConfigPatch", mgr.GetClient(), patchHandlers..., ) diff --git a/pkg/handlers/generic/handlers.go b/pkg/handlers/generic/handlers.go index 03b2edfc6..702680d17 100644 --- a/pkg/handlers/generic/handlers.go +++ b/pkg/handlers/generic/handlers.go @@ -9,7 +9,7 @@ import ( "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers" genericclusterconfig "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/clusterconfig" genericmutation "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation" - v2genericmutation "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/v2/generic/mutation" + genericmutationvprev "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/v3/generic/mutation" ) type Handlers struct{} @@ -22,8 +22,8 @@ func (h *Handlers) AllHandlers(mgr manager.Manager) []handlers.Named { return []handlers.Named{ genericclusterconfig.NewVariable(), genericmutation.MetaPatchHandler(mgr), - v2genericmutation.MetaPatchHandler(mgr), + genericmutationvprev.MetaPatchHandler(mgr), genericmutation.MetaWorkerPatchHandler(mgr), - v2genericmutation.MetaWorkerPatchHandler(mgr), + genericmutationvprev.MetaWorkerPatchHandler(mgr), } } diff --git a/pkg/handlers/generic/mutation/metapatch_handler.go b/pkg/handlers/generic/mutation/metapatch_handler.go index 0971ada0d..6ebe5fb77 100644 --- a/pkg/handlers/generic/mutation/metapatch_handler.go +++ b/pkg/handlers/generic/mutation/metapatch_handler.go @@ -15,7 +15,7 @@ func MetaPatchHandler(mgr manager.Manager) handlers.Named { patchHandlers := MetaMutators(mgr) patchHandlers = append(patchHandlers, ControlPlaneMetaMutators()...) return mutation.NewMetaGeneratePatchesHandler( - "genericClusterV3ConfigPatch", + "genericClusterV4ConfigPatch", mgr.GetClient(), patchHandlers..., ) @@ -26,7 +26,7 @@ func MetaWorkerPatchHandler(mgr manager.Manager) handlers.Named { patchHandlers := WorkerMetaMutators() return mutation.NewMetaGeneratePatchesHandler( - "genericWorkerV3ConfigPatch", + "genericWorkerV4ConfigPatch", mgr.GetClient(), patchHandlers..., ) diff --git a/pkg/handlers/nutanix/handlers.go b/pkg/handlers/nutanix/handlers.go index cd040ef45..e12b5172a 100644 --- a/pkg/handlers/nutanix/handlers.go +++ b/pkg/handlers/nutanix/handlers.go @@ -12,21 +12,15 @@ import ( nutanixmutation "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/nutanix/mutation" nutanixworkerconfig "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/nutanix/workerconfig" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/options" - "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/v2/generic/mutation/controlplanevirtualip" - v2nutanixmutation "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/v2/nutanix/mutation" + nutanixmutationvprev "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/v3/nutanix/mutation" ) -type Handlers struct { - // kubeVIPConfig holds the configuration for the kube-vip control-plane virtual IP. - controlPlaneVirtualIPConfig *controlplanevirtualip.Config -} +type Handlers struct{} func New( - globalOptions *options.GlobalOptions, + _ *options.GlobalOptions, ) *Handlers { - return &Handlers{ - controlPlaneVirtualIPConfig: &controlplanevirtualip.Config{GlobalOptions: globalOptions}, - } + return &Handlers{} } func (h *Handlers) AllHandlers(mgr manager.Manager) []handlers.Named { @@ -34,12 +28,10 @@ func (h *Handlers) AllHandlers(mgr manager.Manager) []handlers.Named { nutanixclusterconfig.NewVariable(), nutanixworkerconfig.NewVariable(), nutanixmutation.MetaPatchHandler(mgr), - v2nutanixmutation.MetaPatchHandler(mgr, h.controlPlaneVirtualIPConfig), + nutanixmutationvprev.MetaPatchHandler(mgr), nutanixmutation.MetaWorkerPatchHandler(mgr), - v2nutanixmutation.MetaWorkerPatchHandler(mgr), + nutanixmutationvprev.MetaWorkerPatchHandler(mgr), } } -func (h *Handlers) AddFlags(flagSet *pflag.FlagSet) { - h.controlPlaneVirtualIPConfig.AddFlags("nutanix", flagSet) -} +func (h *Handlers) AddFlags(_ *pflag.FlagSet) {} diff --git a/pkg/handlers/nutanix/mutation/metapatch_handler.go b/pkg/handlers/nutanix/mutation/metapatch_handler.go index 067752004..ca88ee153 100644 --- a/pkg/handlers/nutanix/mutation/metapatch_handler.go +++ b/pkg/handlers/nutanix/mutation/metapatch_handler.go @@ -27,7 +27,7 @@ func MetaPatchHandler(mgr manager.Manager) handlers.Named { patchHandlers = append(patchHandlers, genericmutation.ControlPlaneMetaMutators()...) return mutation.NewMetaGeneratePatchesHandler( - "nutanixClusterV3ConfigPatch", + "nutanixClusterV4ConfigPatch", mgr.GetClient(), patchHandlers..., ) @@ -41,7 +41,7 @@ func MetaWorkerPatchHandler(mgr manager.Manager) handlers.Named { patchHandlers = append(patchHandlers, genericmutation.WorkerMetaMutators()...) return mutation.NewMetaGeneratePatchesHandler( - "nutanixWorkerV3ConfigPatch", + "nutanixWorkerV4ConfigPatch", mgr.GetClient(), patchHandlers..., ) diff --git a/pkg/handlers/v2/generic/mutation/controlplanevirtualip/inject.go b/pkg/handlers/v2/generic/mutation/controlplanevirtualip/inject.go deleted file mode 100644 index 0e14f4fe7..000000000 --- a/pkg/handlers/v2/generic/mutation/controlplanevirtualip/inject.go +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright 2023 Nutanix. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package controlplanevirtualip - -import ( - "context" - "fmt" - - "github.com/spf13/pflag" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" - runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" - "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" - "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches" - "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches/selectors" - "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/variables" - "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/options" - "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/v2/generic/mutation/controlplanevirtualip/providers" -) - -const ( - // VariableName is the external patch variable name. - VariableName = "controlPlaneEndpoint" -) - -type Config struct { - *options.GlobalOptions - - defaultKubeVIPConfigMapName string -} - -func (c *Config) AddFlags(prefix string, flags *pflag.FlagSet) { - flags.StringVar( - &c.defaultKubeVIPConfigMapName, - prefix+".default-kube-vip-template-configmap-name", - "default-kube-vip-template", - "default ConfigMap name that holds the kube-vip template used for the control-plane virtual IP", - ) -} - -type ControlPlaneVirtualIP struct { - client client.Reader - config *Config - - variableName string - variableFieldPath []string -} - -// NewControlPlaneVirtualIP is different from other generic handlers. -// It requires variableName and variableFieldPath to be passed from another provider specific handler. -// The code is here to be shared across different providers. -func NewControlPlaneVirtualIP( - cl client.Reader, - config *Config, - variableName string, - variableFieldPath ...string, -) *ControlPlaneVirtualIP { - return &ControlPlaneVirtualIP{ - client: cl, - config: config, - variableName: variableName, - variableFieldPath: variableFieldPath, - } -} - -func (h *ControlPlaneVirtualIP) Mutate( - ctx context.Context, - obj *unstructured.Unstructured, - vars map[string]apiextensionsv1.JSON, - holderRef runtimehooksv1.HolderReference, - _ client.ObjectKey, - clusterGetter mutation.ClusterGetter, -) error { - log := ctrl.LoggerFrom(ctx).WithValues( - "holderRef", holderRef, - ) - - controlPlaneEndpointVar, err := variables.Get[v1alpha1.ControlPlaneEndpointSpec]( - vars, - h.variableName, - h.variableFieldPath..., - ) - if err != nil { - if variables.IsNotFoundError(err) { - log.V(5).Info("ControlPlaneEndpoint variable not defined") - return nil - } - return err - } - - log = log.WithValues( - "variableName", - h.variableName, - "variableFieldPath", - h.variableFieldPath, - "variableValue", - controlPlaneEndpointVar, - ) - - if controlPlaneEndpointVar.VirtualIPSpec == nil { - log.V(5).Info("ControlPlane VirtualIP not set") - return nil - } - - cluster, err := clusterGetter(ctx) - if err != nil { - log.Error( - err, - "failed to get cluster from ControlPlaneVirtualIP mutation handler", - ) - return err - } - - var virtualIPProvider providers.Provider - // only kube-vip is supported, but more providers can be added in the future - if controlPlaneEndpointVar.VirtualIPSpec.Provider == v1alpha1.VirtualIPProviderKubeVIP { - virtualIPProvider = providers.NewKubeVIPFromConfigMapProvider( - h.client, - h.config.defaultKubeVIPConfigMapName, - h.config.DefaultsNamespace(), - ) - } - - return patches.MutateIfApplicable( - obj, - vars, - &holderRef, - selectors.ControlPlane(), - log, - func(obj *controlplanev1.KubeadmControlPlaneTemplate) error { - files, preKubeadmCommands, postKubeadmCommands, generateErr := virtualIPProvider.GenerateFilesAndCommands( - ctx, - controlPlaneEndpointVar, - cluster, - ) - if generateErr != nil { - return generateErr - } - - log.WithValues( - "patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(), - "patchedObjectName", client.ObjectKeyFromObject(obj), - ).Info(fmt.Sprintf( - "adding %s static Pod file to control plane kubeadm config spec", - virtualIPProvider.Name(), - )) - obj.Spec.Template.Spec.KubeadmConfigSpec.Files = append( - obj.Spec.Template.Spec.KubeadmConfigSpec.Files, - files..., - ) - - if len(preKubeadmCommands) > 0 { - log.WithValues( - "patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(), - "patchedObjectName", client.ObjectKeyFromObject(obj), - ).Info(fmt.Sprintf( - "adding %s preKubeadmCommands to control plane kubeadm config spec", - virtualIPProvider.Name(), - )) - obj.Spec.Template.Spec.KubeadmConfigSpec.PreKubeadmCommands = append( - obj.Spec.Template.Spec.KubeadmConfigSpec.PreKubeadmCommands, - preKubeadmCommands..., - ) - } - - if len(postKubeadmCommands) > 0 { - log.WithValues( - "patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(), - "patchedObjectName", client.ObjectKeyFromObject(obj), - ).Info(fmt.Sprintf( - "adding %s postKubeadmCommands to control plane kubeadm config spec", - virtualIPProvider.Name(), - )) - obj.Spec.Template.Spec.KubeadmConfigSpec.PostKubeadmCommands = append( - obj.Spec.Template.Spec.KubeadmConfigSpec.PostKubeadmCommands, - postKubeadmCommands..., - ) - } - - return nil - }, - ) -} diff --git a/pkg/handlers/v2/generic/mutation/controlplanevirtualip/inject_test.go b/pkg/handlers/v2/generic/mutation/controlplanevirtualip/inject_test.go deleted file mode 100644 index 6dc3f8fc4..000000000 --- a/pkg/handlers/v2/generic/mutation/controlplanevirtualip/inject_test.go +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright 2023 Nutanix. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package controlplanevirtualip - -import ( - "context" - "testing" - - . "github.com/onsi/ginkgo/v2" - "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" - runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" - - "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" - "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" - "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest" - "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest/request" - "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/options" - "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/test/helpers" -) - -func TestControlPlaneEndpointPatch(t *testing.T) { - gomega.RegisterFailHandler(Fail) - RunSpecs(t, "ControlPlane virtual IP suite") -} - -var _ = Describe("Generate ControlPlane virtual IP patches", func() { - testDefs := []struct { - capitest.PatchTestDef - virtualIPTemplate string - cluster *clusterv1.Cluster - }{ - { - PatchTestDef: capitest.PatchTestDef{ - Name: "host and port should be templated in a new file and no pre/post commands", - Vars: []runtimehooksv1.Variable{ - capitest.VariableWithValue( - v1alpha1.ClusterConfigVariableName, - v1alpha1.ControlPlaneEndpointSpec{ - Host: "10.20.100.10", - Port: 6443, - VirtualIPSpec: &v1alpha1.ControlPlaneVirtualIPSpec{ - Provider: v1alpha1.VirtualIPProviderKubeVIP, - }, - }, - VariableName, - ), - }, - RequestItem: request.NewKubeadmControlPlaneTemplateRequestItem(""), - ExpectedPatchMatchers: []capitest.JSONPatchMatcher{ - { - Operation: "add", - Path: "/spec/template/spec/kubeadmConfigSpec/files", - ValueMatcher: gomega.ContainElements( - gomega.SatisfyAll( - gomega.HaveKeyWithValue( - "content", - gomega.ContainSubstring("value: \"10.20.100.10\""), - ), - gomega.HaveKeyWithValue( - "content", - gomega.ContainSubstring("value: \"6443\""), - ), - gomega.HaveKey("owner"), - gomega.HaveKeyWithValue( - "path", - gomega.ContainSubstring("kube-vip"), - ), - gomega.HaveKey("permissions"), - ), - ), - }, - }, - UnexpectedPatchMatchers: []capitest.JSONPatchMatcher{ - { - Operation: "add", - Path: "/spec/template/spec/kubeadmConfigSpec/preKubeadmCommands", - ValueMatcher: gomega.ContainElements( - "/bin/bash /etc/caren/configure-for-kube-vip.sh set-host-aliases use-super-admin.conf", - ), - }, - { - Operation: "add", - Path: "/spec/template/spec/kubeadmConfigSpec/postKubeadmCommands", - ValueMatcher: gomega.ContainElements( - "/bin/bash /etc/caren/configure-for-kube-vip.sh use-admin.conf", - ), - }, - }, - }, - virtualIPTemplate: validKubeVIPTemplate, - cluster: &clusterv1.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: request.ClusterName, - Namespace: request.Namespace, - }, - Spec: clusterv1.ClusterSpec{ - Topology: &clusterv1.Topology{ - Class: "dummy-class", - Version: "v1.28.100", - }, - }, - }, - }, - { - PatchTestDef: capitest.PatchTestDef{ - Name: "host and port should be templated in a new file with pre/post commands", - Vars: []runtimehooksv1.Variable{ - capitest.VariableWithValue( - v1alpha1.ClusterConfigVariableName, - v1alpha1.ControlPlaneEndpointSpec{ - Host: "10.20.100.10", - Port: 6443, - VirtualIPSpec: &v1alpha1.ControlPlaneVirtualIPSpec{ - Provider: v1alpha1.VirtualIPProviderKubeVIP, - }, - }, - VariableName, - ), - }, - RequestItem: request.NewKubeadmControlPlaneTemplateRequestItem( - "", - ), - ExpectedPatchMatchers: []capitest.JSONPatchMatcher{ - { - Operation: "add", - Path: "/spec/template/spec/kubeadmConfigSpec/files", - ValueMatcher: gomega.ContainElements( - gomega.SatisfyAll( - gomega.HaveKeyWithValue( - "content", - gomega.ContainSubstring("value: \"10.20.100.10\""), - ), - gomega.HaveKeyWithValue( - "content", - gomega.ContainSubstring("value: \"6443\""), - ), - gomega.HaveKey("owner"), - gomega.HaveKeyWithValue( - "path", - gomega.ContainSubstring("kube-vip"), - ), - gomega.HaveKey("permissions"), - ), - gomega.SatisfyAll( - gomega.HaveKey("content"), - gomega.HaveKeyWithValue( - "path", - gomega.ContainSubstring("configure-for-kube-vip.sh"), - ), - gomega.HaveKey("permissions"), - ), - ), - }, - { - Operation: "add", - Path: "/spec/template/spec/kubeadmConfigSpec/preKubeadmCommands", - ValueMatcher: gomega.ContainElements( - "/bin/bash /etc/caren/configure-for-kube-vip.sh set-host-aliases use-super-admin.conf", - ), - }, - { - Operation: "add", - Path: "/spec/template/spec/kubeadmConfigSpec/postKubeadmCommands", - ValueMatcher: gomega.ContainElements( - "/bin/bash /etc/caren/configure-for-kube-vip.sh use-admin.conf", - ), - }, - }, - }, - virtualIPTemplate: validKubeVIPTemplate, - cluster: &clusterv1.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: request.ClusterName, - Namespace: request.Namespace, - }, - Spec: clusterv1.ClusterSpec{ - Topology: &clusterv1.Topology{ - Class: "dummy-class", - Version: "v1.29.0", - }, - }, - }, - }, - } - - // create test node for each case - for idx := range testDefs { - tt := testDefs[idx] - It(tt.Name, func() { - clientScheme := runtime.NewScheme() - utilruntime.Must(clientgoscheme.AddToScheme(clientScheme)) - utilruntime.Must(clusterv1.AddToScheme(clientScheme)) - // Always initialize the testEnv variable in the closure. - // This will allow ginkgo to initialize testEnv variable during test execution time. - testEnv := helpers.TestEnv - // use direct client instead of controller client. This will allow the patch handler to read k8s object - // that are written by the tests. - // Test cases writes credentials secret that the mutator handler reads. - // Using direct client will enable reading it immediately. - client, err := testEnv.GetK8sClientWithScheme(clientScheme) - gomega.Expect(err).To(gomega.BeNil()) - // setup a test ConfigMap to be used by the handler - cm := &corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - APIVersion: corev1.SchemeGroupVersion.String(), - Kind: "ConfigMap", - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: corev1.NamespaceDefault, - GenerateName: "virtualip-test-", - }, - Data: map[string]string{ - "data": tt.virtualIPTemplate, - }, - } - err = client.Create(context.Background(), cm) - gomega.Expect(err).ToNot(gomega.HaveOccurred()) - - if tt.cluster != nil { - err = client.Create(context.Background(), tt.cluster) - gomega.Expect(err).To(gomega.BeNil()) - defer func() { - err = client.Delete(context.Background(), tt.cluster) - gomega.Expect(err).To(gomega.BeNil()) - }() - } - - cfg := &Config{ - GlobalOptions: options.NewGlobalOptions(), - defaultKubeVIPConfigMapName: cm.Name, - } - patchGenerator := func() mutation.GeneratePatches { - return mutation.NewMetaGeneratePatchesHandler( - "", - client, - NewControlPlaneVirtualIP(client, cfg, v1alpha1.ClusterConfigVariableName, VariableName), - ).(mutation.GeneratePatches) - } - - capitest.AssertGeneratePatches(GinkgoT(), patchGenerator, &tt.PatchTestDef) - }) - } -}) - -var validKubeVIPTemplate = ` -apiVersion: v1 -kind: Pod -metadata: - name: kube-vip - namespace: kube-system -spec: - containers: - - name: kube-vip - image: ghcr.io/kube-vip/kube-vip:v1.1.1 - imagePullPolicy: IfNotPresent - args: - - manager - env: - - name: vip_arp - value: "true" - - name: address - value: "{{ .Address }}" - - name: port - value: "{{ .Port }}" -` diff --git a/pkg/handlers/v2/generic/mutation/controlplanevirtualip/providers/kubevip.go b/pkg/handlers/v2/generic/mutation/controlplanevirtualip/providers/kubevip.go deleted file mode 100644 index dab6bebf8..000000000 --- a/pkg/handlers/v2/generic/mutation/controlplanevirtualip/providers/kubevip.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2023 Nutanix. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package providers - -import ( - "context" - _ "embed" - "fmt" - - "github.com/blang/semver/v4" - corev1 "k8s.io/api/core/v1" - clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" - bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" - "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/common" -) - -const ( - kubeVIPFileOwner = "root:root" - kubeVIPFilePath = "/etc/kubernetes/manifests/kube-vip.yaml" - kubeVIPFilePermissions = "0600" - - configureForKubeVIPScriptPermissions = "0700" -) - -var ( - configureForKubeVIPScriptOnRemote = common.ConfigFilePathOnRemote( - "configure-for-kube-vip.sh") - - configureForKubeVIPScriptOnRemotePreKubeadmCommand = "/bin/bash " + - configureForKubeVIPScriptOnRemote + " set-host-aliases use-super-admin.conf" - configureForKubeVIPScriptOnRemotePostKubeadmCommand = "/bin/bash " + - configureForKubeVIPScriptOnRemote + " use-admin.conf" -) - -//go:embed templates/configure-for-kube-vip.sh -var configureForKubeVIPScript []byte - -type kubeVIPFromConfigMapProvider struct { - client client.Reader - - configMapKey client.ObjectKey -} - -func NewKubeVIPFromConfigMapProvider( - cl client.Reader, - name, namespace string, -) *kubeVIPFromConfigMapProvider { - return &kubeVIPFromConfigMapProvider{ - client: cl, - configMapKey: client.ObjectKey{ - Name: name, - Namespace: namespace, - }, - } -} - -func (p *kubeVIPFromConfigMapProvider) Name() string { - return "kube-vip" -} - -// GenerateFilesAndCommands returns files and pre/post kubeadm commands for kube-vip. -// It reads kube-vip template from a ConfigMap and returns the content a File, templating the required variables. -// If required, it also returns a script file and pre/post kubeadm commands to change the kube-vip Pod to use the new -// super-admin.conf file. -func (p *kubeVIPFromConfigMapProvider) GenerateFilesAndCommands( - ctx context.Context, - spec v1alpha1.ControlPlaneEndpointSpec, - cluster *clusterv1.Cluster, -) (files []bootstrapv1.File, preKubeadmCommands, postKubeadmCommands []string, err error) { - data, err := getTemplateFromConfigMap(ctx, p.client, p.configMapKey) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed getting template data: %w", err) - } - - kubeVIPStaticPod, err := templateValues(spec, data) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed templating static Pod: %w", err) - } - - files = []bootstrapv1.File{ - { - Content: kubeVIPStaticPod, - Owner: kubeVIPFileOwner, - Path: kubeVIPFilePath, - Permissions: kubeVIPFilePermissions, - }, - } - - // The kube-vip static Pod uses admin.conf on the host to connect to the API server. - // But, starting with Kubernetes 1.29, admin.conf first gets created with no RBAC permissions. - // At the same time, 'kubeadm init' command waits for the API server to be reachable on the kube-vip IP. - // And since the kube-vip Pod is crashlooping with a permissions error, 'kubeadm init' fails. - // To work around this: - // 1. return a preKubeadmCommand to change the kube-vip Pod to use the new super-admin.conf file. - // 2. return a postKubeadmCommand to change the kube-vip Pod back to use admin.conf, - // after kubeadm has assigned it the necessary RBAC permissions. - // - // See https://github.com/kube-vip/kube-vip/issues/684 - // - // There is also another issue introduced in Kubernetes 1.29. - // If a cloud provider did not yet initialise the node's .status.addresses, - // the code for creating the /etc/hosts file including the hostAliases does not get run. - // The kube-vip static Pod runs before the cloud provider and will not be able to resolve the kubernetes DNS name. - // To work around this: - // 1. return a preKubeadmCommand to add kubernetes DNS name to /etc/hosts. - // - // See https://github.com/kube-vip/kube-vip/issues/692 - // See https://github.com/kubernetes/kubernetes/issues/122420#issuecomment-1864609518 - needCommands, err := needHackCommands(cluster) - if err != nil { - return nil, nil, nil, fmt.Errorf( - "failed to determine if kube-vip commands are needed: %w", - err, - ) - } - if !needCommands { - return files, nil, nil, nil - } - - files = append( - files, - bootstrapv1.File{ - Content: string(configureForKubeVIPScript), - Path: configureForKubeVIPScriptOnRemote, - Permissions: configureForKubeVIPScriptPermissions, - }, - ) - - preKubeadmCommands = []string{configureForKubeVIPScriptOnRemotePreKubeadmCommand} - postKubeadmCommands = []string{configureForKubeVIPScriptOnRemotePostKubeadmCommand} - - return files, preKubeadmCommands, postKubeadmCommands, nil -} - -type multipleKeysError struct { - configMapKey client.ObjectKey -} - -func (e multipleKeysError) Error() string { - return fmt.Sprintf( - "found multiple keys in ConfigMap %q, when only 1 is expected", - e.configMapKey, - ) -} - -type emptyValuesError struct { - configMapKey client.ObjectKey -} - -func (e emptyValuesError) Error() string { - return fmt.Sprintf( - "could not find any keys with non-empty vaules in ConfigMap %q", - e.configMapKey, - ) -} - -func getTemplateFromConfigMap( - ctx context.Context, - cl client.Reader, - configMapKey client.ObjectKey, -) (string, error) { - configMap := &corev1.ConfigMap{} - err := cl.Get(ctx, configMapKey, configMap) - if err != nil { - return "", fmt.Errorf("failed to get template ConfigMap %q: %w", configMapKey, err) - } - - if len(configMap.Data) > 1 { - return "", multipleKeysError{configMapKey: configMapKey} - } - - // at this point there should only be 1 key ConfigMap, return on the first non-empty value - for _, data := range configMap.Data { - if data != "" { - return data, nil - } - } - - return "", emptyValuesError{configMapKey: configMapKey} -} - -func needHackCommands(cluster *clusterv1.Cluster) (bool, error) { - version, err := semver.ParseTolerant(cluster.Spec.Topology.Version) - if err != nil { - return false, fmt.Errorf("failed to parse version from cluster: %w", err) - } - - return version.Minor >= 29, nil -} diff --git a/pkg/handlers/v2/generic/mutation/controlplanevirtualip/providers/kubevip_test.go b/pkg/handlers/v2/generic/mutation/controlplanevirtualip/providers/kubevip_test.go deleted file mode 100644 index e8318553c..000000000 --- a/pkg/handlers/v2/generic/mutation/controlplanevirtualip/providers/kubevip_test.go +++ /dev/null @@ -1,355 +0,0 @@ -// Copyright 2023 Nutanix. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package providers - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" - bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" -) - -func Test_GenerateFilesAndCommands(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - controlPlaneEndpointSpec v1alpha1.ControlPlaneEndpointSpec - cluster *clusterv1.Cluster - configMap *corev1.ConfigMap - expectedFiles []bootstrapv1.File - expectedPreKubeadmCommands []string - expectedPostKubeadmCommands []string - expectedErr error - }{ - { - name: "should return templated data with both host and port and pre/post kubeadm hack commands", - controlPlaneEndpointSpec: v1alpha1.ControlPlaneEndpointSpec{ - Host: "10.20.100.10", - Port: 6443, - }, - configMap: &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default-kube-vip-template", - Namespace: "default", - }, - Data: map[string]string{ - "data": validKubeVIPTemplate, - }, - }, - cluster: &clusterv1.Cluster{ - Spec: clusterv1.ClusterSpec{ - Topology: &clusterv1.Topology{ - Class: "dummy-class", - Version: "v1.29.0", - }, - }, - }, - expectedFiles: []bootstrapv1.File{ - { - Content: expectedKubeVIPPod, - Owner: kubeVIPFileOwner, - Path: kubeVIPFilePath, - Permissions: kubeVIPFilePermissions, - }, - { - Content: string(configureForKubeVIPScript), - Path: configureForKubeVIPScriptOnRemote, - Permissions: configureForKubeVIPScriptPermissions, - }, - }, - expectedPreKubeadmCommands: []string{ - configureForKubeVIPScriptOnRemotePreKubeadmCommand, - }, - expectedPostKubeadmCommands: []string{ - configureForKubeVIPScriptOnRemotePostKubeadmCommand, - }, - }, - { - name: "should return templated data with both host and port", - controlPlaneEndpointSpec: v1alpha1.ControlPlaneEndpointSpec{ - Host: "10.20.100.10", - Port: 6443, - }, - configMap: &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default-kube-vip-template", - Namespace: "default", - }, - Data: map[string]string{ - "data": validKubeVIPTemplate, - }, - }, - cluster: &clusterv1.Cluster{ - Spec: clusterv1.ClusterSpec{ - Topology: &clusterv1.Topology{ - Class: "dummy-class", - Version: "v1.28.0", - }, - }, - }, - expectedFiles: []bootstrapv1.File{ - { - Content: expectedKubeVIPPod, - Owner: kubeVIPFileOwner, - Path: kubeVIPFilePath, - Permissions: kubeVIPFilePermissions, - }, - }, - }, - { - name: "should return templated data with both IP and port from virtual IP configuration overrides", - controlPlaneEndpointSpec: v1alpha1.ControlPlaneEndpointSpec{ - Host: "10.20.100.10", - Port: 6443, - VirtualIPSpec: &v1alpha1.ControlPlaneVirtualIPSpec{ - Configuration: &v1alpha1.ControlPlaneVirtualIPConfiguration{ - Address: "172.20.100.10", - Port: 8443, - }, - }, - }, - configMap: &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default-kube-vip-template", - Namespace: "default", - }, - Data: map[string]string{ - "data": validKubeVIPTemplate, - }, - }, - cluster: &clusterv1.Cluster{ - Spec: clusterv1.ClusterSpec{ - Topology: &clusterv1.Topology{ - Class: "dummy-class", - Version: "v1.28.0", - }, - }, - }, - expectedFiles: []bootstrapv1.File{ - { - Content: expectedKubeVIPPodWithOverrides, - Owner: kubeVIPFileOwner, - Path: kubeVIPFilePath, - Permissions: kubeVIPFilePermissions, - }, - }, - }, - { - name: "should return templated data with IP from virtual IP configuration overrides", - controlPlaneEndpointSpec: v1alpha1.ControlPlaneEndpointSpec{ - Host: "10.20.100.10", - Port: 8443, - VirtualIPSpec: &v1alpha1.ControlPlaneVirtualIPSpec{ - Configuration: &v1alpha1.ControlPlaneVirtualIPConfiguration{ - Address: "172.20.100.10", - }, - }, - }, - configMap: &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default-kube-vip-template", - Namespace: "default", - }, - Data: map[string]string{ - "data": validKubeVIPTemplate, - }, - }, - cluster: &clusterv1.Cluster{ - Spec: clusterv1.ClusterSpec{ - Topology: &clusterv1.Topology{ - Class: "dummy-class", - Version: "v1.28.0", - }, - }, - }, - expectedFiles: []bootstrapv1.File{ - { - Content: expectedKubeVIPPodWithOverrides, - Owner: kubeVIPFileOwner, - Path: kubeVIPFilePath, - Permissions: kubeVIPFilePermissions, - }, - }, - }, - } - - for idx := range tests { - tt := tests[idx] // Capture range variable - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - fakeClient := fake.NewClientBuilder().WithObjects(tt.configMap).Build() - - provider := kubeVIPFromConfigMapProvider{ - client: fakeClient, - configMapKey: client.ObjectKeyFromObject(tt.configMap), - } - - files, preKubeadmCommands, postKubeadmCommands, err := provider.GenerateFilesAndCommands( - context.TODO(), - tt.controlPlaneEndpointSpec, - tt.cluster, - ) - require.Equal(t, tt.expectedErr, err) - assert.Equal(t, tt.expectedFiles, files) - assert.Equal(t, tt.expectedPreKubeadmCommands, preKubeadmCommands) - assert.Equal(t, tt.expectedPostKubeadmCommands, postKubeadmCommands) - }) - } -} - -func Test_getTemplateFromConfigMap(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - configMap *corev1.ConfigMap - expectedData string - expectedErr error - }{ - { - name: "should return data from the only key", - configMap: &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default-kube-vip-template", - Namespace: "default", - }, - Data: map[string]string{ - "data": "kube-vip-template", - }, - }, - expectedData: "kube-vip-template", - }, - { - name: "should fail with multipleKeysError", - configMap: &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default-kube-vip-template", - Namespace: "default", - }, - Data: map[string]string{ - "data": "kube-vip-template", - "unexpected-key": "unexpected-value", - }, - }, - expectedErr: multipleKeysError{ - configMapKey: client.ObjectKey{ - Name: "default-kube-vip-template", - Namespace: "default", - }, - }, - }, - { - name: "should fail with emptyValuesError", - configMap: &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default-kube-vip-template", - Namespace: "default", - }, - Data: map[string]string{ - "data": "", - }, - }, - expectedErr: emptyValuesError{ - configMapKey: client.ObjectKey{ - Name: "default-kube-vip-template", - Namespace: "default", - }, - }, - }, - } - - for idx := range tests { - tt := tests[idx] // Capture range variable - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - fakeClient := fake.NewClientBuilder().WithObjects(tt.configMap).Build() - - data, err := getTemplateFromConfigMap( - context.TODO(), - fakeClient, - client.ObjectKeyFromObject(tt.configMap), - ) - require.Equal(t, tt.expectedErr, err) - assert.Equal(t, tt.expectedData, data) - }) - } -} - -var ( - validKubeVIPTemplate = ` -apiVersion: v1 -kind: Pod -metadata: - name: kube-vip - namespace: kube-system -spec: - containers: - - name: kube-vip - image: ghcr.io/kube-vip/kube-vip:v1.1.1 - imagePullPolicy: IfNotPresent - args: - - manager - env: - - name: vip_arp - value: "true" - - name: address - value: "{{ .Address }}" - - name: port - value: "{{ .Port }}" -` - - expectedKubeVIPPod = ` -apiVersion: v1 -kind: Pod -metadata: - name: kube-vip - namespace: kube-system -spec: - containers: - - name: kube-vip - image: ghcr.io/kube-vip/kube-vip:v1.1.1 - imagePullPolicy: IfNotPresent - args: - - manager - env: - - name: vip_arp - value: "true" - - name: address - value: "10.20.100.10" - - name: port - value: "6443" -` - - expectedKubeVIPPodWithOverrides = ` -apiVersion: v1 -kind: Pod -metadata: - name: kube-vip - namespace: kube-system -spec: - containers: - - name: kube-vip - image: ghcr.io/kube-vip/kube-vip:v1.1.1 - imagePullPolicy: IfNotPresent - args: - - manager - env: - - name: vip_arp - value: "true" - - name: address - value: "172.20.100.10" - - name: port - value: "8443" -` -) diff --git a/pkg/handlers/v2/generic/mutation/controlplanevirtualip/providers/providers.go b/pkg/handlers/v2/generic/mutation/controlplanevirtualip/providers/providers.go deleted file mode 100644 index 16cf993f2..000000000 --- a/pkg/handlers/v2/generic/mutation/controlplanevirtualip/providers/providers.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2023 Nutanix. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package providers - -import ( - "bytes" - "cmp" - "context" - "fmt" - "text/template" - - clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" - bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" - - "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" -) - -// Provider is an interface for getting the virtual IP provider static Pod as a file. -type Provider interface { - Name() string - GenerateFilesAndCommands( - ctx context.Context, - spec v1alpha1.ControlPlaneEndpointSpec, - cluster *clusterv1.Cluster, - ) ([]bootstrapv1.File, []string, []string, error) -} - -func templateValues( - controlPlaneEndpoint v1alpha1.ControlPlaneEndpointSpec, - text string, -) (string, error) { - virtualIPTemplate, err := template.New("").Parse(text) - if err != nil { - return "", fmt.Errorf("failed to parse template: %w", err) - } - - type input struct { - Address string - Port int32 - } - - // If specified, use the virtual IP address and/or port, - // otherwise fall back to the control plane endpoint host and port. - var virtualIPConfig v1alpha1.ControlPlaneVirtualIPConfiguration - if controlPlaneEndpoint.VirtualIPSpec != nil && - controlPlaneEndpoint.VirtualIPSpec.Configuration != nil { - virtualIPConfig = *controlPlaneEndpoint.VirtualIPSpec.Configuration - } - templateInput := input{ - Address: cmp.Or(virtualIPConfig.Address, controlPlaneEndpoint.Host), - Port: cmp.Or(virtualIPConfig.Port, controlPlaneEndpoint.Port), - } - - var b bytes.Buffer - err = virtualIPTemplate.Execute(&b, templateInput) - if err != nil { - return "", fmt.Errorf("failed setting API endpoint configuration in template: %w", err) - } - - return b.String(), nil -} diff --git a/pkg/handlers/v2/generic/mutation/controlplanevirtualip/providers/templates/configure-for-kube-vip.sh b/pkg/handlers/v2/generic/mutation/controlplanevirtualip/providers/templates/configure-for-kube-vip.sh deleted file mode 100644 index cc9a1b1ee..000000000 --- a/pkg/handlers/v2/generic/mutation/controlplanevirtualip/providers/templates/configure-for-kube-vip.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/bash -set -euo pipefail -IFS=$'\n\t' - -SCRIPT_NAME="$(basename "${0}")" -readonly SCRIPT_NAME - -declare -r KUBEADM_INIT_FILE="/run/kubeadm/kubeadm.yaml" -declare -r KUBE_VIP_MANIFEST_FILE="/etc/kubernetes/manifests/kube-vip.yaml" - -function use_super_admin_conf { - if [[ -f ${KUBEADM_INIT_FILE} && -f ${KUBE_VIP_MANIFEST_FILE} ]]; then - sed -i 's#path: /etc/kubernetes/admin.conf#path: /etc/kubernetes/super-admin.conf#' \ - /etc/kubernetes/manifests/kube-vip.yaml - fi -} - -function use_admin_conf() { - if [[ -f ${KUBEADM_INIT_FILE} && -f ${KUBE_VIP_MANIFEST_FILE} ]]; then - sed -i 's#path: /etc/kubernetes/super-admin.conf#path: /etc/kubernetes/admin.conf#' \ - /etc/kubernetes/manifests/kube-vip.yaml - fi -} - -function set_host_aliases() { - echo "127.0.0.1 kubernetes" >>/etc/hosts -} - -function print_usage { - cat >&2 <