diff --git a/cmd/command/bootstrap/bootstrap.go b/cmd/command/bootstrap/bootstrap.go deleted file mode 100644 index 47df494d..00000000 --- a/cmd/command/bootstrap/bootstrap.go +++ /dev/null @@ -1,392 +0,0 @@ -package bootstrap - -import ( - "context" - "fmt" - "os" - "path" - "strings" - "time" - - "github.com/pluralsh/plural-cli/pkg/client" - "github.com/pluralsh/plural-cli/pkg/common" - "github.com/pluralsh/plural-cli/pkg/exp" - - "github.com/pluralsh/plural-cli/pkg/kubernetes" - "github.com/pluralsh/plural-cli/pkg/manifest" - "github.com/pluralsh/plural-cli/pkg/provider" - "github.com/pluralsh/plural-cli/pkg/utils" - "github.com/urfave/cli" - corev1 "k8s.io/api/core/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - clusterapioperator "sigs.k8s.io/cluster-api-operator/api/v1alpha1" - clusterapi "sigs.k8s.io/cluster-api/api/v1beta1" - apiclient "sigs.k8s.io/cluster-api/cmd/clusterctl/client" - ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client" -) - -var runtimescheme = runtime.NewScheme() - -func init() { - utilruntime.Must(corev1.AddToScheme(runtimescheme)) - utilruntime.Must(apiextensionsv1.AddToScheme(runtimescheme)) - utilruntime.Must(clusterapi.AddToScheme(runtimescheme)) - utilruntime.Must(clusterapioperator.AddToScheme(runtimescheme)) -} - -const ( - kindConfig = `kind: Cluster -apiVersion: kind.x-k8s.io/v1alpha4 -networking: - ipFamily: dual -nodes: -- role: control-plane - extraMounts: - - hostPath: /var/run/docker.sock - containerPath: /var/run/docker.sock` -) - -type Plural struct { - client.Plural -} - -func Command(clients client.Plural) cli.Command { - p := Plural{ - Plural: clients, - } - return cli.Command{ - Name: "bootstrap", - Usage: "Commands for bootstrapping cluster", - Subcommands: p.bootstrapCommands(), - Category: "Bootstrap", - Hidden: !exp.IsFeatureEnabled(exp.EXP_PLURAL_CAPI), - } -} - -func (p *Plural) bootstrapCommands() []cli.Command { - return []cli.Command{ - { - Name: "cluster", - Subcommands: p.bootstrapClusterCommands(), - Usage: "Manage bootstrap cluster", - }, - { - Name: "namespace", - Subcommands: p.namespaceCommands(), - Usage: "Manage bootstrap cluster", - }, - } -} - -func (p *Plural) namespaceCommands() []cli.Command { - return []cli.Command{ - { - Name: "create", - ArgsUsage: "NAME", - Usage: "Creates bootstrap namespace", - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "skip-if-exists", - Usage: "skip creating when namespace exists", - }, - }, - Action: common.LatestVersion(common.InitKubeconfig(common.RequireArgs(p.handleCreateNamespace, []string{"NAME"}))), - }, - } -} - -func (p *Plural) bootstrapClusterCommands() []cli.Command { - return []cli.Command{ - { - Name: "create", - ArgsUsage: "NAME", - Usage: "Creates bootstrap cluster", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "image", - Usage: "kind image to use", - }, - cli.BoolFlag{ - Name: "skip-if-exists", - Usage: "skip creating when cluster exists", - }, - }, - Action: common.LatestVersion(common.RequireKind(common.RequireArgs(handleCreateCluster, []string{"NAME"}))), - }, - { - Name: "delete", - ArgsUsage: "NAME", - Usage: "Deletes bootstrap cluster", - Action: common.LatestVersion(common.RequireKind(common.RequireArgs(handleDeleteCluster, []string{"NAME"}))), - }, - { - Name: "move", - Usage: "Move cluster API objects", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "kubeconfig", - Usage: "path to the kubeconfig file for the source management cluster. If unspecified, default discovery rules apply.", - }, - cli.StringFlag{ - Name: "kubeconfig-context", - Usage: "context to be used within the kubeconfig file for the source management cluster. If empty, current context will be used.", - }, - cli.StringFlag{ - Name: "to-kubeconfig", - Usage: "path to the kubeconfig file to use for the destination management cluster.", - }, - cli.StringFlag{ - Name: "to-kubeconfig-context", - Usage: "Context to be used within the kubeconfig file for the destination management cluster. If empty, current context will be used.", - }, - }, - Action: common.LatestVersion(p.handleMoveCluster), - }, - { - Name: "destroy-cluster-api", - ArgsUsage: "NAME", - Usage: "Destroy cluster API", - Action: common.LatestVersion(common.RequireArgs(p.handleDestroyClusterAPI, []string{"NAME"})), - }, - } -} - -func (p *Plural) handleDestroyClusterAPI(c *cli.Context) error { - name := c.Args().Get(0) - _, found := utils.ProjectRoot() - if !found { - return fmt.Errorf("You're not within an installation repo") - } - pm, err := manifest.FetchProject() - if err != nil { - return err - } - prov := &provider.KINDProvider{Clust: "bootstrap"} - if err := prov.KubeConfig(); err != nil { - return err - } - config, err := kubernetes.KubeConfig() - if err != nil { - return err - } - client, err := genClientFromConfig(config) - if err != nil { - return err - } - utils.Warn("Waiting for the operator ") - if err := utils.WaitFor(20*time.Minute, 10*time.Second, func() (bool, error) { - pods := &corev1.PodList{} - providerName := pm.Provider - if providerName == "kind" { - providerName = "docker" - } - selector := fmt.Sprintf("infrastructure-%s", strings.ToLower(providerName)) - if err := client.List(context.Background(), pods, ctrlruntimeclient.MatchingLabels{"cluster.x-k8s.io/provider": selector}); err != nil { - if !apierrors.IsNotFound(err) { - return false, fmt.Errorf("failed to get pods: %w", err) - } - return false, nil - } - if len(pods.Items) > 0 { - if isReady(pods.Items[0].Status.Conditions) { - return true, nil - } - } - utils.Warn(".") - return false, nil - }); err != nil { - return err - } - if err := client.Delete(context.Background(), &clusterapi.Cluster{ - ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "bootstrap"}, - }); err != nil { - return err - } - utils.Warn("\nDeleting cluster") - return utils.WaitFor(40*time.Minute, 10*time.Second, func() (bool, error) { - if err := client.Get(context.Background(), ctrlruntimeclient.ObjectKey{Name: name, Namespace: "bootstrap"}, &clusterapi.Cluster{}); err != nil { - if !apierrors.IsNotFound(err) { - return false, fmt.Errorf("failed to get Cluster: %w", err) - } - return true, nil - } - utils.Warn(".") - return false, nil - }) -} - -func isReady(conditions []corev1.PodCondition) bool { - for _, cond := range conditions { - if cond.Type == corev1.PodReady && cond.Status == corev1.ConditionTrue { - return true - } - } - return false -} - -func (p *Plural) handleMoveCluster(c *cli.Context) error { - _, found := utils.ProjectRoot() - if !found { - return fmt.Errorf("You're not within an installation repo") - } - - client, err := apiclient.New(context.Background(), "") - if err != nil { - return err - } - - kubeconfig := c.String("kubeconfig") - kubeconfigContext := c.String("kubeconfig-context") - toKubeconfig := c.String("to-kubeconfig") - toKubeconfigContext := c.String("to-kubeconfig-context") - - options := apiclient.MoveOptions{ - FromKubeconfig: apiclient.Kubeconfig{ - Path: kubeconfig, - Context: kubeconfigContext, - }, - ToKubeconfig: apiclient.Kubeconfig{ - Path: toKubeconfig, - Context: toKubeconfigContext, - }, - Namespace: "bootstrap", - DryRun: false, - } - if err := client.Move(context.Background(), options); err != nil { - return err - } - - return nil -} - -func (p *Plural) handleCreateNamespace(c *cli.Context) error { - name := c.Args().Get(0) - fmt.Printf("Creating namespace %s ...\n", name) - err := p.InitKube() - if err != nil { - return err - } - if err := p.CreateNamespace(name, true); err != nil { - if apierrors.IsAlreadyExists(err) { - return nil - } - return err - } - - return nil -} - -func handleDeleteCluster(c *cli.Context) error { - name := c.Args().Get(0) - return utils.Exec("kind", "delete", "cluster", "--name", name) -} - -func handleCreateCluster(c *cli.Context) error { - name := c.Args().Get(0) - imageFlag := c.String("image") - skipCreation := c.Bool("skip-if-exists") - if utils.IsKindClusterAlreadyExists(name) && skipCreation { - utils.Highlight("Cluster %s already exists \n", name) - return nil - } - - dir, err := os.MkdirTemp("", "kind") - if err != nil { - return err - } - defer os.RemoveAll(dir) - config := path.Join(dir, "config.yaml") - if err := os.WriteFile(config, []byte(kindConfig), 0644); err != nil { - return err - } - args := []string{"create", "cluster", "--name", name, "--config", config} - if imageFlag != "" { - args = append(args, "--image", imageFlag) - } - - if err := utils.Exec("kind", args...); err != nil { - return err - } - - kubeconfig, err := utils.GetKindClusterKubeconfig(name, false) - if err != nil { - return err - } - - client, err := getClient(kubeconfig) - if err != nil { - return err - } - if err := client.Create(context.Background(), &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bootstrap", - }, - }); err != nil { - return err - } - internalKubeconfig, err := utils.GetKindClusterKubeconfig(name, true) - if err != nil { - return err - } - kubeconfigSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "kubeconfig", - Namespace: "bootstrap", - }, - Data: map[string][]byte{ - "value": []byte(internalKubeconfig), - }, - } - if err := client.Create(context.Background(), kubeconfigSecret); err != nil { - return err - } - - return nil -} - -func getClient(rawKubeconfig string) (ctrlruntimeclient.Client, error) { - - cfg, err := clientcmd.Load([]byte(rawKubeconfig)) - if err != nil { - return nil, err - } - clientConfig, err := getRestConfig(cfg) - if err != nil { - return nil, err - } - - return genClientFromConfig(clientConfig) -} - -func genClientFromConfig(cfg *rest.Config) (ctrlruntimeclient.Client, error) { - return ctrlruntimeclient.New(cfg, ctrlruntimeclient.Options{ - Scheme: runtimescheme, - }) -} - -func getRestConfig(cfg *clientcmdapi.Config) (*rest.Config, error) { - iconfig := clientcmd.NewNonInteractiveClientConfig( - *cfg, - "", - &clientcmd.ConfigOverrides{}, - nil, - ) - - clientConfig, err := iconfig.ClientConfig() - if err != nil { - return nil, err - } - - // Avoid blocking of the controller by increasing the QPS for user cluster interaction - clientConfig.QPS = 20 - clientConfig.Burst = 50 - - return clientConfig, nil -} diff --git a/cmd/command/clusters/clusters.go b/cmd/command/clusters/clusters.go index 448cfa04..7c3623d0 100644 --- a/cmd/command/clusters/clusters.go +++ b/cmd/command/clusters/clusters.go @@ -3,20 +3,13 @@ package clusters import ( "fmt" + "github.com/pluralsh/plural-cli/pkg/api" "github.com/pluralsh/plural-cli/pkg/client" "github.com/pluralsh/plural-cli/pkg/common" - - "github.com/urfave/cli" - "sigs.k8s.io/yaml" - - "github.com/pluralsh/plural-cli/pkg/api" - "github.com/pluralsh/plural-cli/pkg/bootstrap/aws" - "github.com/pluralsh/plural-cli/pkg/cluster" "github.com/pluralsh/plural-cli/pkg/config" - "github.com/pluralsh/plural-cli/pkg/kubernetes" - "github.com/pluralsh/plural-cli/pkg/machinepool" "github.com/pluralsh/plural-cli/pkg/manifest" "github.com/pluralsh/plural-cli/pkg/utils" + "github.com/urfave/cli" ) type Plural struct { @@ -83,124 +76,9 @@ func (p *Plural) clusterCommands() []cli.Command { Usage: "promote pending upgrades to your cluster", Action: common.LatestVersion(p.promoteCluster), }, - { - Name: "wait", - Usage: "waits on a cluster until it becomes ready", - ArgsUsage: "NAMESPACE NAME", - Action: common.LatestVersion(common.InitKubeconfig(common.RequireArgs(handleClusterWait, []string{"NAMESPACE", "NAME"}))), - Category: "Debugging", - }, - { - Name: "mpwait", - Usage: "waits on a machine pool until it becomes ready", - ArgsUsage: "NAMESPACE NAME", - Action: common.LatestVersion(common.InitKubeconfig(common.RequireArgs(handleMPWait, []string{"NAMESPACE", "NAME"}))), - Category: "Debugging", - }, - // { - // Name: "migrate", - // Usage: "migrate to Cluster API", - // Action: common.LatestVersion(common.Rooted(common.InitKubeconfig(p.handleMigration))), - // Category: "Publishing", - // Hidden: !exp.IsFeatureEnabled(exp.EXP_PLURAL_CAPI), - // }, - { - Name: "aws-auth", - Usage: "fetches the current state of your aws auth config map", - Subcommands: awsAuthCommands(), - }, } } -// func (p *Plural) handleMigration(_ *cli.Context) error { -// p.InitPluralClient() -// if err := validation.ValidateMigration(p); err != nil { -// return err -// } -// -// project, err := manifest.FetchProject() -// if err != nil { -// return err -// } -// -// if project.ClusterAPI { -// utils.Success("Cluster already migrated.\n") -// return nil -// } -// -// return bootstrap.MigrateCluster(plural.RunPlural) -// } - -func awsAuthCommands() []cli.Command { - return []cli.Command{ - { - Name: "fetch", - Usage: "gets the current state of your aws auth configmap", - Action: handleAwsAuth, - }, - { - Name: "update", - Usage: "adds a user or role to the aws auth configmap", - Flags: []cli.Flag{ - cli.StringFlag{Name: "role-arn"}, - cli.StringFlag{Name: "user-arn"}, - }, - Action: handleModifyAwsAuth, - }, - } -} - -func handleAwsAuth(_ *cli.Context) error { - auth, err := aws.FetchAuth() - if err != nil { - return err - } - - res, err := yaml.Marshal(auth) - if err != nil { - return err - } - - fmt.Println(string(res)) - return nil -} - -func handleModifyAwsAuth(c *cli.Context) error { - role, user := c.String("role-arn"), c.String("user-arn") - - if role != "" { - return aws.AddRole(role) - } - - if user != "" { - return aws.AddUser(user) - } - - return fmt.Errorf("you must specify at least one of role-arn or user-arn") -} - -func handleClusterWait(c *cli.Context) error { - namespace := c.Args().Get(0) - name := c.Args().Get(1) - kubeConf, err := kubernetes.KubeConfig() - if err != nil { - return err - } - - return cluster.Wait(kubeConf, namespace, name) -} - -func handleMPWait(c *cli.Context) error { - namespace := c.Args().Get(0) - name := c.Args().Get(1) - kubeConf, err := kubernetes.KubeConfig() - if err != nil { - return err - } - - return machinepool.WaitAll(kubeConf, namespace, name) -} - func (p *Plural) listClusters(c *cli.Context) error { p.InitPluralClient() clusters, err := p.Client.Clusters() diff --git a/cmd/command/destroy/destroy.go b/cmd/command/destroy/destroy.go index 577e65e0..e72e3da4 100644 --- a/cmd/command/destroy/destroy.go +++ b/cmd/command/destroy/destroy.go @@ -59,11 +59,6 @@ func (p *Plural) destroy(c *cli.Context) error { force := c.Bool("force") all := c.Bool("all") - project, err := manifest.FetchProject() - if err != nil { - return err - } - infix := "this workspace" if repoName != "" { infix = repoName @@ -87,7 +82,7 @@ func (p *Plural) destroy(c *cli.Context) error { return fmt.Errorf("No installation for app %s to destroy, if the app is still in your repo, you can always run cd %s/terraform && terraform destroy", repoName, repoName) } - return p.doDestroy(repoRoot, installation, delete, project.ClusterAPI) + return p.doDestroy(repoRoot, installation, delete) } installations, err := client.GetSortedInstallations(p.Plural, repoName) @@ -107,7 +102,7 @@ func (p *Plural) destroy(c *cli.Context) error { continue } - if err := p.doDestroy(repoRoot, installation, delete, project.ClusterAPI); err != nil { + if err := p.doDestroy(repoRoot, installation, delete); err != nil { return err } } @@ -139,7 +134,7 @@ func (p *Plural) destroy(c *cli.Context) error { return nil } -func (p *Plural) doDestroy(repoRoot string, installation *api.Installation, delete, clusterAPI bool) error { +func (p *Plural) doDestroy(repoRoot string, installation *api.Installation, delete bool) error { p.Plural.InitPluralClient() if err := os.Chdir(repoRoot); err != nil { return err @@ -155,18 +150,6 @@ func (p *Plural) doDestroy(repoRoot string, installation *api.Installation, dele return err } - // TODO fix for clusterAPI - // if repo == Bootstrap && clusterAPI { - // if err = bootstrap.DestroyCluster(workspace.Destroy, plural.RunPlural); err != nil { - // return err - // } - // - // } else { - // if err := workspace.Destroy(); err != nil { - // return err - // } - // } - if err := workspace.Destroy(); err != nil { return err } diff --git a/cmd/command/plural/plural.go b/cmd/command/plural/plural.go index 552ffd39..948814fc 100644 --- a/cmd/command/plural/plural.go +++ b/cmd/command/plural/plural.go @@ -4,7 +4,6 @@ import ( "github.com/pluralsh/plural-cli/cmd/command/ai" "github.com/pluralsh/plural-cli/cmd/command/api" "github.com/pluralsh/plural-cli/cmd/command/auth" - "github.com/pluralsh/plural-cli/cmd/command/bootstrap" "github.com/pluralsh/plural-cli/cmd/command/bounce" "github.com/pluralsh/plural-cli/cmd/command/buildcmd" "github.com/pluralsh/plural-cli/cmd/command/bundle" @@ -226,7 +225,6 @@ func CreateNewApp(plural *Plural) *cli.App { api.Command(plural.Plural), auth.Command(plural.Plural), ai.Command(plural.Plural), - bootstrap.Command(plural.Plural), bounce.Command(plural.Plural), bundle.Command(plural.Plural), buildcmd.Command(plural.Plural), diff --git a/pkg/bootstrap/aws/auth.go b/pkg/bootstrap/aws/auth.go deleted file mode 100644 index 95470635..00000000 --- a/pkg/bootstrap/aws/auth.go +++ /dev/null @@ -1,137 +0,0 @@ -package aws - -import ( - "context" - "fmt" - "strings" - - "github.com/pluralsh/plural-cli/pkg/kubernetes" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/eks/api/v1beta2" - "sigs.k8s.io/yaml" -) - -const ( - awsAuthNs = "kube-system" - awsAuthName = "aws-auth" - roleKey = "mapRoles" - usersKey = "mapUsers" -) - -func FetchAuth() (*ekscontrolplanev1.IAMAuthenticatorConfig, error) { - ctx := context.Background() - kube, err := kubernetes.Kubernetes() - if err != nil { - return nil, err - } - - return fetchAwsAuth(ctx, kube) -} - -func AddUser(userArn string) error { - ctx := context.Background() - kube, err := kubernetes.Kubernetes() - if err != nil { - return err - } - - eksConfig, err := fetchAwsAuth(ctx, kube) - if err != nil { - return err - } - - eksConfig.UserMappings = append(eksConfig.UserMappings, ekscontrolplanev1.UserMapping{ - KubernetesMapping: ekscontrolplanev1.KubernetesMapping{ - Groups: []string{"system:masters"}, - UserName: username(userArn), - }, - UserARN: userArn, - }) - - return persistAuth(ctx, kube, eksConfig) -} - -func AddRole(roleArn string) error { - ctx := context.Background() - kube, err := kubernetes.Kubernetes() - if err != nil { - return err - } - - eksConfig, err := fetchAwsAuth(ctx, kube) - if err != nil { - return err - } - - eksConfig.RoleMappings = append(eksConfig.RoleMappings, ekscontrolplanev1.RoleMapping{ - KubernetesMapping: ekscontrolplanev1.KubernetesMapping{ - Groups: []string{"system:masters"}, - UserName: username(roleArn), - }, - RoleARN: roleArn, - }) - - return persistAuth(ctx, kube, eksConfig) -} - -func username(arn string) string { - parts := strings.Split(arn, "/") - return parts[len(parts)-1] -} - -func fetchAwsAuth(ctx context.Context, kube kubernetes.Kube) (*ekscontrolplanev1.IAMAuthenticatorConfig, error) { - client := kube.GetClient() - cm, err := client.CoreV1().ConfigMaps(awsAuthNs).Get(ctx, awsAuthName, metav1.GetOptions{}) - if err != nil { - return nil, err - } - - res := &ekscontrolplanev1.IAMAuthenticatorConfig{ - RoleMappings: []ekscontrolplanev1.RoleMapping{}, - UserMappings: []ekscontrolplanev1.UserMapping{}, - } - - if rolesSection, ok := cm.Data[roleKey]; ok { - err := yaml.Unmarshal([]byte(rolesSection), &res.RoleMappings) - if err != nil { - return nil, fmt.Errorf("unmarshalling mapped roles: %w", err) - } - } - - if usersSection, ok := cm.Data[usersKey]; ok { - err := yaml.Unmarshal([]byte(usersSection), &res.UserMappings) - if err != nil { - return nil, fmt.Errorf("unmarshalling mapped users: %w", err) - } - } - - return res, nil -} - -func persistAuth(ctx context.Context, kube kubernetes.Kube, authConfig *ekscontrolplanev1.IAMAuthenticatorConfig) error { - client := kube.GetClient() - cmClient := client.CoreV1().ConfigMaps(awsAuthNs) - cm, err := cmClient.Get(ctx, awsAuthName, metav1.GetOptions{}) - if err != nil { - return err - } - - if len(authConfig.RoleMappings) > 0 { - roleMappings, err := yaml.Marshal(authConfig.RoleMappings) - if err != nil { - return fmt.Errorf("marshalling auth config roles: %w", err) - } - cm.Data[roleKey] = string(roleMappings) - } - - if len(authConfig.UserMappings) > 0 { - userMappings, err := yaml.Marshal(authConfig.UserMappings) - if err != nil { - return fmt.Errorf("marshalling auth config users: %w", err) - } - cm.Data[usersKey] = string(userMappings) - } - - _, err = cmClient.Update(ctx, cm, metav1.UpdateOptions{}) - return err -} diff --git a/pkg/bootstrap/azure/auth.go b/pkg/bootstrap/azure/auth.go deleted file mode 100644 index 3ec7f21c..00000000 --- a/pkg/bootstrap/azure/auth.go +++ /dev/null @@ -1,112 +0,0 @@ -package azure - -import ( - "context" - "fmt" - - "github.com/Azure/azure-sdk-for-go/sdk/azidentity" - azwi "github.com/Azure/azure-workload-identity/pkg/cloud" - "github.com/Azure/go-autorest/autorest/azure" - msgraph "github.com/microsoftgraph/msgraph-sdk-go" - "github.com/microsoftgraph/msgraph-sdk-go/models" - "github.com/microsoftgraph/msgraph-sdk-go/serviceprincipals" - "github.com/pluralsh/plural-cli/pkg/utils" -) - -type AuthService struct { - subscriptionID string - - azwiClient *azwi.AzureClient - msgraphClient *msgraph.GraphServiceClient - context context.Context - - app models.Applicationable - sp models.ServicePrincipalable -} - -func GetAuthService(subscriptionID string) (*AuthService, error) { - credential, err := azidentity.NewDefaultAzureCredential(nil) - if err != nil { - return nil, err - } - - azwiClient, err := azwi.NewAzureClientWithCLI(azure.PublicCloud, subscriptionID, nil) - if err != nil { - return nil, err - } - - msgraphClient, err := msgraph.NewGraphServiceClientWithCredentials(credential, nil) - if err != nil { - return nil, err - } - - return &AuthService{ - subscriptionID: subscriptionID, - msgraphClient: msgraphClient, - azwiClient: azwiClient, - context: context.Background(), - }, nil -} - -func (as *AuthService) addServicePrincipalPassword(servicePrincipalId string) (models.PasswordCredentialable, error) { - pwd := serviceprincipals.NewItemAddPasswordPostRequestBody() - pwd.SetPasswordCredential(models.NewPasswordCredential()) - - return as.msgraphClient.ServicePrincipalsById(servicePrincipalId).AddPassword(). - Post(as.context, pwd, nil) -} - -func (as *AuthService) Setup(name string) (clientId string, clientSecret string, err error) { - app, err := as.azwiClient.CreateApplication(as.context, name) - if err != nil { - return - } - as.app = app - utils.Success("Created %s application\n", *app.GetDisplayName()) - - sp, err := as.azwiClient.CreateServicePrincipal(as.context, *app.GetAppId(), nil) - if err != nil { - return - } - as.sp = sp - utils.Success("Created %s service principal\n", *sp.GetDisplayName()) - - role := "Contributor" - scope := fmt.Sprintf("/subscriptions/%s/", as.subscriptionID) - _, err = as.azwiClient.CreateRoleAssignment(as.context, scope, role, *sp.GetId()) - if err != nil { - return - } - utils.Success("Assigned %s role to %s service principal\n", role, *sp.GetDisplayName()) - - pwd, err := as.addServicePrincipalPassword(*sp.GetId()) - if err != nil { - return - } - utils.Success("Added password for %s service principal\n", *sp.GetDisplayName()) - - clientId = *sp.GetAppId() - clientSecret = *pwd.GetSecretText() - - return -} - -func (as *AuthService) Cleanup() error { - if as.sp != nil { - err := as.azwiClient.DeleteServicePrincipal(as.context, *as.sp.GetId()) - if err != nil { - return err - } - utils.Success("Deleted %s service principal\n", *as.sp.GetDisplayName()) - } - - if as.app != nil { - err := as.azwiClient.DeleteApplication(as.context, *as.app.GetId()) - if err != nil { - return err - } - utils.Success("Deleted %s application\n", *as.app.GetDisplayName()) - } - - return nil -} diff --git a/pkg/bootstrap/bootstrap.go b/pkg/bootstrap/bootstrap.go deleted file mode 100644 index 944c922f..00000000 --- a/pkg/bootstrap/bootstrap.go +++ /dev/null @@ -1,231 +0,0 @@ -package bootstrap - -import ( - "os/exec" - "path/filepath" - - capi "sigs.k8s.io/cluster-api/api/v1beta1" - "sigs.k8s.io/cluster-api/cmd/clusterctl/client" - - "github.com/pluralsh/plural-cli/pkg/api" - "github.com/pluralsh/plural-cli/pkg/manifest" - "github.com/pluralsh/plural-cli/pkg/provider" - "github.com/pluralsh/plural-cli/pkg/utils" - "github.com/pluralsh/plural-cli/pkg/utils/backup" -) - -func shouldDeleteProviderCluster(cluster, namespace string) bool { - clusterExists := bootstrapClusterExists() - deleting, err := IsClusterPhase(localClusterContext, cluster, namespace, capi.ClusterPhaseDeleting) - - if err != nil { - return false - } - - return clusterExists && !deleting -} - -func shouldDeleteBootstrapCluster(cluster, namespace string) bool { - clusterExists := bootstrapClusterExists() - deleting, err := IsClusterPhase(localClusterContext, cluster, namespace, capi.ClusterPhaseDeleting) - - if err != nil { - return false - } - - return clusterExists && deleting -} - -// getBootstrapSteps returns list of steps to run during cluster bootstrap. -func getBootstrapSteps(runPlural ActionFunc, additionalFlags []string) ([]*Step, error) { - man, err := manifest.FetchProject() - if err != nil { - return nil, err - } - - kubeconfigPath, err := getKubeconfigPath() - if err != nil { - return nil, err - } - - bootstrapPath, err := GetBootstrapPath() - if err != nil { - return nil, err - } - - flags := append(getBootstrapFlags(man.Provider), additionalFlags...) - - prov, err := provider.GetProvider() - if err != nil { - return nil, err - } - - clusterBackup := backup.NewCAPIBackup(man.Cluster) - - return []*Step{ - { - Name: "Destroy cluster API", - Args: []string{"plural", "bootstrap", "cluster", "destroy-cluster-api", man.Cluster}, - Execute: runPlural, - Confirm: "It looks like your existing bootstrap cluster has a provider cluster configuration. All resources at your provider should be removed before continuing. Would you like to try and remove it automatically?", - Skip: !shouldDeleteProviderCluster(man.Cluster, "bootstrap"), - }, - { - Name: "Destroy local bootstrap cluster", - Args: []string{"plural", "--bootstrap", "bootstrap", "cluster", "delete", "bootstrap"}, - Execute: runPlural, - Confirm: "It looks like your existing bootstrap cluster has a provider cluster configuration in a non-recoverable state. Please make sure to manually delete all existing cluster resources at your provider before continuing. Would you like to destroy the bootstrap cluster?", - Skip: !shouldDeleteBootstrapCluster(man.Cluster, "bootstrap"), - }, - { - Name: "Create local bootstrap cluster", - Args: []string{"plural", "bootstrap", "cluster", "create", "bootstrap", "--skip-if-exists"}, - Execute: runPlural, - }, - { - Name: "Bootstrap CRDs in local cluster", - Args: []string{"plural", "--bootstrap", "wkspace", "crds", "bootstrap"}, - Execute: runPlural, - }, - { - Name: "Install Cluster API operators in local cluster", - Args: append([]string{"plural", "--bootstrap", "wkspace", "helm", "bootstrap", "--skip", "cluster-api-cluster"}, append(flags, disableAzurePodIdentityFlag...)...), - Execute: runPlural, - }, - { - Name: "Deploy cluster", - Args: append([]string{"plural", "--bootstrap", "wkspace", "helm", "bootstrap"}, flags...), - Execute: runPlural, - Skip: clusterBackup.Exists(), - }, - { - Name: "Restore cluster", - Execute: func(_ []string) error { - options := client.MoveOptions{ - ToKubeconfig: client.Kubeconfig{ - Path: kubeconfigPath, - Context: "kind-bootstrap", - }, - } - - return clusterBackup.Restore(options) - }, - Skip: !clusterBackup.Exists(), - }, - { - Name: "Wait for cluster", - Args: []string{"plural", "--bootstrap", "clusters", "wait", "bootstrap", man.Cluster}, - Execute: runPlural, - }, - { - Name: "Install Network", - Execute: func(_ []string) error { - return installCilium(man.Cluster) - }, - Skip: man.Provider != api.ProviderKind, - }, - { - Name: "Install StorageClass", - Execute: func(_ []string) error { - return applyManifest(storageClassManifest) - }, - Skip: man.Provider != api.ProviderKind, - }, - { - Name: "Save kubeconfig", - Execute: func(_ []string) error { - cmd := exec.Command("kind", "export", "kubeconfig", "--name", man.Cluster, - "--kubeconfig", filepath.Join(bootstrapPath, "terraform", "kube_config_cluster.yaml")) - return utils.Execute(cmd) - }, - Skip: man.Provider != api.ProviderKind, - }, - { - Name: "Wait for machine pools", - Args: []string{"plural", "--bootstrap", "clusters", "mpwait", "bootstrap", man.Cluster}, - Execute: runPlural, - OnAfter: func() { - options := client.MoveOptions{ - FromKubeconfig: client.Kubeconfig{ - Path: kubeconfigPath, - Context: "kind-bootstrap", - }, - } - - err := clusterBackup.Save(options) - if err != nil { - _ = clusterBackup.Remove() - utils.Error("error during saving state backup: %s", err) - } - }, - }, - { - Name: "Initialize kubeconfig for target cluster", - Args: []string{"plural", "wkspace", "kube-init"}, - Execute: runPlural, - }, - { - Name: "Create bootstrap namespace in target cluster", - Args: []string{"plural", "bootstrap", "namespace", "create", "bootstrap"}, - Execute: runPlural, - }, - { - Name: "Bootstrap CRDs in target cluster", - Args: []string{"plural", "wkspace", "crds", "bootstrap"}, - Execute: runPlural, - }, - { - Name: "Install Cluster API operators in target cluster", - Args: append([]string{"plural", "wkspace", "helm", "bootstrap", "--skip", "cluster-api-cluster"}, append(flags, disableAzurePodIdentityFlag...)...), - Execute: runPlural, - }, - { - Name: "Move resources from local to target cluster", - Args: []string{"plural", "bootstrap", "cluster", "move", "--kubeconfig-context", localClusterContext, "--to-kubeconfig", kubeconfigPath}, - Execute: runPlural, - Retries: 2, - }, - { - Name: "Move Helm secrets", - Execute: func(_ []string) error { - return moveHelmSecrets(localClusterContext, prov.KubeContext()) - }, - Retries: 2, - }, - { - Name: "Destroy local bootstrap cluster", - Args: []string{"plural", "--bootstrap", "bootstrap", "cluster", "delete", "bootstrap"}, - Execute: runPlural, - OnAfter: func() { - err := clusterBackup.Remove() - if err != nil { - utils.Error("error during removing state backup: %s", err) - } - }, - }, - }, nil -} - -// BootstrapCluster bootstraps cluster with Cluster API. -func BootstrapCluster(runPlural ActionFunc) error { - utils.Highlight("Bootstrapping cluster with Cluster API...\n") - - if err := RunWithTempCredentials(func(flags []string) error { - steps, err := getBootstrapSteps(runPlural, flags) - if err != nil { - return err - } - - err = ExecuteSteps(steps) - if err != nil { - utils.Error("Cluster bootstrapping failed\n") - return err - } - return nil - }); err != nil { - return err - } - - utils.Success("Cluster bootstrapped successfully!\n") - return nil -} diff --git a/pkg/bootstrap/check.go b/pkg/bootstrap/check.go deleted file mode 100644 index bd6d7fe7..00000000 --- a/pkg/bootstrap/check.go +++ /dev/null @@ -1,91 +0,0 @@ -package bootstrap - -import ( - "context" - "fmt" - "time" - - "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/rest" - capi "sigs.k8s.io/cluster-api/api/v1beta1" - - "github.com/pluralsh/plural-cli/pkg/cluster" - "github.com/pluralsh/plural-cli/pkg/config" - "github.com/pluralsh/plural-cli/pkg/kubernetes" - "github.com/pluralsh/plural-cli/pkg/provider" - "github.com/pluralsh/plural-cli/pkg/utils" -) - -const ( - ClusterNotReadyError = "cluster exists but it is not ready yet" -) - -// getCluster returns Cluster resource. -func getCluster(kubeContext, name, namespace string) (*capi.Cluster, error) { - prov, err := provider.GetProvider() - if err != nil { - return nil, err - } - - err = prov.KubeConfig() - if err != nil { - return nil, err - } - - var kubeConf *rest.Config - if len(kubeContext) > 0 { - kubeConf, err = kubernetes.KubeConfigWithContext(kubeContext) - } else { - kubeConf, err = kubernetes.KubeConfig() - } - - if err != nil { - return nil, err - } - - conf := config.Read() - ctx := context.Background() - clusters, err := cluster.NewForConfig(kubeConf) - if err != nil { - return nil, err - } - - client := clusters.Clusters(conf.Namespace(namespace)) - return client.Get(ctx, name, metav1.GetOptions{}) -} - -func IsClusterPhase(context, name, namespace string, phase capi.ClusterPhase) (bool, error) { - c, err := getCluster(context, name, namespace) - if err != nil { - return false, err - } - - return c.Status.GetTypedPhase() == phase, nil -} - -// CheckClusterReadiness checks if Cluster API cluster is in ready state. -func CheckClusterReadiness(name, namespace string) (bool, error) { - utils.Highlight("Checking cluster status") - - err := util.WaitFor(10*time.Second, time.Second, func() (bool, error) { - utils.Highlight(".") - - c, err := getCluster("", name, namespace) - if err != nil { - return false, err - } - - for _, cond := range c.Status.Conditions { - if cond.Type == capi.ReadyCondition && cond.Status == "True" { - return true, nil - } - } - - return true, fmt.Errorf(ClusterNotReadyError) - }) - - utils.Highlight("\n") - - return err == nil, err -} diff --git a/pkg/bootstrap/cilium.go b/pkg/bootstrap/cilium.go deleted file mode 100644 index df81eb7e..00000000 --- a/pkg/bootstrap/cilium.go +++ /dev/null @@ -1,66 +0,0 @@ -package bootstrap - -import ( - "os/exec" - "time" - - "github.com/pkg/errors" - "github.com/pluralsh/plural-cli/pkg/helm" - "github.com/pluralsh/plural-cli/pkg/utils" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/storage/driver" -) - -var settings = cli.New() - -const ( - ciliumRepoName = "cilium" - ciliumRepoUrl = "https://helm.cilium.io/" -) - -func installCilium(cluster string) error { - namespace := "kube-system" - cmd := exec.Command("kind", "export", "kubeconfig", "--name", cluster) - if err := utils.Execute(cmd); err != nil { - return err - } - - if err := helm.AddRepo(ciliumRepoName, ciliumRepoUrl); err != nil { - return err - } - - helmConfig, err := helm.GetActionConfig(namespace) - if err != nil { - return nil - } - - cp, err := action.NewInstall(helmConfig).ChartPathOptions.LocateChart("cilium/cilium", settings) - if err != nil { - return err - } - - chart, err := loader.Load(cp) - if err != nil { - return err - } - - histClient := action.NewHistory(helmConfig) - histClient.Max = 5 - if _, err := histClient.Run(ciliumRepoName); errors.Is(err, driver.ErrReleaseNotFound) { - instClient := action.NewInstall(helmConfig) - instClient.Namespace = namespace - instClient.ReleaseName = ciliumRepoName - instClient.Timeout = time.Minute * 10 - - _, err = instClient.Run(chart, map[string]interface{}{}) - return err - } - client := action.NewUpgrade(helmConfig) - client.Namespace = namespace - client.Timeout = time.Minute * 10 - _, err = client.Run(ciliumRepoName, chart, map[string]interface{}{}) - - return err -} diff --git a/pkg/bootstrap/common.go b/pkg/bootstrap/common.go deleted file mode 100644 index a1b64187..00000000 --- a/pkg/bootstrap/common.go +++ /dev/null @@ -1,315 +0,0 @@ -package bootstrap - -import ( - "context" - "fmt" - "os" - "path/filepath" - - "github.com/AlecAivazis/survey/v2" - awsConfig "github.com/aws/aws-sdk-go-v2/config" - "golang.org/x/oauth2/google" - v1 "k8s.io/api/core/v1" - meta "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/pluralsh/plural-cli/pkg/api" - "github.com/pluralsh/plural-cli/pkg/bootstrap/azure" - "github.com/pluralsh/plural-cli/pkg/kubernetes" - "github.com/pluralsh/plural-cli/pkg/manifest" - "github.com/pluralsh/plural-cli/pkg/utils" - "github.com/pluralsh/plural-cli/pkg/utils/git" - "github.com/pluralsh/plural-cli/pkg/utils/pathing" -) - -const localClusterContext = "kind-bootstrap" - -var disableAzurePodIdentityFlag = []string{"--set", "bootstrap.azurePodIdentity.enabled=false"} - -func bootstrapClusterExists() bool { - return utils.IsKindClusterAlreadyExists("bootstrap") -} - -func applyManifest(manifest string) error { - kube, err := kubernetes.Kubernetes() - if err != nil { - return err - } - - f, err := os.CreateTemp("", "manifest") - if err != nil { - return err - } - defer func(name string) { - err := os.Remove(name) - if err != nil { - utils.Error("%s", err) - } - }(f.Name()) - - _, err = f.WriteString(manifest) - if err != nil { - return err - } - - return kube.Apply(f.Name(), true) -} - -// deleteSecrets deletes secrets matching label selector from given namespace in given context. -func deleteSecrets(context, namespace, labelSelector string) error { - kubernetesClient, err := kubernetes.KubernetesWithContext(context) - if err != nil { - return err - } - - return kubernetesClient.SecretDeleteCollection(namespace, meta.DeleteOptions{}, meta.ListOptions{LabelSelector: labelSelector}) -} - -// getSecrets returns secrets matching label selector from given namespace in given context. -func getSecrets(context, namespace, labelSelector string) (*v1.SecretList, error) { - kubernetesClient, err := kubernetes.KubernetesWithContext(context) - if err != nil { - return nil, err - } - - return kubernetesClient.SecretList(namespace, meta.ListOptions{LabelSelector: labelSelector}) -} - -// createSecrets creates secrets in given context. -func createSecrets(context string, secrets []v1.Secret) error { - kubernetesClient, err := kubernetes.KubernetesWithContext(context) - if err != nil { - return err - } - - for _, secret := range secrets { - _, err := kubernetesClient.SecretCreate(secret.Namespace, prepareSecret(secret)) - if err != nil { - return err - } - } - - return nil -} - -// prepareSecret unsets read-only secret fields to prepare it for creation. -func prepareSecret(secret v1.Secret) *v1.Secret { - secret.UID = "" - secret.ResourceVersion = "" - secret.Generation = 0 - secret.CreationTimestamp = meta.Time{} - return &secret -} - -// moveHelmSecrets moves secrets owned by Helm from one cluster to another. -func moveHelmSecrets(sourceContext, targetContext string) error { - err := deleteSecrets(targetContext, "bootstrap", "owner=helm") - if err != nil { - return err - } - - secrets, err := getSecrets(sourceContext, "bootstrap", "owner=helm") - if err != nil { - return err - } - - return createSecrets(targetContext, secrets.Items) -} - -// getBootstrapFlags returns list of provider-specific flags used during cluster bootstrap and destroy. -func getBootstrapFlags(prov string) []string { - switch prov { - case api.ProviderAWS: - return []string{ - "--set", "cluster-api-provider-aws.cluster-api-provider-aws.bootstrapMode=true", - "--set", "bootstrap.aws-ebs-csi-driver.enabled=false", - "--set", "bootstrap.aws-load-balancer-controller.enabled=false", - "--set", "bootstrap.cluster-autoscaler.enabled=false", - "--set", "bootstrap.metrics-server.enabled=false", - "--set", "bootstrap.snapshot-controller.enabled=false", - "--set", "bootstrap.snapshot-validation-webhook.enabled=false", - "--set", "bootstrap.tigera-operator.enabled=false", - "--set", "bootstrap.external-dns.enabled=false", - "--set", "plural-certmanager-webhook.enabled=false", - } - case api.ProviderAzure: - return []string{ - "--set", "cluster-api-cluster.cluster.azure.clusterIdentity.bootstrapMode=true", - "--set", "cluster-api-provider-azure.cluster-api-provider-azure.bootstrapMode=true", - "--set", "bootstrap.external-dns.enabled=false", - "--set", "plural-certmanager-webhook.enabled=false", - } - case api.ProviderGCP: - return []string{ - "--set", "cluster-api-provider-gcp.cluster-api-provider-gcp.bootstrapMode=true", - "--set", "bootstrap.external-dns.enabled=false", - "--set", "plural-certmanager-webhook.enabled=false", - } - default: - return []string{} - } -} - -// getKubeconfigPath returns path to kubeconfig in user home directory. -func getKubeconfigPath() (string, error) { - homeDir, err := os.UserHomeDir() - if err != nil { - return "", err - } - - return pathing.SanitizeFilepath(filepath.Join(homeDir, ".kube", "config")), nil -} - -// GetBootstrapPath returns bootstrap repository path. -func GetBootstrapPath() (string, error) { - gitRootPath, err := git.Root() - if err != nil { - return "", err - } - - return pathing.SanitizeFilepath(filepath.Join(gitRootPath, "bootstrap")), nil -} - -// GetStepPath returns path from which step will be executed. -func GetStepPath(step *Step, defaultPath string) string { - if step != nil && step.TargetPath != "" { - return step.TargetPath - } - - return defaultPath -} - -func FilterSteps(steps []*Step) []*Step { - filteredSteps := make([]*Step, 0, len(steps)) - for _, step := range steps { - if !step.Skip { - filteredSteps = append(filteredSteps, step) - } - } - - return filteredSteps -} - -// ExecuteSteps of a bootstrap, migration or destroy process. -func ExecuteSteps(steps []*Step) error { - defaultPath, err := GetBootstrapPath() - if err != nil { - return err - } - - filteredSteps := FilterSteps(steps) - for i, step := range filteredSteps { - if len(step.Confirm) > 0 { - res := true - prompt := &survey.Confirm{Message: step.Confirm} - if err := survey.AskOne(prompt, &res, survey.WithValidator(survey.Required)); err != nil || !res { - continue - } - } - - utils.Highlight("[%d/%d] %s\n", i+1, len(filteredSteps), step.Name) - - if step.SkipFunc != nil && step.SkipFunc() { - utils.Highlight("Skipping step [%d/%d]\n", i+1, len(filteredSteps)) - continue - } - - path := GetStepPath(step, defaultPath) - err := os.Chdir(path) - if err != nil { - return err - } - - for j := 0; j <= step.Retries; j++ { - if j > 0 { - utils.Highlight("Retrying, attempt %d of %d...\n", j, step.Retries) - } - err = step.Execute(step.Args) - if err == nil { - if step.OnAfter != nil { - step.OnAfter() - } - - break - } - utils.Error("[%d/%d] %s failed: %s\n", i+1, len(filteredSteps), step.Name, err) - } - if err != nil { - if step.OnError != nil { - step.OnError() - } - - return err - } - } - - return nil -} - -// RunWithTempCredentials is a function wrapper that provides provider-specific flags with credentials -// that are used during bootstrap and destroy. -func RunWithTempCredentials(function ActionFunc) error { - man, err := manifest.FetchProject() - if err != nil { - return err - } - - var flags []string - switch man.Provider { - case api.ProviderAzure: - as, err := azure.GetAuthService(utils.ToString(man.Context["SubscriptionId"])) - if err != nil { - return err - } - - clientId, clientSecret, err := as.Setup(man.Cluster) - if err != nil { - return err - } - - pathPrefix := "cluster-api-cluster.cluster.azure.clusterIdentity.bootstrapCredentials" - asoPathPrefix := "cluster-api-provider-azure.cluster-api-provider-azure.asoControllerSettings" - flags = []string{ - "--set", fmt.Sprintf("%s.%s=%s", pathPrefix, "clientID", clientId), - "--set", fmt.Sprintf("%s.%s=%s", pathPrefix, "clientSecret", clientSecret), - "--set", fmt.Sprintf("%s.%s=%s", asoPathPrefix, "azureClientId", clientId), - "--set", fmt.Sprintf("%s.%s=%s", asoPathPrefix, "azureClientSecret", clientSecret), - } - - defer func(as *azure.AuthService) { - err := as.Cleanup() - if err != nil { - utils.Error("%s", err) - } - }(as) - case api.ProviderAWS: - ctx := context.Background() - cfg, err := awsConfig.LoadDefaultConfig(ctx) - if err != nil { - return err - } - - cred, err := cfg.Credentials.Retrieve(ctx) - if err != nil { - return err - } - - pathPrefix := "cluster-api-provider-aws.cluster-api-provider-aws.managerBootstrapCredentials" - flags = []string{ - "--set", fmt.Sprintf("%s.%s=%s", pathPrefix, "AWS_ACCESS_KEY_ID", cred.AccessKeyID), - "--set", fmt.Sprintf("%s.%s=%s", pathPrefix, "AWS_SECRET_ACCESS_KEY", cred.SecretAccessKey), - "--set", fmt.Sprintf("%s.%s=%s", pathPrefix, "AWS_SESSION_TOKEN", cred.SessionToken), - "--set", fmt.Sprintf("%s.%s=%s", pathPrefix, "AWS_REGION", man.Region), - } - case api.ProviderGCP: - credentials, err := google.FindDefaultCredentials(context.Background()) - if err != nil { - return err - } - - flags = []string{ - "--setJSON", fmt.Sprintf(`cluster-api-provider-gcp.cluster-api-provider-gcp.managerBootstrapCredentials.credentialsJson=%s`, string(credentials.JSON)), - } - } - - return function(flags) -} diff --git a/pkg/bootstrap/common_test.go b/pkg/bootstrap/common_test.go deleted file mode 100644 index 0bb5a0ea..00000000 --- a/pkg/bootstrap/common_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package bootstrap_test - -import ( - "testing" - - "github.com/pluralsh/plural-cli/pkg/bootstrap" - "github.com/stretchr/testify/assert" - "golang.org/x/exp/slices" -) - -func doNothing(_ []string) error { - return nil -} - -func TestGetStepPath(t *testing.T) { - tests := []struct { - name string - step *bootstrap.Step - defaultPath string - expectedPath string - }{ - { - name: `step path should be used if it was set`, - step: &bootstrap.Step{ - Name: "Test", - Args: []string{}, - TargetPath: "/test/path", - Execute: doNothing, - }, - defaultPath: "/default/path", - expectedPath: "/test/path", - }, - { - name: `step path should be defaulted if not set`, - step: &bootstrap.Step{ - Name: "Test", - Args: []string{}, - Execute: doNothing, - }, - defaultPath: "/default/path", - expectedPath: "/default/path", - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - path := bootstrap.GetStepPath(test.step, test.defaultPath) - assert.Equal(t, path, test.expectedPath) - }) - } -} - -func TestFilterSteps(t *testing.T) { - tests := []struct { - name string - steps []*bootstrap.Step - expectedSteps []*bootstrap.Step - }{ - { - name: `steps without skip flag should not be filtered`, - steps: []*bootstrap.Step{ - { - Name: "Test", - Execute: doNothing, - }, - }, - expectedSteps: []*bootstrap.Step{ - { - Name: "Test", - Execute: doNothing, - }, - }, - }, - { - name: `steps with skip flag should be filtered`, - steps: []*bootstrap.Step{ - { - Name: "Test", - Execute: doNothing, - }, - { - Name: "Test AWS", - Execute: doNothing, - Skip: true, - }, - { - Name: "Test Azure", - Execute: doNothing, - Skip: false, - }, - { - Name: "Test GCP", - Execute: doNothing, - Skip: true, - }, - }, - expectedSteps: []*bootstrap.Step{ - { - Name: "Test", - Execute: doNothing, - }, - { - Name: "Test Azure", - Execute: doNothing, - Skip: false, - }, - }, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - filteredSteps := bootstrap.FilterSteps(test.steps) - assert.Equal(t, len(filteredSteps), len(test.expectedSteps)) - assert.True(t, slices.EqualFunc(filteredSteps, test.expectedSteps, - func(a *bootstrap.Step, b *bootstrap.Step) bool { - return a.Name == b.Name && a.Skip == b.Skip - })) - }) - } -} diff --git a/pkg/bootstrap/destroy.go b/pkg/bootstrap/destroy.go deleted file mode 100644 index 1f734fbf..00000000 --- a/pkg/bootstrap/destroy.go +++ /dev/null @@ -1,117 +0,0 @@ -package bootstrap - -import ( - "github.com/pluralsh/plural-cli/pkg/manifest" - "github.com/pluralsh/plural-cli/pkg/provider" - "github.com/pluralsh/plural-cli/pkg/utils" -) - -// getDestroySteps returns list of steps to run during cluster destroy. -func getDestroySteps(destroy func() error, runPlural ActionFunc, additionalFlags []string) ([]*Step, error) { - man, err := manifest.FetchProject() - if err != nil { - return nil, err - } - - kubeconfigPath, err := getKubeconfigPath() - if err != nil { - return nil, err - } - - flags := append(getBootstrapFlags(man.Provider), additionalFlags...) - - prov, err := provider.GetProvider() - if err != nil { - return nil, err - } - - return []*Step{ - { - Name: "Create local bootstrap cluster", - Args: []string{"plural", "bootstrap", "cluster", "create", "bootstrap", "--skip-if-exists"}, - Execute: runPlural, - }, - { - Name: "Bootstrap CRDs in local cluster", - Args: []string{"plural", "--bootstrap", "wkspace", "crds", "bootstrap"}, - Execute: runPlural, - }, - { - Name: "Install Cluster API operators in local cluster", - Args: append([]string{"plural", "--bootstrap", "wkspace", "helm", "bootstrap", "--skip", "cluster-api-cluster"}, append(flags, disableAzurePodIdentityFlag...)...), - Execute: runPlural, - }, - { - Name: "Move resources from target to local cluster", - Args: []string{"plural", "bootstrap", "cluster", "move", "--kubeconfig-context", prov.KubeContext(), "--to-kubeconfig", kubeconfigPath, "--to-kubeconfig-context", localClusterContext}, - Execute: runPlural, - SkipFunc: func() bool { - _, err := CheckClusterReadiness(man.Cluster, "bootstrap") - return err != nil - }, - Retries: 2, - }, - { - Name: "Move Helm secrets", - Execute: func(_ []string) error { - return moveHelmSecrets(prov.KubeContext(), localClusterContext) - }, - Retries: 2, - }, - { - Name: "Reinstall Helm charts to update configuration", - Args: append([]string{"plural", "--bootstrap", "wkspace", "helm", "bootstrap"}, flags...), - Execute: runPlural, - }, - { - Name: "Destroy bootstrap on target cluster", - Execute: func(_ []string) error { - return destroy() - }, - }, - { - Name: "Wait for cluster", - Args: []string{"plural", "--bootstrap", "clusters", "wait", "bootstrap", man.Cluster}, - Execute: runPlural, - }, - { - Name: "Wait for machine pools", - Args: []string{"plural", "--bootstrap", "clusters", "mpwait", "bootstrap", man.Cluster}, - Execute: runPlural, - }, - { - Name: "Destroy cluster API", - Args: []string{"plural", "bootstrap", "cluster", "destroy-cluster-api", man.Cluster}, - Execute: runPlural, - }, - { - Name: "Destroy local bootstrap cluster", - Args: []string{"plural", "--bootstrap", "bootstrap", "cluster", "delete", "bootstrap"}, - Execute: runPlural, - }, - }, nil -} - -// DestroyCluster destroys cluster managed by Cluster API. -func DestroyCluster(destroy func() error, runPlural ActionFunc) error { - utils.Highlight("Destroying Cluster API cluster...\n") - - if err := RunWithTempCredentials(func(flags []string) error { - steps, err := getDestroySteps(destroy, runPlural, flags) - if err != nil { - return err - } - - err = ExecuteSteps(steps) - if err != nil { - return err - } - - return nil - }); err != nil { - return err - } - - utils.Success("Cluster destroyed successfully!\n") - return nil -} diff --git a/pkg/bootstrap/manifests.go b/pkg/bootstrap/manifests.go deleted file mode 100644 index 7095f490..00000000 --- a/pkg/bootstrap/manifests.go +++ /dev/null @@ -1,132 +0,0 @@ -package bootstrap - -const storageClassManifest = ` -apiVersion: v1 -kind: Namespace -metadata: - name: local-path-storage - ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: local-path-provisioner-service-account - namespace: local-path-storage - ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: local-path-provisioner-role -rules: - - apiGroups: [ "" ] - resources: [ "nodes", "persistentvolumeclaims", "configmaps" ] - verbs: [ "get", "list", "watch" ] - - apiGroups: [ "" ] - resources: [ "endpoints", "persistentvolumes", "pods" ] - verbs: [ "*" ] - - apiGroups: [ "" ] - resources: [ "events" ] - verbs: [ "create", "patch" ] - - apiGroups: [ "storage.k8s.io" ] - resources: [ "storageclasses" ] - verbs: [ "get", "list", "watch" ] - ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: local-path-provisioner-bind -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: local-path-provisioner-role -subjects: - - kind: ServiceAccount - name: local-path-provisioner-service-account - namespace: local-path-storage - ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: local-path-provisioner - namespace: local-path-storage -spec: - replicas: 1 - selector: - matchLabels: - app: local-path-provisioner - template: - metadata: - labels: - app: local-path-provisioner - spec: - serviceAccountName: local-path-provisioner-service-account - containers: - - name: local-path-provisioner - image: rancher/local-path-provisioner:v0.0.24 - imagePullPolicy: IfNotPresent - command: - - local-path-provisioner - - --debug - - start - - --config - - /etc/config/config.json - volumeMounts: - - name: config-volume - mountPath: /etc/config/ - env: - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - volumes: - - name: config-volume - configMap: - name: local-path-config ---- -apiVersion: storage.k8s.io/v1 -kind: StorageClass -metadata: - annotations: - storageclass.kubernetes.io/is-default-class: "true" - name: standard -provisioner: rancher.io/local-path -reclaimPolicy: Delete -volumeBindingMode: WaitForFirstConsumer ---- -kind: ConfigMap -apiVersion: v1 -metadata: - name: local-path-config - namespace: local-path-storage -data: - config.json: |- - { - "nodePathMap":[ - { - "node":"DEFAULT_PATH_FOR_NON_LISTED_NODES", - "paths":["/opt/local-path-provisioner"] - } - ] - } - setup: |- - #!/bin/sh - set -eu - mkdir -m 0777 -p "$VOL_DIR" - teardown: |- - #!/bin/sh - set -eu - rm -rf "$VOL_DIR" - helperPod.yaml: |- - apiVersion: v1 - kind: Pod - metadata: - name: helper-pod - spec: - containers: - - name: helper-pod - image: busybox - imagePullPolicy: IfNotPresent -` diff --git a/pkg/bootstrap/migrate.go b/pkg/bootstrap/migrate.go deleted file mode 100644 index 748557a9..00000000 --- a/pkg/bootstrap/migrate.go +++ /dev/null @@ -1,416 +0,0 @@ -package bootstrap - -import ( - "fmt" - "log" - "os" - "path/filepath" - "strings" - - tfjson "github.com/hashicorp/terraform-json" - migratorapi "github.com/pluralsh/cluster-api-migration/pkg/api" - "github.com/pluralsh/cluster-api-migration/pkg/migrator" - "github.com/pluralsh/polly/containers" - delinkeranalyze "github.com/pluralsh/terraform-delinker/api/analyze/v1alpha1" - delinkerdelink "github.com/pluralsh/terraform-delinker/api/delink/v1alpha1" - delinkerexec "github.com/pluralsh/terraform-delinker/api/exec/v1alpha1" - delinkerplan "github.com/pluralsh/terraform-delinker/api/plan/v1alpha1" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - "sigs.k8s.io/yaml" - - "github.com/pluralsh/plural-cli/pkg/api" - bootstrapaws "github.com/pluralsh/plural-cli/pkg/bootstrap/aws" - "github.com/pluralsh/plural-cli/pkg/manifest" - "github.com/pluralsh/plural-cli/pkg/provider" - "github.com/pluralsh/plural-cli/pkg/utils" - "github.com/pluralsh/plural-cli/pkg/utils/git" - "github.com/pluralsh/plural-cli/pkg/utils/pathing" -) - -func newConfiguration(cliProvider provider.Provider, clusterProvider migratorapi.ClusterProvider) (*migratorapi.Configuration, error) { - switch clusterProvider { - case migratorapi.ClusterProviderGCP: - kubeconfigPath, err := getKubeconfigPath() - if err != nil { - log.Fatalln(err) - } - - return &migratorapi.Configuration{ - GCPConfiguration: &migratorapi.GCPConfiguration{ - Project: cliProvider.Project(), - Region: cliProvider.Region(), - Name: cliProvider.Cluster(), - KubeconfigPath: kubeconfigPath, - }, - }, nil - case migratorapi.ClusterProviderAzure: - context := cliProvider.Context() - config := migratorapi.Configuration{ - AzureConfiguration: &migratorapi.AzureConfiguration{ - SubscriptionID: utils.ToString(context["SubscriptionId"]), - ResourceGroup: cliProvider.Project(), - Name: cliProvider.Cluster(), - }, - } - - if err := config.Validate(); err != nil { - log.Fatalln(err) - } - - return &config, nil - case migratorapi.ClusterProviderAWS: - err := os.Setenv("AWS_REGION", cliProvider.Region()) - if err != nil { - return nil, err - } - - config := &migratorapi.Configuration{ - AWSConfiguration: &migratorapi.AWSConfiguration{ - ClusterName: cliProvider.Cluster(), - Region: cliProvider.Region(), - }, - } - return config, nil - case migratorapi.ClusterProviderKind: - return &migratorapi.Configuration{ - KindConfiguration: &migratorapi.KindConfiguration{ - ClusterName: cliProvider.Cluster(), - }, - }, nil - - } - - return nil, fmt.Errorf("unknown provider, no configuration found") -} - -// getMigrator returns configured migrator for current provider. -func getMigrator() (migratorapi.Migrator, error) { - prov, err := provider.GetProvider() - if err != nil { - return nil, err - } - - clusterProvider := migratorapi.ClusterProvider(prov.Name()) - - configuration, err := newConfiguration(prov, clusterProvider) - if err != nil { - return nil, err - } - - return migrator.NewMigrator(clusterProvider, configuration) -} - -func isDesiredKubernetesVersion(key string, value, diffValue any) bool { - if key != "kubernetesVersion" { - return false - } - - defaultKubernetesVersion, _ := diffValue.(string) - currentKubernetesVersion, _ := value.(string) - - defaultKubernetesVersion = strings.TrimPrefix(defaultKubernetesVersion, "v") - currentKubernetesVersion = strings.TrimPrefix(currentKubernetesVersion, "v") - - return len(defaultKubernetesVersion) > 0 && strings.HasPrefix(currentKubernetesVersion, defaultKubernetesVersion) -} - -// generateValuesFile generates values.yaml file based on current cluster configuration that will be used by Cluster API. -func generateValuesFile() error { - utils.Highlight("Generating values.yaml file based on current cluster configuration...\n") - - gitRootDir, err := git.Root() - if err != nil { - return err - } - - bootstrapHelmDir := pathing.SanitizeFilepath(filepath.Join(gitRootDir, "bootstrap", "helm", "bootstrap")) - valuesFile := pathing.SanitizeFilepath(filepath.Join(bootstrapHelmDir, "values.yaml")) - defaultValuesFile := pathing.SanitizeFilepath(filepath.Join(bootstrapHelmDir, "default-values.yaml")) - - m, err := getMigrator() - if err != nil { - return err - } - - migratorValues, err := m.Convert() - if err != nil { - return err - } - - prov, err := provider.GetProvider() - if err != nil { - return err - } - - if prov.Name() == api.ProviderAWS { - availabilityZoneSet := containers.NewSet[string]() - for _, subnet := range migratorValues.Cluster.AWSCloudSpec.NetworkSpec.Subnets { - availabilityZoneSet.Add(subnet.AvailabilityZone) - } - man, err := manifest.FetchProject() - if err != nil { - return err - } - man.AvailabilityZones = availabilityZoneSet.List() - if err := man.Flush(); err != nil { - return err - } - } - - chart, err := loader.Load(bootstrapHelmDir) - if err != nil { - return err - } - - defaultValues, err := chartutil.ReadValuesFile(defaultValuesFile) - if err != nil { - return err - } - - // Nullify main values.yaml as we only use it to generate migration values - chart.Values = nil - chartValues, err := chartutil.CoalesceValues(chart, defaultValues) - if err != nil { - return err - } - - migrationYamlData, err := yaml.Marshal(Bootstrap{ClusterAPICluster: migratorValues}) - if err != nil { - return err - } - - migrationValues, err := chartutil.ReadValues(migrationYamlData) - if err != nil { - return err - } - - values := utils.DiffMap(migrationValues, chartValues, isDesiredKubernetesVersion) - - valuesYamlData, err := yaml.Marshal(values) - if err != nil { - return err - } - - if utils.Exists(valuesFile) { - if err := os.WriteFile(valuesFile, valuesYamlData, 0644); err != nil { - return err - } - } else { - return fmt.Errorf("can't save %s file", valuesFile) - } - - utils.Success("values.yaml saved successfully!\n") - return nil -} - -// GetProviderTags returns map of tags to set on provider resources during migration. -func GetProviderTags(prov, cluster string) map[string]string { - switch prov { - case api.ProviderAWS: - return map[string]string{ - fmt.Sprintf("kubernetes.io/cluster/%s", cluster): "owned", - fmt.Sprintf("sigs.k8s.io/cluster-api-provider-aws/cluster/%s", cluster): "owned", - } - case api.ProviderAzure: - return map[string]string{ - fmt.Sprintf("sigs.k8s.io_cluster-api-provider-azure_cluster_%s", cluster): "owned", - "sigs.k8s.io_cluster-api-provider-azure_role": "common", - } - default: - return map[string]string{} - } -} - -// delinkTerraformState delinks resources managed by Cluster API from Terraform state. -func delinkTerraformState(path string) error { - planner := delinkerplan.NewPlanner(delinkerplan.WithTerraform(delinkerexec.WithDir(path))) - - plan, err := planner.Plan() - if err != nil { - return err - } - - report := delinkeranalyze.NewAnalyzer(plan).Analyze(tfjson.ActionDelete) - delinker := delinkerdelink.NewDelinker(delinkerdelink.WithTerraform(delinkerexec.WithDir(path))) - return delinker.Run(report) -} - -// getMigrationFlags returns list of provider-specific flags used during cluster migration. -func getMigrationFlags(prov string) []string { - switch prov { - case api.ProviderAWS: - return []string{ - "--set", "cluster-api-provider-aws.cluster-api-provider-aws.bootstrapMode=false", - } - case api.ProviderAzure: - return []string{ - "--set", "cluster-api-provider-azure.cluster-api-provider-azure.bootstrapMode=false", - } - case api.ProviderGCP: - return []string{ - "--set", "cluster-api-provider-gcp.cluster-api-provider-gcp.bootstrapMode=false", - } - default: - return []string{} - } -} - -// getMigrationSteps returns list of steps to run during cluster migration. -func getMigrationSteps(runPlural ActionFunc) ([]*Step, error) { - man, err := manifest.FetchProject() - if err != nil { - return nil, err - } - - gitRootDir, err := git.Root() - if err != nil { - return nil, err - } - - bootstrapPath := pathing.SanitizeFilepath(filepath.Join(gitRootDir, "bootstrap")) - terraformPath := filepath.Join(bootstrapPath, "terraform") - flags := getMigrationFlags(man.Provider) - - if man.Provider == api.ProviderAzure { - // Setting PLURAL_PACKAGES_UNINSTALL variable to avoid confirmation prompt on package uninstall. - err := os.Setenv("PLURAL_PACKAGES_UNINSTALL", "true") - if err != nil { - return nil, err - } - } - - return []*Step{ - { - Name: "Ensure Cluster API IAM role has access", - Execute: func(_ []string) error { - roleArn := fmt.Sprintf("arn:aws:iam::%s:role/%s-capa-controller", man.Project, man.Cluster) - return bootstrapaws.AddRole(roleArn) - }, - Skip: man.Provider != api.ProviderAWS, - }, - { - Name: "Uninstall azure-identity package", - Args: []string{"plural", "packages", "uninstall", "helm", "bootstrap", "azure-identity"}, - TargetPath: gitRootDir, - Execute: runPlural, - Skip: man.Provider != api.ProviderAzure, - Retries: 2, - }, - { - Name: "Clear package cache", - TargetPath: gitRootDir, - Execute: func(_ []string) error { - api.ClearPackageCache() - return nil - }, - Skip: man.Provider != api.ProviderAzure, - }, - { - Name: "Normalize GCP provider value", - Execute: func(_ []string) error { - path := manifest.ProjectManifestPath() - project, err := manifest.ReadProject(path) - if err != nil { - return err - } - - project.Provider = api.ProviderGCP - return project.Write(path) - }, - Skip: man.Provider != api.ProviderGCP, - }, - { - Name: "Set Cluster API flag", - TargetPath: gitRootDir, - Execute: func(_ []string) error { - path := manifest.ProjectManifestPath() - project, err := manifest.ReadProject(path) - if err != nil { - return err - } - - project.ClusterAPI = true - return project.Write(path) - }, - }, - { - Name: "Build values", - Args: []string{"plural", "build", "--only", "bootstrap", "--force"}, - TargetPath: gitRootDir, - Execute: runPlural, - }, - { - Name: "Bootstrap CRDs", - Args: []string{"plural", "wkspace", "crds", bootstrapPath}, - Execute: runPlural, - }, - { - Name: "Install Cluster API operators", - Args: append([]string{"plural", "wkspace", "helm", "bootstrap", "--skip", "cluster-api-cluster"}, flags...), - Execute: runPlural, - }, - { - Name: "Add Cluster API tags for provider resources", - Execute: func(_ []string) error { - m, err := getMigrator() - if err != nil { - return err - } - - return m.AddTags(GetProviderTags(man.Provider, man.Cluster)) - }, - }, - { - Name: "Deploy cluster", - Args: append([]string{"plural", "wkspace", "helm", "bootstrap"}, flags...), - Execute: runPlural, - }, - { - Name: "Wait for cluster", - Args: []string{"plural", "clusters", "wait", "bootstrap", man.Cluster}, - Execute: runPlural, - }, - { - Name: "Wait for machine pools", - Args: []string{"plural", "clusters", "mpwait", "bootstrap", man.Cluster}, - Execute: runPlural, - }, - { - Name: "Delink resources managed by Cluster API from Terraform state", - Execute: func(_ []string) error { - return delinkTerraformState(terraformPath) - }, - Retries: 2, - }, - { - Name: "Run deploy", - Args: []string{"plural", "deploy", "--from", "bootstrap", "--silence", "--commit", "migrate to cluster api"}, - TargetPath: gitRootDir, - Execute: runPlural, - }, - }, nil -} - -// MigrateCluster migrates existing clusters to Cluster API. -func MigrateCluster(runPlural ActionFunc) error { - utils.Highlight("Migrating cluster to Cluster API...\n") - - err := generateValuesFile() - if err != nil { - return err - } - - steps, err := getMigrationSteps(runPlural) - if err != nil { - return err - } - - err = ExecuteSteps(steps) - if err != nil { - return err - } - - utils.Success("Cluster migrated successfully!\n") - return nil -} diff --git a/pkg/bootstrap/types.go b/pkg/bootstrap/types.go deleted file mode 100644 index 9b198cf7..00000000 --- a/pkg/bootstrap/types.go +++ /dev/null @@ -1,32 +0,0 @@ -package bootstrap - -import "github.com/pluralsh/cluster-api-migration/pkg/api" - -// ActionFunc is an action function that is executed as a part of single bootstrap, migrate and destroy step. -type ActionFunc func(arguments []string) error - -// ConditionFunc is a condition function that is checks if step should be executed or skipped. -type ConditionFunc func() bool - -type HookFunc func() - -// Step is a representation of a single step in a process of bootstrap, migrate and destroy. -type Step struct { - Name string - Args []string - TargetPath string - Execute ActionFunc - Skip bool - SkipFunc ConditionFunc - Retries int - OnError HookFunc - OnAfter HookFunc - Confirm string -} - -// Bootstrap is a representation of existing cluster to be migrated to Cluster API. -// This data is fetched from provider with migrator tool. -// See github.com/pluralsh/cluster-api-migration for more details. -type Bootstrap struct { - ClusterAPICluster *api.Values `json:"cluster-api-cluster"` -} diff --git a/pkg/bootstrap/validation/migration.go b/pkg/bootstrap/validation/migration.go deleted file mode 100644 index 371fed8a..00000000 --- a/pkg/bootstrap/validation/migration.go +++ /dev/null @@ -1,69 +0,0 @@ -package validation - -import ( - "fmt" - - "github.com/Masterminds/semver" - "github.com/pluralsh/plural-cli/pkg/api" -) - -var ( - tfRequirements = map[string]string{ - "aws-bootstrap": ">= 0.1.53", - "gcp-bootstrap": ">= 0.2.24", - "azure-bootstrap": ">= 0.2.0", - } - helmRequirements = map[string]string{ - "bootstrap": ">= 0.8.72", - } -) - -func ValidateMigration(client api.Client) error { - repo, err := client.GetRepository("bootstrap") - if err != nil { - return err - } - - charts, tfs, err := client.GetPackageInstallations(repo.Id) - if err != nil { - return err - } - chartsByName, tfsByName := map[string]*api.ChartInstallation{}, map[string]*api.TerraformInstallation{} - for _, chart := range charts { - chartsByName[chart.Chart.Name] = chart - } - for _, tf := range tfs { - tfsByName[tf.Terraform.Name] = tf - } - - for name, req := range tfRequirements { - if tf, ok := tfsByName[name]; ok { - if !testSemver(req, tf.Version.Version) { - return fmt.Errorf("You must have installed the %s terraform module at least at version %s to run cluster migration, your version is %s", name, req, tf.Version.Version) - } - } - } - - for name, req := range helmRequirements { - if chart, ok := chartsByName[name]; ok { - if !testSemver(req, chart.Version.Version) { - return fmt.Errorf("You must have installed the %s helm chart at least at version %s to run cluster migration, your version is %s", name, req, chart.Version.Version) - } - } - } - - return nil -} - -func testSemver(constraint, vsn string) bool { - c, err := semver.NewConstraint(constraint) - if err != nil { - return false - } - v, err := semver.NewVersion(vsn) - if err != nil { - return false - } - - return c.Check(v) -} diff --git a/pkg/bundle/configuration_test.go b/pkg/bundle/configuration_test.go index 36ecf161..44ec1ea5 100644 --- a/pkg/bundle/configuration_test.go +++ b/pkg/bundle/configuration_test.go @@ -114,7 +114,7 @@ func TestConfigureEnvVariables(t *testing.T) { ctx: map[string]interface{}{}, repo: "test", envVars: map[string]string{"PLURAL_TEST_TEST_ITEM": "workspace.yaml"}, - expectedValue: "apiVersion: \"\"\nkind: \"\"\nmetadata: null\nspec:\n clusterapi: false\n cluster: \"\"\n bucket: \"\"\n project: test\n provider: \"\"\n region: \"\"\n owner: null\n network: null\n availabilityzones: []\n bucketPrefix: \"\"\n context: {}\n", + expectedValue: "apiVersion: \"\"\nkind: \"\"\nmetadata: null\nspec:\n cluster: \"\"\n bucket: \"\"\n project: test\n provider: \"\"\n region: \"\"\n owner: null\n network: null\n availabilityzones: []\n bucketPrefix: \"\"\n context: {}\n", }, } for _, test := range tests { diff --git a/pkg/client/build.go b/pkg/client/build.go index 7f9b6692..fc294a1e 100644 --- a/pkg/client/build.go +++ b/pkg/client/build.go @@ -77,23 +77,6 @@ func (p *Plural) Deploy(c *cli.Context) error { continue } - // if repo == Bootstrap && project.ClusterAPI { - // ready, err := bootstrap.CheckClusterReadiness(project.Cluster, Bootstrap) - // - // // Stop if cluster exists, but it is not ready yet. - // if err != nil && err.Error() == bootstrap.ClusterNotReadyError { - // return err - // } - // - // // If cluster does not exist bootstrap needs to be done first. - // if !ready { - // err := bootstrap.BootstrapCluster(plural.RunPlural) - // if err != nil { - // return err - // } - // } - //} - execution, err := executor.GetExecution(pathing.SanitizeFilepath(filepath.Join(repoRoot, repo)), "deploy") if err != nil { return err diff --git a/pkg/cluster/clientset.go b/pkg/cluster/clientset.go deleted file mode 100644 index 95b58355..00000000 --- a/pkg/cluster/clientset.go +++ /dev/null @@ -1,41 +0,0 @@ -package cluster - -import ( - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" - clusterapi "sigs.k8s.io/cluster-api/api/v1beta1" -) - -type ClusterV1Beta1Interface interface { - Clusters(namespace string) ClusterInterface -} - -type ClusterV1Beta1Client struct { - restClient rest.Interface -} - -func NewForConfig(c *rest.Config) (*ClusterV1Beta1Client, error) { - if err := AddToScheme(scheme.Scheme); err != nil { - return nil, err - } - - config := *c - config.ContentConfig.GroupVersion = &clusterapi.GroupVersion - config.APIPath = "/apis" - config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() - config.UserAgent = rest.DefaultKubernetesUserAgent() - - client, err := rest.RESTClientFor(&config) - if err != nil { - return nil, err - } - - return &ClusterV1Beta1Client{restClient: client}, nil -} - -func (c *ClusterV1Beta1Client) Clusters(namespace string) ClusterInterface { - return &clusterClient{ - restClient: c.restClient, - ns: namespace, - } -} diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go deleted file mode 100644 index d9cbd8ca..00000000 --- a/pkg/cluster/cluster.go +++ /dev/null @@ -1,83 +0,0 @@ -package cluster - -import ( - "context" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" - clusterapi "sigs.k8s.io/cluster-api/api/v1beta1" -) - -type ClusterInterface interface { - List(ctx context.Context, opts metav1.ListOptions) (*clusterapi.ClusterList, error) - Get(ctx context.Context, name string, options metav1.GetOptions) (*clusterapi.Cluster, error) - Create(ctx context.Context, clust *clusterapi.Cluster) (*clusterapi.Cluster, error) - Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) - // ... -} - -type clusterClient struct { - restClient rest.Interface - ns string -} - -func (c *clusterClient) List(ctx context.Context, opts metav1.ListOptions) (*clusterapi.ClusterList, error) { - result := clusterapi.ClusterList{} - err := c.restClient. - Get(). - Namespace(c.ns). - Resource("clusters"). - VersionedParams(&opts, scheme.ParameterCodec). - Do(ctx). - Into(&result) - - return &result, err -} - -func (c *clusterClient) Get(ctx context.Context, name string, opts metav1.GetOptions) (*clusterapi.Cluster, error) { - result := clusterapi.Cluster{} - err := c.restClient. - Get(). - Namespace(c.ns). - Resource("clusters"). - Name(name). - VersionedParams(&opts, scheme.ParameterCodec). - Do(ctx). - Into(&result) - - return &result, err -} - -func (c *clusterClient) Create(ctx context.Context, cluster *clusterapi.Cluster) (*clusterapi.Cluster, error) { - result := clusterapi.Cluster{} - err := c.restClient. - Post(). - Namespace(c.ns). - Resource("clusters"). - Body(cluster). - Do(ctx). - Into(&result) - - return &result, err -} - -func (c *clusterClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { - opts.Watch = true - return c.restClient. - Get(). - Namespace(c.ns). - Resource("clusters"). - VersionedParams(&opts, scheme.ParameterCodec). - Watch(ctx) -} - -func WatchNamespace(ctx context.Context, client ClusterInterface) (watch.Interface, error) { - clusters, err := client.List(ctx, metav1.ListOptions{}) - if err != nil { - return nil, err - } - resourceVersion := clusters.ListMeta.ResourceVersion - return client.Watch(ctx, metav1.ListOptions{ResourceVersion: resourceVersion}) -} diff --git a/pkg/cluster/printer.go b/pkg/cluster/printer.go deleted file mode 100644 index 89f95bd7..00000000 --- a/pkg/cluster/printer.go +++ /dev/null @@ -1,84 +0,0 @@ -package cluster - -import ( - "fmt" - "strings" - - tm "github.com/buger/goterm" - clusterapi "sigs.k8s.io/cluster-api/api/v1beta1" -) - -func Ready(cluster *clusterapi.Cluster) bool { - cond := findReadiness(cluster) - tm.Printf("Cluster %s ", cluster.Name) - if cond == nil { - warn("WAITING") - tm.Println("") - return false - } - - if cond.Status == "True" { - success("READY") - tm.Println("") - return true - } - - if cond.Status == "False" { - warn("WAITING") - } else if cond.Status == "Unknown" { - highlight("UNKNOWN") - } - - tm.Println("") - return false -} - -func Flush() { - for idx, str := range strings.SplitAfter(tm.Screen.String(), "\n") { - if idx == tm.Height()-1 { - _, err := tm.Output.WriteString("...") - if err != nil { - return - } - break - } - - _, err := tm.Output.WriteString(str) - if err != nil { - return - } - } - - if err := tm.Output.Flush(); err != nil { - return - } - tm.Screen.Reset() -} - -func findReadiness(cluster *clusterapi.Cluster) (condition *clusterapi.Condition) { - for _, cond := range cluster.Status.Conditions { - if cond.Type == clusterapi.ReadyCondition { - condition = &cond - return - } - } - return -} - -func warn(line string, args ...interface{}) { - if _, err := tm.Print(tm.Color(fmt.Sprintf(line, args...), tm.YELLOW)); err != nil { - return - } -} - -func success(line string, args ...interface{}) { - if _, err := tm.Print(tm.Color(fmt.Sprintf(line, args...), tm.GREEN)); err != nil { - return - } -} - -func highlight(line string, args ...interface{}) { - if _, err := tm.Print(tm.Bold(fmt.Sprintf(line, args...))); err != nil { - return - } -} diff --git a/pkg/cluster/register.go b/pkg/cluster/register.go deleted file mode 100644 index b7ce864a..00000000 --- a/pkg/cluster/register.go +++ /dev/null @@ -1,38 +0,0 @@ -package cluster - -import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - schema "k8s.io/apimachinery/pkg/runtime/schema" - serializer "k8s.io/apimachinery/pkg/runtime/serializer" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - clusterapi "sigs.k8s.io/cluster-api/api/v1beta1" -) - -var Scheme = runtime.NewScheme() -var Codecs = serializer.NewCodecFactory(Scheme) -var ParameterCodec = runtime.NewParameterCodec(Scheme) -var localSchemeBuilder = runtime.SchemeBuilder{ - clusterapi.AddToScheme, -} - -// AddToScheme adds all types of this clientset into the given scheme. This allows composition -// of clientsets, like in: -// -// import ( -// "k8s.io/client-go/kubernetes" -// clientsetscheme "k8s.io/client-go/kubernetes/scheme" -// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" -// ) -// -// kclientset, _ := kubernetes.NewForConfig(c) -// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) -// -// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types -// correctly. -var AddToScheme = localSchemeBuilder.AddToScheme - -func init() { - v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) - utilruntime.Must(AddToScheme(Scheme)) -} diff --git a/pkg/cluster/waiter.go b/pkg/cluster/waiter.go deleted file mode 100644 index d7c01fca..00000000 --- a/pkg/cluster/waiter.go +++ /dev/null @@ -1,75 +0,0 @@ -package cluster - -import ( - "context" - "fmt" - "time" - - tm "github.com/buger/goterm" - "github.com/pluralsh/plural-cli/pkg/config" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/rest" - clusterapi "sigs.k8s.io/cluster-api/api/v1beta1" -) - -const ( - waitTime = 40 * 60 * time.Second -) - -func Waiter(kubeConf *rest.Config, namespace string, name string, clustFunc func(cluster *clusterapi.Cluster) (bool, error), timeout func() error) error { - conf := config.Read() - ctx := context.Background() - clusters, err := NewForConfig(kubeConf) - if err != nil { - return err - } - - client := clusters.Clusters(conf.Namespace(namespace)) - cluster, err := client.Get(ctx, name, metav1.GetOptions{}) - if err != nil { - return err - } - - tm.Clear() - if ready, err := clustFunc(cluster); ready || err != nil { - return err - } - - watcher, err := WatchNamespace(ctx, client) - if err != nil { - return err - } - - ch := watcher.ResultChan() - for { - select { - case event := <-ch: - tm.Clear() - cluster, ok := event.Object.(*clusterapi.Cluster) - if !ok { - return fmt.Errorf("failed to parse watch event") - } - - if stop, err := clustFunc(cluster); stop || err != nil { - return err - } - case <-time.After(waitTime): - if err := timeout(); err != nil { - return err - } - } - } -} - -func Wait(kubeConf *rest.Config, namespace string, name string) error { - timeout := func() error { - return fmt.Errorf("Failed to become ready after 40 minutes, try running `plural cluster watch %s %s` to get an idea where to debug", namespace, name) - } - - return Waiter(kubeConf, namespace, name, func(cluster *clusterapi.Cluster) (bool, error) { - tm.MoveCursor(1, 1) - ready := Ready(cluster) - Flush() - return ready, nil - }, timeout) -} diff --git a/pkg/machinepool/clientset.go b/pkg/machinepool/clientset.go deleted file mode 100644 index b87bc4a0..00000000 --- a/pkg/machinepool/clientset.go +++ /dev/null @@ -1,41 +0,0 @@ -package machinepool - -import ( - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" - clusterapiExp "sigs.k8s.io/cluster-api/exp/api/v1beta1" -) - -type MachinePoolV1Beta1Interface interface { - MachinePools(namespace string) MachinePoolInterface -} - -type MachinePoolV1Beta1Client struct { - restClient rest.Interface -} - -func NewForConfig(c *rest.Config) (*MachinePoolV1Beta1Client, error) { - if err := AddToScheme(scheme.Scheme); err != nil { - return nil, err - } - - config := *c - config.ContentConfig.GroupVersion = &clusterapiExp.GroupVersion - config.APIPath = "/apis" - config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() - config.UserAgent = rest.DefaultKubernetesUserAgent() - - client, err := rest.RESTClientFor(&config) - if err != nil { - return nil, err - } - - return &MachinePoolV1Beta1Client{restClient: client}, nil -} - -func (c *MachinePoolV1Beta1Client) MachinePools(namespace string) MachinePoolInterface { - return &machinepoolClient{ - restClient: c.restClient, - ns: namespace, - } -} diff --git a/pkg/machinepool/machinepool.go b/pkg/machinepool/machinepool.go deleted file mode 100644 index e6718672..00000000 --- a/pkg/machinepool/machinepool.go +++ /dev/null @@ -1,98 +0,0 @@ -package machinepool - -import ( - "context" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" - clusterapiExp "sigs.k8s.io/cluster-api/exp/api/v1beta1" -) - -type MachinePoolInterface interface { - List(ctx context.Context, opts metav1.ListOptions) (*clusterapiExp.MachinePoolList, error) - Get(ctx context.Context, name string, options metav1.GetOptions) (*clusterapiExp.MachinePool, error) - Create(ctx context.Context, mp *clusterapiExp.MachinePool) (*clusterapiExp.MachinePool, error) - Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) - Update(ctx context.Context, mp *clusterapiExp.MachinePool) (*clusterapiExp.MachinePool, error) - // ... -} - -type machinepoolClient struct { - restClient rest.Interface - ns string -} - -func (c *machinepoolClient) List(ctx context.Context, opts metav1.ListOptions) (*clusterapiExp.MachinePoolList, error) { - result := clusterapiExp.MachinePoolList{} - err := c.restClient. - Get(). - Namespace(c.ns). - Resource("machinepools"). - VersionedParams(&opts, scheme.ParameterCodec). - Do(ctx). - Into(&result) - - return &result, err -} - -func (c *machinepoolClient) Get(ctx context.Context, name string, opts metav1.GetOptions) (*clusterapiExp.MachinePool, error) { - result := clusterapiExp.MachinePool{} - err := c.restClient. - Get(). - Namespace(c.ns). - Resource("machinepools"). - Name(name). - VersionedParams(&opts, scheme.ParameterCodec). - Do(ctx). - Into(&result) - - return &result, err -} - -func (c *machinepoolClient) Update(ctx context.Context, mp *clusterapiExp.MachinePool) (*clusterapiExp.MachinePool, error) { - result := clusterapiExp.MachinePool{} - err := c.restClient. - Put(). - Namespace(c.ns). - Resource("machinepools"). - Name(mp.Name). - Body(mp). - Do(ctx). - Into(&result) - - return &result, err -} - -func (c *machinepoolClient) Create(ctx context.Context, machinepool *clusterapiExp.MachinePool) (*clusterapiExp.MachinePool, error) { - result := clusterapiExp.MachinePool{} - err := c.restClient. - Post(). - Namespace(c.ns). - Resource("machinepools"). - Body(machinepool). - Do(ctx). - Into(&result) - - return &result, err -} - -func (c *machinepoolClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { - opts.Watch = true - return c.restClient. - Get(). - Namespace(c.ns). - Resource("machinepools"). - VersionedParams(&opts, scheme.ParameterCodec). - Watch(ctx) -} - -func WatchNamespace(ctx context.Context, client MachinePoolInterface, listOps metav1.ListOptions) (watch.Interface, error) { - mps, err := client.List(ctx, listOps) - if err != nil { - return nil, err - } - resourceVersion := mps.ListMeta.ResourceVersion - return client.Watch(ctx, metav1.ListOptions{ResourceVersion: resourceVersion}) -} diff --git a/pkg/machinepool/printer.go b/pkg/machinepool/printer.go deleted file mode 100644 index 3def94f5..00000000 --- a/pkg/machinepool/printer.go +++ /dev/null @@ -1,115 +0,0 @@ -package machinepool - -import ( - "fmt" - "strings" - - tm "github.com/buger/goterm" - clusterapi "sigs.k8s.io/cluster-api/api/v1beta1" - clusterapiExp "sigs.k8s.io/cluster-api/exp/api/v1beta1" -) - -func Ready(mp *clusterapiExp.MachinePool) bool { - phase := findReadiness(mp) - tm.Printf("MachinePool %s ", mp.Name) - - switch phase { - case clusterapiExp.MachinePoolPhasePending: - warn("PENDING") - tm.Println("") - return false - case clusterapiExp.MachinePoolPhaseProvisioning: - warn("PROVISIONING") - tm.Println("") - return false - case clusterapiExp.MachinePoolPhaseProvisioned: - warn("PROVISIONED") - tm.Println("") - return false - case clusterapiExp.MachinePoolPhaseRunning: - success("RUNNING") - tm.Println("") - return true - case clusterapiExp.MachinePoolPhaseDeleting: - warn("DELETING") - tm.Println("") - return false - case clusterapiExp.MachinePoolPhaseFailed: - warn("FAILED") - tm.Println("") - return false - case clusterapiExp.MachinePoolPhaseUnknown: - highlight("UNKNOWN") - tm.Println("") - return false - case clusterapiExp.MachinePoolPhaseScalingUp: - warn("SCALING UP") - tm.Println("") - return false - case clusterapiExp.MachinePoolPhaseScalingDown: - warn("SCALING DOWN") - tm.Println("") - return false - case clusterapiExp.MachinePoolPhaseScaling: - warn("SCALING") - tm.Println("") - return false - } - - tm.Println("") - return false -} - -func Flush() { - for idx, str := range strings.SplitAfter(tm.Screen.String(), "\n") { - if idx == tm.Height()-1 { - _, err := tm.Output.WriteString("...") - if err != nil { - return - } - break - } - - _, err := tm.Output.WriteString(str) - if err != nil { - return - } - } - - if err := tm.Output.Flush(); err != nil { - return - } - tm.Screen.Reset() -} - -func findReadiness(mp *clusterapiExp.MachinePool) clusterapiExp.MachinePoolPhase { - return clusterapiExp.MachinePoolPhase(mp.Status.Phase) -} - -func findCondition(mp *clusterapiExp.MachinePool) (condition clusterapi.Condition) { - for _, cond := range mp.Status.Conditions { - if cond.Type == clusterapi.ReadyCondition { - condition = cond - return - } - } - return -} - -func warn(line string, args ...interface{}) { - if _, err := tm.Print(tm.Color(fmt.Sprintf(line, args...), tm.YELLOW)); err != nil { - return - } -} - -func success(line string, args ...interface{}) { - if _, err := tm.Print(tm.Color(fmt.Sprintf(line, args...), tm.GREEN)); err != nil { - return - } -} - -func highlight(line string, args ...interface{}) { - if _, err := tm.Print(tm.Bold(fmt.Sprintf(line, args...))); err != nil { - return - } -} diff --git a/pkg/machinepool/register.go b/pkg/machinepool/register.go deleted file mode 100644 index 9546b6be..00000000 --- a/pkg/machinepool/register.go +++ /dev/null @@ -1,38 +0,0 @@ -package machinepool - -import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - schema "k8s.io/apimachinery/pkg/runtime/schema" - serializer "k8s.io/apimachinery/pkg/runtime/serializer" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - clusterapiExp "sigs.k8s.io/cluster-api/exp/api/v1beta1" -) - -var Scheme = runtime.NewScheme() -var Codecs = serializer.NewCodecFactory(Scheme) -var ParameterCodec = runtime.NewParameterCodec(Scheme) -var localSchemeBuilder = runtime.SchemeBuilder{ - clusterapiExp.AddToScheme, -} - -// AddToScheme adds all types of this clientset into the given scheme. This allows composition -// of clientsets, like in: -// -// import ( -// "k8s.io/client-go/kubernetes" -// clientsetscheme "k8s.io/client-go/kubernetes/scheme" -// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" -// ) -// -// kclientset, _ := kubernetes.NewForConfig(c) -// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) -// -// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types -// correctly. -var AddToScheme = localSchemeBuilder.AddToScheme - -func init() { - v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) - utilruntime.Must(AddToScheme(Scheme)) -} diff --git a/pkg/machinepool/waiter.go b/pkg/machinepool/waiter.go deleted file mode 100644 index 26878260..00000000 --- a/pkg/machinepool/waiter.go +++ /dev/null @@ -1,294 +0,0 @@ -package machinepool - -import ( - "context" - "fmt" - "time" - - tm "github.com/buger/goterm" - "github.com/gdamore/tcell/v2" - "github.com/pluralsh/plural-cli/pkg/config" - "github.com/pluralsh/plural-cli/pkg/utils" - "github.com/rivo/tview" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/rest" - clusterapi "sigs.k8s.io/cluster-api/api/v1beta1" - clusterapiExp "sigs.k8s.io/cluster-api/exp/api/v1beta1" -) - -const ( - waitTime = 40 * 60 * time.Second -) - -func ListAll(kubeConf *rest.Config) ([]clusterapiExp.MachinePool, error) { - mps, err := NewForConfig(kubeConf) - if err != nil { - return nil, err - } - - client := mps.MachinePools("") - l, err := client.List(context.Background(), metav1.ListOptions{}) - if err != nil { - return nil, err - } - - return l.Items, nil -} - -type MachinePoolWaiter interface { - Init() - Check(mp *clusterapiExp.MachinePool) bool -} - -type machinePoolWaiterClient struct { - pools *clusterapiExp.MachinePoolList - phase map[string]clusterapiExp.MachinePoolPhase - condition map[string]clusterapi.Condition - app *tview.Application - table *tview.Table -} - -func (c *machinePoolWaiterClient) Init() { - c.phase = make(map[string]clusterapiExp.MachinePoolPhase) - c.condition = make(map[string]clusterapi.Condition) - for _, mp := range c.pools.Items { - c.phase[mp.Name] = findReadiness(&mp) - } - for _, mp := range c.pools.Items { - c.condition[mp.Name] = findCondition(&mp) - } - - app := tview.NewApplication() - c.app = app - table := tview.NewTable(). - SetBorders(true).SetDoneFunc(func(key tcell.Key) { - if key == tcell.KeyEscape { - app.Stop() - } - }) - c.table = table -} - -// UpdateTable updates the table with the current status of the machine pools -// the table has 2 columns, the first one is the name of the machine pool and the second one is the phase -func (c *machinePoolWaiterClient) UpdateTable() { - c.table.Clear() - headers := []string{"Machine Pool", "Phase"} - for i, header := range headers { - c.table.SetCell(0, i, tview.NewTableCell(header).SetTextColor(tcell.ColorYellow)) - } - for i, mp := range c.pools.Items { - name := mp.Name - phase := string(c.phase[name]) - c.table.SetCell(i+1, 0, tview.NewTableCell(name)) - c.table.SetCell(i+1, 1, tview.NewTableCell(phase)) - } -} - -func (c *machinePoolWaiterClient) Check(mp *clusterapiExp.MachinePool) bool { - c.phase[mp.Name] = findReadiness(mp) - c.condition[mp.Name] = findCondition(mp) - c.UpdateTable() - c.app.Draw() - - return areAllConditionsTrue(c.condition) -} - -// areAllConditionsTrue checks if all conditions in provided map are true. -func areAllConditionsTrue(conditions map[string]clusterapi.Condition) bool { - for _, condition := range conditions { - if condition.Status != corev1.ConditionTrue { - return false - } - } - return true -} - -func AllWaiter(kubeConf *rest.Config, namespace string, clusterName string, timeout func() error) error { - conf := config.Read() - ctx := context.Background() - mps, err := NewForConfig(kubeConf) - if err != nil { - return err - } - - label := &metav1.LabelSelector{MatchLabels: map[string]string{"cluster.x-k8s.io/cluster-name": clusterName}} - - client := mps.MachinePools(conf.Namespace(namespace)) - pools, err := client.List(ctx, metav1.ListOptions{LabelSelector: metav1.FormatLabelSelector(label)}) - if err != nil { - return err - } - if len(pools.Items) == 0 { - return fmt.Errorf("No machine pools found for cluster %s", clusterName) - } - - waitClient := &machinePoolWaiterClient{pools: pools} - - waitClient.Init() - - go func() { - if err := waitClient.app.SetRoot(waitClient.table, true).SetFocus(waitClient.table).Run(); err != nil { - utils.Error("%s\n", err) - panic(err) - } - }() - - if ready := waitClient.Check(&pools.Items[0]); ready { - waitClient.app.Stop() - return err - } - - watcher, err := WatchNamespace(ctx, client, metav1.ListOptions{LabelSelector: metav1.FormatLabelSelector(label)}) - if err != nil { - return err - } - - ch := watcher.ResultChan() - for { - select { - case event := <-ch: - mp, ok := event.Object.(*clusterapiExp.MachinePool) - if !ok { - waitClient.app.Stop() - return fmt.Errorf("Failed to parse watch event") - } - - if stop := waitClient.Check(mp); stop { - waitClient.app.Stop() - return nil - } - case <-time.After(waitTime): - waitClient.app.Stop() - if err := timeout(); err != nil { - return err - } - } - } -} - -func Waiter(kubeConf *rest.Config, namespace string, name string, mpFunc func(mp *clusterapiExp.MachinePool) (bool, error), timeout func() error) error { - conf := config.Read() - ctx := context.Background() - mps, err := NewForConfig(kubeConf) - if err != nil { - return err - } - - client := mps.MachinePools(conf.Namespace(namespace)) - mp, err := client.Get(ctx, name, metav1.GetOptions{}) - if err != nil { - return err - } - - if ready, err := mpFunc(mp); ready || err != nil { - return err - } - - watcher, err := WatchNamespace(ctx, client, metav1.ListOptions{}) - if err != nil { - return err - } - - ch := watcher.ResultChan() - for { - select { - case event := <-ch: - mp, ok := event.Object.(*clusterapiExp.MachinePool) - if !ok { - return fmt.Errorf("Failed to parse watch event") - } - - if stop, err := mpFunc(mp); stop || err != nil { - return err - } - case <-time.After(waitTime): - if err := timeout(); err != nil { - return err - } - } - } -} - -func SilentWait(kubeConf *rest.Config, namespace string, name string) error { - timeout := func() error { - return fmt.Errorf("Failed to become ready after 40 minutes, try running `plural cluster mpwait %s %s` to get an idea where to debug", namespace, name) - } - - return Waiter(kubeConf, namespace, name, func(mp *clusterapiExp.MachinePool) (bool, error) { - phase := findReadiness(mp) - if phase == clusterapiExp.MachinePoolPhaseRunning { - fmt.Printf("MachinePool %s is finally ready!", name) - return true, nil - } - return false, nil - }, timeout) -} - -func Wait(kubeConf *rest.Config, namespace string, name string) error { - timeout := func() error { - return fmt.Errorf("Failed to become ready after 40 minutes, try running `plural cluster mpwait %s %s` to get an idea where to debug", namespace, name) - } - - return Waiter(kubeConf, namespace, name, func(mp *clusterapiExp.MachinePool) (bool, error) { - ready := Ready(mp) - Flush() - return ready, nil - }, timeout) -} - -func NoTableAllWaiter(kubeConf *rest.Config, namespace string, clusterName string) error { - conf := config.Read() - ctx := context.Background() - mps, err := NewForConfig(kubeConf) - if err != nil { - return err - } - - label := &metav1.LabelSelector{MatchLabels: map[string]string{"cluster.x-k8s.io/cluster-name": clusterName}} - - client := mps.MachinePools(conf.Namespace(namespace)) - pools, err := client.List(ctx, metav1.ListOptions{LabelSelector: metav1.FormatLabelSelector(label)}) - if err != nil { - return err - } - if len(pools.Items) == 0 { - return fmt.Errorf("No machine pools found for cluster %s", clusterName) - } - condition := map[string]clusterapi.Condition{} - - if err := utils.WaitFor(20*time.Minute, 5*time.Second, func() (bool, error) { - pools, err := client.List(ctx, metav1.ListOptions{LabelSelector: metav1.FormatLabelSelector(label)}) - if err != nil { - return false, err - } - for y, mp := range pools.Items { - tm.MoveCursor(1, y+1) - Ready(&mp) - Flush() - condition[mp.Name] = findCondition(&mp) - if areAllConditionsTrue(condition) { - return true, nil - } - } - - return false, nil - }); err != nil { - return err - } - - return nil -} - -func WaitAll(kubeConf *rest.Config, namespace string, clusterName string) error { - value, ok := utils.GetEnvBoolValue("PLURAL_DISABLE_MP_TABLE_VIEW") - if ok && value { - return NoTableAllWaiter(kubeConf, namespace, clusterName) - } - timeout := func() error { - return fmt.Errorf("Failed to become ready after 40 minutes, try running `plural cluster mpwait %s %s` to get an idea where to debug", namespace, clusterName) - } - - return AllWaiter(kubeConf, namespace, clusterName, timeout) -} diff --git a/pkg/manifest/manifest.go b/pkg/manifest/manifest.go index a728dcb7..d720fa96 100644 --- a/pkg/manifest/manifest.go +++ b/pkg/manifest/manifest.go @@ -10,7 +10,6 @@ import ( "gopkg.in/yaml.v2" "github.com/pluralsh/plural-cli/pkg/api" - "github.com/pluralsh/plural-cli/pkg/exp" "github.com/pluralsh/plural-cli/pkg/utils" "github.com/pluralsh/plural-cli/pkg/utils/pathing" ) @@ -76,11 +75,6 @@ func ReadProject(path string) (man *ProjectManifest, err error) { man = versioned.Spec - // Override Cluster API flag silently - if !exp.IsFeatureEnabled(exp.EXP_PLURAL_CAPI) { - man.ClusterAPI = false - } - man.Provider = api.NormalizeProvider(man.Provider) return @@ -136,11 +130,6 @@ func (pMan *ProjectManifest) Configure(cloud bool) Writer { return nil } } - - if exp.IsFeatureEnabled(exp.EXP_PLURAL_CAPI) { - pMan.ClusterAPI = true - } - return func() error { return pMan.Write(ProjectManifestPath()) } } diff --git a/pkg/manifest/types.go b/pkg/manifest/types.go index 99b76a72..6e87ef2a 100644 --- a/pkg/manifest/types.go +++ b/pkg/manifest/types.go @@ -51,7 +51,6 @@ type NetworkConfig struct { } type ProjectManifest struct { - ClusterAPI bool Cluster string Bucket string Project string diff --git a/pkg/scaffold/helm.go b/pkg/scaffold/helm.go index 70ee1dbd..26204d60 100644 --- a/pkg/scaffold/helm.go +++ b/pkg/scaffold/helm.go @@ -182,7 +182,6 @@ func (s *Scaffold) buildChartValues(w *wkspace.Workspace) error { "Config": conf, "Provider": w.Provider.Name(), "Context": w.Provider.Context(), - "ClusterAPI": proj.ClusterAPI, "Network": proj.Network, "Applications": apps, } diff --git a/pkg/scaffold/terraform.go b/pkg/scaffold/terraform.go index b73aee7a..1efbff6d 100644 --- a/pkg/scaffold/terraform.go +++ b/pkg/scaffold/terraform.go @@ -130,7 +130,6 @@ func (scaffold *Scaffold) handleTerraform(wk *wkspace.Workspace) error { "Region": wk.Provider.Region(), "Context": wk.Provider.Context(), "Config": config.Read(), - "ClusterAPI": wk.Manifest.ClusterAPI, "Applications": apps, } if err := tmpl.Execute(&buf, values); err != nil { diff --git a/pkg/utils/backup/capi.go b/pkg/utils/backup/capi.go deleted file mode 100644 index e10c320a..00000000 --- a/pkg/utils/backup/capi.go +++ /dev/null @@ -1,84 +0,0 @@ -package backup - -import ( - "context" - "fmt" - "os" - "path/filepath" - - apiclient "sigs.k8s.io/cluster-api/cmd/clusterctl/client" - - "github.com/pluralsh/plural-cli/pkg/config" -) - -type CAPIBackup struct { - dirPath string - ctx context.Context -} - -func (this CAPIBackup) createDir() { - if this.Exists() { - return - } - - _ = os.MkdirAll(this.dirPath, os.ModePerm) -} - -func (this CAPIBackup) Exists() bool { - _, err := os.Stat(this.dirPath) - return !os.IsNotExist(err) -} - -func (this CAPIBackup) Save(options apiclient.MoveOptions) error { - client, err := apiclient.New(this.ctx, "") - if err != nil { - return err - } - - this.createDir() - if len(options.FromKubeconfig.Context) == 0 || len(options.FromKubeconfig.Path) == 0 { - return fmt.Errorf("both FromKubeconfig context and path have to be configured\n") - } - - options.ToDirectory = this.dirPath - options.Namespace = "bootstrap" - - return client.Move(this.ctx, options) -} - -func (this CAPIBackup) Restore(options apiclient.MoveOptions) error { - client, err := apiclient.New(this.ctx, "") - if err != nil { - return err - } - - if len(options.ToKubeconfig.Context) == 0 || len(options.ToKubeconfig.Path) == 0 { - return fmt.Errorf("both ToKubeconfig context and path have to be configured\n") - } - - if !this.Exists() { - return fmt.Errorf("could not find move backup to restore from") - } - - options.FromDirectory = this.dirPath - options.Namespace = "bootstrap" - - return client.Move(this.ctx, options) -} - -func (this CAPIBackup) Remove() error { - if !this.Exists() { - return nil - } - - return os.RemoveAll(this.dirPath) -} - -func NewCAPIBackup(cluster string) Backup[apiclient.MoveOptions] { - path, _ := config.PluralDir() - - return CAPIBackup{ - ctx: context.Background(), - dirPath: filepath.Join(path, backupsDir, cluster), - } -} diff --git a/pkg/utils/backup/types.go b/pkg/utils/backup/types.go deleted file mode 100644 index fc579e02..00000000 --- a/pkg/utils/backup/types.go +++ /dev/null @@ -1,14 +0,0 @@ -package backup - -type BackupOptions any - -const ( - backupsDir = "backups" -) - -type Backup[T BackupOptions] interface { - Exists() bool - Save(opts T) error - Restore(opts T) error - Remove() error -}