From c5e563f67fd1ecafb90b165ec646b903ce51fd17 Mon Sep 17 00:00:00 2001 From: CristhianF7 Date: Tue, 19 Sep 2023 14:32:17 -0500 Subject: [PATCH] feat: civo create using launch up --- cmd/civo/create.go | 1366 +++---------------------------- cmd/civo/destroy.go | 259 +----- cmd/launch.go | 4 +- go.mod | 20 +- go.sum | 51 +- internal/gitShim/init.go | 94 +++ internal/k3d/menu.go | 6 +- internal/launch/cmd.go | 171 ++-- internal/launch/utils.go | 29 - internal/progress/progress.go | 379 +++++++++ internal/progress/types.go | 24 + internal/utilities/utilities.go | 196 ++++- main.go | 9 +- styles.json | 195 +++++ 14 files changed, 1146 insertions(+), 1657 deletions(-) create mode 100644 internal/progress/progress.go create mode 100644 internal/progress/types.go create mode 100644 styles.json diff --git a/cmd/civo/create.go b/cmd/civo/create.go index bc2202be2..d65686517 100644 --- a/cmd/civo/create.go +++ b/cmd/civo/create.go @@ -7,160 +7,122 @@ See the LICENSE file for more details. package civo import ( - "context" - "encoding/base64" "fmt" - "io/ioutil" - "net/http" "os" "strings" "time" - v1alpha1ArgocdApplication "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" - argocdapi "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned" - "github.com/atotto/clipboard" - "github.com/go-git/go-git/v5" - githttps "github.com/go-git/go-git/v5/plumbing/transport/http" + "github.com/kubefirst/kubefirst/configs" "github.com/kubefirst/kubefirst/internal/gitShim" - "github.com/kubefirst/kubefirst/internal/telemetryShim" + "github.com/kubefirst/kubefirst/internal/launch" + "github.com/kubefirst/kubefirst/internal/progress" "github.com/kubefirst/kubefirst/internal/utilities" - "github.com/kubefirst/runtime/configs" "github.com/kubefirst/runtime/pkg" - "github.com/kubefirst/runtime/pkg/argocd" - "github.com/kubefirst/runtime/pkg/civo" - "github.com/kubefirst/runtime/pkg/dns" - "github.com/kubefirst/runtime/pkg/github" - gitlab "github.com/kubefirst/runtime/pkg/gitlab" - "github.com/kubefirst/runtime/pkg/handlers" - "github.com/kubefirst/runtime/pkg/helpers" - "github.com/kubefirst/runtime/pkg/k8s" - "github.com/kubefirst/runtime/pkg/progressPrinter" - "github.com/kubefirst/runtime/pkg/providerConfigs" - "github.com/kubefirst/runtime/pkg/reports" - "github.com/kubefirst/runtime/pkg/segment" - "github.com/kubefirst/runtime/pkg/services" internalssh "github.com/kubefirst/runtime/pkg/ssh" - "github.com/kubefirst/runtime/pkg/ssl" - "github.com/kubefirst/runtime/pkg/terraform" - runtimetypes "github.com/kubefirst/runtime/pkg/types" - utils "github.com/kubefirst/runtime/pkg/utils" "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/spf13/viper" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func createCivo(cmd *cobra.Command, args []string) error { + progress.DisplayLogHints() + alertsEmailFlag, err := cmd.Flags().GetString("alerts-email") if err != nil { + progress.Error(err.Error()) return err } - ciFlag, err := cmd.Flags().GetBool("ci") - if err != nil { - return err - } + // ToDo: do we still need this one? + // ciFlag, err := cmd.Flags().GetBool("ci") + // if err != nil { + // progress.Error(err.Error()) + // return err + // } cloudRegionFlag, err := cmd.Flags().GetString("cloud-region") if err != nil { + progress.Error(err.Error()) return err } clusterNameFlag, err := cmd.Flags().GetString("cluster-name") if err != nil { - return err - } - - clusterTypeFlag, err := cmd.Flags().GetString("cluster-type") - if err != nil { + progress.Error(err.Error()) return err } dnsProviderFlag, err := cmd.Flags().GetString("dns-provider") if err != nil { + progress.Error(err.Error()) return err } domainNameFlag, err := cmd.Flags().GetString("domain-name") if err != nil { + progress.Error(err.Error()) return err } githubOrgFlag, err := cmd.Flags().GetString("github-org") if err != nil { + progress.Error(err.Error()) return err } githubOrgFlag = strings.ToLower(githubOrgFlag) gitlabGroupFlag, err := cmd.Flags().GetString("gitlab-group") if err != nil { + progress.Error(err.Error()) return err } gitlabGroupFlag = strings.ToLower(gitlabGroupFlag) gitProviderFlag, err := cmd.Flags().GetString("git-provider") if err != nil { + progress.Error(err.Error()) return err } gitProtocolFlag, err := cmd.Flags().GetString("git-protocol") if err != nil { + progress.Error(err.Error()) return err } gitopsTemplateURLFlag, err := cmd.Flags().GetString("gitops-template-url") if err != nil { + progress.Error(err.Error()) return err } gitopsTemplateBranchFlag, err := cmd.Flags().GetString("gitops-template-branch") if err != nil { + progress.Error(err.Error()) return err } - useTelemetryFlag, err := cmd.Flags().GetBool("use-telemetry") - if err != nil { - return err - } + // useTelemetryFlag, err := cmd.Flags().GetBool("use-telemetry") + // if err != nil { + // progress.Error(err.Error()) + // return err + // } - // If cluster setup is complete, return clusterSetupComplete := viper.GetBool("kubefirst-checks.cluster-install-complete") if clusterSetupComplete { return fmt.Errorf("this cluster install process has already completed successfully") } - utilities.CreateK1ClusterDirectory(clusterNameFlag) - helpers.DisplayLogHints() + progress.Log(":female_detective: Validating provided flags", "") - switch gitProviderFlag { - case "github": - key, err := internalssh.GetHostKey("github.com") - if err != nil { - return fmt.Errorf("known_hosts file does not exist - please run `ssh-keyscan github.com >> ~/.ssh/known_hosts` to remedy") - } else { - log.Info().Msgf("%s %s\n", "github.com", key.Type()) - } - case "gitlab": - key, err := internalssh.GetHostKey("gitlab.com") - if err != nil { - return fmt.Errorf("known_hosts file does not exist - please run `ssh-keyscan gitlab.com >> ~/.ssh/known_hosts` to remedy") - } else { - log.Info().Msgf("%s %s\n", "gitlab.com", key.Type()) - } - } + err = ValidateProvidedFlags(gitProviderFlag) - // Check for existing port forwards before continuing - err = k8s.CheckForExistingPortForwards(8080, 8200, 9094) if err != nil { - return fmt.Errorf("%s - this port is required to set up your kubefirst environment - please close any existing port forwards before continuing", err.Error()) + progress.Error(err.Error()) + return err } - // Validate required environment variables for dns provider - if dnsProviderFlag == "cloudflare" { - if os.Getenv("CF_API_TOKEN") == "" { - return fmt.Errorf("your CF_API_TOKEN environment variable is not set. Please set and try again") - } - } + utilities.CreateK1ClusterDirectory(clusterNameFlag) // required for destroy command viper.Set("flags.alerts-email", alertsEmailFlag) @@ -170,1262 +132,120 @@ func createCivo(cmd *cobra.Command, args []string) error { viper.Set("flags.git-provider", gitProviderFlag) viper.Set("flags.git-protocol", gitProtocolFlag) viper.Set("flags.cloud-region", cloudRegionFlag) - viper.WriteConfig() - - progressPrinter.AddTracker("preflight-checks", "Running preflight checks", 6) - progressPrinter.SetupProgress(progressPrinter.TotalOfTrackers(), false) - - // Switch based on git provider, set params - var cGitHost, cGitOwner, cGitToken, cGitUser, containerRegistryHost string - var cGitlabOwnerGroupID int - switch gitProviderFlag { - case "github": - if githubOrgFlag == "" { - return fmt.Errorf("please provide a github organization using the --github-org flag") - } - if os.Getenv("GITHUB_TOKEN") == "" { - return fmt.Errorf("your GITHUB_TOKEN is not set. Please set and try again") - } - - cGitHost = providerConfigs.GithubHost - cGitOwner = githubOrgFlag - cGitToken = os.Getenv("GITHUB_TOKEN") - containerRegistryHost = "ghcr.io" - - // Verify token scopes - err = github.VerifyTokenPermissions(cGitToken) - if err != nil { - return err - } - - // Handle authorization checks - httpClient := http.DefaultClient - gitHubService := services.NewGitHubService(httpClient) - gitHubHandler := handlers.NewGitHubHandler(gitHubService) - - // get github data to set user based on the provided token - log.Info().Msg("verifying github authentication") - githubUser, err := gitHubHandler.GetGitHubUser(cGitToken) - if err != nil { - return err - } - cGitUser = githubUser - viper.Set("github.user", githubUser) - err = viper.WriteConfig() - if err != nil { - return err - } - err = gitHubHandler.CheckGithubOrganizationPermissions(cGitToken, githubOrgFlag, githubUser) - if err != nil { - return err - } - viper.Set("flags.github-owner", githubOrgFlag) - viper.WriteConfig() - case "gitlab": - if gitlabGroupFlag == "" { - return fmt.Errorf("please provide a gitlab group using the --gitlab-group flag") - } - if os.Getenv("GITLAB_TOKEN") == "" { - return fmt.Errorf("your GITLAB_TOKEN is not set. please set and try again") - } - - cGitToken = os.Getenv("GITLAB_TOKEN") - - // Verify token scopes - err = gitlab.VerifyTokenPermissions(cGitToken) - if err != nil { - return err - } - - gitlabClient, err := gitlab.NewGitLabClient(cGitToken, gitlabGroupFlag) - if err != nil { - return err - } - - cGitHost = providerConfigs.GitlabHost - cGitOwner = gitlabClient.ParentGroupPath - cGitlabOwnerGroupID = gitlabClient.ParentGroupID - log.Info().Msgf("set gitlab owner to %s", cGitOwner) - - // Get authenticated user's name - user, _, err := gitlabClient.Client.Users.CurrentUser() - if err != nil { - return fmt.Errorf("unable to get authenticated user info - please make sure GITLAB_TOKEN env var is set %s", err.Error()) - } - cGitUser = user.Username - - containerRegistryHost = "registry.gitlab.com" - viper.Set("flags.gitlab-owner", gitlabGroupFlag) - viper.Set("flags.gitlab-owner-group-id", cGitlabOwnerGroupID) - viper.WriteConfig() - default: - log.Error().Msgf("invalid git provider option") - } - - // Instantiate config - config := providerConfigs.GetConfig( - clusterNameFlag, - domainNameFlag, - gitProviderFlag, - cGitOwner, - gitProtocolFlag, - os.Getenv("CF_API_TOKEN"), - os.Getenv("CF_ORIGIN_CA_ISSUER_API_TOKEN"), - ) - config.CivoToken = os.Getenv("CIVO_TOKEN") - switch gitProviderFlag { - case "github": - config.GithubToken = cGitToken - case "gitlab": - config.GitlabToken = cGitToken - - // gitlab may have subgroups, so the destination gitops/metaphor repo git urls may be different - gitlabClient, err := gitlab.NewGitLabClient(cGitToken, gitlabGroupFlag) - if err != nil { - return err - } - // Format git url based on full path to group - // Format git url based on full path to group - config.DestinationGitopsRepoURL = fmt.Sprintf("https://gitlab.com/%s/gitops.git", gitlabClient.ParentGroupPath) - config.DestinationMetaphorRepoURL = fmt.Sprintf("https://gitlab.com/%s/metaphor.git", gitlabClient.ParentGroupPath) - config.DestinationGitopsRepoGitURL = fmt.Sprintf("git@gitlab.com:%s/gitops.git", gitlabClient.ParentGroupPath) - config.DestinationMetaphorRepoGitURL = fmt.Sprintf("git@gitlab.com:%s/metaphor.git", gitlabClient.ParentGroupPath) - } - - civoConf := civo.CivoConfiguration{ - Client: civo.NewCivo(config.CivoToken, cloudRegionFlag), - Context: context.Background(), - } - - var sshPrivateKey, sshPublicKey string - - // todo placed in configmap in kubefirst namespace, included in telemetry - clusterId := viper.GetString("kubefirst.cluster-id") - if clusterId == "" { - clusterId = pkg.GenerateClusterID() - viper.Set("kubefirst.cluster-id", clusterId) - viper.WriteConfig() - } - kubefirstStateStoreBucketName := fmt.Sprintf("k1-state-store-%s-%s", clusterNameFlag, clusterId) - - // Detokenize - kubefirstTeam := os.Getenv("KUBEFIRST_TEAM") - if kubefirstTeam == "" { - kubefirstTeam = "false" - } - - // Swap tokens for cloudflare - var externalDNSProviderTokenEnvName, externalDNSProviderSecretKey string - if dnsProviderFlag == "cloudflare" { - externalDNSProviderTokenEnvName = "CF_API_TOKEN" - externalDNSProviderSecretKey = "cf-api-token" - } else { - externalDNSProviderTokenEnvName = "CIVO_TOKEN" - externalDNSProviderSecretKey = fmt.Sprintf("%s-auth", civo.CloudProvider) - } - - // Swap tokens for git protocol; used by tokens, argocd registry object, and secret bootstrapping for argo template credentials - var gitopsRepoURL string - switch config.GitProtocol { - case "https": - gitopsRepoURL = config.DestinationGitopsRepoURL - default: - gitopsRepoURL = config.DestinationGitopsRepoGitURL - } - - gitopsDirectoryTokens := providerConfigs.GitopsDirectoryValues{ - AlertsEmail: alertsEmailFlag, - AtlantisAllowList: fmt.Sprintf("%s/%s/*", cGitHost, cGitOwner), - CloudProvider: civo.CloudProvider, - CloudRegion: cloudRegionFlag, - ClusterName: clusterNameFlag, - ClusterType: clusterTypeFlag, - DNSProvider: dnsProviderFlag, - DomainName: domainNameFlag, - KubeconfigPath: config.Kubeconfig, - KubefirstStateStoreBucket: kubefirstStateStoreBucketName, - KubefirstTeam: kubefirstTeam, - KubefirstVersion: configs.K1Version, - - ExternalDNSProviderName: dnsProviderFlag, - ExternalDNSProviderTokenEnvName: externalDNSProviderTokenEnvName, - ExternalDNSProviderSecretName: fmt.Sprintf("%s-auth", dnsProviderFlag), - ExternalDNSProviderSecretKey: externalDNSProviderSecretKey, - - ArgoCDIngressURL: fmt.Sprintf("https://argocd.%s", domainNameFlag), - ArgoCDIngressNoHTTPSURL: fmt.Sprintf("argocd.%s", domainNameFlag), - ArgoWorkflowsIngressURL: fmt.Sprintf("https://argo.%s", domainNameFlag), - ArgoWorkflowsIngressNoHTTPSURL: fmt.Sprintf("argo.%s", domainNameFlag), - AtlantisIngressURL: fmt.Sprintf("https://atlantis.%s", domainNameFlag), - AtlantisIngressNoHTTPSURL: fmt.Sprintf("atlantis.%s", domainNameFlag), - ChartMuseumIngressURL: fmt.Sprintf("https://chartmuseum.%s", domainNameFlag), - VaultIngressURL: fmt.Sprintf("https://vault.%s", domainNameFlag), - VaultIngressNoHTTPSURL: fmt.Sprintf("vault.%s", domainNameFlag), - VouchIngressURL: fmt.Sprintf("https://vouch.%s", domainNameFlag), + viper.Set("kubefirst.cloud-provider", "civo") - GitDescription: fmt.Sprintf("%s hosted git", config.GitProvider), - GitNamespace: "N/A", - GitProvider: config.GitProvider, - GitopsRepoURL: gitopsRepoURL, - GitRunner: fmt.Sprintf("%s Runner", config.GitProvider), - GitRunnerDescription: fmt.Sprintf("Self Hosted %s Runner", config.GitProvider), - GitRunnerNS: fmt.Sprintf("%s-runner", config.GitProvider), - GitURL: gitopsTemplateURLFlag, - - GitHubHost: fmt.Sprintf("https://github.com/%s/gitops.git", strings.ToLower(cGitOwner)), - GitHubOwner: strings.ToLower(cGitOwner), - GitHubUser: strings.ToLower(cGitUser), - - GitlabHost: providerConfigs.GitlabHost, - GitlabOwner: cGitOwner, - GitlabOwnerGroupID: cGitlabOwnerGroupID, - GitlabUser: cGitUser, - - GitopsRepoAtlantisWebhookURL: fmt.Sprintf("https://atlantis.%s/events", domainNameFlag), - GitopsRepoNoHTTPSURL: fmt.Sprintf("%s.com/%s/gitops.git", cGitHost, cGitOwner), - ClusterId: clusterId, - } - - viper.Set(fmt.Sprintf("%s.atlantis.webhook.url", config.GitProvider), fmt.Sprintf("https://atlantis.%s/events", domainNameFlag)) viper.WriteConfig() - config.GitopsDirectoryValues = &gitopsDirectoryTokens - - // Segment Client - segmentClient := &segment.SegmentClient{ - CliVersion: configs.K1Version, - CloudProvider: civo.CloudProvider, - ClusterID: clusterId, - ClusterType: clusterTypeFlag, - DomainName: domainNameFlag, - GitProvider: gitProviderFlag, - KubefirstClient: "cli", - KubefirstTeam: kubefirstTeam, - KubefirstTeamInfo: os.Getenv("KUBEFIRST_TEAM_INFO"), - } - segmentClient.SetupClient() - defer func(c segment.SegmentClient) { - err := c.Client.Close() - if err != nil { - log.Info().Msgf("error closing segment client %s", err.Error()) - } - }(*segmentClient) - if useTelemetryFlag { - gitopsDirectoryTokens.UseTelemetry = "true" - - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricInitStarted, "") - } else { - gitopsDirectoryTokens.UseTelemetry = "false" - } - - // this branch flag value is overridden with a tag when running from a - // kubefirst binary for version compatibility - switch configs.K1Version { - case "development": - if strings.Contains(gitopsTemplateURLFlag, "https://github.com/kubefirst/gitops-template.git") && gitopsTemplateBranchFlag == "" { - gitopsTemplateBranchFlag = "main" - } - default: - switch strings.ToLower(gitopsTemplateBranchFlag) { - case "https://github.com/kubefirst/gitops-template.git": //default value - if gitopsTemplateBranchFlag == "" { - log.Info().Msgf("--gitops-template-repo-url supplied and branch not supplied so setting branch name to main") - gitopsTemplateBranchFlag = configs.K1Version - } - case "https://github.com/kubefirst/gitops-template": // edge case for valid but incomplete url - if gitopsTemplateBranchFlag == "" { - log.Info().Msgf("--gitops-template-repo-url supplied and branch not supplied so setting branch name to main") - gitopsTemplateBranchFlag = configs.K1Version - } - default: // not equal to our defaults - if len(strings.TrimSpace(gitopsTemplateBranchFlag)) == 0 { //didn't supply the branch flag but they did supply the repo flag - log.Info().Msgf("--gitops-template-repo-url supplied and branch not supplied but if branch is not supplied then --gitops-template-url must be set to https://github.com/kubefirst/gitops-template or https://github.com/kubefirst/gitops-template.git ") - return fmt.Errorf("must supply gitops-template-branch flag when gitops-template-url is overridden") - } - log.Info().Msgf("--gitops-template-repo-url supplied and branch supplied so continuing on") - } - } - - log.Info().Msgf("kubefirst version configs.K1Version: %s ", configs.K1Version) - log.Info().Msgf("cloning gitops-template repo url: %s ", gitopsTemplateURLFlag) - log.Info().Msgf("cloning gitops-template repo branch: %s ", gitopsTemplateBranchFlag) - - atlantisWebhookSecret := viper.GetString("secrets.atlantis-webhook") - if atlantisWebhookSecret == "" { - atlantisWebhookSecret = pkg.Random(20) - viper.Set("secrets.atlantis-webhook", atlantisWebhookSecret) - viper.WriteConfig() - } + progress.Log(":male_detective: Validating git credentials", "") - log.Info().Msg("checking authentication to required providers") + gitAuth, err := gitShim.ValidateGitCredentials(gitProviderFlag, githubOrgFlag, gitlabGroupFlag) - executionControl := viper.GetBool("kubefirst-checks.cloud-credentials") - if !executionControl { - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricCloudCredentialsCheckStarted, "") - - if config.CivoToken == "" { - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricCloudCredentialsCheckFailed, "CIVO_TOKEN environment variable was not set") - return fmt.Errorf("your CIVO_TOKEN is not set - please set and re-run your last command") - } - log.Info().Msg("CIVO_TOKEN set - continuing") - viper.Set("kubefirst-checks.cloud-credentials", true) - viper.WriteConfig() - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricCloudCredentialsCheckCompleted, "") - progressPrinter.IncrementTracker("preflight-checks", 1) - } else { - log.Info().Msg("already completed cloud credentials check - continuing") - progressPrinter.IncrementTracker("preflight-checks", 1) - } - - executionControl = viper.GetBool("kubefirst-checks.state-store-creds") - if !executionControl { - creds, err := civoConf.GetAccessCredentials(kubefirstStateStoreBucketName, cloudRegionFlag) - if err != nil { - log.Info().Msg(err.Error()) - } - - // StateStoreCredentials - viper.Set("kubefirst.state-store-creds.access-key-id", creds.AccessKeyID) - viper.Set("kubefirst.state-store-creds.secret-access-key-id", creds.SecretAccessKeyID) - viper.Set("kubefirst.state-store-creds.name", creds.Name) - viper.Set("kubefirst.state-store-creds.id", creds.ID) - viper.Set("kubefirst-checks.state-store-creds", true) - viper.WriteConfig() - log.Info().Msg("civo object storage credentials created and set") - progressPrinter.IncrementTracker("preflight-checks", 1) - } else { - log.Info().Msg("already created civo object storage credentials - continuing") - progressPrinter.IncrementTracker("preflight-checks", 1) - } - - skipDomainCheck := viper.GetBool("kubefirst-checks.domain-liveness") - if !skipDomainCheck { - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricDomainLivenessStarted, "") - - switch dnsProviderFlag { - case "civo": - // verify dns - err := dns.VerifyProviderDNS(civo.CloudProvider, cloudRegionFlag, domainNameFlag, nil) - if err != nil { - return err - } - - // domain id - domainId, err := civoConf.GetDNSInfo(domainNameFlag, cloudRegionFlag) - if err != nil { - log.Info().Msg(err.Error()) - } - - // viper values set in above function - log.Info().Msgf("domainId: %s", domainId) - domainLiveness := civoConf.TestDomainLiveness(domainNameFlag, domainId, cloudRegionFlag) - if !domainLiveness { - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricDomainLivenessFailed, "domain liveness test failed") - msg := "failed to check the liveness of the Domain. A valid public Domain on the same CIVO " + - "account as the one where Kubefirst will be installed is required for this operation to " + - "complete.\nTroubleshoot Steps:\n\n - Make sure you are using the correct CIVO account and " + - "region.\n - Verify that you have the necessary permissions to access the domain.\n - Check " + - "that the domain is correctly configured and is a public domain\n - Check if the " + - "domain exists and has the correct name and domain.\n - If you don't have a Domain," + - "please follow these instructions to create one: " + - "https://www.civo.com/learn/configure-dns \n\n" + - "if you are still facing issues please reach out to support team for further assistance" - - return fmt.Errorf(msg) - } - viper.Set("kubefirst-checks.domain-liveness", true) - viper.WriteConfig() - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricDomainLivenessCompleted, "") - progressPrinter.IncrementTracker("preflight-checks", 1) - case "cloudflare": - // Implement a Cloudflare check at some point - log.Info().Msg("domain check already complete - continuing") - progressPrinter.IncrementTracker("preflight-checks", 1) - } - } else { - log.Info().Msg("domain check already complete - continuing") - progressPrinter.IncrementTracker("preflight-checks", 1) - } - - executionControl = viper.GetBool("kubefirst-checks.state-store-create") - if !executionControl { - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricStateStoreCreateStarted, "") - - accessKeyId := viper.GetString("kubefirst.state-store-creds.access-key-id") - log.Info().Msgf("access key id %s", accessKeyId) - - bucket, err := civoConf.CreateStorageBucket(accessKeyId, kubefirstStateStoreBucketName, cloudRegionFlag) - if err != nil { - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricStateStoreCreateFailed, err.Error()) - log.Info().Msg(err.Error()) - return err - } - - viper.Set("kubefirst.state-store.id", bucket.ID) - viper.Set("kubefirst.state-store.name", bucket.Name) - viper.Set("kubefirst.state-store.hostname", bucket.BucketURL) - viper.Set("kubefirst-checks.state-store-create", true) - viper.WriteConfig() - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricStateStoreCreateCompleted, "") - log.Info().Msg("civo state store bucket created") - progressPrinter.IncrementTracker("preflight-checks", 1) - } else { - log.Info().Msg("already created civo state store bucket - continuing") - progressPrinter.IncrementTracker("preflight-checks", 1) - } - - // Check quotas - quotaMessage, quotaFailures, quotaWarnings, err := returnCivoQuotaEvaluation(cloudRegionFlag) if err != nil { - return err - } - switch { - case quotaFailures > 0: - fmt.Println(reports.StyleMessage(quotaMessage)) - return fmt.Errorf("at least one of your Civo quotas is close to its limit. Please check the error message above for additional details") - case quotaWarnings > 0: - fmt.Println(reports.StyleMessage(quotaMessage)) + progress.Error("unable to validate git credentials") } - //* CIVO END - - // Objects to check for - newRepositoryNames := []string{"gitops", "metaphor"} - newTeamNames := []string{"admins", "developers"} - executionControl = viper.GetBool(fmt.Sprintf("kubefirst-checks.%s-credentials", config.GitProvider)) + // Validate git + executionControl := viper.GetBool(fmt.Sprintf("kubefirst-checks.%s-credentials", gitProviderFlag)) if !executionControl { - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricGitCredentialsCheckStarted, "") - if len(cGitToken) == 0 { - msg := fmt.Sprintf( - "please set a %s_TOKEN environment variable to continue", - strings.ToUpper(config.GitProvider), - ) - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricGitCredentialsCheckFailed, msg) - return fmt.Errorf(msg) - } + newRepositoryNames := []string{"gitops", "metaphor"} + newTeamNames := []string{"admins", "developers"} initGitParameters := gitShim.GitInitParameters{ GitProvider: gitProviderFlag, - GitToken: cGitToken, - GitOwner: cGitOwner, + GitToken: gitAuth.Token, + GitOwner: gitAuth.Owner, Repositories: newRepositoryNames, Teams: newTeamNames, } - err = gitShim.InitializeGitProvider(&initGitParameters) - if err != nil { - return err - } - - viper.Set(fmt.Sprintf("kubefirst-checks.%s-credentials", config.GitProvider), true) - viper.WriteConfig() - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricGitCredentialsCheckCompleted, "") - progressPrinter.IncrementTracker("preflight-checks", 1) - } else { - log.Info().Msg(fmt.Sprintf("already completed %s checks - continuing", config.GitProvider)) - progressPrinter.IncrementTracker("preflight-checks", 1) - } - - executionControl = viper.GetBool("kubefirst-checks.kbot-setup") - if !executionControl { - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricKbotSetupStarted, "") - - log.Info().Msg("creating an ssh key pair for your new cloud infrastructure") - sshPrivateKey, sshPublicKey, err = internalssh.CreateSshKeyPair() - if err != nil { - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricKbotSetupFailed, err.Error()) - return err - } - log.Info().Msg("ssh key pair creation complete") - - viper.Set("kbot.private-key", sshPrivateKey) - viper.Set("kbot.public-key", sshPublicKey) - viper.Set("kbot.username", "kbot") - viper.Set("kubefirst-checks.kbot-setup", true) - viper.WriteConfig() - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricKbotSetupCompleted, "") - log.Info().Msg("kbot-setup complete") - progressPrinter.IncrementTracker("preflight-checks", 1) - } else { - log.Info().Msg("already setup kbot user - continuing") - progressPrinter.IncrementTracker("preflight-checks", 1) - } - - log.Info().Msg("validation and kubefirst cli environment check is complete") - - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricInitCompleted, "") - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricClusterInstallStarted, "") - - //removed because we no longer default to ssh for kubefirst cli calls since we require the token anyways - // publicKeys, err := ssh.NewPublicKeys("git", []byte(viper.GetString("kbot.private-key")), "") - // if err != nil { - // log.Info().Msgf("generate public keys failed: %s\n", err.Error()) - // } - - //* generate http credentials for git auth over https - httpAuth := &githttps.BasicAuth{ - Username: cGitUser, - Password: cGitToken, - } - - //* download dependencies to `$HOME/.k1/tools` - progressPrinter.AddTracker("downloading-tools", "Downloading tools", 1) - progressPrinter.SetupProgress(progressPrinter.TotalOfTrackers(), false) - if !viper.GetBool("kubefirst-checks.tools-downloaded") { - log.Info().Msg("installing kubefirst dependencies") - - err := civo.DownloadTools( - config.KubectlClient, - providerConfigs.KubectlClientVersion, - providerConfigs.LocalhostOS, - providerConfigs.LocalhostArch, - providerConfigs.TerraformClientVersion, - config.ToolsDir, - ) - if err != nil { - return err - } - - log.Info().Msg("download dependencies `$HOME/.k1/tools` complete") - viper.Set("kubefirst-checks.tools-downloaded", true) - viper.WriteConfig() - progressPrinter.IncrementTracker("downloading-tools", 1) - } else { - log.Info().Msg("already completed download of dependencies to `$HOME/.k1/tools` - continuing") - progressPrinter.IncrementTracker("downloading-tools", 1) - } - - // todo should metaphor tokens be global? - metaphorDirectoryTokens := providerConfigs.MetaphorTokenValues{ - ClusterName: clusterNameFlag, - CloudRegion: cloudRegionFlag, - ContainerRegistryURL: fmt.Sprintf("%s/%s/metaphor", containerRegistryHost, cGitOwner), - DomainName: domainNameFlag, - MetaphorDevelopmentIngressURL: fmt.Sprintf("metaphor-development.%s", domainNameFlag), - MetaphorStagingIngressURL: fmt.Sprintf("metaphor-staging.%s", domainNameFlag), - MetaphorProductionIngressURL: fmt.Sprintf("metaphor-production.%s", domainNameFlag), - } - - config.MetaphorDirectoryValues = &metaphorDirectoryTokens - progressPrinter.AddTracker("cloning-and-formatting-git-repositories", "Cloning and formatting git repositories", 1) - progressPrinter.SetupProgress(progressPrinter.TotalOfTrackers(), false) - if !viper.GetBool("kubefirst-checks.gitops-ready-to-push") { - - log.Info().Msg("generating your new gitops repository") - - // These need to be set for reference elsewhere - viper.Set(fmt.Sprintf("%s.repos.gitops.git-url", config.GitProvider), config.DestinationGitopsRepoURL) - viper.WriteConfig() - - // Determine if anything exists at domain apex - apexContentExists := civo.GetDomainApexContent(domainNameFlag) - - err = providerConfigs.PrepareGitRepositories( - civo.CloudProvider, - config.GitProvider, - clusterNameFlag, - clusterTypeFlag, - config.DestinationGitopsRepoURL, //default to https for git interactions when creating remotes - config.GitopsDir, - gitopsTemplateBranchFlag, - gitopsTemplateURLFlag, - config.DestinationMetaphorRepoURL, //default to https for git interactions when creating remotes - config.K1Dir, - &gitopsDirectoryTokens, - config.MetaphorDir, - &metaphorDirectoryTokens, - apexContentExists, - gitProtocolFlag, - ) + progress.Log(":dizzy: Validating git environment", "") + err = gitShim.InitializeGitProvider(&initGitParameters) if err != nil { - return err + progress.Error(err.Error()) } - - // todo emit init telemetry end - viper.Set("kubefirst-checks.gitops-ready-to-push", true) - viper.WriteConfig() - progressPrinter.IncrementTracker("cloning-and-formatting-git-repositories", 1) - } else { - log.Info().Msg("already completed gitops repo generation - continuing") - progressPrinter.IncrementTracker("cloning-and-formatting-git-repositories", 1) } + viper.Set(fmt.Sprintf("kubefirst-checks.%s-credentials", gitProviderFlag), true) + viper.WriteConfig() - //* handle git terraform apply - progressPrinter.AddTracker("applying-git-terraform", fmt.Sprintf("Applying %s Terraform", config.GitProvider), 1) - progressPrinter.SetupProgress(progressPrinter.TotalOfTrackers(), false) - log.Info().Msgf("referencing gitops repository: %s", config.DestinationGitopsRepoURL) - log.Info().Msgf("referencing metaphor repository: %s", config.DestinationMetaphorRepoURL) - switch config.GitProvider { - case "github": - // //* create teams and repositories in github - executionControl = viper.GetBool("kubefirst-checks.terraform-apply-github") - if !executionControl { - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricGitTerraformApplyStarted, "") - - log.Info().Msg("Creating GitHub resources with Terraform") - - tfEntrypoint := config.GitopsDir + "/terraform/github" - tfEnvs := map[string]string{} - tfEnvs = civo.GetGithubTerraformEnvs(config, tfEnvs) - // Erase public key to prevent it from being created if the git protocol argument is set to htps - switch config.GitProtocol { - case "https": - tfEnvs["TF_VAR_kbot_ssh_public_key"] = "" - } - err := terraform.InitApplyAutoApprove(config.TerraformClient, tfEntrypoint, tfEnvs) - if err != nil { - msg := fmt.Sprintf("error creating github resources with terraform %s: %s", tfEntrypoint, err) - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricGitTerraformApplyFailed, msg) - return fmt.Errorf(msg) - } - - log.Info().Msgf("Created git repositories and teams for github.com/%s", cGitOwner) - viper.Set("kubefirst-checks.terraform-apply-github", true) - viper.WriteConfig() - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricGitTerraformApplyCompleted, "") - progressPrinter.IncrementTracker("applying-git-terraform", 1) - } else { - log.Info().Msg("already created GitHub Terraform resources") - progressPrinter.IncrementTracker("applying-git-terraform", 1) - } - case "gitlab": - // //* create teams and repositories in gitlab - executionControl = viper.GetBool("kubefirst-checks.terraform-apply-gitlab") - if !executionControl { - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricGitTerraformApplyStarted, "") - - log.Info().Msg("Creating GitLab resources with Terraform") - - tfEntrypoint := config.GitopsDir + "/terraform/gitlab" - tfEnvs := map[string]string{} - tfEnvs = civo.GetGitlabTerraformEnvs(config, tfEnvs, cGitlabOwnerGroupID) - // Erase public key to prevent it from being created if the git protocol argument is set to htps - switch config.GitProtocol { - case "https": - tfEnvs["TF_VAR_kbot_ssh_public_key"] = "" - } - err := terraform.InitApplyAutoApprove(config.TerraformClient, tfEntrypoint, tfEnvs) - if err != nil { - msg := fmt.Sprintf("error creating gitlab resources with terraform %s: %s", tfEntrypoint, err) - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricGitTerraformApplyFailed, msg) - return fmt.Errorf(msg) - } - - log.Info().Msgf("created git projects and groups for gitlab.com/%s", gitlabGroupFlag) - progressPrinter.IncrementTracker("applying-git-terraform", 1) - viper.Set("kubefirst-checks.terraform-apply-gitlab", true) - viper.WriteConfig() - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricGitTerraformApplyCompleted, "") - } else { - log.Info().Msg("already created GitLab Terraform resources") - progressPrinter.IncrementTracker("applying-git-terraform", 1) - } + k3dClusterCreationComplete := viper.GetBool("launch.deployed") + if !k3dClusterCreationComplete { + launch.Up(nil, true, useTelemetryFlag) } - //* push detokenized gitops-template repository content to new remote - progressPrinter.AddTracker("pushing-gitops-repos-upstream", "Pushing git repositories", 1) - progressPrinter.SetupProgress(progressPrinter.TotalOfTrackers(), false) - - log.Info().Msgf("referencing gitops repository: %s", config.DestinationGitopsRepoURL) - log.Info().Msgf("referencing metaphor repository: %s", config.DestinationMetaphorRepoURL) - - executionControl = viper.GetBool("kubefirst-checks.gitops-repo-pushed") - if !executionControl { - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricGitopsRepoPushStarted, "") - - gitopsRepo, err := git.PlainOpen(config.GitopsDir) - if err != nil { - log.Info().Msgf("error opening repo at: %s", config.GitopsDir) - } - - metaphorRepo, err := git.PlainOpen(config.MetaphorDir) - if err != nil { - log.Info().Msgf("error opening repo at: %s", config.MetaphorDir) - } - - err = internalssh.EvalSSHKey(&internalssh.EvalSSHKeyRequest{ - GitProvider: gitProviderFlag, - GitlabGroupFlag: gitlabGroupFlag, - GitToken: cGitToken, - }) - if err != nil { - return err - } - - // Push gitops repo to remote - err = gitopsRepo.Push(&git.PushOptions{ - RemoteName: config.GitProvider, - Auth: httpAuth, - }) - if err != nil { - msg := fmt.Sprintf("error pushing detokenized gitops repository to remote %s: %s", config.DestinationGitopsRepoURL, err) - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricGitopsRepoPushFailed, msg) - if !strings.Contains(msg, "already up-to-date") { - log.Panic().Msg(msg) - } - } - - // push metaphor repo to remote - err = metaphorRepo.Push( - &git.PushOptions{ - RemoteName: "origin", - Auth: httpAuth, - }, - ) - if err != nil { - msg := fmt.Sprintf("error pushing detokenized metaphor repository to remote %s: %s", config.DestinationMetaphorRepoURL, err) - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricGitopsRepoPushFailed, msg) - if !strings.Contains(msg, "already up-to-date") { - log.Panic().Msg(msg) - } - } - log.Info().Msgf("successfully pushed gitops and metaphor repositories to https://%s/%s", cGitHost, cGitOwner) - - // todo delete the local gitops repo and re-clone it - // todo that way we can stop worrying about which origin we're going to push to - log.Info().Msgf("successfully pushed gitops to git@%s/%s/gitops", cGitHost, cGitOwner) - viper.Set("kubefirst-checks.gitops-repo-pushed", true) - viper.WriteConfig() - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricGitopsRepoPushCompleted, "") - progressPrinter.IncrementTracker("pushing-gitops-repos-upstream", 1) - } else { - log.Info().Msg("already pushed detokenized gitops repository content") - progressPrinter.IncrementTracker("pushing-gitops-repos-upstream", 1) + err = pkg.IsAppAvailable(fmt.Sprintf("%s/api/proxyHealth", "https://console.kubefirst.dev"), "kubefirst api") + if err != nil { + progress.Error("unable to start kubefirst api") } - //* create civo cloud resources - progressPrinter.AddTracker("applying-civo-terraform", "Applying Civo Terraform", 1) - progressPrinter.SetupProgress(progressPrinter.TotalOfTrackers(), false) - if !viper.GetBool("kubefirst-checks.terraform-apply-civo") { - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricCloudTerraformApplyStarted, "") + cluster := utilities.CreateClusterDefinitionRecordFromRaw( + gitAuth, + gitopsTemplateURLFlag, + gitopsTemplateBranchFlag, + ) - log.Info().Msg("Creating civo cloud resources with terraform") + if cluster.GitopsTemplateBranch == "" { + cluster.GitopsTemplateBranch = configs.K1Version - tfEntrypoint := config.GitopsDir + "/terraform/civo" - tfEnvs := map[string]string{} - tfEnvs = civo.GetCivoTerraformEnvs(config, tfEnvs) - err := terraform.InitApplyAutoApprove(config.TerraformClient, tfEntrypoint, tfEnvs) - if err != nil { - msg := fmt.Sprintf("error creating civo resources with terraform %s : %s", tfEntrypoint, err) - viper.Set("kubefirst-checks.terraform-apply-civo-failed", true) - viper.WriteConfig() - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricCloudTerraformApplyFailed, msg) - return fmt.Errorf(msg) + if configs.K1Version == "development" { + cluster.GitopsTemplateBranch = "main" } - - log.Info().Msg("Created civo cloud resources") - viper.Set("kubefirst-checks.terraform-apply-civo", true) - viper.WriteConfig() - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricCloudTerraformApplyCompleted, "") - progressPrinter.IncrementTracker("applying-civo-terraform", 1) - } else { - log.Info().Msg("already created GitHub Terraform resources") - progressPrinter.IncrementTracker("applying-civo-terraform", 1) - } - - //* civo needs extra time to be ready - progressPrinter.AddTracker("wait-for-civo", "Wait for Civo Kubernetes", 1) - progressPrinter.SetupProgress(progressPrinter.TotalOfTrackers(), false) - if !viper.GetBool("kubefirst-checks.k8s-secrets-created") { - time.Sleep(time.Second * 60) - } else { - time.Sleep(time.Second * 5) } - progressPrinter.IncrementTracker("wait-for-civo", 1) - kcfg := k8s.CreateKubeConfig(false, config.Kubeconfig) - - // Civo Readiness checks - progressPrinter.AddTracker("verifying-civo-cluster-readiness", "Verifying Kubernetes cluster is ready", 1) - progressPrinter.SetupProgress(progressPrinter.TotalOfTrackers(), false) - - // CoreDNS - coreDNSDeployment, err := k8s.ReturnDeploymentObject( - kcfg.Clientset, - "kubernetes.io/name", - "CoreDNS", - "kube-system", - 240, - ) + clusterCreated, err := utilities.GetCluster(cluster.ClusterName) if err != nil { - log.Error().Msgf("Error finding CoreDNS deployment: %s", err) - return err - } - _, err = k8s.WaitForDeploymentReady(kcfg.Clientset, coreDNSDeployment, 240) - if err != nil { - log.Error().Msgf("Error waiting for CoreDNS deployment ready state: %s", err) - return err + log.Info().Msg("cluster not found") } - progressPrinter.IncrementTracker("verifying-civo-cluster-readiness", 1) - // kubernetes.BootstrapSecrets - // todo there is a secret condition in AddK3DSecrets to this not checked - // todo deconstruct CreateNamespaces / CreateSecret - // todo move secret structs to constants to be leveraged by either local or civo - progressPrinter.AddTracker("bootstrapping-kubernetes-resources", "Bootstrapping Kubernetes resources", 3) - progressPrinter.SetupProgress(progressPrinter.TotalOfTrackers(), false) - executionControl = viper.GetBool("kubefirst-checks.k8s-secrets-created") - if !executionControl { - err := civo.BootstrapCivoMgmtCluster( - config.CivoToken, - config.Kubeconfig, - config.GitProvider, - cGitUser, - os.Getenv("CF_API_TOKEN"), - os.Getenv("CF_ORIGIN_CA_ISSUER_API_TOKEN"), - gitopsRepoURL, - config.GitProtocol, - dnsProviderFlag, - gitopsDirectoryTokens.CloudProvider, - ) + if !clusterCreated.InProgress { + err := utilities.CreateCluster(cluster) if err != nil { - log.Info().Msg("Error adding kubernetes secrets for bootstrap") - return err + progress.Error("Unable to create the cluster") } - viper.Set("kubefirst-checks.k8s-secrets-created", true) - viper.WriteConfig() - progressPrinter.IncrementTracker("bootstrapping-kubernetes-resources", 1) - } else { - log.Info().Msg("already added secrets to civo cluster") - progressPrinter.IncrementTracker("bootstrapping-kubernetes-resources", 1) } - //* check for ssl restore - log.Info().Msg("checking for tls secrets to restore") - secretsFilesToRestore, err := ioutil.ReadDir(config.SSLBackupDir + "/secrets") - if err != nil { - log.Info().Msgf("%s", err) - } - if len(secretsFilesToRestore) != 0 { - // todo would like these but requires CRD's and is not currently supported - // add crds ( use execShellReturnErrors? ) - // https://raw.githubusercontent.com/cert-manager/cert-manager/v1.11.0/deploy/crds/crd-clusterissuers.yaml - // https://raw.githubusercontent.com/cert-manager/cert-manager/v1.11.0/deploy/crds/crd-certificates.yaml - // add certificates, and clusterissuers - log.Info().Msgf("found %d tls secrets to restore", len(secretsFilesToRestore)) - ssl.Restore(config.SSLBackupDir, domainNameFlag, config.Kubeconfig) - progressPrinter.IncrementTracker("bootstrapping-kubernetes-resources", 1) - } else { - log.Info().Msg("no files found in secrets directory, continuing") - progressPrinter.IncrementTracker("bootstrapping-kubernetes-resources", 1) + if clusterCreated.Status == "error" { + utilities.ResetClusterProgress(cluster.ClusterName) + utilities.CreateCluster(cluster) } - // Container registry authentication creation - containerRegistryAuth := gitShim.ContainerRegistryAuth{ - GitProvider: gitProviderFlag, - GitUser: cGitUser, - GitToken: cGitToken, - GitlabGroupFlag: gitlabGroupFlag, - GithubOwner: cGitOwner, - ContainerRegistryHost: containerRegistryHost, - Clientset: kcfg.Clientset, - } - containerRegistryAuthToken, err := gitShim.CreateContainerRegistrySecret(&containerRegistryAuth) - if err != nil { - return err - } + time.Sleep(time.Second * 2) + progress.StartProvisioning(cluster.ClusterName, 10) - progressPrinter.IncrementTracker("bootstrapping-kubernetes-resources", 1) - progressPrinter.AddTracker("installing-argo-cd", "Installing and configuring Argo CD", 3) - progressPrinter.SetupProgress(progressPrinter.TotalOfTrackers(), false) - - argoCDInstallPath := fmt.Sprintf("github.com:kubefirst/manifests/argocd/cloud?ref=%s", pkg.KubefirstManifestRepoRef) - - //* install argocd - executionControl = viper.GetBool("kubefirst-checks.argocd-install") - if !executionControl { - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricArgoCDInstallStarted, "") - - log.Info().Msgf("installing argocd") - err = argocd.ApplyArgoCDKustomize(kcfg.Clientset, argoCDInstallPath) - if err != nil { - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricArgoCDInstallFailed, err.Error()) - return err - } - viper.Set("kubefirst-checks.argocd-install", true) - viper.WriteConfig() - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricArgoCDInstallCompleted, "") - progressPrinter.IncrementTracker("installing-argo-cd", 1) - } else { - log.Info().Msg("argo cd already installed, continuing") - progressPrinter.IncrementTracker("installing-argo-cd", 1) - } + return nil +} - // Wait for ArgoCD to be ready - _, err = k8s.VerifyArgoCDReadiness(kcfg.Clientset, true, 300) - if err != nil { - log.Error().Msgf("error waiting for ArgoCD to become ready: %s", err) - return err +func ValidateProvidedFlags(gitProviderFlag string) error { + if os.Getenv("CIVO_TOKEN") == "" { + // telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricCloudCredentialsCheckFailed, "CIVO_TOKEN environment variable was not set") + return fmt.Errorf("your CIVO_TOKEN is not set - please set and re-run your last command") } - //* ArgoCD port-forward - argoCDStopChannel := make(chan struct{}, 1) - defer func() { - close(argoCDStopChannel) - }() - k8s.OpenPortForwardPodWrapper( - kcfg.Clientset, - kcfg.RestConfig, - "argocd-server", // todo fix this, it should `argocd - "argocd", - 8080, - 8080, - argoCDStopChannel, - ) - log.Info().Msgf("port-forward to argocd is available at %s", providerConfigs.ArgocdPortForwardURL) - - //* argocd pods are ready, get and set credentials - var argocdPassword string - executionControl = viper.GetBool("kubefirst-checks.argocd-credentials-set") - if !executionControl { - log.Info().Msg("Setting argocd username and password credentials") - - argocd.ArgocdSecretClient = kcfg.Clientset.CoreV1().Secrets("argocd") - - argocdPassword = k8s.GetSecretValue(argocd.ArgocdSecretClient, "argocd-initial-admin-secret", "password") - if argocdPassword == "" { - log.Info().Msg("argocd password not found in secret") - return err - } - - viper.Set("components.argocd.password", argocdPassword) - viper.Set("components.argocd.username", "admin") - viper.WriteConfig() - log.Info().Msg("argocd username and password credentials set successfully") - - log.Info().Msg("Getting an argocd auth token") - // todo return in here and pass argocdAuthToken as a parameter - token, err := argocd.GetArgoCDToken("admin", argocdPassword) - if err != nil { - return err - } - - log.Info().Msg("argocd admin auth token set") - - viper.Set("components.argocd.auth-token", token) - viper.Set("kubefirst-checks.argocd-credentials-set", true) - viper.WriteConfig() - progressPrinter.IncrementTracker("installing-argo-cd", 1) - } else { - log.Info().Msg("argo credentials already set, continuing") - progressPrinter.IncrementTracker("installing-argo-cd", 1) + if os.Getenv("GITHUB_TOKEN") == "" { + return fmt.Errorf("your GITHUB_TOKEN is not set. Please set and try again") } - - if configs.K1Version == "development" { - err := clipboard.WriteAll(argocdPassword) - if err != nil { - log.Error().Err(err).Msg("") - } - - err = pkg.OpenBrowser(pkg.ArgocdPortForwardURL) - if err != nil { - log.Error().Err(err).Msg("") + // Validate required environment variables for dns provider + if dnsProviderFlag == "cloudflare" { + if os.Getenv("CF_API_TOKEN") == "" { + return fmt.Errorf("your CF_API_TOKEN environment variable is not set. Please set and try again") } } - //* argocd sync registry and start sync waves - executionControl = viper.GetBool("kubefirst-checks.argocd-create-registry") - if !executionControl { - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricCreateRegistryStarted, "") - argocdClient, err := argocdapi.NewForConfig(kcfg.RestConfig) + switch gitProviderFlag { + case "github": + key, err := internalssh.GetHostKey("github.com") if err != nil { - return err - } - - log.Info().Msg("applying the registry application to argocd") - - var registryApplicationObject *v1alpha1ArgocdApplication.Application = nil - if gitProviderFlag == "github" { - registryApplicationObject = argocd.GetArgoCDApplicationObject(gitopsRepoURL, fmt.Sprintf("registry/clusters/%s", clusterNameFlag)) + return fmt.Errorf("known_hosts file does not exist - please run `ssh-keyscan github.com >> ~/.ssh/known_hosts` to remedy") } else { - registryApplicationObject = argocd.GetArgoCDApplicationObject(gitopsRepoURL, fmt.Sprintf("registry/%s", clusterNameFlag)) - } - - _, _ = argocdClient.ArgoprojV1alpha1().Applications("argocd").Create(context.Background(), registryApplicationObject, metav1.CreateOptions{}) - viper.Set("kubefirst-checks.argocd-create-registry", true) - viper.WriteConfig() - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricCreateRegistryCompleted, "") - progressPrinter.IncrementTracker("installing-argo-cd", 1) - } else { - log.Info().Msg("argocd registry create already done, continuing") - progressPrinter.IncrementTracker("installing-argo-cd", 1) - } - - // Wait for Vault StatefulSet Pods to transition to Running - progressPrinter.AddTracker("configuring-vault", "Configuring Vault", 4) - progressPrinter.SetupProgress(progressPrinter.TotalOfTrackers(), false) - - vaultStatefulSet, err := k8s.ReturnStatefulSetObject( - kcfg.Clientset, - "app.kubernetes.io/instance", - "vault", - "vault", - 240, - ) - if err != nil { - log.Error().Msgf("Error finding Vault StatefulSet: %s", err) - return err - } - _, err = k8s.WaitForStatefulSetReady(kcfg.Clientset, vaultStatefulSet, 240, true) - if err != nil { - log.Error().Msgf("Error waiting for Vault StatefulSet ready state: %s", err) - return err - } - progressPrinter.IncrementTracker("configuring-vault", 1) - - // Init and unseal vault - // We need to wait before we try to run any of these commands or there may be - // unexpected timeouts - time.Sleep(time.Second * 10) - progressPrinter.IncrementTracker("configuring-vault", 1) - - executionControl = viper.GetBool("kubefirst-checks.vault-initialized") - if !executionControl { - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricVaultInitializationStarted, "") - - // Initialize and unseal Vault - vaultHandlerPath := "github.com:kubefirst/manifests.git/vault-handler/replicas-3" - - // Build and apply manifests - yamlData, err := kcfg.KustomizeBuild(vaultHandlerPath) - if err != nil { - return err - } - output, err := kcfg.SplitYAMLFile(yamlData) - if err != nil { - return err - } - err = kcfg.ApplyObjects("", output) - if err != nil { - return err - } - - // Wait for the Job to finish - job, err := k8s.ReturnJobObject(kcfg.Clientset, "vault", "vault-handler") - if err != nil { - return err + log.Info().Msgf("%s %s\n", "github.com", key.Type()) } - _, err = k8s.WaitForJobComplete(kcfg.Clientset, job, 240) + case "gitlab": + key, err := internalssh.GetHostKey("gitlab.com") if err != nil { - msg := fmt.Sprintf("could not run vault unseal job: %s", err) - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricVaultInitializationFailed, msg) - log.Fatal().Msg(msg) - } - - viper.Set("kubefirst-checks.vault-initialized", true) - viper.WriteConfig() - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricVaultInitializationCompleted, "") - progressPrinter.IncrementTracker("configuring-vault", 1) - } else { - log.Info().Msg("vault is already initialized - skipping") - progressPrinter.IncrementTracker("configuring-vault", 1) - } - - //* configure vault with terraform - //* vault port-forward - vaultStopChannel := make(chan struct{}, 1) - defer func() { - close(vaultStopChannel) - }() - k8s.OpenPortForwardPodWrapper( - kcfg.Clientset, - kcfg.RestConfig, - "vault-0", - "vault", - 8200, - 8200, - vaultStopChannel, - ) - - executionControl = viper.GetBool("kubefirst-checks.terraform-apply-vault") - if !executionControl { - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricVaultTerraformApplyStarted, "") - - tfEnvs := map[string]string{} - var usernamePasswordString, base64DockerAuth string - - //* run vault terraform - log.Info().Msg("configuring vault with terraform") - if config.GitProvider == "gitlab" { - usernamePasswordString = fmt.Sprintf("%s:%s", "container-registry-auth", containerRegistryAuthToken) - base64DockerAuth = base64.StdEncoding.EncodeToString([]byte(usernamePasswordString)) - - tfEnvs["TF_VAR_container_registry_auth"] = containerRegistryAuthToken - } else { - usernamePasswordString = fmt.Sprintf("%s:%s", cGitUser, cGitToken) - base64DockerAuth = base64.StdEncoding.EncodeToString([]byte(usernamePasswordString)) - } - - if viper.GetString("flags.dns-provider") == "cloudflare" { - tfEnvs[fmt.Sprintf("TF_VAR_%s_secret", gitopsDirectoryTokens.ExternalDNSProviderName)] = config.CloudflareAPIToken - tfEnvs[fmt.Sprintf("TF_VAR_%s_secret", gitopsDirectoryTokens.ExternalDNSProviderName)] = config.CloudflareOriginCaIssuerAPIToken + return fmt.Errorf("known_hosts file does not exist - please run `ssh-keyscan gitlab.com >> ~/.ssh/known_hosts` to remedy") } else { - tfEnvs[fmt.Sprintf("TF_VAR_%s_secret", gitopsDirectoryTokens.ExternalDNSProviderName)] = config.CivoToken - } - - tfEnvs["TF_VAR_b64_docker_auth"] = base64DockerAuth - tfEnvs = civo.GetVaultTerraformEnvs(kcfg.Clientset, config, tfEnvs) - tfEnvs = civo.GetCivoTerraformEnvs(config, tfEnvs) - - //dns provider secret to be stored in vault for external dns lifecycle - switch dnsProviderFlag { - case "cloudflare": - tfEnvs[fmt.Sprintf("TF_VAR_%s_secret", strings.ToLower(dnsProviderFlag))] = config.CloudflareAPIToken - default: - tfEnvs[fmt.Sprintf("TF_VAR_%s_secret", strings.ToLower(dnsProviderFlag))] = config.CivoToken - } - - tfEntrypoint := config.GitopsDir + "/terraform/vault" - err := terraform.InitApplyAutoApprove(config.TerraformClient, tfEntrypoint, tfEnvs) - if err != nil { - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricVaultTerraformApplyFailed, err.Error()) - return err - } - - log.Info().Msg("vault terraform executed successfully") - viper.Set("kubefirst-checks.terraform-apply-vault", true) - viper.WriteConfig() - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricVaultTerraformApplyCompleted, "") - progressPrinter.IncrementTracker("configuring-vault", 1) - } else { - log.Info().Msg("already executed vault terraform") - progressPrinter.IncrementTracker("configuring-vault", 1) - } - - //* create users - progressPrinter.AddTracker("creating-users", "Creating users", 1) - progressPrinter.SetupProgress(progressPrinter.TotalOfTrackers(), false) - - executionControl = viper.GetBool("kubefirst-checks.terraform-apply-users") - if !executionControl { - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricUsersTerraformApplyStarted, "") - - log.Info().Msg("applying users terraform") - - tfEnvs := map[string]string{} - tfEnvs = civo.GetCivoTerraformEnvs(config, tfEnvs) - tfEnvs = civo.GetUsersTerraformEnvs(kcfg.Clientset, config, tfEnvs) - tfEntrypoint := config.GitopsDir + "/terraform/users" - err := terraform.InitApplyAutoApprove(config.TerraformClient, tfEntrypoint, tfEnvs) - if err != nil { - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricUsersTerraformApplyStarted, err.Error()) - return err - } - log.Info().Msg("executed users terraform successfully") - // progressPrinter.IncrementTracker("step-users", 1) - viper.Set("kubefirst-checks.terraform-apply-users", true) - viper.WriteConfig() - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricUsersTerraformApplyCompleted, "") - progressPrinter.IncrementTracker("creating-users", 1) - } else { - log.Info().Msg("already created users with terraform") - progressPrinter.IncrementTracker("creating-users", 1) - } - - // Wait for console Deployment Pods to transition to Running - progressPrinter.AddTracker("syncing-remaining-argocd-apps", "Syncing Remaining ArgoCD Apps", 1) - progressPrinter.SetupProgress(progressPrinter.TotalOfTrackers(), false) - - consoleDeployment, err := k8s.ReturnDeploymentObject( - kcfg.Clientset, - "app.kubernetes.io/instance", - "kubefirst", - "kubefirst", - 1200, - ) - if err != nil { - log.Error().Msgf("Error finding console Deployment: %s", err) - return err - } - _, err = k8s.WaitForDeploymentReady(kcfg.Clientset, consoleDeployment, 240) - if err != nil { - log.Error().Msgf("Error waiting for console Deployment ready state: %s", err) - return err - } - - //* console port-forward - progressPrinter.IncrementTracker("syncing-remaining-argocd-apps", 1) - consoleStopChannel := make(chan struct{}, 1) - defer func() { - close(consoleStopChannel) - }() - k8s.OpenPortForwardPodWrapper( - kcfg.Clientset, - kcfg.RestConfig, - "kubefirst-console", - "kubefirst", - 8080, - 9094, - consoleStopChannel, - ) - - err = pkg.IsConsoleUIAvailable(pkg.KubefirstConsoleLocalURLCloud) - if err != nil { - log.Error().Err(err).Msg("") - } - - // Mark cluster install as complete - telemetryShim.Transmit(useTelemetryFlag, segmentClient, segment.MetricClusterInstallCompleted, "") - viper.Set("kubefirst-checks.cluster-install-complete", true) - viper.WriteConfig() - - // Set flags used to track status of active options - helpers.SetClusterStatusFlags(civo.CloudProvider, config.GitProvider) - - //Export and Import Cluster - cl := utilities.CreateClusterRecordFromRaw(useTelemetryFlag, cGitOwner, cGitUser, cGitToken, cGitlabOwnerGroupID, gitopsTemplateURLFlag, gitopsTemplateBranchFlag) - - var localFilePath = fmt.Sprintf("%s/%s.json", "/tmp/api/cluster/export", clusterNameFlag) - var remoteFilePath = fmt.Sprintf("%s.json", clusterNameFlag) - utilities.CreateClusterRecordFile(clusterNameFlag, cl) - - pushObject := runtimetypes.PushBucketObject{ - LocalFilePath: localFilePath, - RemoteFilePath: remoteFilePath, - ContentType: "application/json", - } - - err = utils.PutClusterObject(&cl.StateStoreCredentials, &cl.StateStoreDetails, &pushObject) - if err != nil { - log.Error().Err(err).Msgf("error pushing cluster object, %s", cl.StateStoreDetails.Hostname) - return err - } - - kubernetesConfig := runtimetypes.KubernetesClient{ - Clientset: kcfg.Clientset, - KubeConfigPath: kcfg.KubeConfigPath, - RestConfig: kcfg.RestConfig, - } - - err = utils.ExportCluster(kubernetesConfig, cl) - if err != nil { - log.Error().Err(err).Msg("error exporting cluster object") - viper.Set("kubefirst.setup-complete", false) - viper.Set("kubefirst-checks.cluster-install-complete", false) - viper.WriteConfig() - return err - } else { - err = pkg.OpenBrowser(pkg.KubefirstConsoleLocalURLCloud) - if err != nil { - log.Error().Err(err).Msg("") - } - - log.Info().Msg("kubefirst installation complete") - log.Info().Msg("welcome to your new kubefirst platform running in K3d") - time.Sleep(time.Second * 1) // allows progress bars to finish - - if !ciFlag { - reports.CivoHandoffScreen(viper.GetString("components.argocd.password"), clusterNameFlag, domainNameFlag, cGitOwner, config, false) + log.Info().Msgf("%s %s\n", "gitlab.com", key.Type()) } } - defer func(c segment.SegmentClient) { - err := c.Client.Close() - if err != nil { - log.Info().Msgf("error closing segment client %s", err.Error()) - } - }(*segmentClient) - return nil - } diff --git a/cmd/civo/destroy.go b/cmd/civo/destroy.go index 37fc1994f..cc489ddea 100644 --- a/cmd/civo/destroy.go +++ b/cmd/civo/destroy.go @@ -7,48 +7,25 @@ See the LICENSE file for more details. package civo import ( - "crypto/tls" "fmt" - "net/http" "os" - "strconv" "strings" - "time" - "github.com/civo/civogo" + "github.com/kubefirst/kubefirst/internal/launch" + "github.com/kubefirst/kubefirst/internal/progress" "github.com/kubefirst/runtime/pkg" - "github.com/kubefirst/runtime/pkg/argocd" - "github.com/kubefirst/runtime/pkg/civo" - gitlab "github.com/kubefirst/runtime/pkg/gitlab" - "github.com/kubefirst/runtime/pkg/helpers" - "github.com/kubefirst/runtime/pkg/k8s" - "github.com/kubefirst/runtime/pkg/progressPrinter" "github.com/kubefirst/runtime/pkg/providerConfigs" - "github.com/kubefirst/runtime/pkg/terraform" "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/spf13/viper" ) func destroyCivo(cmd *cobra.Command, args []string) error { - helpers.DisplayLogHints() + progress.DisplayLogHints() // Determine if there are active installs gitProvider := viper.GetString("flags.git-provider") gitProtocol := viper.GetString("flags.git-protocol") - // _, err := helpers.EvalDestroy(civo.CloudProvider, gitProvider) - // if err != nil { - // return err - // } - - // Check for existing port forwards before continuing - err := k8s.CheckForExistingPortForwards(8080) - if err != nil { - return fmt.Errorf("%s - this port is required to tear down your kubefirst environment - please close any existing port forwards before continuing", err.Error()) - } - - progressPrinter.AddTracker("preflight-checks", "Running preflight checks", 1) - progressPrinter.SetupProgress(progressPrinter.TotalOfTrackers(), false) log.Info().Msg("destroying kubefirst platform in civo") @@ -95,223 +72,23 @@ func destroyCivo(cmd *cobra.Command, args []string) error { if len(config.CivoToken) == 0 { return fmt.Errorf("\n\nYour CIVO_TOKEN environment variable isn't set,\nvisit this link https://dashboard.civo.com/security and set the environment variable") } - progressPrinter.IncrementTracker("preflight-checks", 1) - - progressPrinter.AddTracker("platform-destroy", "Destroying your kubefirst platform", 2) - progressPrinter.SetupProgress(progressPrinter.TotalOfTrackers(), false) - - switch gitProvider { - case "github": - if viper.GetBool("kubefirst-checks.terraform-apply-github") { - log.Info().Msg("destroying github resources with terraform") - - tfEntrypoint := config.GitopsDir + "/terraform/github" - tfEnvs := map[string]string{} - tfEnvs = civo.GetCivoTerraformEnvs(config, tfEnvs) - tfEnvs = civo.GetGithubTerraformEnvs(config, tfEnvs) - err := terraform.InitDestroyAutoApprove(config.TerraformClient, tfEntrypoint, tfEnvs) - if err != nil { - log.Printf("error executing terraform destroy %s", tfEntrypoint) - return err - } - viper.Set("kubefirst-checks.terraform-apply-github", false) - viper.WriteConfig() - log.Info().Msg("github resources terraform destroyed") - progressPrinter.IncrementTracker("platform-destroy", 1) - } - case "gitlab": - if viper.GetBool("kubefirst-checks.terraform-apply-gitlab") { - log.Info().Msg("destroying gitlab resources with terraform") - gitlabClient, err := gitlab.NewGitLabClient(cGitToken, cGitOwner) - if err != nil { - return err - } - - // Before removing Terraform resources, remove any container registry repositories - // since failing to remove them beforehand will result in an apply failure - var projectsForDeletion = []string{"gitops", "metaphor"} - for _, project := range projectsForDeletion { - projectExists, err := gitlabClient.CheckProjectExists(project) - if err != nil { - log.Fatal().Msgf("could not check for existence of project %s: %s", project, err) - } - if projectExists { - log.Info().Msgf("checking project %s for container registries...", project) - crr, err := gitlabClient.GetProjectContainerRegistryRepositories(project) - if err != nil { - log.Fatal().Msgf("could not retrieve container registry repositories: %s", err) - } - if len(crr) > 0 { - for _, cr := range crr { - err := gitlabClient.DeleteContainerRegistryRepository(project, cr.ID) - if err != nil { - log.Fatal().Msgf("error deleting container registry repository: %s", err) - } - } - } else { - log.Info().Msgf("project %s does not have any container registries, skipping", project) - } - } else { - log.Info().Msgf("project %s does not exist, skipping", project) - } - } - - tfEntrypoint := config.GitopsDir + "/terraform/gitlab" - tfEnvs := map[string]string{} - tfEnvs = civo.GetCivoTerraformEnvs(config, tfEnvs) - tfEnvs = civo.GetGitlabTerraformEnvs(config, tfEnvs, gitlabClient.ParentGroupID) - err = terraform.InitDestroyAutoApprove(config.TerraformClient, tfEntrypoint, tfEnvs) - if err != nil { - log.Printf("error executing terraform destroy %s", tfEntrypoint) - return err - } - viper.Set("kubefirst-checks.terraform-apply-gitlab", false) - viper.WriteConfig() - log.Info().Msg("github resources terraform destroyed") - - progressPrinter.IncrementTracker("platform-destroy", 1) - } - } - - if viper.GetBool("kubefirst-checks.terraform-apply-civo") || viper.GetBool("kubefirst-checks.terraform-apply-civo-failed") { - kcfg := k8s.CreateKubeConfig(false, config.Kubeconfig) - - log.Info().Msg("destroying civo resources with terraform") - - clusterName := viper.GetString("flags.cluster-name") - region := viper.GetString("flags.cloud-region") - - client, err := civogo.NewClient(os.Getenv("CIVO_TOKEN"), region) - if err != nil { - return fmt.Errorf(err.Error()) - } - - cluster, err := client.FindKubernetesCluster(clusterName) - if err != nil { - return err - } - log.Info().Msg("cluster name: " + cluster.ID) - - clusterVolumes, err := client.ListVolumesForCluster(cluster.ID) - if err != nil { - return err - } - - // Only port-forward to ArgoCD and delete registry if ArgoCD was installed - if viper.GetBool("kubefirst-checks.argocd-install") { - log.Info().Msg("opening argocd port forward") - //* ArgoCD port-forward - argoCDStopChannel := make(chan struct{}, 1) - defer func() { - close(argoCDStopChannel) - }() - k8s.OpenPortForwardPodWrapper( - kcfg.Clientset, - kcfg.RestConfig, - "argocd-server", - "argocd", - 8080, - 8080, - argoCDStopChannel, - ) - - log.Info().Msg("getting new auth token for argocd") - - secData, err := k8s.ReadSecretV2(kcfg.Clientset, "argocd", "argocd-initial-admin-secret") - if err != nil { - return err - } - argocdPassword := secData["password"] - - argocdAuthToken, err := argocd.GetArgoCDToken("admin", argocdPassword) - if err != nil { - return err - } - - log.Info().Msgf("port-forward to argocd is available at %s", providerConfigs.ArgocdPortForwardURL) - - customTransport := http.DefaultTransport.(*http.Transport).Clone() - customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - argocdHttpClient := http.Client{Transport: customTransport} - log.Info().Msg("deleting the registry application") - httpCode, _, err := argocd.DeleteApplication(&argocdHttpClient, config.RegistryAppName, argocdAuthToken, "true") - if err != nil { - return err - } - log.Info().Msgf("http status code %d", httpCode) - } - - for _, vol := range clusterVolumes { - log.Info().Msg("removing volume with name: " + vol.Name) - _, err := client.DeleteVolume(vol.ID) - if err != nil { - return err - } - log.Info().Msg("volume " + vol.ID + " deleted") - } - - // Pause before cluster destroy to prevent a race condition - log.Info().Msg("waiting for Civo Kubernetes cluster resource removal to finish...") - time.Sleep(time.Second * 10) - log.Info().Msg("destroying civo cloud resources") - tfEntrypoint := config.GitopsDir + "/terraform/civo" - tfEnvs := map[string]string{} - tfEnvs = civo.GetCivoTerraformEnvs(config, tfEnvs) + launch.Down(true) - switch gitProvider { - case "github": - tfEnvs = civo.GetGithubTerraformEnvs(config, tfEnvs) - case "gitlab": - gid, err := strconv.Atoi(viper.GetString("flags.gitlab-owner-group-id")) - if err != nil { - return fmt.Errorf("couldn't convert gitlab group id to int: %s", err) - } - tfEnvs = civo.GetGitlabTerraformEnvs(config, tfEnvs, gid) - } - err = terraform.InitDestroyAutoApprove(config.TerraformClient, tfEntrypoint, tfEnvs) - if err != nil { - log.Printf("error executing terraform destroy %s", tfEntrypoint) - return err - } - viper.Set("kubefirst-checks.terraform-apply-civo", false) - viper.WriteConfig() - log.Info().Msg("civo resources terraform destroyed") - progressPrinter.IncrementTracker("platform-destroy", 1) - } - - // remove ssh key provided one was created - if viper.GetString("kbot.gitlab-user-based-ssh-key-title") != "" { - gitlabClient, err := gitlab.NewGitLabClient(cGitToken, cGitOwner) - if err != nil { - return err - } - log.Info().Msg("attempting to delete managed ssh key...") - err = gitlabClient.DeleteUserSSHKey(viper.GetString("kbot.gitlab-user-based-ssh-key-title")) - if err != nil { - log.Warn().Msg(err.Error()) - } + err := pkg.ResetK1Dir(config.K1Dir) + if err != nil { + return err } + log.Info().Msg("previous platform content removed") - //* remove local content and kubefirst config file for re-execution - if !viper.GetBool(fmt.Sprintf("kubefirst-checks.terraform-apply-%s", gitProvider)) && !viper.GetBool("kubefirst-checks.terraform-apply-civo") { - log.Info().Msg("removing previous platform content") - - err := pkg.ResetK1Dir(config.K1Dir) - if err != nil { - return err - } - log.Info().Msg("previous platform content removed") - - log.Info().Msg("resetting `$HOME/.kubefirst` config") - viper.Set("argocd", "") - viper.Set(gitProvider, "") - viper.Set("components", "") - viper.Set("kbot", "") - viper.Set("kubefirst-checks", "") - viper.Set("kubefirst", "") - viper.WriteConfig() - } + log.Info().Msg("resetting `$HOME/.kubefirst` config") + viper.Set("argocd", "") + viper.Set(gitProvider, "") + viper.Set("components", "") + viper.Set("kbot", "") + viper.Set("kubefirst-checks", "") + viper.Set("kubefirst", "") + viper.WriteConfig() if _, err := os.Stat(config.K1Dir + "/kubeconfig"); !os.IsNotExist(err) { err = os.Remove(config.K1Dir + "/kubeconfig") @@ -319,8 +96,8 @@ func destroyCivo(cmd *cobra.Command, args []string) error { return fmt.Errorf("unable to delete %q folder, error: %s", config.K1Dir+"/kubeconfig", err) } } - time.Sleep(time.Second * 2) // allows progress bars to finish - fmt.Printf("Your kubefirst platform running in %s has been destroyed.", civo.CloudProvider) + + progress.Success("Your kubefirst platform has been destroyed.") return nil } diff --git a/cmd/launch.go b/cmd/launch.go index 53ff146ed..9e9160add 100644 --- a/cmd/launch.go +++ b/cmd/launch.go @@ -41,7 +41,7 @@ func launchUp() *cobra.Command { TraverseChildren: true, PreRun: checkDocker, Run: func(cmd *cobra.Command, args []string) { - launch.Up(additionalHelmFlags) + launch.Up(additionalHelmFlags, false, true) }, } @@ -57,7 +57,7 @@ func launchDown() *cobra.Command { Short: "remove console and api instance", TraverseChildren: true, Run: func(cmd *cobra.Command, args []string) { - launch.Down() + launch.Down(false) }, } diff --git a/go.mod b/go.mod index 3fca01e59..44d29d3b8 100644 --- a/go.mod +++ b/go.mod @@ -6,15 +6,15 @@ require ( github.com/argoproj/argo-cd/v2 v2.6.7 github.com/atotto/clipboard v0.1.4 github.com/aws/aws-sdk-go v1.44.230 - github.com/charmbracelet/bubbles v0.15.0 - github.com/charmbracelet/bubbletea v0.23.2 + github.com/charmbracelet/bubbles v0.16.1 + github.com/charmbracelet/bubbletea v0.24.2 github.com/charmbracelet/lipgloss v0.7.1 github.com/chromedp/chromedp v0.8.7 github.com/civo/civogo v0.3.28 github.com/dustin/go-humanize v1.0.1 github.com/go-git/go-git/v5 v5.6.1 github.com/hashicorp/vault/api v1.9.0 - github.com/kubefirst/runtime v0.3.17 + github.com/kubefirst/runtime v0.3.18 github.com/rs/zerolog v1.29.0 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.7.0 @@ -49,6 +49,7 @@ require ( github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/alecthomas/chroma v0.10.0 // indirect github.com/argoproj/gitops-engine v0.7.3 // indirect github.com/argoproj/pkg v0.13.7-0.20221221191914-44694015343d // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect @@ -67,6 +68,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.18.7 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bombsimon/logrusr/v2 v2.0.1 // indirect github.com/bradleyfalzon/ghinstallation/v2 v2.1.0 // indirect @@ -74,12 +76,14 @@ require ( github.com/caarlos0/sshmarshal v0.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chai2010/gettext-go v0.1.0 // indirect + github.com/charmbracelet/harmonica v0.2.0 // indirect github.com/chromedp/cdproto v0.0.0-20230109101555-6b041c6303cc // indirect github.com/chromedp/sysutil v1.0.0 // indirect github.com/cloudflare/circl v1.1.0 // indirect github.com/denisbrodbeck/machineid v1.0.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/digitalocean/godo v1.98.0 // indirect + github.com/dlclark/regexp2 v1.4.0 // indirect github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/docker v23.0.5+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect @@ -115,6 +119,7 @@ require ( github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.11.0 // indirect + github.com/gorilla/css v1.0.0 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/jedib0t/go-pretty/v6 v6.4.6 // indirect github.com/jonboulle/clockwork v0.2.2 // indirect @@ -127,6 +132,7 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/microcosm-cc/bluemonday v1.0.21 // indirect github.com/miekg/dns v1.1.40 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/sha256-simd v1.0.0 // indirect @@ -136,6 +142,7 @@ require ( github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect github.com/otiai10/copy v1.7.0 // indirect @@ -166,6 +173,8 @@ require ( github.com/xlab/treeprint v1.1.0 // indirect github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect + github.com/yuin/goldmark v1.5.2 // indirect + github.com/yuin/goldmark-emoji v1.0.1 // indirect go.opencensus.io v0.24.0 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect golang.org/x/crypto v0.11.0 // indirect @@ -210,7 +219,8 @@ require ( github.com/aws/aws-sdk-go-v2/service/sso v1.12.6 // indirect github.com/aws/smithy-go v1.13.5 // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect - github.com/containerd/console v1.0.3 // indirect + github.com/charmbracelet/glamour v0.6.0 + github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/fatih/color v1.13.0 @@ -241,7 +251,7 @@ require ( github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/minio/minio-go/v7 v7.0.49 github.com/mitchellh/go-homedir v1.1.0 // indirect diff --git a/go.sum b/go.sum index 6ce2aaaca..34e2574b4 100644 --- a/go.sum +++ b/go.sum @@ -104,6 +104,8 @@ github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= +github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -194,9 +196,10 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.18.7/go.mod h1:JuTnSoeePXmMVe9G8Ncjj github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= -github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -234,13 +237,14 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= github.com/chai2010/gettext-go v0.1.0 h1:aA1B8BzqN7Df1JOuH91iwchFl+9wckvwUUTMCiQ0qXM= github.com/chai2010/gettext-go v0.1.0/go.mod h1:PBHWqCsO+bS+OxcVEwt0tCMNOXKykAEfB63RjWDvNvM= -github.com/charmbracelet/bubbles v0.15.0 h1:c5vZ3woHV5W2b8YZI1q7v4ZNQaPetfHuoHzx+56Z6TI= -github.com/charmbracelet/bubbles v0.15.0/go.mod h1:Y7gSFbBzlMpUDR/XM9MhZI374Q+1p1kluf1uLl8iK74= -github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU= -github.com/charmbracelet/bubbletea v0.23.2 h1:vuUJ9HJ7b/COy4I30e8xDVQ+VRDUEFykIjryPfgsdps= -github.com/charmbracelet/bubbletea v0.23.2/go.mod h1:FaP3WUivcTM0xOKNmhciz60M6I+weYLF76mr1JyI7sM= +github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY= +github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= +github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY= +github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg= +github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= +github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc= +github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= -github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= @@ -279,8 +283,9 @@ github.com/container-storage-interface/spec v1.5.0/go.mod h1:8K96oQNkJ7pFcC2R9Z1 github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= -github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.12/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= @@ -324,6 +329,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/digitalocean/godo v1.98.0 h1:potyC1eD0N9n5/P4/WmJuKgg+OGYZOBWEW+/aKTX6QQ= github.com/digitalocean/godo v1.98.0/go.mod h1:NRpFznZFvhHjBoqZAaOD3khVzsJ3EibzKqFL4R60dmA= +github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/distribution v2.8.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= @@ -578,6 +585,8 @@ github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEo github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= @@ -717,10 +726,9 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kubefirst/runtime v0.3.17 h1:Wt66Q/86DbUm4dTGd1xt3nShlmC7AXX39W37DW2aKZw= -github.com/kubefirst/runtime v0.3.17/go.mod h1:NUa8VcB99UKEwVUvhdCfiuEYQI44XfGFV2pBtX8YnVo= +github.com/kubefirst/runtime v0.3.18 h1:09ISYy23pZrtpYWAoJwwdCrrXTlfuzinMqJTho/1NiQ= +github.com/kubefirst/runtime v0.3.18/go.mod h1:NUa8VcB99UKEwVUvhdCfiuEYQI44XfGFV2pBtX8YnVo= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= github.com/libopenstorage/openstorage v1.0.0/go.mod h1:Sp1sIObHjat1BeXhfMqLZ14wnOzEhNx2YQedreMcUyc= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= @@ -756,13 +764,13 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= @@ -771,6 +779,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg= +github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA= github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= @@ -825,12 +835,9 @@ github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTd github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= -github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8= github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= @@ -856,6 +863,8 @@ github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -1132,6 +1141,10 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU= +github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= +github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -1328,6 +1341,7 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= @@ -1361,7 +1375,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1454,7 +1467,6 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1467,6 +1479,7 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/internal/gitShim/init.go b/internal/gitShim/init.go index 67894765a..68faffa14 100644 --- a/internal/gitShim/init.go +++ b/internal/gitShim/init.go @@ -8,10 +8,16 @@ package gitShim import ( "fmt" + "net/http" + "os" "github.com/kubefirst/runtime/pkg/github" "github.com/kubefirst/runtime/pkg/gitlab" + "github.com/kubefirst/runtime/pkg/handlers" + "github.com/kubefirst/runtime/pkg/services" + "github.com/kubefirst/runtime/pkg/types" "github.com/rs/zerolog/log" + "github.com/spf13/viper" ) type GitInitParameters struct { @@ -107,3 +113,91 @@ func InitializeGitProvider(p *GitInitParameters) error { return nil } + +func ValidateGitCredentials(gitProviderFlag string, githubOrgFlag string, gitlabGroupFlag string) (types.GitAuth, error) { + gitAuth := types.GitAuth{} + + // Switch based on git provider, set params + switch gitProviderFlag { + case "github": + if githubOrgFlag == "" { + return gitAuth, fmt.Errorf("please provide a github organization using the --github-org flag") + } + if os.Getenv("GITHUB_TOKEN") == "" { + return gitAuth, fmt.Errorf("your GITHUB_TOKEN is not set. Please set and try again") + } + + gitAuth.Owner = githubOrgFlag + gitAuth.Token = os.Getenv("GITHUB_TOKEN") + + // Verify token scopes + err := github.VerifyTokenPermissions(gitAuth.Token) + if err != nil { + return gitAuth, err + } + + // Handle authorization checks + httpClient := http.DefaultClient + gitHubService := services.NewGitHubService(httpClient) + gitHubHandler := handlers.NewGitHubHandler(gitHubService) + + // get github data to set user based on the provided token + log.Info().Msg("verifying github authentication") + githubUser, err := gitHubHandler.GetGitHubUser(gitAuth.Token) + if err != nil { + return gitAuth, err + } + + gitAuth.User = githubUser + viper.Set("github.user", githubUser) + err = viper.WriteConfig() + if err != nil { + return gitAuth, err + } + err = gitHubHandler.CheckGithubOrganizationPermissions(gitAuth.Token, githubOrgFlag, githubUser) + if err != nil { + return gitAuth, err + } + viper.Set("flags.github-owner", githubOrgFlag) + viper.WriteConfig() + case "gitlab": + if gitlabGroupFlag == "" { + return gitAuth, fmt.Errorf("please provide a gitlab group using the --gitlab-group flag") + } + if os.Getenv("GITLAB_TOKEN") == "" { + return gitAuth, fmt.Errorf("your GITLAB_TOKEN is not set. please set and try again") + } + + gitAuth.Token = os.Getenv("GITLAB_TOKEN") + + // Verify token scopes + err := gitlab.VerifyTokenPermissions(gitAuth.Token) + if err != nil { + return gitAuth, err + } + + gitlabClient, err := gitlab.NewGitLabClient(gitAuth.Token, gitlabGroupFlag) + if err != nil { + return gitAuth, err + } + + gitAuth.Owner = gitlabClient.ParentGroupPath + cGitlabOwnerGroupID := gitlabClient.ParentGroupID + log.Info().Msgf("set gitlab owner to %s", gitAuth.Owner) + + // Get authenticated user's name + user, _, err := gitlabClient.Client.Users.CurrentUser() + if err != nil { + return gitAuth, fmt.Errorf("unable to get authenticated user info - please make sure GITLAB_TOKEN env var is set %s", err) + } + gitAuth.User = user.Username + + viper.Set("flags.gitlab-owner", gitlabGroupFlag) + viper.Set("flags.gitlab-owner-group-id", cGitlabOwnerGroupID) + viper.WriteConfig() + default: + log.Error().Msgf("invalid git provider option") + } + + return gitAuth, nil +} diff --git a/internal/k3d/menu.go b/internal/k3d/menu.go index bff511caa..2080c2c52 100644 --- a/internal/k3d/menu.go +++ b/internal/k3d/menu.go @@ -102,7 +102,11 @@ func (m Model) View() string { return "\n" + m.List.View() } -func MongoDestinationChooser() string { +func MongoDestinationChooser(inCluster bool) string { + if inCluster { + return "in-cluster" + } + items := []list.Item{ Item("in-cluster"), Item("atlas"), diff --git a/internal/launch/cmd.go b/internal/launch/cmd.go index 882c8cf0f..7d5a3c8bb 100644 --- a/internal/launch/cmd.go +++ b/internal/launch/cmd.go @@ -14,16 +14,17 @@ import ( "math/rand" "net/http" "os" + "strconv" "strings" "time" "github.com/kubefirst/kubefirst/internal/helm" k3dint "github.com/kubefirst/kubefirst/internal/k3d" + "github.com/kubefirst/kubefirst/internal/progress" "github.com/kubefirst/runtime/configs" "github.com/kubefirst/runtime/pkg" "github.com/kubefirst/runtime/pkg/db" "github.com/kubefirst/runtime/pkg/downloadManager" - "github.com/kubefirst/runtime/pkg/helpers" "github.com/kubefirst/runtime/pkg/k3d" "github.com/kubefirst/runtime/pkg/k8s" log "github.com/sirupsen/logrus" @@ -44,42 +45,39 @@ var ( ) // Up -func Up(additionalHelmFlags []string) { +func Up(additionalHelmFlags []string, inCluster bool, useTelemetry bool) { if viper.GetBool("launch.deployed") { - fmt.Println("Kubefirst console has already been deployed. To start over, run `kubefirst launch down` to completely remove the existing console.") - os.Exit(1) + progress.Error("Kubefirst console has already been deployed. To start over, run `kubefirst launch down` to completely remove the existing console.") } - helpers.DisplayLogHints() + if !inCluster { + progress.DisplayLogHints() + } homeDir, err := os.UserHomeDir() if err != nil { - log.Fatalf("something went wrong getting home path: %s", err) + progress.Error(fmt.Sprintf("something went wrong getting home path: %s", err)) } dir := fmt.Sprintf("%s/.k1/%s", homeDir, consoleClusterName) if _, err := os.Stat(dir); os.IsNotExist(err) { err := os.MkdirAll(dir, os.ModePerm) if err != nil { - log.Infof("%s directory already exists, continuing", dir) + progress.Log(fmt.Sprintf("%s directory already exists, continuing", dir), "info") } } toolsDir := fmt.Sprintf("%s/tools", dir) if _, err := os.Stat(toolsDir); os.IsNotExist(err) { err := os.MkdirAll(toolsDir, os.ModePerm) if err != nil { - log.Infof("%s directory already exists, continuing", toolsDir) + progress.Log(fmt.Sprintf("%s directory already exists, continuing", toolsDir), "info") } } - if err := setupLaunchConfigFile(dir); err != nil { - log.Fatal(err) - } - dbInitialized := viper.GetBool("launch.database-initialized") var dbHost, dbUser, dbPassword string if !dbInitialized { - dbDestination := k3dint.MongoDestinationChooser() + dbDestination := k3dint.MongoDestinationChooser(inCluster) switch dbDestination { case "atlas": fmt.Println("MongoDB Atlas Host String: ") @@ -91,14 +89,12 @@ func Up(additionalHelmFlags []string) { fmt.Printf("\nMongoDB Atlas Password: ") dbPasswordInput, err := term.ReadPassword(0) if err != nil { - log.Fatalf("error parsing password: %s", err) + progress.Error(fmt.Sprintf("error parsing password: %s", err)) } dbPassword = string(dbPasswordInput) dbHost = strings.Replace(dbHost, "mongodb+srv://", "", -1) - fmt.Println() - // Verify database connectivity mdbcl := db.Connect(&db.MongoDBClientParameters{ HostType: dbDestination, @@ -108,7 +104,7 @@ func Up(additionalHelmFlags []string) { }) err = mdbcl.TestDatabaseConnection() if err != nil { - log.Fatalf("Error validating Mongo credentials: %s", err) + progress.Error(fmt.Sprintf("Error validating Mongo credentials: %s", err)) } mdbcl.Client.Disconnect(mdbcl.Context) @@ -122,21 +118,21 @@ func Up(additionalHelmFlags []string) { viper.Set("launch.database-initialized", true) viper.WriteConfig() default: - log.Fatalf("%s is not a valid option", dbDestination) + progress.Error(fmt.Sprintf("%s is not a valid option", dbDestination)) } } else { - log.Info("Database has already been initialized, skipping") + progress.Log("Database has already been initialized, skipping", "info") } fmt.Println() - log.Infof("%s/%s", k3d.LocalhostOS, k3d.LocalhostARCH) + progress.Log(fmt.Sprintf("%s/%s", k3d.LocalhostOS, k3d.LocalhostARCH), "info") // Download k3d k3dClient := fmt.Sprintf("%s/k3d", toolsDir) _, err = os.Stat(k3dClient) if err != nil { - log.Info("Downloading k3d...") + progress.Log("Downloading k3d...", "info") k3dDownloadUrl := fmt.Sprintf( "https://github.com/k3d-io/k3d/releases/download/%s/k3d-%s-%s", k3d.K3dVersion, @@ -145,21 +141,21 @@ func Up(additionalHelmFlags []string) { ) err = downloadManager.DownloadFile(k3dClient, k3dDownloadUrl) if err != nil { - log.Fatalf("error while trying to download k3d: %s", err) + progress.Error(fmt.Sprintf("error while trying to download k3d: %s", err)) } err = os.Chmod(k3dClient, 0755) if err != nil { log.Fatal(err.Error()) } } else { - log.Info("k3d is already installed, continuing") + progress.Log("k3d is already installed, continuing", "info") } // Download helm helmClient := fmt.Sprintf("%s/helm", toolsDir) _, err = os.Stat(helmClient) if err != nil { - log.Info("Downloading helm...") + progress.Log("Downloading helm...", "info") helmVersion := "v3.12.0" helmDownloadUrl := fmt.Sprintf( "https://get.helm.sh/helm-%s-%s-%s.tar.gz", @@ -170,11 +166,11 @@ func Up(additionalHelmFlags []string) { helmDownloadTarGzPath := fmt.Sprintf("%s/helm.tar.gz", toolsDir) err = downloadManager.DownloadFile(helmDownloadTarGzPath, helmDownloadUrl) if err != nil { - log.Fatalf("error while trying to download helm: %s", err) + progress.Error(fmt.Sprintf("error while trying to download helm: %s", err)) } helmTarDownload, err := os.Open(helmDownloadTarGzPath) if err != nil { - log.Fatalf("could not read helm download content") + progress.Error(fmt.Sprintf("could not read helm download content")) } downloadManager.ExtractFileFromTarGz( @@ -188,14 +184,14 @@ func Up(additionalHelmFlags []string) { } os.Remove(helmDownloadTarGzPath) } else { - log.Info("helm is already installed, continuing") + progress.Log("helm is already installed, continuing", "info") } // Download mkcert mkcertClient := fmt.Sprintf("%s/mkcert", toolsDir) _, err = os.Stat(mkcertClient) if err != nil { - log.Info("Downloading mkcert...") + progress.Log("Downloading mkcert...", "info") mkcertDownloadURL := fmt.Sprintf( "https://github.com/FiloSottile/mkcert/releases/download/%s/mkcert-%s-%s-%s", "v1.4.4", @@ -205,14 +201,14 @@ func Up(additionalHelmFlags []string) { ) err = downloadManager.DownloadFile(mkcertClient, mkcertDownloadURL) if err != nil { - log.Fatalf("error while trying to download mkcert: %s", err) + progress.Error(fmt.Sprintf("error while trying to download mkcert: %s", err)) } err = os.Chmod(mkcertClient, 0755) if err != nil { - log.Fatal(err.Error()) + progress.Error(err.Error()) } } else { - log.Info("mkcert is already installed, continuing") + progress.Log("mkcert is already installed, continuing", "info") } // Create k3d cluster @@ -225,7 +221,7 @@ func Up(additionalHelmFlags []string) { ) if err != nil { log.Warn("k3d cluster does not exist and will be created") - log.Info("Creating k3d cluster for Kubefirst console and API...") + progress.Log("Creating k3d cluster for Kubefirst console and API...", "info") err = k3d.ClusterCreateConsoleAPI( consoleClusterName, kubeconfigPath, @@ -234,13 +230,13 @@ func Up(additionalHelmFlags []string) { ) if err != nil { msg := fmt.Sprintf("error creating k3d cluster: %s", err) - log.Fatal(msg) + progress.Error(msg) } - log.Info("k3d cluster for Kubefirst console and API created successfully") + progress.Log("k3d cluster for Kubefirst console and API created successfully", "info") // Wait for traefik kcfg := k8s.CreateKubeConfig(false, kubeconfigPath) - log.Info("Waiting for traefik...") + progress.Log("Waiting for traefik...", "info") traefikDeployment, err := k8s.ReturnDeploymentObject( kcfg.Clientset, "app.kubernetes.io/name", @@ -249,16 +245,16 @@ func Up(additionalHelmFlags []string) { 240, ) if err != nil { - log.Fatalf("error looking for traefik: %s", err) + progress.Error(fmt.Sprintf("error looking for traefik: %s", err)) } _, err = k8s.WaitForDeploymentReady(kcfg.Clientset, traefikDeployment, 120) if err != nil { - log.Fatalf("error waiting for traefik: %s", err) + progress.Error(fmt.Sprintf("error waiting for traefik: %s", err)) } } else { log.Warn("Kubefirst console has already been deployed. To start over, run `kubefirst launch down` to completely remove the existing console.") log.Warnf("If you have manually removed %s, the k3d cluster must be manually removed by running the following command: ", dir) - log.Info(" k3d cluster delete kubefirst-console") + progress.Log(" k3d cluster delete kubefirst-console", "info") log.Warn("You will have to install the k3d utility if you do not have it installed if the directory shown above has been deleted.") os.Exit(1) } @@ -283,7 +279,7 @@ func Up(additionalHelmFlags []string) { err = yaml.Unmarshal([]byte(res), &existingHelmRepositories) if err != nil { - log.Fatalf("could not get existing helm repositories: %s", err) + progress.Error(fmt.Sprintf("could not get existing helm repositories: %s", err)) } for _, repo := range existingHelmRepositories { if repo.Name == helmChartRepoName && repo.URL == helmChartRepoURL { @@ -303,9 +299,9 @@ func Up(additionalHelmFlags []string) { if err != nil { log.Errorf("error adding helm chart repository: %s", err) } - log.Info("Added Kubefirst helm chart repository") + progress.Log("Added Kubefirst helm chart repository", "info") } else { - log.Info("Kubefirst helm chart repository already added") + progress.Log("Kubefirst helm chart repository already added", "info") } // Update helm chart repository locally @@ -317,7 +313,7 @@ func Up(additionalHelmFlags []string) { if err != nil { log.Errorf("error updating helm chart repository: %s", err) } - log.Info("Kubefirst helm chart repository updated") + progress.Log("Kubefirst helm chart repository updated", "info") // Determine if helm release has already been installed res, _, err = pkg.ExecShellReturnStrings( @@ -338,7 +334,7 @@ func Up(additionalHelmFlags []string) { err = yaml.Unmarshal([]byte(res), &existingHelmReleases) if err != nil { - log.Fatalf("could not get existing helm releases: %s", err) + progress.Error(fmt.Sprintf("could not get existing helm releases: %s", err)) } for _, release := range existingHelmReleases { if release.Name == helmChartName { @@ -370,6 +366,8 @@ func Up(additionalHelmFlags []string) { fmt.Sprintf("kubefirst-api.kubefirstTeam=%s", kubefirstTeam), "--set", fmt.Sprintf("kubefirst-api.kubefirstTeamInfo=%s", kubefirstTeamInfo), + "--set", + fmt.Sprintf("kubefirst-api.useTelemetry=%s", strconv.FormatBool(useTelemetry)), } if len(additionalHelmFlags) > 0 { @@ -402,7 +400,7 @@ func Up(additionalHelmFlags []string) { // Create Namespace _, err = kcfg.Clientset.CoreV1().Namespaces().Get(context.Background(), namespace, metav1.GetOptions{}) if err == nil { - log.Info("kubernetes Namespace already created - skipping") + progress.Log("kubernetes Namespace already created - skipping", "info") } else if strings.Contains(err.Error(), "not found") { _, err = kcfg.Clientset.CoreV1().Namespaces().Create(context.Background(), &v1.Namespace{ ObjectMeta: metav1.ObjectMeta{ @@ -410,15 +408,15 @@ func Up(additionalHelmFlags []string) { }, }, metav1.CreateOptions{}) if err != nil { - log.Fatalf("error creating kubernetes secret for initial secret: %s", err) + progress.Error(fmt.Sprintf("error creating kubernetes secret for initial secret: %s", err)) } - log.Info("Created Kubernetes Namespace for kubefirst") + progress.Log("Created Kubernetes Namespace for kubefirst", "info") } // Create Secret _, err = kcfg.Clientset.CoreV1().Secrets(namespace).Get(context.Background(), secretName, metav1.GetOptions{}) if err == nil { - log.Infof("kubernetes secret %s/%s already created - skipping", namespace, secretName) + progress.Log(fmt.Sprintf("kubernetes secret %s/%s already created - skipping", namespace, secretName), "info") } else if strings.Contains(err.Error(), "not found") { _, err = kcfg.Clientset.CoreV1().Secrets(namespace).Create(context.Background(), &v1.Secret{ Type: "Opaque", @@ -431,25 +429,25 @@ func Up(additionalHelmFlags []string) { }, }, metav1.CreateOptions{}) if err != nil { - log.Fatalf("error creating kubernetes secret for initial secret: %s", err) + progress.Error(fmt.Sprintf("error creating kubernetes secret for initial secret: %s", err)) } - log.Info("Created Kubernetes Secret for database authentication") + progress.Log("Created Kubernetes Secret for database authentication", "info") } } // Install helm chart a, b, err := pkg.ExecShellReturnStrings(helmClient, installFlags...) if err != nil { - log.Fatalf("error installing helm chart: %s %s %s", err, a, b) + progress.Error(fmt.Sprintf("error installing helm chart: %s %s %s", err, a, b)) } - log.Info("Kubefirst console helm chart installed successfully") + progress.Log("Kubefirst console helm chart installed successfully", "info") } else { - log.Info("Kubefirst console helm chart already installed") + progress.Log("Kubefirst console helm chart already installed", "info") } // Wait for API Deployment Pods to transition to Running - log.Info("Waiting for Kubefirst API Deployment...") + progress.Log("Waiting for Kubefirst API Deployment...", "info") apiDeployment, err := k8s.ReturnDeploymentObject( kcfg.Clientset, "app.kubernetes.io/name", @@ -458,11 +456,11 @@ func Up(additionalHelmFlags []string) { 240, ) if err != nil { - log.Fatalf("error looking for kubefirst api: %s", err) + progress.Error(fmt.Sprintf("error looking for kubefirst api: %s", err)) } _, err = k8s.WaitForDeploymentReady(kcfg.Clientset, apiDeployment, 300) if err != nil { - log.Fatalf("error waiting for kubefirst api: %s", err) + progress.Error(fmt.Sprintf("error waiting for kubefirst api: %s", err)) } // Generate certificate for console @@ -473,7 +471,7 @@ func Up(additionalHelmFlags []string) { log.Warnf("%s directory already exists, continuing", sslPemDir) } } - log.Info("Certificate directory created") + progress.Log("Certificate directory created", "info") mkcertPemDir := fmt.Sprintf("%s/%s/pem", sslPemDir, "kubefirst.dev") if _, err := os.Stat(mkcertPemDir); os.IsNotExist(err) { @@ -497,22 +495,22 @@ func Up(additionalHelmFlags []string) { fullAppAddress, ) if err != nil { - log.Fatalf("error generating certificate for console: %s", err) + progress.Error(fmt.Sprintf("error generating certificate for console: %s", err)) } //* read certificate files certPem, err := os.ReadFile(fmt.Sprintf("%s/%s-cert.pem", mkcertPemDir, "kubefirst-console")) if err != nil { - log.Fatalf("error generating certificate for console: %s", err) + progress.Error(fmt.Sprintf("error generating certificate for console: %s", err)) } keyPem, err := os.ReadFile(fmt.Sprintf("%s/%s-key.pem", mkcertPemDir, "kubefirst-console")) if err != nil { - log.Fatalf("error generating certificate for console: %s", err) + progress.Error(fmt.Sprintf("error generating certificate for console: %s", err)) } _, err = kcfg.Clientset.CoreV1().Secrets(namespace).Get(context.Background(), "kubefirst-console-tls", metav1.GetOptions{}) if err == nil { - log.Infof("kubernetes secret %s/%s already created - skipping", namespace, "kubefirst-console") + progress.Log(fmt.Sprintf("kubernetes secret %s/%s already created - skipping", namespace, "kubefirst-console"), "info") } else if strings.Contains(err.Error(), "not found") { _, err = kcfg.Clientset.CoreV1().Secrets(namespace).Create(context.Background(), &v1.Secret{ Type: "kubernetes.io/tls", @@ -526,25 +524,27 @@ func Up(additionalHelmFlags []string) { }, }, metav1.CreateOptions{}) if err != nil { - log.Fatalf("error creating kubernetes secret for cert: %s", err) + progress.Error(fmt.Sprintf("error creating kubernetes secret for cert: %s", err)) } - log.Info("Created Kubernetes Secret for certificate") + progress.Log("Created Kubernetes Secret for certificate", "info") } - log.Infof("Kubefirst Console is now available! %s", consoleURL) + if !inCluster { + progress.Log(fmt.Sprintf("Kubefirst Console is now available! %s", consoleURL), "info") - log.Warn("Kubefirst has generated local certificates for use with the console using `mkcert`.") - log.Warn("If you experience certificate errors when accessing the console, please run the following command: ") - log.Warnf(" %s -install", mkcertClient) - log.Warn() - log.Warn("For more information on `mkcert`, check out: https://github.com/FiloSottile/mkcert") + log.Warn("Kubefirst has generated local certificates for use with the console using `mkcert`.") + log.Warn("If you experience certificate errors when accessing the console, please run the following command: ") + log.Warnf(" %s -install", mkcertClient) + log.Warn() + log.Warn("For more information on `mkcert`, check out: https://github.com/FiloSottile/mkcert") - log.Info("To remove Kubefirst Console and the k3d cluster it runs in, please run the following command: ") - log.Info(" kubefirst launch down") + progress.Log("To remove Kubefirst Console and the k3d cluster it runs in, please run the following command: ", "") + progress.Log("kubefirst launch down", "") - err = pkg.OpenBrowser(consoleURL) - if err != nil { - log.Errorf("error attempting to open console in browser: %s", err) + err = pkg.OpenBrowser(consoleURL) + if err != nil { + log.Errorf("error attempting to open console in browser: %s", err) + } } viper.Set("launch.deployed", true) @@ -552,35 +552,40 @@ func Up(additionalHelmFlags []string) { } // Down destroys a k3d cluster for Kubefirst console and API -func Down() { - helpers.DisplayLogHints() +func Down(inCluster bool) { + if !inCluster { + progress.DisplayLogHints() + } homeDir, err := os.UserHomeDir() if err != nil { - log.Fatalf("something went wrong getting home path: %s", err) + progress.Error(fmt.Sprintf("something went wrong getting home path: %s", err)) } - log.Info("Deleting k3d cluster for Kubefirst console and API") + progress.Log("Deleting k3d cluster for Kubefirst console and API", "info") dir := fmt.Sprintf("%s/.k1/%s", homeDir, consoleClusterName) if _, err := os.Stat(dir); os.IsNotExist(err) { - log.Fatalf("cluster %s directory does not exist", dir) + progress.Error(fmt.Sprintf("cluster %s directory does not exist", dir)) } toolsDir := fmt.Sprintf("%s/tools", dir) k3dClient := fmt.Sprintf("%s/k3d", toolsDir) _, _, err = pkg.ExecShellReturnStrings(k3dClient, "cluster", "delete", consoleClusterName) if err != nil { - log.Fatalf("error deleting k3d cluster: %s", err) + progress.Error(fmt.Sprintf("error deleting k3d cluster: %s", err)) } - log.Info("k3d cluster for Kubefirst console and API deleted successfully") + progress.Log("k3d cluster for Kubefirst console and API deleted successfully", "info") - log.Infof("Deleting cluster directory at %s", dir) + progress.Log(fmt.Sprintf("Deleting cluster directory at %s", dir), "info") err = os.RemoveAll(dir) if err != nil { log.Warnf("unable to remove directory at %s", dir) } + + progress.Success("Your kubefirst platform has been destroyed.") + progress.Progress.Quit() } // ListClusters makes a request to the console API to list created clusters @@ -592,7 +597,7 @@ func ListClusters() { dir := fmt.Sprintf("%s/.k1/%s", homeDir, consoleClusterName) if _, err := os.Stat(dir); os.IsNotExist(err) { - log.Infof("unable to list clusters - cluster %s directory does not exist", dir) + log.Info(fmt.Sprintf("unable to list clusters - cluster %s directory does not exist", dir)) } // Port forward to API @@ -663,7 +668,7 @@ func DeleteCluster(managedClusterName string) { dir := fmt.Sprintf("%s/.k1/%s", homeDir, consoleClusterName) if _, err := os.Stat(dir); os.IsNotExist(err) { - log.Infof("unable to delete cluster - cluster %s directory does not exist", dir) + log.Info(fmt.Sprintf("unable to delete cluster - cluster %s directory does not exist", dir)) } // Port forward to API diff --git a/internal/launch/utils.go b/internal/launch/utils.go index 40cb84e0c..e7b13dc00 100644 --- a/internal/launch/utils.go +++ b/internal/launch/utils.go @@ -7,13 +7,9 @@ See the LICENSE file for more details. package launch import ( - "errors" "fmt" "os" "text/tabwriter" - - log "github.com/sirupsen/logrus" - "github.com/spf13/viper" ) // displayFormattedClusterInfo uses tabwriter to pretty print information on clusters using @@ -38,28 +34,3 @@ func displayFormattedClusterInfo(clusters []map[string]interface{}) error { return nil } - -// setupLaunchConfigFile -func setupLaunchConfigFile(dir string) error { - viperConfigFile := fmt.Sprintf("%s/.launch", dir) - - if _, err := os.Stat(viperConfigFile); errors.Is(err, os.ErrNotExist) { - log.Debugf("launch config file not found, creating a blank one: %s", viperConfigFile) - err = os.WriteFile(viperConfigFile, []byte(""), 0700) - if err != nil { - return fmt.Errorf("unable to create blank config file, error is: %s", err) - } - } - - viper.SetConfigFile(viperConfigFile) - viper.SetConfigType("yaml") - viper.AutomaticEnv() // read in environment variables that match - - // if a config file is found, read it in. - err := viper.ReadInConfig() - if err != nil { - return fmt.Errorf("unable to read config file, error is: %s", err) - } - - return nil -} diff --git a/internal/progress/progress.go b/internal/progress/progress.go new file mode 100644 index 000000000..5f9cf1b16 --- /dev/null +++ b/internal/progress/progress.go @@ -0,0 +1,379 @@ +/* +Copyright (C) 2021-2023, Kubefirst + +This program is licensed under MIT. +See the LICENSE file for more details. + +Emojis definition https://github.com/yuin/goldmark-emoji/blob/master/definition/github.go +*/ +package progress + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/charmbracelet/bubbles/progress" + "github.com/charmbracelet/bubbles/spinner" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/glamour" + "github.com/charmbracelet/lipgloss" + "github.com/kubefirst/kubefirst/internal/utilities" + "github.com/kubefirst/runtime/pkg/types" + "github.com/spf13/viper" +) + +const ( + padding = 2 + maxWidth = 200 +) + +const debounceDuration = time.Second * 10 + +var ( + currentPkgNameStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("211")) + doneStyle = lipgloss.NewStyle().Margin(1, 2) + checkMark = lipgloss.NewStyle().Foreground(lipgloss.Color("42")).SetString("āœ“") + dizzyIcon = lipgloss.NewStyle().Foreground(lipgloss.NoColor{}).SetString("šŸ’«") + helpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#626262")).Render + StatusStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFFFFF")).Bold(true).Render + spinnerStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("69")) +) + +var CompletedStepsLabels = ProvisionSteps{ + install_tools_check: "Installing tools", + domain_liveness_check: "Domain liveness check", + kbot_setup_check: "Kbot setup", + git_init_check: "Initializing Git", + gitops_ready_check: "Initializing gitops", + git_terraform_apply_check: "Git Terraform apply", + gitops_pushed_check: "Gitops repos pushed", + cloud_terraform_apply_check: "Cloud Terraform apply", + cluster_secrets_created_check: "Creating cluster secrets", + argocd_install_check: "Installing Argo CD", + argocd_initialize_check: "Initializing Argo CD", + vault_initialized_check: "Initializing Vault", + vault_terraform_apply_check: "Vault Terraform apply", + users_terraform_apply_check: "Users Terraform apply", +} + +var Progress *tea.Program + +type CusterProvisioningMsg types.Cluster + +type startProvision struct { + clusterName string +} + +type addMsg struct { + message string +} + +type successMsg struct { + message string +} + +type errorMsg struct { + message string +} + +// Terminal model +type progressModel struct { + // Terminal + logs []addMsg + error string + isProvisioning bool + + // Provisioning fields + clusterName string + provisionProgress float64 + provisioningCluster types.Cluster + completedSteps []string + nextStep string + progress progress.Model + spinner spinner.Model + successMessage string +} + +func NewModel() progressModel { + p := progress.New( + progress.WithScaledGradient("#8851C8", "#81E2B4"), + ) + s := spinner.New() + s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFD700")) + s.Style.Width(200) + return progressModel{ + spinner: s, + progress: p, + isProvisioning: false, + } +} + +func DisplayLogHints() { + logFile := viper.GetString("k1-paths.log-file") + logInfo := fmt.Sprintf("```tail -f -n +1 %s```", logFile) + Log("\n\n", "") + Log("# Welcome to Kubefirst", "") + Log("Follow your logs in a new terminal with:", "") + Log(logInfo, "") +} + +// Custom actions to update terminal +func renderMessage(message string) string { + r, _ := glamour.NewTermRenderer( + glamour.WithStylesFromJSONFile("styles.json"), + glamour.WithEmoji(), + ) + + out, _ := r.Render(message) + return out +} + +func createLog(message string) addMsg { + out := renderMessage(message) + + return addMsg{ + message: out, + } +} + +func createErrorLog(message string) errorMsg { + out := renderMessage(fmt.Sprintf("`Error: %s`", message)) + + return errorMsg{ + message: out, + } +} + +func Log(message string, logType string) { + icon := "" + + if logType == "info" { + icon = ":dizzy: " + } + + renderedMessage := createLog(fmt.Sprintf("%s %s", icon, message)) + Progress.Send(renderedMessage) +} + +func Error(message string) { + renderedMessage := createErrorLog(message) + Progress.Send(renderedMessage) +} + +func Success(message string) { + out := renderMessage(message) + successMessage := successMsg{ + message: out, + } + Progress.Send(successMessage) +} + +func StartProvisioning(clusterName string, estimatedTime int) { + provisioningMessage := startProvision{ + clusterName: clusterName, + } + + Progress.Send(provisioningMessage) + Progress.Send(createLog("---")) + Progress.Send(createLog(fmt.Sprintf("## **Estimated time: %s minutes**", strconv.Itoa(estimatedTime)))) +} + +// Bubbletea functions +func InitializeProgressTerminal() { + Progress = tea.NewProgram(NewModel()) +} + +func (m progressModel) Init() tea.Cmd { + return m.spinner.Tick +} + +func (m progressModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "ctrl+c": + return m, tea.Quit + default: + return m, nil + } + + case tea.WindowSizeMsg: + m.progress.Width = msg.Width - padding*2 - 4 + if m.progress.Width > maxWidth { + m.progress.Width = maxWidth + } + return m, nil + + case spinner.TickMsg: + var cmd tea.Cmd + m.spinner, cmd = m.spinner.Update(msg) + return m, cmd + + case addMsg: + m.logs = append(m.logs, msg) + + return m, nil + + case errorMsg: + m.error = msg.message + return m, tea.Quit + + case successMsg: + m.successMessage = msg.message + return m, nil + + case startProvision: + m.clusterName = msg.clusterName + m.isProvisioning = true + + return m, GetClusterInterval(m.clusterName) + + case CusterProvisioningMsg: + m.provisioningCluster = types.Cluster(msg) + completedSteps, nextStep := BuildCompletedSteps(types.Cluster(msg), m) + m.completedSteps = completedSteps + m.nextStep = fmt.Sprintf("%s %s", dizzyIcon, nextStep) + + m.provisionProgress = float64(len(m.completedSteps)) / float64(14) + + if m.provisionProgress == 1 && m.provisioningCluster.Status == "provisioning" { + m.provisionProgress = 0.98 + } + + if m.provisioningCluster.Status == "error" { + errorMessage := createErrorLog(m.provisioningCluster.LastCondition) + m.error = errorMessage.message + return m, tea.Quit + } + + if m.provisioningCluster.Status != "provisioning" { + m.provisionProgress = 100 + m.nextStep = "" + return m, AddSuccesMessage(fmt.Sprintf(":tada: Cluster **%s** is now up and running.", m.clusterName)) + } + + return m, GetClusterInterval(m.clusterName) + + default: + return m, nil + } +} + +func (m progressModel) View() string { + pad := strings.Repeat(" ", padding) + spin := m.spinner.View() + " " + + logs := "" + for _, logValue := range m.logs { + logs = logs + pad + logValue.message + } + + provisioning := "" + if m.isProvisioning { + if m.provisioningCluster.Status == "" { + provisioning = "\n" + pad + spin + } else { + + completedSteps := "" + for _, v := range m.completedSteps { + completedSteps = completedSteps + pad + fmt.Sprintf("%s %s", checkMark, v) + "\n\n" + } + + provisioning = "\n" + + completedSteps + + pad + m.nextStep + "\n\n" + + pad + m.progress.ViewAs(m.provisionProgress) + "\n\n" + } + } + + return logs + provisioning + pad + m.successMessage + m.error + "\n\n" + helpStyle("Press ctrl + c to quit") +} + +// Commands +func GetClusterInterval(clusterName string) tea.Cmd { + return tea.Every(time.Second*10, func(t time.Time) tea.Msg { + provisioningCluster, err := utilities.GetCluster(clusterName) + + if err != nil { + + } + + return CusterProvisioningMsg(provisioningCluster) + }) +} + +func AddSuccesMessage(message string) tea.Cmd { + return tea.Tick(0, func(t time.Time) tea.Msg { + out := renderMessage(message) + successMessage := successMsg{ + message: out, + } + + return successMsg(successMessage) + }) +} + +func BuildCompletedSteps(cluster types.Cluster, model progressModel) ([]string, string) { + completedSteps := []string{} + nextStep := "" + if cluster.InstallToolsCheck { + completedSteps = append(completedSteps, CompletedStepsLabels.install_tools_check) + nextStep = CompletedStepsLabels.domain_liveness_check + } + if cluster.DomainLivenessCheck { + completedSteps = append(completedSteps, CompletedStepsLabels.domain_liveness_check) + nextStep = CompletedStepsLabels.kbot_setup_check + } + if cluster.KbotSetupCheck { + completedSteps = append(completedSteps, CompletedStepsLabels.kbot_setup_check) + nextStep = CompletedStepsLabels.git_init_check + } + if cluster.GitInitCheck { + completedSteps = append(completedSteps, CompletedStepsLabels.git_init_check) + nextStep = CompletedStepsLabels.gitops_ready_check + } + if cluster.GitopsReadyCheck { + completedSteps = append(completedSteps, CompletedStepsLabels.gitops_ready_check) + nextStep = CompletedStepsLabels.git_terraform_apply_check + } + if cluster.GitTerraformApplyCheck { + completedSteps = append(completedSteps, CompletedStepsLabels.git_terraform_apply_check) + nextStep = CompletedStepsLabels.gitops_pushed_check + } + if cluster.GitopsPushedCheck { + completedSteps = append(completedSteps, CompletedStepsLabels.gitops_pushed_check) + nextStep = CompletedStepsLabels.cloud_terraform_apply_check + } + if cluster.CloudTerraformApplyCheck { + completedSteps = append(completedSteps, CompletedStepsLabels.cloud_terraform_apply_check) + nextStep = CompletedStepsLabels.cluster_secrets_created_check + } + if cluster.ClusterSecretsCreatedCheck { + completedSteps = append(completedSteps, CompletedStepsLabels.cluster_secrets_created_check) + nextStep = CompletedStepsLabels.argocd_install_check + } + if cluster.ArgoCDInstallCheck { + completedSteps = append(completedSteps, CompletedStepsLabels.argocd_install_check) + nextStep = CompletedStepsLabels.argocd_initialize_check + } + if cluster.ArgoCDInitializeCheck { + completedSteps = append(completedSteps, CompletedStepsLabels.argocd_initialize_check) + nextStep = CompletedStepsLabels.vault_initialized_check + } + if cluster.VaultInitializedCheck { + completedSteps = append(completedSteps, CompletedStepsLabels.vault_initialized_check) + nextStep = CompletedStepsLabels.vault_terraform_apply_check + } + if cluster.VaultTerraformApplyCheck { + completedSteps = append(completedSteps, CompletedStepsLabels.vault_terraform_apply_check) + nextStep = CompletedStepsLabels.users_terraform_apply_check + } + if cluster.UsersTerraformApplyCheck { + completedSteps = append(completedSteps, CompletedStepsLabels.users_terraform_apply_check) + nextStep = "Wrapping up" + } + + return completedSteps, nextStep +} diff --git a/internal/progress/types.go b/internal/progress/types.go new file mode 100644 index 000000000..02f3c3854 --- /dev/null +++ b/internal/progress/types.go @@ -0,0 +1,24 @@ +/* +Copyright (C) 2021-2023, Kubefirst + +This program is licensed under MIT. +See the LICENSE file for more details. +*/ +package progress + +type ProvisionSteps struct { + install_tools_check string + domain_liveness_check string + kbot_setup_check string + git_init_check string + gitops_ready_check string + git_terraform_apply_check string + gitops_pushed_check string + cloud_terraform_apply_check string + cluster_secrets_created_check string + argocd_install_check string + argocd_initialize_check string + vault_initialized_check string + vault_terraform_apply_check string + users_terraform_apply_check string +} diff --git a/internal/utilities/utilities.go b/internal/utilities/utilities.go index 35010447c..3fb454203 100644 --- a/internal/utilities/utilities.go +++ b/internal/utilities/utilities.go @@ -7,8 +7,12 @@ See the LICENSE file for more details. package utilities import ( + "bytes" + "crypto/tls" "encoding/json" "fmt" + "io" + "net/http" "os" "time" @@ -18,6 +22,10 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) +// Uncomment this for debbuging purposes +// var ConsoleIngresUrl = "http://localhost:3000" +var ConsoleIngresUrl = "https://console.kubefirst.dev" + // CreateK1ClusterDirectory func CreateK1ClusterDirectory(clusterName string) { // Create k1 dir if it doesn't exist @@ -39,9 +47,6 @@ const ( ) func CreateClusterRecordFromRaw(useTelemetry bool, gitOwner string, gitUser string, gitToken string, gitlabOwnerGroupID int, gitopsTemplateURL string, gitopsTemplateBranch string) types.Cluster { - // viper.Set("flags.dns-provider", dnsProviderFlag) - // viper.Set("flags.git-protocol", gitProtocolFlag) - cloudProvider := viper.GetString("kubefirst.cloud-provider") domainName := viper.GetString("flags.domain-name") gitProvider := viper.GetString("flags.git-provider") @@ -118,6 +123,59 @@ func CreateClusterRecordFromRaw(useTelemetry bool, gitOwner string, gitUser stri return cl } +func CreateClusterDefinitionRecordFromRaw(gitAuth types.GitAuth, gitopsTemplateURL string, gitopsTemplateBranch string) types.ClusterDefinition { + cloudProvider := viper.GetString("kubefirst.cloud-provider") + domainName := viper.GetString("flags.domain-name") + gitProvider := viper.GetString("flags.git-provider") + + kubefirstTeam := os.Getenv("KUBEFIRST_TEAM") + if kubefirstTeam == "" { + kubefirstTeam = "false" + } + + cl := types.ClusterDefinition{ + AdminEmail: viper.GetString("flags.alerts-email"), + ClusterName: viper.GetString("flags.cluster-name"), + CloudProvider: cloudProvider, + CloudRegion: viper.GetString("flags.cloud-region"), + DomainName: domainName, + Type: "mgmt", + GitopsTemplateURL: gitopsTemplateURL, + GitopsTemplateBranch: gitopsTemplateBranch, + GitProvider: gitProvider, + GitProtocol: viper.GetString("flags.git-protocol"), + DnsProvider: viper.GetString("flags.dns-provider"), + GitAuth: types.GitAuth{ + Token: gitAuth.Token, + User: gitAuth.User, + Owner: gitAuth.Owner, + PublicKey: viper.GetString("kbot.public-key"), + PrivateKey: viper.GetString("kbot.private-key"), + }, + CloudflareAuth: types.CloudflareAuth{ + Token: os.Getenv("CF_API_TOKEN"), + }, + } + + switch cloudProvider { + case "civo": + cl.CivoAuth.Token = os.Getenv("CIVO_TOKEN") + case "aws": + //ToDo: where to get credentials? + cl.AWSAuth.AccessKeyID = viper.GetString("kubefirst.state-store-creds.access-key-id") + cl.AWSAuth.SecretAccessKey = viper.GetString("kubefirst.state-store-creds.secret-access-key-id") + cl.AWSAuth.SessionToken = viper.GetString("kubefirst.state-store-creds.token") + case "digitalocean": + cl.DigitaloceanAuth.Token = os.Getenv("DO_TOKEN") + cl.DigitaloceanAuth.SpacesKey = os.Getenv("DO_SPACES_KEY") + cl.DigitaloceanAuth.SpacesSecret = os.Getenv("DO_SPACES_SECRET") + case "vultr": + cl.VultrAuth.Token = os.Getenv("VULTR_API_KEY") + } + + return cl +} + func CreateClusterRecordFile(clustername string, cluster types.Cluster) error { var localFilePath = fmt.Sprintf("%s/%s.json", exportFilePath, clustername) @@ -138,3 +196,135 @@ func CreateClusterRecordFile(clustername string, cluster types.Cluster) error { return nil } + +func CreateCluster(cluster types.ClusterDefinition) error { + customTransport := http.DefaultTransport.(*http.Transport).Clone() + customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + httpClient := http.Client{Transport: customTransport} + + requestObject := types.ProxyCreateClusterRequest{ + Body: cluster, + Url: fmt.Sprintf("/cluster/%s", cluster.ClusterName), + } + + payload, err := json.Marshal(requestObject) + if err != nil { + return err + } + + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/proxy", ConsoleIngresUrl), bytes.NewReader(payload)) + if err != nil { + log.Info().Msgf("error %s", err) + return err + } + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Accept", "application/json") + + res, err := httpClient.Do(req) + if err != nil { + log.Info().Msgf("error %s", err) + return err + } + + if res.StatusCode != http.StatusOK { + log.Info().Msgf("unable to create cluster %s", res.Status) + return err + } + + body, err := io.ReadAll(res.Body) + if err != nil { + log.Info().Msgf("unable to create cluster %s", err) + + return err + } + + log.Info().Msgf("Created cluster: %s", string(body)) + + return nil +} + +func ResetClusterProgress(clusterName string) error { + customTransport := http.DefaultTransport.(*http.Transport).Clone() + customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + httpClient := http.Client{Transport: customTransport} + + requestObject := types.ProxyResetClusterRequest{ + Url: fmt.Sprintf("/cluster/%s/reset_progress", clusterName), + } + + payload, err := json.Marshal(requestObject) + if err != nil { + return err + } + + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/proxy", ConsoleIngresUrl), bytes.NewReader(payload)) + if err != nil { + log.Info().Msgf("error %s", err) + return err + } + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Accept", "application/json") + + res, err := httpClient.Do(req) + if err != nil { + log.Info().Msgf("error %s", err) + return err + } + + if res.StatusCode != http.StatusOK { + log.Info().Msgf("unable to create cluster %s", res.Status) + return err + } + + body, err := io.ReadAll(res.Body) + if err != nil { + log.Info().Msgf("unable to create cluster %s", err) + + return err + } + + log.Info().Msgf("Import: %s", string(body)) + + return nil +} + +func GetCluster(clusterName string) (types.Cluster, error) { + customTransport := http.DefaultTransport.(*http.Transport).Clone() + customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + httpClient := http.Client{Transport: customTransport} + + cluster := types.Cluster{} + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/proxy?url=/cluster/%s", ConsoleIngresUrl, clusterName), nil) + if err != nil { + log.Info().Msgf("error %s", err) + return cluster, err + } + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Accept", "application/json") + + res, err := httpClient.Do(req) + if err != nil { + log.Info().Msgf("error %s", err) + return cluster, err + } + + if res.StatusCode != http.StatusOK { + log.Info().Msgf("unable to get cluster %s", res.Status) + return cluster, err + } + + body, err := io.ReadAll(res.Body) + if err != nil { + log.Info().Msgf("unable to get cluster %s", err) + + return cluster, err + } + + err = json.Unmarshal(body, &cluster) + if err != nil { + log.Info().Msgf("unable to cast cluster object %s", err) + return cluster, err + } + + return cluster, nil +} diff --git a/main.go b/main.go index efeaa9377..0abb13dc7 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ import ( "github.com/rs/zerolog/log" "github.com/kubefirst/kubefirst/cmd" + "github.com/kubefirst/kubefirst/internal/progress" "github.com/kubefirst/runtime/configs" "github.com/kubefirst/runtime/pkg" "github.com/spf13/viper" @@ -84,5 +85,11 @@ func main() { stdLog.Panicf("unable to set log-file-location, error is: %s", err) } - cmd.Execute() + progress.InitializeProgressTerminal() + + go func() { + cmd.Execute() + }() + + progress.Progress.Run() } diff --git a/styles.json b/styles.json new file mode 100644 index 000000000..9927f3c18 --- /dev/null +++ b/styles.json @@ -0,0 +1,195 @@ +{ + "document": { + "block_prefix": "\n", + "block_suffix": "", + "color": "252", + "margin": 2 + }, + "block_quote": { + "indent": 1, + "indent_token": "ā”‚ " + }, + "paragraph": {}, + "list": { + "level_indent": 2 + }, + "heading": { + "block_suffix": "\n", + "color": "39", + "bold": true + }, + "h1": { + "prefix": " ", + "suffix": " ", + "color": "228", + "background_color": "63", + "bold": true + }, + "h2": { + "prefix": "" + }, + "h3": { + "prefix": "### " + }, + "h4": { + "prefix": "#### " + }, + "h5": { + "prefix": "##### " + }, + "h6": { + "prefix": "###### ", + "color": "35", + "bold": false + }, + "text": {}, + "strikethrough": { + "crossed_out": true + }, + "emph": { + "italic": true + }, + "strong": { + "bold": true + }, + "hr": { + "color": "240", + "format": "\n--------\n" + }, + "item": { + "block_prefix": "ā€¢ " + }, + "enumeration": { + "block_prefix": ". " + }, + "task": { + "ticked": "[āœ“] ", + "unticked": "[ ] " + }, + "link": { + "color": "30", + "underline": true + }, + "link_text": { + "color": "35", + "bold": true + }, + "image": { + "color": "212", + "underline": true + }, + "image_text": { + "color": "243", + "format": "Image: {{.text}} ā†’" + }, + "code": { + "prefix": " ", + "suffix": " ", + "color": "#FFFFFF", + "background_color": "" + }, + "code_block": { + "color": "244", + "margin": 1, + "chroma": { + "text": { + "color": "#C4C4C4" + }, + "error": { + "color": "#F1F1F1", + "background_color": "#F05B5B" + }, + "comment": { + "color": "#676767" + }, + "comment_preproc": { + "color": "#FF875F" + }, + "keyword": { + "color": "#00AAFF" + }, + "keyword_reserved": { + "color": "#FF5FD2" + }, + "keyword_namespace": { + "color": "#FF5F87" + }, + "keyword_type": { + "color": "#6E6ED8" + }, + "operator": { + "color": "#EF8080" + }, + "punctuation": { + "color": "#E8E8A8" + }, + "name": { + "color": "#C4C4C4" + }, + "name_builtin": { + "color": "#FF8EC7" + }, + "name_tag": { + "color": "#B083EA" + }, + "name_attribute": { + "color": "#7A7AE6" + }, + "name_class": { + "color": "#F1F1F1", + "underline": true, + "bold": true + }, + "name_constant": {}, + "name_decorator": { + "color": "#FFFF87" + }, + "name_exception": {}, + "name_function": { + "color": "#00D787" + }, + "name_other": {}, + "literal": {}, + "literal_number": { + "color": "#6EEFC0" + }, + "literal_date": {}, + "literal_string": { + "color": "#C69669" + }, + "literal_string_escape": { + "color": "#AFFFD7" + }, + "generic_deleted": { + "color": "#FD5B5B" + }, + "generic_emph": { + "italic": true + }, + "generic_inserted": { + "color": "#00D787" + }, + "generic_strong": { + "bold": true + }, + "generic_subheading": { + "color": "#777777" + }, + "background": { + "background_color": "#373737" + } + } + }, + "table": { + "center_separator": "ā”¼", + "column_separator": "ā”‚", + "row_separator": "ā”€" + }, + "definition_list": {}, + "definition_term": {}, + "definition_description": { + "block_prefix": "\nšŸ ¶ " + }, + "html_block": {}, + "html_span": {} +} \ No newline at end of file