diff --git a/cmd/create.go b/cmd/create.go index 28fee6963..697925852 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -267,7 +267,7 @@ cluster provisioning process spinning up the services, and validates the livenes // todo: wire it up in the architecture / files / folder // update terraform s3 backend to internal k8s dns (s3/minio bucket) - err = pkg.ReplaceS3Backend() + err = pkg.ReplaceTerraformS3Backend() if err != nil { return err } @@ -280,7 +280,7 @@ cluster provisioning process spinning up the services, and validates the livenes branchName := "update-s3-backend" branchNameRef := plumbing.ReferenceName("refs/heads/" + branchName) - gitClient.UpdateLocalTFFilesAndPush( + gitClient.UpdateLocalTerraformFilesAndPush( githubHost, githubOwner, localRepo, diff --git a/cmd/createUtils.go b/cmd/createUtils.go index f0ad9d4cb..3bdef8016 100644 --- a/cmd/createUtils.go +++ b/cmd/createUtils.go @@ -19,6 +19,7 @@ import ( ) // todo: move it to internals/ArgoCD +// deprecated func setArgocdCreds(dryRun bool) { if dryRun { log.Printf("[#99] Dry-run mode, setArgocdCreds skipped.") @@ -43,6 +44,7 @@ func setArgocdCreds(dryRun bool) { viper.WriteConfig() } +// deprecated func waitArgoCDToBeReady(dryRun bool) { if dryRun { log.Printf("[#99] Dry-run mode, waitArgoCDToBeReady skipped.") @@ -76,6 +78,7 @@ func waitArgoCDToBeReady(dryRun bool) { } } +// deprecated func waitVaultToBeRunning(dryRun bool) { if dryRun { log.Printf("[#99] Dry-run mode, waitVaultToBeRunning skipped.") @@ -115,6 +118,7 @@ func waitVaultToBeRunning(dryRun bool) { } } +// deprecated func loopUntilPodIsReady(dryRun bool) { if dryRun { log.Printf("[#99] Dry-run mode, loopUntilPodIsReady skipped.") diff --git a/cmd/local.go b/cmd/local.go index 27e080ddd..57e3175a5 100644 --- a/cmd/local.go +++ b/cmd/local.go @@ -65,7 +65,7 @@ func init() { //Group Flags - rootCmd.AddCommand(localCmd) + //rootCmd.AddCommand(localCmd) currentCommand := localCmd //log.SetPrefix("LOG: ") //log.SetFlags(log.Ldate | log.Lmicroseconds | log.Llongfile) diff --git a/cmd/local/local.go b/cmd/local/local.go new file mode 100644 index 000000000..381f0725b --- /dev/null +++ b/cmd/local/local.go @@ -0,0 +1,483 @@ +package local + +import ( + "context" + "fmt" + "github.com/go-git/go-git/v5/plumbing" + "github.com/kubefirst/kubefirst/configs" + "github.com/kubefirst/kubefirst/internal/argocd" + "github.com/kubefirst/kubefirst/internal/domain" + "github.com/kubefirst/kubefirst/internal/gitClient" + "github.com/kubefirst/kubefirst/internal/githubWrapper" + "github.com/kubefirst/kubefirst/internal/handlers" + "github.com/kubefirst/kubefirst/internal/helm" + "github.com/kubefirst/kubefirst/internal/k3d" + "github.com/kubefirst/kubefirst/internal/k8s" + "github.com/kubefirst/kubefirst/internal/metaphor" + "github.com/kubefirst/kubefirst/internal/progressPrinter" + "github.com/kubefirst/kubefirst/internal/services" + "github.com/kubefirst/kubefirst/internal/terraform" + "github.com/kubefirst/kubefirst/internal/vault" + "github.com/kubefirst/kubefirst/pkg" + "github.com/segmentio/analytics-go" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "log" + "os/exec" + "sync" + "syscall" + "time" +) + +var ( + useTelemetry bool + dryRun bool + silentMode bool + gitOpsBranch string + gitOpsRepo string + awsHostedZone string + metaphorBranch string + adminEmail string + enableConsole bool +) + +func NewCommand() *cobra.Command { + + localCmd := &cobra.Command{ + Use: "local", + Short: "Kubefirst localhost installation", + Long: "Kubefirst localhost enable a localhost installation without the requirement of a cloud provider.", + PreRunE: validateLocal, + RunE: runLocal, + PostRunE: runPostLocal, + } + + localCmd.Flags().BoolVar(&useTelemetry, "use-telemetry", true, "installer will not send telemetry about this installation") + localCmd.Flags().BoolVar(&dryRun, "dry-run", false, "set to dry-run mode, no changes done on cloud provider selected") + localCmd.Flags().BoolVar(&silentMode, "silent", false, "enable silentMode mode will make the UI return less content to the screen") + // todo: get it from GH token , use it for console + localCmd.Flags().StringVar(&adminEmail, "admin-email", "", "the email address for the administrator as well as for lets-encrypt certificate emails") + + localCmd.Flags().StringVar(&metaphorBranch, "metaphor-branch", "main", "metaphro application branch") + localCmd.Flags().StringVar(&gitOpsBranch, "gitops-branch", "main", "version/branch used on git clone - former: version-gitops flag") + localCmd.Flags().StringVar(&gitOpsRepo, "gitops-repo", "gitops", "") + + localCmd.Flags().BoolVar(&enableConsole, "enable-console", true, "If hand-off screen will be presented on a browser UI") + // todo: + //initCmd.Flags().StringP("config", "c", "", "File to be imported to bootstrap configs") + //viper.BindPFlag("config.file", currentCommand.Flags().Lookup("config-load")) + + return localCmd +} + +func runLocal(cmd *cobra.Command, args []string) error { + + config := configs.ReadConfig() + + //tools.RunInfo(cmd, args) + + progressPrinter.AddTracker("step-github", "Setup gitops on github", 3) + progressPrinter.AddTracker("step-base", "Setup base cluster", 2) + progressPrinter.AddTracker("step-apps", "Install apps to cluster", 5) + + progressPrinter.IncrementTracker("step-base", 1) + progressPrinter.IncrementTracker("step-base", 1) + + if useTelemetry { + progressPrinter.AddTracker("step-telemetry", pkg.SendTelemetry, 1) + } + + progressPrinter.SetupProgress(progressPrinter.TotalOfTrackers(), silentMode) + + // telemetry + if useTelemetry { + // Instantiates a SegmentIO client to send messages to the segment API. + segmentIOClientStart := analytics.New(pkg.SegmentIOWriteKey) + + // SegmentIO library works with queue that is based on timing, we explicit close the http client connection + // to force flush in case there is still some pending message in the SegmentIO library queue. + defer func(segmentIOClient analytics.Client) { + err := segmentIOClient.Close() + if err != nil { + log.Println(err) + } + }(segmentIOClientStart) + + telemetryDomainStart, err := domain.NewTelemetry( + pkg.MetricMgmtClusterInstallStarted, + "", + configs.K1Version, + ) + if err != nil { + log.Println(err) + } + telemetryServiceStart := services.NewSegmentIoService(segmentIOClientStart) + telemetryHandlerStart := handlers.NewTelemetryHandler(telemetryServiceStart) + + err = telemetryHandlerStart.SendCountMetric(telemetryDomainStart) + if err != nil { + log.Println(err) + } + + progressPrinter.IncrementTracker("step-telemetry", 1) + } + + // todo need to add go channel to control when ngrok should close + // and use context to handle closing the open goroutine/connection + go pkg.RunNgrok(context.TODO(), pkg.LocalAtlantisURL) + time.Sleep(5 * time.Second) + + if !viper.GetBool("kubefirst.done") { + if viper.GetString("gitprovider") == "github" { + log.Println("Installing Github version of Kubefirst") + viper.Set("git.mode", "github") + err := k3d.CreateK3dCluster() + if err != nil { + return err + } + } + viper.Set("kubefirst.done", true) + viper.WriteConfig() + } + + var kPortForwardArgocd *exec.Cmd + progressPrinter.SetupProgress(progressPrinter.TotalOfTrackers(), silentMode) + + executionControl := viper.GetBool("terraform.github.apply.complete") + // create github teams in the org and gitops repo + if !executionControl { + pkg.InformUser("Creating github resources with terraform", silentMode) + + tfEntrypoint := config.GitOpsRepoPath + "/terraform/github" + terraform.InitApplyAutoApprove(dryRun, tfEntrypoint) + + pkg.InformUser(fmt.Sprintf("Created gitops Repo in github.com/%s", viper.GetString("github.owner")), silentMode) + progressPrinter.IncrementTracker("step-github", 1) + } else { + log.Println("already created github terraform resources") + } + + // push our locally detokenized gitops repo to remote github + githubHost := viper.GetString("github.host") + githubOwner := viper.GetString("github.owner") + localRepo := "gitops" + remoteName := "github" + executionControl = viper.GetBool("github.gitops.hydrated") // todo fix this executionControl value `github.detokenized-gitops.pushed`? + if !executionControl { + pkg.InformUser(fmt.Sprintf("pushing local detokenized gitops content to new remote github.com/%s", viper.GetString("github.owner")), silentMode) + gitClient.PushLocalRepoToEmptyRemote(githubHost, githubOwner, localRepo, remoteName) + } else { + log.Println("already hydrated the github gitops repository") + } + progressPrinter.IncrementTracker("step-github", 1) + + // create kubernetes cluster + executionControl = viper.GetBool("k3d.created") + if !executionControl { + pkg.InformUser("Creating K8S Cluster", silentMode) + err := k3d.CreateK3dCluster() + if err != nil { + log.Println("Error installing k3d cluster") + return err + } + progressPrinter.IncrementTracker("step-base", 1) + } else { + log.Println("already created k3d cluster") + } + progressPrinter.IncrementTracker("step-github", 1) + + // add secrets to cluster + // todo there is a secret condition in AddK3DSecrets to this not checked + executionControl = viper.GetBool("kubernetes.vault.secret.created") + if !executionControl { + err := k3d.AddK3DSecrets(dryRun) + if err != nil { + log.Println("Error AddK3DSecrets") + return err + } + } else { + log.Println("already added secrets to k3d cluster") + } + + // create argocd initial repository config + executionControl = viper.GetBool("argocd.initial-repository.created") + if !executionControl { + pkg.InformUser("create initial argocd repository", silentMode) + //Enterprise users need to be able to set the hostname for git. + gitopsRepo := fmt.Sprintf("git@%s:%s/gitops.git", viper.GetString("github.host"), viper.GetString("github.owner")) + err := argocd.CreateInitialArgoCDRepository(gitopsRepo) + if err != nil { + log.Println("Error CreateInitialArgoCDRepository") + return err + } + } else { + log.Println("already created initial argocd repository") + } + + // helm add argo repository && update + helmRepo := helm.HelmRepo{ + RepoName: pkg.HelmRepoName, + RepoURL: pkg.HelmRepoURL, + ChartName: pkg.HelmRepoChartName, + Namespace: pkg.HelmRepoNamespace, + ChartVersion: pkg.HelmRepoChartVersion, + } + + executionControl = viper.GetBool("argocd.helm.repo.updated") + if !executionControl { + pkg.InformUser(fmt.Sprintf("helm repo add %s %s and helm repo update", helmRepo.RepoName, helmRepo.RepoURL), silentMode) + helm.AddRepoAndUpdateRepo(dryRun, helmRepo) + } + + // helm install argocd + executionControl = viper.GetBool("argocd.helm.install.complete") + if !executionControl { + pkg.InformUser(fmt.Sprintf("helm install %s and wait", helmRepo.RepoName), silentMode) + helm.Install(dryRun, helmRepo) + } + progressPrinter.IncrementTracker("step-apps", 1) + + // argocd pods are running + executionControl = viper.GetBool("argocd.ready") + if !executionControl { + argocd.WaitArgoCDToBeReady(dryRun) + pkg.InformUser("ArgoCD is running, continuing", silentMode) + } else { + log.Println("already waited for argocd to be ready") + } + + // establish port-forward + kPortForwardArgocd, err := k8s.PortForward(dryRun, "argocd", "svc/argocd-server", "8080:80") + defer func() { + err = kPortForwardArgocd.Process.Signal(syscall.SIGTERM) + if err != nil { + log.Println("Error closing kPortForwardArgocd") + } + }() + pkg.InformUser(fmt.Sprintf("port-forward to argocd is available at %s", viper.GetString("argocd.local.service")), silentMode) + + // argocd pods are ready, get and set credentials + executionControl = viper.GetBool("argocd.credentials.set") + if !executionControl { + pkg.InformUser("Setting argocd username and password credentials", silentMode) + k8s.SetArgocdCreds(dryRun) + pkg.InformUser("argocd username and password credentials set successfully", silentMode) + + pkg.InformUser("Getting an argocd auth token", silentMode) + _ = argocd.GetArgocdAuthToken(dryRun) + pkg.InformUser("argocd admin auth token set", silentMode) + + viper.Set("argocd.credentials.set", true) + viper.WriteConfig() + } + + // argocd sync registry and start sync waves + executionControl = viper.GetBool("argocd.registry.applied") + if !executionControl { + pkg.InformUser("applying the registry application to argocd", silentMode) + err = argocd.ApplyRegistryLocal(dryRun) + if err != nil { + log.Println("Error applying registry application to argocd") + return err + } + } + + progressPrinter.IncrementTracker("step-apps", 1) + + // vault in running state + executionControl = viper.GetBool("vault.status.running") + if !executionControl { + pkg.InformUser("Waiting for vault to be ready", silentMode) + vault.WaitVaultToBeRunning(dryRun) + if err != nil { + log.Println("error waiting for vault to become running") + return err + } + } + kPortForwardVault, err := k8s.PortForward(dryRun, "vault", "svc/vault", "8200:8200") + defer func() { + err = kPortForwardVault.Process.Signal(syscall.SIGTERM) + if err != nil { + log.Println("Error closing kPortForwardVault") + } + }() + + k8s.LoopUntilPodIsReady(dryRun) + kPortForwardMinio, err := k8s.PortForward(dryRun, "minio", "svc/minio", "9000:9000") + defer func() { + err = kPortForwardMinio.Process.Signal(syscall.SIGTERM) + if err != nil { + log.Println("Error closing kPortForwardMinio") + } + }() + + // configure vault with terraform + executionControl = viper.GetBool("terraform.vault.apply.complete") + if !executionControl { + // todo evaluate progressPrinter.IncrementTracker("step-vault", 1) + //* set known vault token + viper.Set("vault.token", "k1_local_vault_token") + viper.WriteConfig() + + //* run vault terraform + pkg.InformUser("configuring vault with terraform", silentMode) + tfEntrypoint := config.GitOpsRepoPath + "/terraform/vault" + terraform.InitApplyAutoApprove(dryRun, tfEntrypoint) + + pkg.InformUser("vault terraform executed successfully", silentMode) + + //* create vault configurerd secret + // todo remove this code + log.Println("creating vault configured secret") + k8s.CreateVaultConfiguredSecret(dryRun, config) + pkg.InformUser("Vault secret created", silentMode) + } else { + log.Println("already executed vault terraform") + } + + // create users + executionControl = viper.GetBool("terraform.users.apply.complete") + if !executionControl { + pkg.InformUser("applying users terraform", silentMode) + + tfEntrypoint := config.GitOpsRepoPath + "/terraform/users" + terraform.InitApplyAutoApprove(dryRun, tfEntrypoint) + + pkg.InformUser("executed users terraform successfully", silentMode) + // progressPrinter.IncrementTracker("step-users", 1) + } else { + log.Println("already created users with terraform") + } + + // TODO: K3D => NEED TO REMOVE local-backend.tf and rename remote-backend.md + + pkg.InformUser("Welcome to local kubefirst experience", silentMode) + pkg.InformUser("To use your cluster port-forward - argocd", silentMode) + pkg.InformUser("If not automatically injected, your kubeconfig is at:", silentMode) + pkg.InformUser("k3d kubeconfig get "+viper.GetString("cluster-name"), silentMode) + pkg.InformUser("Expose Argo-CD", silentMode) + pkg.InformUser("kubectl -n argocd port-forward svc/argocd-server 8080:80", silentMode) + pkg.InformUser("Argo User: "+viper.GetString("argocd.admin.username"), silentMode) + pkg.InformUser("Argo Password: "+viper.GetString("argocd.admin.password"), silentMode) + + progressPrinter.IncrementTracker("step-apps", 1) + progressPrinter.IncrementTracker("step-base", 1) + progressPrinter.IncrementTracker("step-apps", 1) + + if !viper.GetBool("chartmuseum.host.resolved") { + + //* establish port-forward + var kPortForwardChartMuseum *exec.Cmd + kPortForwardChartMuseum, err = k8s.PortForward(dryRun, "chartmuseum", "svc/chartmuseum", "8181:8080") + defer func() { + err = kPortForwardChartMuseum.Process.Signal(syscall.SIGTERM) + if err != nil { + log.Println("Error closing kPortForwardChartMuseum") + } + }() + pkg.AwaitHostNTimes("http://localhost:8181/health", 5, 5) + viper.Set("chartmuseum.host.resolved", true) + viper.WriteConfig() + } else { + log.Println("already resolved host for chartmuseum, continuing") + } + + pkg.InformUser("Deploying metaphor applications", silentMode) + err = metaphor.DeployMetaphorGithubLocal(dryRun, githubOwner, metaphorBranch, "") + if err != nil { + pkg.InformUser("Error deploy metaphor applications", silentMode) + log.Println("Error running deployMetaphorCmd") + log.Println(err) + } + + // update terraform s3 backend to internal k8s dns (s3/minio bucket) + err = pkg.ReplaceTerraformS3Backend() + if err != nil { + return err + } + + // create a new branch and push changes + branchName := "update-s3-backend" + branchNameRef := plumbing.ReferenceName("refs/heads/" + branchName) + + // force update cloned gitops-template terraform files to use Minio backend + err = gitClient.UpdateLocalTerraformFilesAndPush( + githubHost, + githubOwner, + localRepo, + remoteName, + branchNameRef, + ) + if err != nil { + log.Println(err) + } + + log.Println("sleeping after git commit with Minio backend update for Terraform") + time.Sleep(3 * time.Second) + + // create a PR, atlantis will identify it's a Terraform change/file update and trigger atlantis plan + // it's a goroutine since it can run in background + var wg sync.WaitGroup + wg.Add(1) + go func() { + gitHubClient := githubWrapper.New() + err = gitHubClient.CreatePR(branchName) + if err != nil { + fmt.Println(err) + } + log.Println("sleeping after create PR, atlantis plan should be running...") + time.Sleep(5 * time.Second) + + // todo: confirm apply isn't necessary + //fmt.Println("sleeping before apply...") + //time.Sleep(120 * time.Second) + // + //// after 120 seconds, it will comment in the PR with atlantis plan + //err = gitHubClient.CommentPR(1, "atlantis apply") + //if err != nil { + // log.Println(err) + //} + wg.Done() + }() + + log.Println("sending mgmt cluster install completed metric") + + if useTelemetry { + // Instantiates a SegmentIO client to send messages to the segment API. + segmentIOClientCompleted := analytics.New(pkg.SegmentIOWriteKey) + + // SegmentIO library works with queue that is based on timing, we explicit close the http client connection + // to force flush in case there is still some pending message in the SegmentIO library queue. + defer func(segmentIOClientCompleted analytics.Client) { + err := segmentIOClientCompleted.Close() + if err != nil { + log.Println(err) + } + }(segmentIOClientCompleted) + + telemetryDomainCompleted, err := domain.NewTelemetry( + pkg.MetricMgmtClusterInstallCompleted, + "", + configs.K1Version, + ) + if err != nil { + log.Println(err) + } + telemetryServiceCompleted := services.NewSegmentIoService(segmentIOClientCompleted) + telemetryHandlerCompleted := handlers.NewTelemetryHandler(telemetryServiceCompleted) + + err = telemetryHandlerCompleted.SendCountMetric(telemetryDomainCompleted) + if err != nil { + log.Println(err) + } + } + + log.Println("Kubefirst installation finished successfully") + pkg.InformUser("Kubefirst installation finished successfully", silentMode) + + // waiting GitHub/atlantis step + wg.Wait() + + return nil + +} diff --git a/cmd/local/postrun.go b/cmd/local/postrun.go new file mode 100644 index 000000000..6e0bcdeb6 --- /dev/null +++ b/cmd/local/postrun.go @@ -0,0 +1,63 @@ +package local + +import ( + "fmt" + "github.com/kubefirst/kubefirst/configs" + sw "github.com/kubefirst/kubefirst/internal/api" + "github.com/kubefirst/kubefirst/internal/k8s" + "github.com/kubefirst/kubefirst/internal/reports" + "github.com/kubefirst/kubefirst/pkg" + "github.com/spf13/cobra" + "log" + "net/http" + "time" +) + +func runPostLocal(cmd *cobra.Command, args []string) error { + + if !enableConsole { + log.Println("not calling console, console flag is disabled") + return nil + } + + // open all port forwards, wait console ui be ready, and open console ui in the browser + err := k8s.OpenPortForwardForKubeConConsole() + if err != nil { + log.Println(err) + } + + time.Sleep(time.Millisecond * 2000) + + log.Println("Starting the presentation of console and api for the handoff screen") + + // todo: fix the memory leak, there is no joint point after the process fork + go func() { + log.Printf("Console API started") + consoleApiRouter := sw.NewRouter() + log.Println(http.ListenAndServe(":9095", consoleApiRouter)) + }() + go func() { + config := configs.ReadConfig() + distFolder := fmt.Sprintf("%s/tools/console/dist", config.K1FolderPath) + fileServer := http.FileServer(http.Dir(distFolder)) + http.Handle("/", fileServer) + + log.Printf("Starting server at port 9094\n") + log.Println(http.ListenAndServe(":9094", nil)) + }() + + err = pkg.IsConsoleUIAvailable(pkg.LocalConsoleUI) + if err != nil { + log.Println(err) + } + err = pkg.OpenBrowser(pkg.LocalConsoleUI) + if err != nil { + return err + } + + reports.LocalHandoffScreen(dryRun, silentMode) + + log.Println("Kubefirst Console available at: http://localhost:9094", silentMode) + + return nil +} diff --git a/cmd/local/prerun.go b/cmd/local/prerun.go new file mode 100644 index 000000000..dbc121797 --- /dev/null +++ b/cmd/local/prerun.go @@ -0,0 +1,185 @@ +package local + +import ( + "errors" + "github.com/kubefirst/kubefirst/configs" + "github.com/kubefirst/kubefirst/internal/domain" + "github.com/kubefirst/kubefirst/internal/downloadManager" + "github.com/kubefirst/kubefirst/internal/handlers" + "github.com/kubefirst/kubefirst/internal/progressPrinter" + "github.com/kubefirst/kubefirst/internal/repo" + "github.com/kubefirst/kubefirst/internal/services" + "github.com/kubefirst/kubefirst/pkg" + "github.com/segmentio/analytics-go" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "log" + "net/http" + "os" +) + +func validateLocal(cmd *cobra.Command, args []string) error { + + config := configs.ReadConfig() + + progressPrinter.AddTracker("step-0", "Process Parameters", 1) + progressPrinter.AddTracker("step-download", pkg.DownloadDependencies, 3) + progressPrinter.AddTracker("step-gitops", pkg.CloneAndDetokenizeGitOpsTemplate, 1) + progressPrinter.AddTracker("step-ssh", pkg.CreateSSHKey, 1) + + log.Println("sending init started metric") + + var telemetryHandler handlers.TelemetryHandler + if useTelemetry { + // Instantiates a SegmentIO client to use send messages to the segment API. + segmentIOClient := analytics.New(pkg.SegmentIOWriteKey) + + // SegmentIO library works with queue that is based on timing, we explicit close the http client connection + // to force flush in case there is still some pending message in the SegmentIO library queue. + defer func(segmentIOClient analytics.Client) { + err := segmentIOClient.Close() + if err != nil { + log.Println(err) + } + }(segmentIOClient) + + // validate telemetryDomain data + telemetryDomain, err := domain.NewTelemetry( + pkg.MetricInitStarted, + awsHostedZone, + configs.K1Version, + ) + if err != nil { + log.Println(err) + } + telemetryService := services.NewSegmentIoService(segmentIOClient) + telemetryHandler = handlers.NewTelemetryHandler(telemetryService) + + err = telemetryHandler.SendCountMetric(telemetryDomain) + if err != nil { + log.Println(err) + } + } + + if err := pkg.ValidateK1Folder(config.K1FolderPath); err != nil { + return err + } + + // set default values to kubefirst file + viper.Set("gitops.repo", gitOpsRepo) + viper.Set("gitops.owner", "kubefirst") + viper.Set("gitprovider", pkg.GitHubProviderName) + viper.Set("metaphor.branch", metaphorBranch) + + viper.Set("gitops.branch", gitOpsBranch) + viper.Set("github.owner", viper.GetString("github.user")) + viper.Set("cloud", pkg.CloudK3d) + viper.Set("cluster-name", pkg.LocalClusterName) + viper.Set("adminemail", adminEmail) + + // todo: set constants + viper.Set("argocd.local.service", "http://localhost:8080") + viper.Set("gitlab.local.service", "http://localhost:8888") + viper.Set("vault.local.service", "http://localhost:8200") + // used for letsencrypt notifications and the gitlab root account + + atlantisWebhookSecret := pkg.Random(20) + viper.Set("github.atlantis.webhook.secret", atlantisWebhookSecret) + + viper.WriteConfig() + + err := viper.WriteConfig() + if err != nil { + return err + } + + // todo: wrap business logic into the handler + if config.GitHubPersonalAccessToken == "" { + + httpClient := http.DefaultClient + gitHubService := services.NewGitHubService(httpClient) + gitHubHandler := handlers.NewGitHubHandler(gitHubService) + gitHubAccessToken, err := gitHubHandler.AuthenticateUser() + if err != nil { + return err + } + + if len(gitHubAccessToken) == 0 { + return errors.New("unable to retrieve a GitHub token for the user") + } + + viper.Set("github.token", gitHubAccessToken) + err = viper.WriteConfig() + if err != nil { + return err + } + + // todo: set common way to load env. values (viper->struct->load-env) + // todo: use viper file to load it, not load env. value + if err := os.Setenv("GITHUB_AUTH_TOKEN", gitHubAccessToken); err != nil { + return err + } + log.Println("\nGITHUB_AUTH_TOKEN set via OAuth") + } + + if silentMode { + pkg.InformUser( + "Silent mode enabled, most of the UI prints wont be showed. Please check the logs for more details.\n", + silentMode, + ) + } + + log.Println("installing kubefirst dependencies") + progressPrinter.IncrementTracker("step-download", 1) + err = downloadManager.DownloadTools(config) + if err != nil { + return err + } + log.Println("dependency installation complete") + progressPrinter.IncrementTracker("step-download", 1) + err = downloadManager.DownloadLocalTools(config) + if err != nil { + return err + } + + progressPrinter.IncrementTracker("step-download", 1) + + log.Println("creating an ssh key pair for your new cloud infrastructure") + pkg.CreateSshKeyPair() + log.Println("ssh key pair creation complete") + progressPrinter.IncrementTracker("step-ssh", 1) + + repo.PrepareKubefirstTemplateRepo( + dryRun, + config, + viper.GetString("github.owner"), + viper.GetString("gitops.repo"), + viper.GetString("gitops.branch"), + viper.GetString("template.tag"), + ) + log.Println("clone and detokenization of gitops-template repository complete") + progressPrinter.IncrementTracker("step-gitops", 1) + + log.Println("sending init completed metric") + + pkg.InformUser("init is done!\n", silentMode) + + if useTelemetry { + telemetryInitCompleted, err := domain.NewTelemetry( + pkg.MetricInitCompleted, + awsHostedZone, + configs.K1Version, + ) + if err != nil { + log.Println(err) + } + err = telemetryHandler.SendCountMetric(telemetryInitCompleted) + if err != nil { + log.Println(err) + } + } + + progressPrinter.IncrementTracker("step-0", 1) + + return nil +} diff --git a/cmd/postInstall.go b/cmd/postInstall.go index bf26371b9..4f5123f83 100644 --- a/cmd/postInstall.go +++ b/cmd/postInstall.go @@ -1,15 +1,10 @@ package cmd import ( - "fmt" + "github.com/kubefirst/kubefirst/internal/k8s" "log" - "net/http" - "runtime" - "sync" "time" - "github.com/kubefirst/kubefirst/internal/k8s" - "github.com/kubefirst/kubefirst/internal/flagset" "github.com/kubefirst/kubefirst/internal/reports" @@ -59,7 +54,10 @@ var postInstallCmd = &cobra.Command{ log.Println("Kubefirst Console available at: http://localhost:9094", globalFlags.SilentMode) - openbrowser(pkg.LocalConsoleUI) + err := pkg.OpenBrowser(pkg.LocalConsoleUI) + if err != nil { + return err + } } else { log.Println("Skipping the presentation of console and api for the handoff screen") @@ -67,16 +65,19 @@ var postInstallCmd = &cobra.Command{ // open all port forwards, wait console ui be ready, and open console ui in the browser if cloud == pkg.CloudK3d { - err := openPortForwardForKubeConConsole() + err := k8s.OpenPortForwardForKubeConConsole() if err != nil { log.Println(err) } - err = isConsoleUIAvailable(pkg.LocalConsoleUI) + err = pkg.IsConsoleUIAvailable(pkg.LocalConsoleUI) if err != nil { log.Println(err) } - openbrowser(pkg.LocalConsoleUI) + err = pkg.OpenBrowser(pkg.LocalConsoleUI) + if err != nil { + return err + } } if viper.GetString("cloud") == flagset.CloudK3d { @@ -98,133 +99,3 @@ func init() { //postInstallCmd.Flags().Bool("enable-console", true, "If hand-off screen will be presented on a browser UI") //flagset.DefineCreateFlags(currentCommand) } - -func openbrowser(url string) { - var err error - - switch runtime.GOOS { - case "linux": - _, _, err = pkg.ExecShellReturnStrings("xdg-open", url) - case "windows": - _, _, err = pkg.ExecShellReturnStrings("rundll32", "url.dll,FileProtocolHandler", url) - case "darwin": - _, _, err = pkg.ExecShellReturnStrings("open", url) - default: - err = fmt.Errorf("unsupported platform") - } - if err != nil { - log.Println(err) - } -} - -// todo: this is temporary -func isConsoleUIAvailable(url string) error { - attempts := 10 - httpClient := http.DefaultClient - for i := 0; i < attempts; i++ { - - req, err := http.NewRequest(http.MethodGet, url, nil) - if err != nil { - log.Printf("unable to reach %q (%d/%d)", url, i+1, attempts) - time.Sleep(5 * time.Second) - continue - } - resp, err := httpClient.Do(req) - if err != nil { - log.Printf("unable to reach %q (%d/%d)", url, i+1, attempts) - time.Sleep(5 * time.Second) - continue - } - - if resp.StatusCode == http.StatusOK { - log.Println("console UI is up and running") - return nil - } - - log.Println("waiting UI console to be ready") - time.Sleep(5 * time.Second) - } - - return nil -} - -// todo: this is temporary -func openPortForwardForKubeConConsole() error { - - var wg sync.WaitGroup - wg.Add(8) - // argo workflows - go func() { - _, err := k8s.PortForward(false, "argo", "svc/argo-server", "2746:2746") - if err != nil { - log.Println("error opening Argo Workflows port forward") - } - wg.Done() - }() - // argocd - go func() { - _, err := k8s.PortForward(false, "argocd", "svc/argocd-server", "8080:80") - if err != nil { - log.Println("error opening ArgoCD port forward") - } - wg.Done() - }() - - // atlantis - go func() { - _, err := k8s.PortForward(false, "atlantis", "svc/atlantis", "4141:80") - if err != nil { - log.Println("error opening Atlantis port forward") - } - wg.Done() - }() - - // chartmuseum - go func() { - _, err := k8s.PortForward(false, "chartmuseum", "svc/chartmuseum", "8181:8080") - if err != nil { - log.Println("error opening Chartmuseum port forward") - } - wg.Done() - }() - - // vault - go func() { - _, err := k8s.PortForward(false, "vault", "svc/vault", "8200:8200") - if err != nil { - log.Println("error opening Vault port forward") - } - wg.Done() - }() - - // minio - go func() { - _, err := k8s.PortForward(false, "minio", "svc/minio", "9000:9000") - if err != nil { - log.Println("error opening Minio port forward") - } - wg.Done() - }() - - // minio console - go func() { - _, err := k8s.PortForward(false, "minio", "svc/minio-console", "9001:9001") - if err != nil { - log.Println("error opening Minio-console port forward") - } - wg.Done() - }() - - // Kubecon console ui - go func() { - _, err := k8s.PortForward(false, "kubefirst", "svc/kubefirst-console", "9094:80") - if err != nil { - log.Println("error opening Kubefirst-console port forward") - } - wg.Done() - }() - - wg.Wait() - - return nil -} diff --git a/cmd/root.go b/cmd/root.go index 88c3d5976..ff8b74736 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/kubefirst/kubefirst/cmd/local" "os" "github.com/kubefirst/kubefirst/internal/progressPrinter" @@ -41,4 +42,7 @@ func init() { // Cobra also supports local flags, which will only run, when this action is called directly. rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + // todo: temporary, move it into CLI tree + rootCmd.AddCommand(local.NewCommand()) + } diff --git a/internal/argocd/argocd.go b/internal/argocd/argocd.go index 4e0628324..d2984cfbb 100644 --- a/internal/argocd/argocd.go +++ b/internal/argocd/argocd.go @@ -402,3 +402,37 @@ func IsAppSynched(token string, applicationName string) (bool, error) { } return false, nil } + +// todo: document it, deprecate the other waitArgoCDToBeReady +func WaitArgoCDToBeReady(dryRun bool) { + if dryRun { + log.Printf("[#99] Dry-run mode, waitArgoCDToBeReady skipped.") + return + } + config := configs.ReadConfig() + x := 50 + for i := 0; i < x; i++ { + _, _, err := pkg.ExecShellReturnStrings(config.KubectlClientPath, "--kubeconfig", config.KubeConfigPath, "get", "namespace/argocd") + if err != nil { + log.Println("Waiting argocd to be born") + time.Sleep(10 * time.Second) + } else { + log.Println("argocd namespace found, continuing") + time.Sleep(5 * time.Second) + break + } + } + for i := 0; i < x; i++ { + _, _, err := pkg.ExecShellReturnStrings(config.KubectlClientPath, "--kubeconfig", config.KubeConfigPath, "-n", "argocd", "get", "pods", "-l", "app.kubernetes.io/name=argocd-server") + if err != nil { + log.Println("Waiting for argocd pods to create, checking in 10 seconds") + time.Sleep(10 * time.Second) + } else { + log.Println("argocd pods found, waiting for them to be running") + viper.Set("argocd.ready", true) + viper.WriteConfig() + time.Sleep(15 * time.Second) + break + } + } +} diff --git a/internal/gitClient/git.go b/internal/gitClient/git.go index b21677012..bcf0250f7 100644 --- a/internal/gitClient/git.go +++ b/internal/gitClient/git.go @@ -61,7 +61,7 @@ func PopulateRepoWithToken(owner string, repo string, sourceFolder string, gitHo //Push config := configs.ReadConfig() - token := os.Getenv("GITHUB_AUTH_TOKEN") + token := viper.GetString("github.token") if token == "" { log.Println("Unauthorized: No token present") return fmt.Errorf("missing github token") @@ -212,6 +212,7 @@ func PushGitopsToSoftServe() { func CloneTemplateRepoWithFallBack(githubOrg string, repoName string, directory string, branch string, fallbackTag string) error { defer viper.WriteConfig() // todo need to refactor this and have the repoName include -template + githubOrg = "kubefirst" repoURL := fmt.Sprintf("https://github.com/%s/%s-template", githubOrg, repoName) isMainBranch := true @@ -326,7 +327,7 @@ func PushLocalRepoToEmptyRemote(githubHost, githubOwner, localRepo, remoteName s }, }) - token := os.Getenv("GITHUB_AUTH_TOKEN") + token := viper.GetString("github.token") if len(token) == 0 { token = viper.GetString("github.token") } @@ -386,7 +387,7 @@ func PushLocalRepoUpdates(githubHost, githubOwner, localRepo, remoteName string) }, }) - token := os.Getenv("GITHUB_AUTH_TOKEN") + token := viper.GetString("github.token") err = repo.Push(&git.PushOptions{ RemoteName: remoteName, Auth: &http.BasicAuth{ @@ -401,7 +402,7 @@ func PushLocalRepoUpdates(githubHost, githubOwner, localRepo, remoteName string) } // todo: refactor -func UpdateLocalTFFilesAndPush(githubHost, githubOwner, localRepo, remoteName string, branchDestiny plumbing.ReferenceName) { +func UpdateLocalTerraformFilesAndPush(githubHost, githubOwner, localRepo, remoteName string, branchDestiny plumbing.ReferenceName) error { cfg := configs.ReadConfig() @@ -418,16 +419,12 @@ func UpdateLocalTFFilesAndPush(githubHost, githubOwner, localRepo, remoteName st url := fmt.Sprintf("https://%s/%s/%s", githubHost, githubOwner, localRepo) log.Printf("git push to remote: %s url: %s", remoteName, url) - w, _ := repo.Worktree() - - //headRef, err := repo.Head() - //ref := plumbing.NewHashReference(branchDestiny, headRef.Hash()) - //if err = repo.Storer.SetReference(ref); err != nil { - // log.Panic(err) - //} + w, err := repo.Worktree() + if err != nil { + return err + } err = w.Checkout(&git.CheckoutOptions{ - //Branch: plumbing.ReferenceName("ref/heads/update-s3-backend"), Branch: branchDestiny, Create: true, }) @@ -436,19 +433,6 @@ func UpdateLocalTFFilesAndPush(githubHost, githubOwner, localRepo, remoteName st } log.Println("Committing new changes... PushLocalRepoUpdates") - //status, err := w.Status() - //if err != nil { - // log.Println("error getting worktree status", err) - //} - - //for file, s := range status { - // //log.Printf("the file is %s the status is %v", file, s.Worktree) - // fmt.Printf("the file is %s the status is %v", file, s.Worktree) - // _, err = w.Add(file) - // if err != nil { - // log.Println("error getting worktree status", err) - // } - //} if viper.GetString("gitprovider") == "github" { kubefirstGitHubFile := "terraform/users/kubefirst-github.tf" @@ -474,7 +458,7 @@ func UpdateLocalTFFilesAndPush(githubHost, githubOwner, localRepo, remoteName st fmt.Println(err) } - token := os.Getenv("GITHUB_AUTH_TOKEN") + token := viper.GetString("github.token") err = repo.Push(&git.PushOptions{ RemoteName: remoteName, Auth: &http.BasicAuth{ @@ -486,4 +470,6 @@ func UpdateLocalTFFilesAndPush(githubHost, githubOwner, localRepo, remoteName st log.Panicf("error pushing to remote %s: %s", remoteName, err) } log.Println("successfully pushed detokenized gitops content to github/", viper.GetString("github.owner")) + + return nil } diff --git a/internal/github/github.go b/internal/github/github.go index 56d8610b7..2aa49a976 100644 --- a/internal/github/github.go +++ b/internal/github/github.go @@ -26,7 +26,7 @@ func ApplyGitHubTerraform(dryRun bool) { envs["AWS_SDK_LOAD_CONFIG"] = "1" aws.ProfileInjection(&envs) // Prepare for terraform gitlab execution - envs["GITHUB_TOKEN"] = os.Getenv("GITHUB_AUTH_TOKEN") + envs["GITHUB_TOKEN"] = viper.GetString("github.token") envs["GITHUB_OWNER"] = viper.GetString("github.owner") envs["TF_VAR_atlantis_repo_webhook_secret"] = viper.GetString("github.atlantis.webhook.secret") envs["TF_VAR_kubefirst_bot_ssh_public_key"] = viper.GetString("botPublicKey") @@ -66,7 +66,7 @@ func DestroyGitHubTerraform(dryRun bool) { envs["AWS_SDK_LOAD_CONFIG"] = "1" aws.ProfileInjection(&envs) // Prepare for terraform gitlab execution - envs["GITHUB_TOKEN"] = os.Getenv("GITHUB_AUTH_TOKEN") + envs["GITHUB_TOKEN"] = viper.GetString("github.token") envs["GITHUB_OWNER"] = viper.GetString("github.owner") envs["TF_VAR_atlantis_repo_webhook_secret"] = viper.GetString("github.atlantis.webhook.secret") envs["TF_VAR_kubefirst_bot_ssh_public_key"] = viper.GetString("botPublicKey") diff --git a/internal/githubWrapper/github.go b/internal/githubWrapper/github.go index a76341d41..031bf4673 100644 --- a/internal/githubWrapper/github.go +++ b/internal/githubWrapper/github.go @@ -3,13 +3,11 @@ package githubWrapper import ( "context" "fmt" + "github.com/google/go-github/v45/github" "github.com/spf13/viper" + "golang.org/x/oauth2" "log" "net/http" - "os" - - "github.com/google/go-github/v45/github" - "golang.org/x/oauth2" ) type GithubSession struct { @@ -21,7 +19,7 @@ type GithubSession struct { // New - Create a new client for github wrapper func New() GithubSession { - token := os.Getenv("GITHUB_AUTH_TOKEN") + token := viper.GetString("github.token") if token == "" { log.Fatal("Unauthorized: No token present") } diff --git a/internal/k3d/secrets.go b/internal/k3d/secrets.go index 20a15e3c4..5a48f3078 100644 --- a/internal/k3d/secrets.go +++ b/internal/k3d/secrets.go @@ -5,13 +5,11 @@ import ( "encoding/base64" "errors" "fmt" - "log" - "os" - "github.com/kubefirst/kubefirst/internal/k8s" "github.com/spf13/viper" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "log" ) func AddK3DSecrets(dryrun bool) error { @@ -49,9 +47,9 @@ func AddK3DSecrets(dryrun bool) error { "BASIC_AUTH_USER": []byte("k-ray"), "BASIC_AUTH_PASS": []byte("feedkraystars"), "USERNAME": []byte(viper.GetString("github.user")), - "PERSONAL_ACCESS_TOKEN": []byte(os.Getenv("GITHUB_AUTH_TOKEN")), + "PERSONAL_ACCESS_TOKEN": []byte(viper.GetString("github.token")), "username": []byte(viper.GetString("github.user")), - "password": []byte(os.Getenv("GITHUB_AUTH_TOKEN")), + "password": []byte(viper.GetString("github.token")), } argoCiSecrets := &v1.Secret{ ObjectMeta: metav1.ObjectMeta{Name: "ci-secrets", Namespace: "argo"}, @@ -65,7 +63,7 @@ func AddK3DSecrets(dryrun bool) error { viper.Set("kubernetes.argo-ci.secret.created", true) viper.WriteConfig() - usernamePasswordString := fmt.Sprintf("%s:%s", viper.GetString("github.user"), os.Getenv("GITHUB_AUTH_TOKEN")) + usernamePasswordString := fmt.Sprintf("%s:%s", viper.GetString("github.user"), viper.GetString("github.token")) usernamePasswordStringB64 := base64.StdEncoding.EncodeToString([]byte(usernamePasswordString)) dockerConfigString := fmt.Sprintf(`{"auths": {"https://ghcr.io/": {"auth": "%s"}}}`, usernamePasswordStringB64) @@ -82,7 +80,7 @@ func AddK3DSecrets(dryrun bool) error { viper.WriteConfig() dataArgoCd := map[string][]byte{ - "password": []byte(os.Getenv("GITHUB_AUTH_TOKEN")), + "password": []byte(viper.GetString("github.token")), "url": []byte(fmt.Sprintf("https://%s/%s/gitops.git", viper.GetString("github.host"), viper.GetString("github.owner"))), "username": []byte(viper.GetString("github.user")), } @@ -155,7 +153,7 @@ func AddK3DSecrets(dryrun bool) error { viper.WriteConfig() dataGh := map[string][]byte{ - "github_token": []byte(os.Getenv("GITHUB_AUTH_TOKEN")), + "github_token": []byte(viper.GetString("github.token")), } ghRunnerSecret := &v1.Secret{ ObjectMeta: metav1.ObjectMeta{Name: "controller-manager", Namespace: "github-runner"}, diff --git a/internal/k8s/kubernetes.go b/internal/k8s/kubernetes.go index 2051442a9..e0d85c44c 100644 --- a/internal/k8s/kubernetes.go +++ b/internal/k8s/kubernetes.go @@ -5,9 +5,12 @@ import ( "context" "encoding/json" "fmt" + "io" "log" + "net/http" "os" "os/exec" + "sync" "time" "github.com/itchyny/gojq" @@ -366,3 +369,170 @@ func (p *secret) patchSecret(k8sClient *kubernetes.Clientset, payload []PatchJso } return nil } + +// todo: deprecate the other functions +func LoopUntilPodIsReady(dryRun bool) { + if dryRun { + log.Printf("[#99] Dry-run mode, loopUntilPodIsReady skipped.") + return + } + token := viper.GetString("vault.token") + if len(token) == 0 { + + totalAttempts := 50 + url := "http://localhost:8200/v1/sys/health" + for i := 0; i < totalAttempts; i++ { + log.Printf("vault is not ready yet, sleeping and checking again, attempt (%d/%d)", i+1, totalAttempts) + time.Sleep(10 * time.Second) + + req, _ := http.NewRequest("GET", url, nil) + + req.Header.Add("Content-Type", "application/json") + + res, err := http.DefaultClient.Do(req) + if err != nil { + log.Println("error with http request Do, vault is not available", err) + // todo: temporary code + log.Println("trying to open port-forward again...") + go func() { + _, err := PortForward(false, "vault", "svc/vault", "8200:8200") + if err != nil { + log.Println("error opening Vault port forward") + } + }() + continue + } + + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + log.Println("vault is available but the body is not what is expected ", err) + continue + } + + var responseJson map[string]interface{} + + if err := json.Unmarshal(body, &responseJson); err != nil { + log.Printf("vault is available but the body is not what is expected %s", err) + continue + } + + _, ok := responseJson["initialized"] + if ok { + log.Printf("vault is initialized and is in the expected state") + return + } + log.Panic("vault was never initialized") + } + viper.Set("vault.status.running", true) + viper.WriteConfig() + } else { + log.Println("vault token already exists, skipping vault health checks loopUntilPodIsReady") + } +} + +// todo: deprecate the other functions +func SetArgocdCreds(dryRun bool) { + if dryRun { + log.Printf("[#99] Dry-run mode, setArgocdCreds skipped.") + viper.Set("argocd.admin.password", "dry-run-not-real-pwd") + viper.Set("argocd.admin.username", "dry-run-not-admin") + viper.WriteConfig() + return + } + clientset, err := GetClientSet(dryRun) + if err != nil { + panic(err.Error()) + } + argocd.ArgocdSecretClient = clientset.CoreV1().Secrets("argocd") + + argocdPassword := GetSecretValue(argocd.ArgocdSecretClient, "argocd-initial-admin-secret", "password") + if argocdPassword == "" { + log.Panicf("Missing argocdPassword") + } + + viper.Set("argocd.admin.password", argocdPassword) + viper.Set("argocd.admin.username", "admin") + viper.WriteConfig() +} + +// todo: this is temporary +func OpenPortForwardForKubeConConsole() error { + + var wg sync.WaitGroup + wg.Add(8) + // argo workflows + go func() { + _, err := PortForward(false, "argo", "svc/argo-server", "2746:2746") + if err != nil { + log.Println("error opening Argo Workflows port forward") + } + wg.Done() + }() + // argocd + go func() { + _, err := PortForward(false, "argocd", "svc/argocd-server", "8080:80") + if err != nil { + log.Println("error opening ArgoCD port forward") + } + wg.Done() + }() + + // atlantis + go func() { + _, err := PortForward(false, "atlantis", "svc/atlantis", "4141:80") + if err != nil { + log.Println("error opening Atlantis port forward") + } + wg.Done() + }() + + // chartmuseum + go func() { + _, err := PortForward(false, "chartmuseum", "svc/chartmuseum", "8181:8080") + if err != nil { + log.Println("error opening Chartmuseum port forward") + } + wg.Done() + }() + + // vault + go func() { + _, err := PortForward(false, "vault", "svc/vault", "8200:8200") + if err != nil { + log.Println("error opening Vault port forward") + } + wg.Done() + }() + + // minio + go func() { + _, err := PortForward(false, "minio", "svc/minio", "9000:9000") + if err != nil { + log.Println("error opening Minio port forward") + } + wg.Done() + }() + + // minio console + go func() { + _, err := PortForward(false, "minio", "svc/minio-console", "9001:9001") + if err != nil { + log.Println("error opening Minio-console port forward") + } + wg.Done() + }() + + // Kubecon console ui + go func() { + _, err := PortForward(false, "kubefirst", "svc/kubefirst-console", "9094:80") + if err != nil { + log.Println("error opening Kubefirst-console port forward") + } + wg.Done() + }() + + wg.Wait() + + return nil +} diff --git a/internal/metaphor/metaphor.go b/internal/metaphor/metaphor.go index 05de8d87d..887eeffab 100644 --- a/internal/metaphor/metaphor.go +++ b/internal/metaphor/metaphor.go @@ -2,6 +2,7 @@ package metaphor import ( "fmt" + "github.com/kubefirst/kubefirst/pkg" "log" "os" @@ -96,3 +97,53 @@ func DeployMetaphorGithub(globalFlags flagset.GlobalFlags) error { viper.WriteConfig() return nil } + +// DeployMetaphorGithubLocal Deploy metaphor applications on github install +func DeployMetaphorGithubLocal(dryRun bool, gitHubOwner string, metaphorBranch string, templateTag string) error { + + if dryRun { + log.Printf("[#99] Dry-run mode, DeployMetaphorGithub skipped.") + return nil + } + + if viper.GetBool("github.metaphor-pushed") { + log.Println("github.metaphor-pushed already executed, skipped") + return nil + } + + config := configs.ReadConfig() + + tfEntrypoint := config.GitOpsRepoPath + "/terraform/github" + err := os.Rename(fmt.Sprintf("%s/%s", tfEntrypoint, "metaphor-repos.md"), fmt.Sprintf("%s/%s", tfEntrypoint, "metaphor-repos.tf")) + if err != nil { + log.Println("error renaming metaphor-repos.md to metaphor-repos.tf", err) + } + + gitClient.PushLocalRepoUpdates(pkg.GitHubHost, gitHubOwner, "gitops", "github") + terraform.InitApplyAutoApprove(dryRun, tfEntrypoint) + + repos := [3]string{"metaphor", "metaphor-go", "metaphor-frontend"} + for _, element := range repos { + log.Println("Processing Repo:", element) + repo.PrepareKubefirstTemplateRepo( + dryRun, + config, + gitHubOwner, + element, + metaphorBranch, + templateTag, + ) + log.Printf("clone and detokenization of %s-template repository complete", element) + + gitClient.PushLocalRepoToEmptyRemote(pkg.GitHubHost, gitHubOwner, element, "github") + + } + + viper.Set("github.metaphor-pushed", true) + err = viper.WriteConfig() + if err != nil { + return err + } + + return nil +} diff --git a/internal/terraform/terraform.go b/internal/terraform/terraform.go index b3d0297ae..2e9937519 100644 --- a/internal/terraform/terraform.go +++ b/internal/terraform/terraform.go @@ -97,7 +97,7 @@ func terraformConfig(terraformEntryPoint string) map[string]string { case "users": envs["VAULT_TOKEN"] = viper.GetString("vault.token") envs["VAULT_ADDR"] = viper.GetString("vault.local.service") - envs["GITHUB_TOKEN"] = os.Getenv("GITHUB_AUTH_TOKEN") + envs["GITHUB_TOKEN"] = viper.GetString("github.token") envs["GITHUB_OWNER"] = viper.GetString("github.owner") return envs } @@ -358,7 +358,7 @@ func ApplyUsersTerraform(dryRun bool, directory string, gitProvider string) erro envs := map[string]string{} if gitProvider == "github" { - envs["GITHUB_TOKEN"] = os.Getenv("GITHUB_AUTH_TOKEN") + envs["GITHUB_TOKEN"] = viper.GetString("github.token") envs["GITHUB_OWNER"] = viper.GetString("github.owner") } else if gitProvider == "gitlab" { envs["GITLAB_TOKEN"] = viper.GetString("gitlab.token") diff --git a/internal/vault/vault.go b/internal/vault/vault.go index 9ef776091..282808d6f 100644 --- a/internal/vault/vault.go +++ b/internal/vault/vault.go @@ -9,6 +9,7 @@ import ( "os" "os/exec" "syscall" + "time" vault "github.com/hashicorp/vault/api" "github.com/kubefirst/kubefirst/configs" @@ -196,3 +197,42 @@ func GetOidcClientCredentials(dryRun bool) { viper.WriteConfig() } + +func WaitVaultToBeRunning(dryRun bool) { + if dryRun { + log.Printf("[#99] Dry-run mode, waitVaultToBeRunning skipped.") + return + } + token := viper.GetString("vault.token") + if len(token) == 0 { + config := configs.ReadConfig() + x := 50 + for i := 0; i < x; i++ { + _, _, err := pkg.ExecShellReturnStrings(config.KubectlClientPath, "--kubeconfig", config.KubeConfigPath, "get", "namespace/vault") + if err != nil { + log.Println("Waiting vault to be born") + time.Sleep(10 * time.Second) + } else { + log.Println("vault namespace found, continuing") + time.Sleep(25 * time.Second) + break + } + } + + //! failing + x = 50 + for i := 0; i < x; i++ { + _, _, err := pkg.ExecShellReturnStrings(config.KubectlClientPath, "--kubeconfig", config.KubeConfigPath, "-n", "vault", "get", "pods", "-l", "app.kubernetes.io/instance=vault") + if err != nil { + log.Println("Waiting vault pods to create") + time.Sleep(10 * time.Second) + } else { + log.Println("vault pods found, continuing") + time.Sleep(15 * time.Second) + break + } + } + } else { + log.Println("vault token arleady exists, skipping vault health checks waitVaultToBeRunning") + } +} diff --git a/pkg/constants.go b/pkg/constants.go index 0ea8870fd..fc3582c64 100644 --- a/pkg/constants.go +++ b/pkg/constants.go @@ -8,6 +8,9 @@ const ( LocalConsoleUI = "http://localhost:9094" GitHubOAuthClientId = "2ced340927e0a6c49a45" CloudK3d = "k3d" + GitHubProviderName = "github" + GitHubHost = "github.com" + LocalClusterName = "kubefirst" ) // SegmentIO constants @@ -20,3 +23,11 @@ const ( MetricMgmtClusterInstallStarted = "kubefirst.mgmt_cluster_install.started" MetricMgmtClusterInstallCompleted = "kubefirst.mgmt_cluster_install.completed" ) + +const ( + HelmRepoName = "argo" + HelmRepoURL = "https://argoproj.github.io/argo-helm" + HelmRepoChartName = "argo-cd" + HelmRepoNamespace = "argocd" + HelmRepoChartVersion = "4.10.5" +) diff --git a/pkg/helpers.go b/pkg/helpers.go index cf0952ab9..ccfad1adc 100644 --- a/pkg/helpers.go +++ b/pkg/helpers.go @@ -3,12 +3,14 @@ package pkg import ( "errors" "fmt" + "github.com/kubefirst/kubefirst/internal/progressPrinter" "log" "math/rand" "net/http" "net/url" "os" "path/filepath" + "runtime" "strings" "time" @@ -133,7 +135,7 @@ func DetokenizeDirectory(path string, fi os.FileInfo, err error) error { githubUser := viper.GetString(("github.user")) //TODO: We need to fix this - githubToken := os.Getenv("GITHUB_AUTH_TOKEN") + githubToken := viper.GetString("github.token") //todo: get from viper gitopsRepo := "gitops" @@ -445,7 +447,7 @@ func AwaitHostNTimes(url string, times int, gracePeriod time.Duration) { // } // this is temporary code -func ReplaceS3Backend() error { +func ReplaceTerraformS3Backend() error { config := configs.ReadConfig() @@ -478,3 +480,69 @@ func ReplaceS3Backend() error { return nil } + +// todo: deprecate cmd.informUser +func InformUser(message string, silentMode bool) { + // if in silent mode, send message to the screen + // silent mode will silent most of the messages, this function is not frequently called + if silentMode { + _, err := fmt.Fprintln(os.Stdout, message) + if err != nil { + log.Println(err) + } + return + } + log.Println(message) + progressPrinter.LogMessage(fmt.Sprintf("- %s", message)) +} + +func OpenBrowser(url string) error { + var err error + + switch runtime.GOOS { + case "linux": + _, _, err = ExecShellReturnStrings("xdg-open", url) + case "windows": + _, _, err = ExecShellReturnStrings("rundll32", "url.dll,FileProtocolHandler", url) + case "darwin": + _, _, err = ExecShellReturnStrings("open", url) + default: + err = fmt.Errorf("unsupported platform") + } + if err != nil { + return err + } + + return nil +} + +// todo: this is temporary +func IsConsoleUIAvailable(url string) error { + attempts := 10 + httpClient := http.DefaultClient + for i := 0; i < attempts; i++ { + + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + log.Printf("unable to reach %q (%d/%d)", url, i+1, attempts) + time.Sleep(5 * time.Second) + continue + } + resp, err := httpClient.Do(req) + if err != nil { + log.Printf("unable to reach %q (%d/%d)", url, i+1, attempts) + time.Sleep(5 * time.Second) + continue + } + + if resp.StatusCode == http.StatusOK { + log.Println("console UI is up and running") + return nil + } + + log.Println("waiting UI console to be ready") + time.Sleep(5 * time.Second) + } + + return nil +}