pkg/executables/clusterctl.go (363 lines of code) (raw):

package executables import ( "context" _ "embed" "fmt" "os" "path" "path/filepath" "github.com/aws/eks-anywhere/pkg/cluster" anywherecluster "github.com/aws/eks-anywhere/pkg/cluster" "github.com/aws/eks-anywhere/pkg/clusterapi" "github.com/aws/eks-anywhere/pkg/constants" "github.com/aws/eks-anywhere/pkg/filewriter" "github.com/aws/eks-anywhere/pkg/manifests" "github.com/aws/eks-anywhere/pkg/manifests/bundles" "github.com/aws/eks-anywhere/pkg/providers" "github.com/aws/eks-anywhere/pkg/templater" "github.com/aws/eks-anywhere/pkg/types" "github.com/aws/eks-anywhere/release/api/v1alpha1" ) const ( clusterCtlPath = "clusterctl" clusterctlConfigFile = "clusterctl_tmp.yaml" capiPrefix = "/generated/overrides" etcdadmBootstrapProviderName = "etcdadm-bootstrap" etcdadmControllerProviderName = "etcdadm-controller" kubeadmBootstrapProviderName = "kubeadm" ) //go:embed config/clusterctl.yaml var clusterctlConfigTemplate string type Clusterctl struct { Executable writer filewriter.FileWriter reader manifests.FileReader } type clusterctlConfiguration struct { coreVersion string bootstrapVersion string controlPlaneVersion string configFile string etcdadmBootstrapVersion string etcdadmControllerVersion string } // NewClusterctl builds a new [Clusterctl]. func NewClusterctl(executable Executable, writer filewriter.FileWriter, reader manifests.FileReader) *Clusterctl { return &Clusterctl{ Executable: executable, writer: writer, reader: reader, } } func imageRepository(image v1alpha1.Image) string { return path.Dir(image.Image()) } // This method will write the configuration files // used by cluster api to install components. // See: https://cluster-api.sigs.k8s.io/clusterctl/configuration.html func (c *Clusterctl) buildOverridesLayer(managementComponents *cluster.ManagementComponents, clusterName string, provider providers.Provider) error { // Adding cluster name to path temporarily following suggestion. // // This adds an implicit dependency between this method // and the writer passed to NewClusterctl // Ideally the writer implementation should be modified to // accept a path and file name and it should create the path in case it // does not exists. prefix := filepath.Join(clusterName, generatedDir, overridesDir) infraBundles := []types.InfrastructureBundle{ { FolderName: filepath.Join("cert-manager", managementComponents.CertManager.Version), Manifests: []v1alpha1.Manifest{ managementComponents.CertManager.Manifest, }, }, { FolderName: filepath.Join("bootstrap-kubeadm", managementComponents.Bootstrap.Version), Manifests: []v1alpha1.Manifest{ managementComponents.Bootstrap.Components, managementComponents.Bootstrap.Metadata, }, }, { FolderName: filepath.Join("cluster-api", managementComponents.ClusterAPI.Version), Manifests: []v1alpha1.Manifest{ managementComponents.ClusterAPI.Components, managementComponents.ClusterAPI.Metadata, }, }, { FolderName: filepath.Join("control-plane-kubeadm", managementComponents.ControlPlane.Version), Manifests: []v1alpha1.Manifest{ managementComponents.ControlPlane.Components, managementComponents.ControlPlane.Metadata, }, }, { FolderName: filepath.Join("bootstrap-etcdadm-bootstrap", managementComponents.ExternalEtcdBootstrap.Version), Manifests: []v1alpha1.Manifest{ managementComponents.ExternalEtcdBootstrap.Components, managementComponents.ExternalEtcdBootstrap.Metadata, }, }, { FolderName: filepath.Join("bootstrap-etcdadm-controller", managementComponents.ExternalEtcdController.Version), Manifests: []v1alpha1.Manifest{ managementComponents.ExternalEtcdController.Components, managementComponents.ExternalEtcdController.Metadata, }, }, } infraBundles = append(infraBundles, *provider.GetInfrastructureBundle(managementComponents)) for _, infraBundle := range infraBundles { if err := c.writeInfrastructureBundle(prefix, &infraBundle); err != nil { return err } } return nil } func (c *Clusterctl) writeInfrastructureBundle(rootFolder string, bundle *types.InfrastructureBundle) error { if bundle == nil { return nil } infraFolder := filepath.Join(rootFolder, bundle.FolderName) if err := os.MkdirAll(infraFolder, os.ModePerm); err != nil { return err } for _, manifest := range bundle.Manifests { m, err := bundles.ReadManifest(c.reader, manifest) if err != nil { return fmt.Errorf("can't load infrastructure bundle for manifest %s: %v", manifest.URI, err) } if err := os.WriteFile(filepath.Join(infraFolder, m.Filename), m.Content, 0o644); err != nil { return fmt.Errorf("generating file for infrastructure bundle %s: %v", m.Filename, err) } } return nil } // BackupManagement saves the CAPI resources of a cluster to the provided path. This will overwrite any existing contents // in the path if the backup succeeds. If `clusterName` is provided, it filters and backs up only the provided cluster. func (c *Clusterctl) BackupManagement(ctx context.Context, cluster *types.Cluster, managementStatePath, clusterName string) error { filePath := filepath.Join(".", cluster.Name, managementStatePath) err := os.MkdirAll(filePath, os.ModePerm) if err != nil { return fmt.Errorf("could not create backup file for CAPI objects: %v", err) } _, err = c.Execute( ctx, "move", "--to-directory", filePath, "--kubeconfig", cluster.KubeconfigFile, "--namespace", constants.EksaSystemNamespace, "--filter-cluster", clusterName, ) if err != nil { return fmt.Errorf("failed taking backup of CAPI objects: %v", err) } return nil } // MoveManagement moves management components `from` cluster `to` cluster // If `clusterName` is provided, it filters and moves only the provided cluster. func (c *Clusterctl) MoveManagement(ctx context.Context, from, to *types.Cluster, clusterName string) error { params := []string{ "move", "--to-kubeconfig", to.KubeconfigFile, "--namespace", constants.EksaSystemNamespace, "--filter-cluster", clusterName, } if from.KubeconfigFile != "" { params = append(params, "--kubeconfig", from.KubeconfigFile) } _, err := c.Execute( ctx, params..., ) if err != nil { return fmt.Errorf("failed moving management cluster: %v", err) } return err } func (c *Clusterctl) GetWorkloadKubeconfig(ctx context.Context, clusterName string, cluster *types.Cluster) ([]byte, error) { stdOut, err := c.Execute( ctx, "get", "kubeconfig", clusterName, "--kubeconfig", cluster.KubeconfigFile, "--namespace", constants.EksaSystemNamespace, ) if err != nil { return nil, fmt.Errorf("executing get kubeconfig: %v", err) } return stdOut.Bytes(), nil } // InitInfrastructure initializes the infrastructure for the cluster using clusterctl. func (c *Clusterctl) InitInfrastructure(ctx context.Context, managementComponents *cluster.ManagementComponents, clusterSpec *cluster.Spec, cluster *types.Cluster, provider providers.Provider) error { if cluster == nil { return fmt.Errorf("invalid cluster (nil)") } if cluster.Name == "" { return fmt.Errorf("invalid cluster name '%s'", cluster.Name) } clusterctlConfig, err := c.buildConfig(managementComponents, cluster.Name, provider) if err != nil { return err } params := []string{ "init", "--core", clusterctlConfig.coreVersion, "--bootstrap", clusterctlConfig.bootstrapVersion, "--control-plane", clusterctlConfig.controlPlaneVersion, "--infrastructure", fmt.Sprintf("%s:%s", provider.Name(), provider.Version(managementComponents)), "--config", clusterctlConfig.configFile, "--bootstrap", clusterctlConfig.etcdadmBootstrapVersion, "--bootstrap", clusterctlConfig.etcdadmControllerVersion, } if cluster.KubeconfigFile != "" { params = append(params, "--kubeconfig", cluster.KubeconfigFile) } envMap, err := provider.EnvMap(managementComponents, clusterSpec) if err != nil { return err } _, err = c.ExecuteWithEnv(ctx, envMap, params...) if err != nil { return fmt.Errorf("executing init: %v", err) } return nil } func (c *Clusterctl) buildConfig(managementComponents *anywherecluster.ManagementComponents, clusterName string, provider providers.Provider) (*clusterctlConfiguration, error) { t := templater.New(c.writer) path, err := os.Getwd() if err != nil { return nil, err } data := map[string]string{ "CertManagerInjectorRepository": imageRepository(managementComponents.CertManager.Cainjector), "CertManagerInjectorTag": managementComponents.CertManager.Cainjector.Tag(), "CertManagerControllerRepository": imageRepository(managementComponents.CertManager.Controller), "CertManagerControllerTag": managementComponents.CertManager.Controller.Tag(), "CertManagerWebhookRepository": imageRepository(managementComponents.CertManager.Webhook), "CertManagerWebhookTag": managementComponents.CertManager.Webhook.Tag(), "CertManagerVersion": managementComponents.CertManager.Version, "ClusterApiControllerRepository": imageRepository(managementComponents.ClusterAPI.Controller), "ClusterApiControllerTag": managementComponents.ClusterAPI.Controller.Tag(), "ClusterApiKubeRbacProxyRepository": imageRepository(managementComponents.ClusterAPI.KubeProxy), "ClusterApiKubeRbacProxyTag": managementComponents.ClusterAPI.KubeProxy.Tag(), "KubeadmBootstrapControllerRepository": imageRepository(managementComponents.Bootstrap.Controller), "KubeadmBootstrapControllerTag": managementComponents.Bootstrap.Controller.Tag(), "KubeadmBootstrapKubeRbacProxyRepository": imageRepository(managementComponents.Bootstrap.KubeProxy), "KubeadmBootstrapKubeRbacProxyTag": managementComponents.Bootstrap.KubeProxy.Tag(), "KubeadmControlPlaneControllerRepository": imageRepository(managementComponents.ControlPlane.Controller), "KubeadmControlPlaneControllerTag": managementComponents.ControlPlane.Controller.Tag(), "KubeadmControlPlaneKubeRbacProxyRepository": imageRepository(managementComponents.ControlPlane.KubeProxy), "KubeadmControlPlaneKubeRbacProxyTag": managementComponents.ControlPlane.KubeProxy.Tag(), "ClusterApiVSphereControllerRepository": imageRepository(managementComponents.VSphere.ClusterAPIController), "ClusterApiVSphereControllerTag": managementComponents.VSphere.ClusterAPIController.Tag(), "ClusterApiNutanixControllerRepository": imageRepository(managementComponents.Nutanix.ClusterAPIController), "ClusterApiNutanixControllerTag": managementComponents.Nutanix.ClusterAPIController.Tag(), "ClusterApiCloudStackManagerRepository": imageRepository(managementComponents.CloudStack.ClusterAPIController), "ClusterApiCloudStackManagerTag": managementComponents.CloudStack.ClusterAPIController.Tag(), "ClusterApiCloudStackKubeRbacProxyRepository": imageRepository(managementComponents.CloudStack.KubeRbacProxy), "ClusterApiCloudStackKubeRbacProxyTag": managementComponents.CloudStack.KubeRbacProxy.Tag(), "ClusterApiVSphereKubeRbacProxyRepository": imageRepository(managementComponents.VSphere.KubeProxy), "ClusterApiVSphereKubeRbacProxyTag": managementComponents.VSphere.KubeProxy.Tag(), "DockerKubeRbacProxyRepository": imageRepository(managementComponents.Docker.KubeProxy), "DockerKubeRbacProxyTag": managementComponents.Docker.KubeProxy.Tag(), "DockerManagerRepository": imageRepository(managementComponents.Docker.Manager), "DockerManagerTag": managementComponents.Docker.Manager.Tag(), "EtcdadmBootstrapProviderRepository": imageRepository(managementComponents.ExternalEtcdBootstrap.Controller), "EtcdadmBootstrapProviderTag": managementComponents.ExternalEtcdBootstrap.Controller.Tag(), "EtcdadmBootstrapProviderKubeRbacProxyRepository": imageRepository(managementComponents.ExternalEtcdBootstrap.KubeProxy), "EtcdadmBootstrapProviderKubeRbacProxyTag": managementComponents.ExternalEtcdBootstrap.KubeProxy.Tag(), "EtcdadmControllerRepository": imageRepository(managementComponents.ExternalEtcdController.Controller), "EtcdadmControllerTag": managementComponents.ExternalEtcdController.Controller.Tag(), "EtcdadmControllerKubeRbacProxyRepository": imageRepository(managementComponents.ExternalEtcdController.KubeProxy), "EtcdadmControllerKubeRbacProxyTag": managementComponents.ExternalEtcdController.KubeProxy.Tag(), "DockerProviderVersion": managementComponents.Docker.Version, "VSphereProviderVersion": managementComponents.VSphere.Version, "CloudStackProviderVersion": managementComponents.CloudStack.Version, "SnowProviderVersion": managementComponents.Snow.Version, "TinkerbellProviderVersion": managementComponents.Tinkerbell.Version, "NutanixProviderVersion": managementComponents.Nutanix.Version, "ClusterApiProviderVersion": managementComponents.ClusterAPI.Version, "KubeadmControlPlaneProviderVersion": managementComponents.ControlPlane.Version, "KubeadmBootstrapProviderVersion": managementComponents.Bootstrap.Version, "EtcdadmBootstrapProviderVersion": managementComponents.ExternalEtcdBootstrap.Version, "EtcdadmControllerProviderVersion": managementComponents.ExternalEtcdController.Version, "dir": path + "/" + clusterName + capiPrefix, } filePath, err := t.WriteToFile(clusterctlConfigTemplate, data, clusterctlConfigFile) if err != nil { return nil, fmt.Errorf("generating configuration file for clusterctl: %v", err) } if err := c.buildOverridesLayer(managementComponents, clusterName, provider); err != nil { return nil, err } return &clusterctlConfiguration{ configFile: filePath, bootstrapVersion: fmt.Sprintf("%s:%s", kubeadmBootstrapProviderName, managementComponents.Bootstrap.Version), controlPlaneVersion: fmt.Sprintf("kubeadm:%s", managementComponents.ControlPlane.Version), coreVersion: fmt.Sprintf("cluster-api:%s", managementComponents.ClusterAPI.Version), etcdadmBootstrapVersion: fmt.Sprintf("%s:%s", etcdadmBootstrapProviderName, managementComponents.ExternalEtcdBootstrap.Version), etcdadmControllerVersion: fmt.Sprintf("%s:%s", etcdadmControllerProviderName, managementComponents.ExternalEtcdController.Version), }, nil } var providerNamespaces = map[string]string{ constants.VSphereProviderName: constants.CapvSystemNamespace, constants.DockerProviderName: constants.CapdSystemNamespace, constants.CloudStackProviderName: constants.CapcSystemNamespace, constants.AWSProviderName: constants.CapaSystemNamespace, constants.SnowProviderName: constants.CapasSystemNamespace, constants.NutanixProviderName: constants.CapxSystemNamespace, constants.TinkerbellProviderName: constants.CaptSystemNamespace, etcdadmBootstrapProviderName: constants.EtcdAdmBootstrapProviderSystemNamespace, etcdadmControllerProviderName: constants.EtcdAdmControllerSystemNamespace, kubeadmBootstrapProviderName: constants.CapiKubeadmBootstrapSystemNamespace, } // Upgrade executes an upgrade of the cluster to the new management components and the spec. func (c *Clusterctl) Upgrade(ctx context.Context, managementCluster *types.Cluster, provider providers.Provider, managementComponents *cluster.ManagementComponents, newSpec *cluster.Spec, changeDiff *clusterapi.CAPIChangeDiff) error { clusterctlConfig, err := c.buildConfig(managementComponents, managementCluster.Name, provider) if err != nil { return err } upgradeCommand := []string{ "upgrade", "apply", "--config", clusterctlConfig.configFile, "--kubeconfig", managementCluster.KubeconfigFile, } if changeDiff.ControlPlane != nil { upgradeCommand = append(upgradeCommand, "--control-plane", fmt.Sprintf("%s/kubeadm:%s", constants.CapiKubeadmControlPlaneSystemNamespace, changeDiff.ControlPlane.NewVersion)) } if changeDiff.Core != nil { upgradeCommand = append(upgradeCommand, "--core", fmt.Sprintf("%s/cluster-api:%s", constants.CapiSystemNamespace, changeDiff.Core.NewVersion)) } if changeDiff.InfrastructureProvider != nil { newInfraProvider := fmt.Sprintf("%s/%s:%s", providerNamespaces[changeDiff.InfrastructureProvider.ComponentName], changeDiff.InfrastructureProvider.ComponentName, changeDiff.InfrastructureProvider.NewVersion) upgradeCommand = append(upgradeCommand, "--infrastructure", newInfraProvider) } for _, bootstrapProvider := range changeDiff.BootstrapProviders { newBootstrapProvider := fmt.Sprintf("%s/%s:%s", providerNamespaces[bootstrapProvider.ComponentName], bootstrapProvider.ComponentName, bootstrapProvider.NewVersion) upgradeCommand = append(upgradeCommand, "--bootstrap", newBootstrapProvider) } providerEnvMap, err := provider.EnvMap(managementComponents, newSpec) if err != nil { return fmt.Errorf("failed generating provider env map for clusterctl upgrade: %v", err) } if _, err = c.ExecuteWithEnv(ctx, providerEnvMap, upgradeCommand...); err != nil { return fmt.Errorf("failed running upgrade apply with clusterctl: %v", err) } return nil } // InstallEtcdadmProviders installs the etcdadm providers for the cluster using clusterctl. func (c *Clusterctl) InstallEtcdadmProviders(ctx context.Context, managementComponents *cluster.ManagementComponents, clusterSpec *cluster.Spec, cluster *types.Cluster, infraProvider providers.Provider, installProviders []string) error { if cluster == nil { return fmt.Errorf("invalid cluster (nil)") } if cluster.Name == "" { return fmt.Errorf("invalid cluster name '%s'", cluster.Name) } clusterctlConfig, err := c.buildConfig(managementComponents, cluster.Name, infraProvider) if err != nil { return err } params := []string{ "init", "--config", clusterctlConfig.configFile, } for _, provider := range installProviders { switch provider { case constants.EtcdAdmBootstrapProviderName: params = append(params, "--bootstrap", clusterctlConfig.etcdadmBootstrapVersion) case constants.EtcdadmControllerProviderName: params = append(params, "--bootstrap", clusterctlConfig.etcdadmControllerVersion) default: return fmt.Errorf("unrecognized capi provider %s", provider) } } if cluster.KubeconfigFile != "" { params = append(params, "--kubeconfig", cluster.KubeconfigFile) } envMap, err := infraProvider.EnvMap(managementComponents, clusterSpec) if err != nil { return err } _, err = c.ExecuteWithEnv(ctx, envMap, params...) if err != nil { return fmt.Errorf("executing init: %v", err) } return nil }