diff --git a/cmd/tk/check.go b/cmd/tk/check.go index dd467ae1..35b17485 100644 --- a/cmd/tk/check.go +++ b/cmd/tk/check.go @@ -2,6 +2,7 @@ package main import ( "context" + "fmt" "os" "os/exec" "strings" @@ -14,12 +15,17 @@ import ( var checkCmd = &cobra.Command{ Use: "check", - Short: "Check requirements", + Short: "Check requirements and installation", Long: ` The check command will perform a series of checks to validate that -the local environment is configured correctly.`, - Example: ` check --pre`, - RunE: runCheckCmd, +the local environment is configured correctly and if the installed components are healthy.`, + Example: ` # Run pre-installation checks + check --pre + + # Run installation checks + check +`, + RunE: runCheckCmd, } var ( @@ -37,7 +43,7 @@ func runCheckCmd(cmd *cobra.Command, args []string) error { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() - logAction("starting verification") + logAction("checking prerequisites") checkFailed := false if !sshCheck() { checkFailed = true @@ -51,18 +57,21 @@ func runCheckCmd(cmd *cobra.Command, args []string) error { checkFailed = true } + if !kubernetesCheck(">=1.14.0") { + checkFailed = true + } + if checkPre { if checkFailed { os.Exit(1) } - logSuccess("all prerequisites checks passed") + logSuccess("prerequisites checks passed") return nil } - if !kubernetesCheck(">=1.14.0") { + if !componentsCheck() { checkFailed = true } - if checkFailed { os.Exit(1) } @@ -188,3 +197,19 @@ func kubernetesCheck(version string) bool { logSuccess("kubernetes %s %s", v.String(), version) return true } + +func componentsCheck() bool { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + for _, deployment := range components { + command := fmt.Sprintf("kubectl -n %s rollout status deployment %s --timeout=%s", + namespace, deployment, timeout.String()) + if output, err := utils.execCommand(ctx, ModeCapture, command); err != nil { + logFailure("%s: %s", deployment, strings.TrimSuffix(output, "\n")) + } else { + logSuccess("%s is healthy", deployment) + } + } + return true +} diff --git a/cmd/tk/install.go b/cmd/tk/install.go index 3cda8504..256e264b 100644 --- a/cmd/tk/install.go +++ b/cmd/tk/install.go @@ -15,10 +15,18 @@ var installCmd = &cobra.Command{ Use: "install", Short: "Install the toolkit components", Long: ` -The install command deploys the toolkit components -on the configured Kubernetes cluster in ~/.kube/config`, - Example: ` install --version=master --namespace=gitops-systems`, - RunE: installCmdRun, +The install command deploys the toolkit components in the specified namespace. +If a previous version is installed, then an in-place upgrade will be performed.`, + Example: ` # Install the latest version in the gitops-systems namespace + install --version=master --namespace=gitops-systems + + # Dry-run install for a specific version and a series of components + install --dry-run --version=0.0.1 --components="source-controller,kustomize-controller" + + # Dry-run install with manifests preview + install --dry-run --verbose +`, + RunE: installCmdRun, } var ( @@ -57,7 +65,7 @@ func installCmdRun(cmd *cobra.Command, args []string) error { logAction("generating install manifests") if kustomizePath == "" { - err = genInstallManifests(installVersion, namespace, tmpDir) + err = genInstallManifests(installVersion, namespace, components, tmpDir) if err != nil { return fmt.Errorf("install failed: %w", err) } @@ -103,7 +111,7 @@ func installCmdRun(cmd *cobra.Command, args []string) error { } logAction("verifying installation") - for _, deployment := range []string{"source-controller", "kustomize-controller"} { + for _, deployment := range components { command = fmt.Sprintf("kubectl -n %s rollout status deployment %s --timeout=%s", namespace, deployment, timeout.String()) if _, err := utils.execCommand(ctx, applyOutput, command); err != nil { @@ -138,6 +146,7 @@ fieldSpecs: ` var kustomizationTmpl = `--- +{{- $version := .Version }} apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization namespace: {{.Namespace}} @@ -146,9 +155,10 @@ transformers: resources: - namespace.yaml - roles - - github.com/fluxcd/toolkit/manifests/bases/source-controller?ref={{.Version}} - - github.com/fluxcd/toolkit/manifests/bases/kustomize-controller?ref={{.Version}} - - github.com/fluxcd/toolkit/manifests/policies?ref={{.Version}} + - github.com/fluxcd/toolkit/manifests/policies?ref={{$version}} +{{- range .Components }} + - github.com/fluxcd/toolkit/manifests/bases/{{.}}?ref={{$version}} +{{- end }} ` var kustomizationRolesTmpl = `--- @@ -159,13 +169,15 @@ resources: nameSuffix: -{{.Namespace}} ` -func genInstallManifests(ver, ns, tmpDir string) error { +func genInstallManifests(version string, namespace string, components []string, tmpDir string) error { model := struct { - Version string - Namespace string + Version string + Namespace string + Components []string }{ - Version: ver, - Namespace: ns, + Version: version, + Namespace: namespace, + Components: components, } if err := utils.execTemplate(model, namespaceTmpl, path.Join(tmpDir, "namespace.yaml")); err != nil { diff --git a/cmd/tk/main.go b/cmd/tk/main.go index 68a954b3..9e6f43cb 100644 --- a/cmd/tk/main.go +++ b/cmd/tk/main.go @@ -28,6 +28,7 @@ var ( namespace string timeout time.Duration verbose bool + components []string utils Utils ) @@ -38,6 +39,9 @@ func init() { "timeout for this operation") rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "", false, "print generated objects") + rootCmd.PersistentFlags().StringSliceVar(&components, "components", + []string{"source-controller", "kustomize-controller"}, + "list of components, accepts comma-separated values") } func main() { diff --git a/cmd/tk/uninstall.go b/cmd/tk/uninstall.go index c3244209..12f0411d 100644 --- a/cmd/tk/uninstall.go +++ b/cmd/tk/uninstall.go @@ -13,9 +13,14 @@ var uninstallCmd = &cobra.Command{ Short: "Uninstall the toolkit components", Long: ` The uninstall command removes the namespace, cluster roles, -cluster role bindings and CRDs`, - Example: ` uninstall --namespace=gitops-system --crds --dry-run`, - RunE: uninstallCmdRun, +cluster role bindings and CRDs.`, + Example: ` # Dry-run uninstall of all components + uninstall --dry-run --namespace=gitops-system + + # Uninstall all components and custom resource definitions + uninstall --crds --namespace=gitops-system +`, + RunE: uninstallCmdRun, } var ( diff --git a/cmd/tk/utils.go b/cmd/tk/utils.go index 1c9ea1c2..c5aaad86 100644 --- a/cmd/tk/utils.go +++ b/cmd/tk/utils.go @@ -49,10 +49,12 @@ func (*Utils) execCommand(ctx context.Context, mode ExecMode, command string) (s } if mode == ModeCapture { - if output, err := c.CombinedOutput(); err != nil { - return "", err + c.Stdout = &stdoutBuf + c.Stderr = &stderrBuf + if err := c.Run(); err != nil { + return stderrBuf.String(), err } else { - return string(output), nil + return stdoutBuf.String(), nil } }