From 3b6845bced66cecfd302f153792ae10052718596 Mon Sep 17 00:00:00 2001 From: Alistair Hey Date: Sat, 6 Jun 2020 16:14:08 +0100 Subject: [PATCH] Refactor into pkg using an options input and add Loki App Signed-off-by: Alistair Hey --- cmd/apps/argocd_app.go | 11 +- cmd/apps/certmanager_app.go | 30 ++-- cmd/apps/chart_app.go | 21 +-- cmd/apps/cronconnector_app.go | 21 +-- cmd/apps/crossplane_app.go | 30 ++-- cmd/apps/grafana_app.go | 24 +-- cmd/apps/inletsoperator_app.go | 33 ++-- cmd/apps/istio_app.go | 38 ++-- cmd/apps/jenkins_app.go | 24 +-- cmd/apps/kafkaconnector_app.go | 21 +-- cmd/apps/kube_state_metrics.go | 18 +- cmd/apps/kubernetes_dashboard_app.go | 9 +- cmd/apps/kubernetes_exec.go | 260 --------------------------- cmd/apps/linkerd_app.go | 8 +- cmd/apps/loki_app.go | 123 +++++++++++++ cmd/apps/metricsserver_app.go | 18 +- cmd/apps/minio_app.go | 25 +-- cmd/apps/mongodb_app.go | 26 +-- cmd/apps/nginx_app.go | 23 +-- cmd/apps/openfaas_app.go | 32 ++-- cmd/apps/openfaas_app_test.go | 98 ---------- cmd/apps/openfaas_ingress_app.go | 7 +- cmd/apps/portainer_app.go | 11 +- cmd/apps/postgres_app.go | 14 +- cmd/apps/registry_app.go | 25 +-- cmd/apps/registry_ingress_app.go | 7 +- cmd/apps/tekton_app.go | 11 +- cmd/apps/traefik2_app.go | 19 +- cmd/info.go | 3 + cmd/install.go | 2 + pkg/apps/loki.go | 28 +++ pkg/config/config.go | 22 +++ pkg/config/config_test.go | 103 +++++++++++ pkg/helm/helm.go | 205 ++++++++++++++++++++- pkg/k8s/kubernetes_exec.go | 52 ++++++ pkg/types/types.go | 80 +++++++++ 36 files changed, 827 insertions(+), 655 deletions(-) delete mode 100644 cmd/apps/kubernetes_exec.go create mode 100644 cmd/apps/loki_app.go create mode 100644 pkg/apps/loki.go create mode 100644 pkg/config/config_test.go create mode 100644 pkg/k8s/kubernetes_exec.go create mode 100644 pkg/types/types.go diff --git a/cmd/apps/argocd_app.go b/cmd/apps/argocd_app.go index 21171874f..7fc49a346 100644 --- a/cmd/apps/argocd_app.go +++ b/cmd/apps/argocd_app.go @@ -7,6 +7,9 @@ import ( "fmt" "strings" + "github.com/alexellis/arkade/pkg/config" + "github.com/alexellis/arkade/pkg/k8s" + "github.com/alexellis/arkade/pkg" "github.com/spf13/cobra" @@ -22,7 +25,7 @@ func MakeInstallArgoCD() *cobra.Command { } command.RunE = func(command *cobra.Command, args []string) error { - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() if command.Flags().Changed("kubeconfig") { kubeConfigPath, _ = command.Flags().GetString("kubeconfig") @@ -30,14 +33,14 @@ func MakeInstallArgoCD() *cobra.Command { fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath) - arch := getNodeArchitecture() + arch := k8s.GetNodeArchitecture() fmt.Printf("Node architecture: %q\n", arch) if arch != IntelArch { return fmt.Errorf(OnlyIntelArch) } - _, err := kubectlTask("create", "ns", + _, err := k8s.KubectlTask("create", "ns", "argocd") if err != nil { if !strings.Contains(err.Error(), "exists") { @@ -45,7 +48,7 @@ func MakeInstallArgoCD() *cobra.Command { } } - _, err = kubectlTask("apply", "-f", + _, err = k8s.KubectlTask("apply", "-f", "https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml", "-n", "argocd") if err != nil { return err diff --git a/cmd/apps/certmanager_app.go b/cmd/apps/certmanager_app.go index ce60270fb..685ebf97c 100644 --- a/cmd/apps/certmanager_app.go +++ b/cmd/apps/certmanager_app.go @@ -9,6 +9,8 @@ import ( "os" "path" + "github.com/alexellis/arkade/pkg/k8s" + "github.com/alexellis/arkade/pkg" "github.com/alexellis/arkade/pkg/config" "github.com/alexellis/arkade/pkg/env" @@ -32,7 +34,7 @@ func MakeInstallCertManager() *cobra.Command { certManager.RunE = func(command *cobra.Command, args []string) error { wait, _ := command.Flags().GetBool("wait") const certManagerVersion = "v0.12.0" - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() if command.Flags().Changed("kubeconfig") { kubeConfigPath, _ = command.Flags().GetString("kubeconfig") @@ -68,21 +70,13 @@ func MakeInstallCertManager() *cobra.Command { return err } - err = addHelmRepo("jetstack", "https://charts.jetstack.io", helm3) + updateRepo, _ := certManager.Flags().GetBool("update-repo") + err = helm.AddHelmRepo("jetstack", "https://charts.jetstack.io", updateRepo, helm3) if err != nil { return err } - updateRepo, _ := certManager.Flags().GetBool("update-repo") - - if updateRepo { - err = updateHelmRepos(helm3) - if err != nil { - return err - } - } - - nsRes, nsErr := kubectlTask("create", "namespace", namespace) + nsRes, nsErr := k8s.KubectlTask("create", "namespace", namespace) if nsErr != nil { return nsErr } @@ -93,14 +87,14 @@ func MakeInstallCertManager() *cobra.Command { chartPath := path.Join(os.TempDir(), "charts") - err = fetchChart(chartPath, "jetstack/cert-manager", certManagerVersion, helm3) + err = helm.FetchChart("jetstack/cert-manager", certManagerVersion, helm3) if err != nil { return err } log.Printf("Applying CRD\n") crdsURL := "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.12/deploy/manifests/00-crds.yaml" - res, err := kubectlTask("apply", "--validate=false", "-f", + res, err := k8s.KubectlTask("apply", "--validate=false", "-f", crdsURL) if err != nil { return err @@ -114,9 +108,7 @@ func MakeInstallCertManager() *cobra.Command { overrides := map[string]string{} if helm3 { - outputPath := path.Join(chartPath, "cert-manager") - - err := helm3Upgrade(outputPath, "jetstack/cert-manager", namespace, + err := helm.Helm3Upgrade("jetstack/cert-manager", namespace, "values.yaml", "v0.12.0", overrides, @@ -126,12 +118,12 @@ func MakeInstallCertManager() *cobra.Command { return err } } else { - err = templateChart(chartPath, "cert-manager", namespace, outputPath, "values.yaml", nil) + err = helm.TemplateChart(chartPath, "cert-manager", namespace, outputPath, "values.yaml", nil) if err != nil { return err } - applyRes, applyErr := kubectlTask("apply", "-R", "-f", outputPath) + applyRes, applyErr := k8s.KubectlTask("apply", "-R", "-f", outputPath) if applyErr != nil { return applyErr } diff --git a/cmd/apps/chart_app.go b/cmd/apps/chart_app.go index 7c09d09db..d98416eea 100644 --- a/cmd/apps/chart_app.go +++ b/cmd/apps/chart_app.go @@ -10,6 +10,8 @@ import ( "path" "strings" + "github.com/alexellis/arkade/pkg/k8s" + "github.com/alexellis/arkade/pkg" "github.com/alexellis/arkade/pkg/config" "github.com/alexellis/arkade/pkg/env" @@ -59,7 +61,7 @@ before using the generic helm chart installer command.`, return fmt.Errorf("--repo-name required") } - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() if command.Flags().Changed("kubeconfig") { kubeConfigPath, _ = command.Flags().GetString("kubeconfig") @@ -88,25 +90,20 @@ before using the generic helm chart installer command.`, } if len(chartRepoURL) > 0 { - err = addHelmRepo(chartPrefix, chartRepoURL, false) + err = helm.AddHelmRepo(chartPrefix, chartRepoURL, true, false) if err != nil { return err } } - err = updateHelmRepos(false) - if err != nil { - return err - } - - res, kcErr := kubectlTask("get", "namespace", namespace) + res, kcErr := k8s.KubectlTask("get", "namespace", namespace) if kcErr != nil { return err } if res.ExitCode != 0 { - err = kubectl("create", "namespace", namespace) + err = k8s.Kubectl("create", "namespace", namespace) if err != nil { return err } @@ -114,7 +111,7 @@ before using the generic helm chart installer command.`, chartPath := path.Join(os.TempDir(), "charts") - err = fetchChart(chartPath, chartRepoName, defaultVersion, false) + err = helm.FetchChart(chartRepoName, defaultVersion, false) if err != nil { return err } @@ -135,12 +132,12 @@ before using the generic helm chart installer command.`, } } - err = templateChart(chartPath, chartName, namespace, outputPath, "values.yaml", setMap) + err = helm.TemplateChart(chartPath, chartName, namespace, outputPath, "values.yaml", setMap) if err != nil { return err } - err = kubectl("apply", "--namespace", namespace, "-R", "-f", outputPath) + err = k8s.Kubectl("apply", "--namespace", namespace, "-R", "-f", outputPath) if err != nil { return err } diff --git a/cmd/apps/cronconnector_app.go b/cmd/apps/cronconnector_app.go index 45c9fa777..730f36796 100644 --- a/cmd/apps/cronconnector_app.go +++ b/cmd/apps/cronconnector_app.go @@ -6,6 +6,8 @@ import ( "os" "path" + "github.com/alexellis/arkade/pkg/k8s" + "github.com/alexellis/arkade/pkg" "github.com/alexellis/arkade/pkg/config" "github.com/alexellis/arkade/pkg/env" @@ -29,7 +31,7 @@ func MakeInstallCronConnector() *cobra.Command { "Use custom flags or override existing flags \n(example --set key=value)") command.RunE = func(command *cobra.Command, args []string) error { - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() if command.Flags().Changed("kubeconfig") { kubeConfigPath, _ = command.Flags().GetString("kubeconfig") @@ -62,20 +64,13 @@ func MakeInstallCronConnector() *cobra.Command { return err } - err = addHelmRepo("openfaas", "https://openfaas.github.io/faas-netes/", false) + err = helm.AddHelmRepo("openfaas", "https://openfaas.github.io/faas-netes/", updateRepo, false) if err != nil { return err } - if updateRepo { - err = updateHelmRepos(false) - if err != nil { - return err - } - } - chartPath := path.Join(os.TempDir(), "charts") - err = fetchChart(chartPath, "openfaas/cron-connector", defaultVersion, false) + err = helm.FetchChart("openfaas/cron-connector", defaultVersion, false) if err != nil { return err @@ -92,7 +87,7 @@ func MakeInstallCronConnector() *cobra.Command { return err } - arch := getNodeArchitecture() + arch := k8s.GetNodeArchitecture() fmt.Printf("Node architecture: %q\n", arch) fmt.Println("Chart path: ", chartPath) @@ -100,7 +95,7 @@ func MakeInstallCronConnector() *cobra.Command { outputPath := path.Join(chartPath, "cron-connector/rendered") ns := namespace - err = templateChart(chartPath, + err = helm.TemplateChart(chartPath, "cron-connector", ns, outputPath, @@ -111,7 +106,7 @@ func MakeInstallCronConnector() *cobra.Command { return err } - err = kubectl("apply", "-R", "-f", outputPath) + err = k8s.Kubectl("apply", "-R", "-f", outputPath) if err != nil { return err diff --git a/cmd/apps/crossplane_app.go b/cmd/apps/crossplane_app.go index 680c5b785..33e2ca237 100644 --- a/cmd/apps/crossplane_app.go +++ b/cmd/apps/crossplane_app.go @@ -10,6 +10,8 @@ import ( "path" "strings" + "github.com/alexellis/arkade/pkg/k8s" + "github.com/alexellis/arkade/pkg" "github.com/alexellis/arkade/pkg/config" "github.com/alexellis/arkade/pkg/env" @@ -33,7 +35,7 @@ schedule workloads to any Kubernetes cluster`, crossplane.RunE = func(command *cobra.Command, args []string) error { wait, _ := command.Flags().GetBool("wait") - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() if command.Flags().Changed("kubeconfig") { kubeConfigPath, _ = command.Flags().GetString("kubeconfig") @@ -52,7 +54,7 @@ schedule workloads to any Kubernetes cluster`, return fmt.Errorf(`to override the namespace, install crossplane via helm manually`) } - arch := getNodeArchitecture() + arch := k8s.GetNodeArchitecture() if !strings.Contains(arch, "64") { return fmt.Errorf(`crossplane is currently only supported on 64-bit architectures`) } @@ -76,37 +78,27 @@ schedule workloads to any Kubernetes cluster`, return err } - err = addHelmRepo("crossplane-alpha", "https://charts.crossplane.io/alpha", helm3) + updateRepo, _ := crossplane.Flags().GetBool("update-repo") + err = helm.AddHelmRepo("crossplane-alpha", "https://charts.crossplane.io/alpha", updateRepo, helm3) if err != nil { return err } - updateRepo, _ := crossplane.Flags().GetBool("update-repo") - - if updateRepo { - err = updateHelmRepos(helm3) - if err != nil { - return err - } - } - chartPath := path.Join(os.TempDir(), "charts") - err = fetchChart(chartPath, "crossplane-alpha/crossplane", defaultVersion, helm3) + err = helm.FetchChart("crossplane-alpha/crossplane", defaultVersion, helm3) if err != nil { return err } if helm3 { - outputPath := path.Join(chartPath, "crossplane") - - _, nsErr := kubectlTask("create", "namespace", "crossplane-system") + _, nsErr := k8s.KubectlTask("create", "namespace", "crossplane-system") if nsErr != nil && !strings.Contains(nsErr.Error(), "AlreadyExists") { return nsErr } - err := helm3Upgrade(outputPath, "crossplane-alpha/crossplane", + err := helm.Helm3Upgrade("crossplane-alpha/crossplane", namespace, "values.yaml", "", map[string]string{}, wait) if err != nil { return err @@ -114,12 +106,12 @@ schedule workloads to any Kubernetes cluster`, } else { outputPath := path.Join(chartPath, "crossplane-alpha/crossplane") - err = templateChart(chartPath, "crossplane", namespace, outputPath, "values.yaml", map[string]string{}) + err = helm.TemplateChart(chartPath, "crossplane", namespace, outputPath, "values.yaml", map[string]string{}) if err != nil { return err } - applyRes, applyErr := kubectlTask("apply", "-R", "-f", outputPath) + applyRes, applyErr := k8s.KubectlTask("apply", "-R", "-f", outputPath) if applyErr != nil { return applyErr } diff --git a/cmd/apps/grafana_app.go b/cmd/apps/grafana_app.go index 3bccbb5f2..4b76c66f8 100644 --- a/cmd/apps/grafana_app.go +++ b/cmd/apps/grafana_app.go @@ -9,6 +9,8 @@ import ( "os" "path" + "github.com/alexellis/arkade/pkg/k8s" + "github.com/alexellis/arkade/pkg" "github.com/alexellis/arkade/pkg/config" "github.com/alexellis/arkade/pkg/env" @@ -56,23 +58,14 @@ func MakeInstallGrafana() *cobra.Command { return err } - err = addHelmRepo("stable", "https://kubernetes-charts.storage.googleapis.com", true) + updateRepo, _ := grafana.Flags().GetBool("update-repo") + err = helm.AddHelmRepo("stable", "https://kubernetes-charts.storage.googleapis.com", updateRepo, true) if err != nil { return err } - // Update chart repositories - updateRepo, _ := grafana.Flags().GetBool("update-repo") - - if updateRepo { - err = updateHelmRepos(true) - if err != nil { - return err - } - } - // create the namespace - nsRes, nsErr := kubectlTask("create", "namespace", namespace) + nsRes, nsErr := k8s.KubectlTask("create", "namespace", namespace) if nsErr != nil { return nsErr } @@ -83,14 +76,11 @@ func MakeInstallGrafana() *cobra.Command { } // download the chart - chartPath := path.Join(os.TempDir(), "charts") - err = fetchChart(chartPath, "stable/grafana", chartVersion, true) + err = helm.FetchChart("stable/grafana", chartVersion, true) if err != nil { return err } - outputPath := path.Join(chartPath, "grafana") - // define the values to override // due the missing arm support. datasource and dashboard sidecars are not possible overrides := map[string]string{ @@ -104,7 +94,7 @@ func MakeInstallGrafana() *cobra.Command { } // install the chart - err = helm3Upgrade(outputPath, "stable/grafana", namespace, + err = helm.Helm3Upgrade("stable/grafana", namespace, "values.yaml", chartVersion, overrides, diff --git a/cmd/apps/inletsoperator_app.go b/cmd/apps/inletsoperator_app.go index 308bb87f7..c56fec9cb 100644 --- a/cmd/apps/inletsoperator_app.go +++ b/cmd/apps/inletsoperator_app.go @@ -11,6 +11,8 @@ import ( "path" "strings" + "github.com/alexellis/arkade/pkg/k8s" + "github.com/alexellis/arkade/pkg" "github.com/alexellis/arkade/pkg/config" "github.com/alexellis/arkade/pkg/env" @@ -44,7 +46,7 @@ func MakeInstallInletsOperator() *cobra.Command { inletsOperator.Flags().StringArray("set", []string{}, "Use custom flags or override existing flags \n(example --set=image=org/repo:tag)") inletsOperator.RunE = func(command *cobra.Command, args []string) error { - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() wait, _ := command.Flags().GetBool("wait") if command.Flags().Changed("kubeconfig") { @@ -67,7 +69,7 @@ func MakeInstallInletsOperator() *cobra.Command { } } - arch := getNodeArchitecture() + arch := k8s.GetNodeArchitecture() fmt.Printf("Node architecture: %q\n", arch) userPath, err := config.InitUserDir() @@ -88,23 +90,15 @@ func MakeInstallInletsOperator() *cobra.Command { return err } - err = addHelmRepo("inlets", "https://inlets.github.io/inlets-operator/", helm3) + updateRepo, _ := inletsOperator.Flags().GetBool("update-repo") + err = helm.AddHelmRepo("inlets", "https://inlets.github.io/inlets-operator/", updateRepo, helm3) if err != nil { return err } - updateRepo, _ := inletsOperator.Flags().GetBool("update-repo") - - if updateRepo { - err = updateHelmRepos(helm3) - if err != nil { - return err - } - } - chartPath := path.Join(os.TempDir(), "charts") - err = fetchChart(chartPath, "inlets/inlets-operator", defaultVersion, helm3) + err = helm.FetchChart("inlets/inlets-operator", defaultVersion, helm3) if err != nil { return err } @@ -114,7 +108,7 @@ func MakeInstallInletsOperator() *cobra.Command { return err } - _, err = kubectlTask("apply", "-f", "https://raw.githubusercontent.com/inlets/inlets-operator/master/artifacts/crd.yaml") + _, err = k8s.KubectlTask("apply", "-f", "https://raw.githubusercontent.com/inlets/inlets-operator/master/artifacts/crd.yaml") if err != nil { return err } @@ -125,7 +119,7 @@ func MakeInstallInletsOperator() *cobra.Command { return fmt.Errorf(`--token-file is a required field for your cloud API token or service account JSON file`) } - res, err := kubectlTask("create", "secret", "generic", + res, err := k8s.KubectlTask("create", "secret", "generic", "inlets-access-key", "--namespace="+namespace, "--from-file", "inlets-access-key="+tokenFileName) @@ -141,7 +135,7 @@ func MakeInstallInletsOperator() *cobra.Command { secretKeyFile, _ := command.Flags().GetString("secret-key-file") if len(secretKeyFile) > 0 { - res, err := kubectlTask("create", "secret", "generic", + res, err := k8s.KubectlTask("create", "secret", "generic", "inlets-secret-key", "--namespace="+namespace, "--from-file", "inlets-secret-key="+secretKeyFile) @@ -181,9 +175,8 @@ func MakeInstallInletsOperator() *cobra.Command { } if helm3 { - outputPath := path.Join(chartPath, "inlets-operator") - err := helm3Upgrade(outputPath, "inlets/inlets-operator", + err := helm.Helm3Upgrade("inlets/inlets-operator", namespace, "values.yaml", "", overrides, wait) if err != nil { return err @@ -191,12 +184,12 @@ func MakeInstallInletsOperator() *cobra.Command { } else { outputPath := path.Join(chartPath, "inlets-operator/rendered") - err = templateChart(chartPath, "inlets-operator", namespace, outputPath, "values.yaml", overrides) + err = helm.TemplateChart(chartPath, "inlets-operator", namespace, outputPath, "values.yaml", overrides) if err != nil { return err } - applyRes, applyErr := kubectlTask("apply", "-R", "-f", outputPath) + applyRes, applyErr := k8s.KubectlTask("apply", "-R", "-f", outputPath) if applyErr != nil { return applyErr } diff --git a/cmd/apps/istio_app.go b/cmd/apps/istio_app.go index eb7b5e11b..fa06be0ef 100644 --- a/cmd/apps/istio_app.go +++ b/cmd/apps/istio_app.go @@ -11,6 +11,8 @@ import ( "path" "time" + "github.com/alexellis/arkade/pkg/k8s" + "github.com/alexellis/arkade/pkg" "github.com/alexellis/arkade/pkg/config" "github.com/alexellis/arkade/pkg/env" @@ -35,7 +37,7 @@ func MakeInstallIstio() *cobra.Command { "Use custom flags or override existing flags \n(example --set=prometheus.enabled=false)") istio.RunE = func(command *cobra.Command, args []string) error { - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() wait, _ := command.Flags().GetBool("wait") if command.Flags().Changed("kubeconfig") { @@ -50,7 +52,7 @@ func MakeInstallIstio() *cobra.Command { return fmt.Errorf(`to override the "istio-system" namespace, install Istio via helm manually`) } - arch := getNodeArchitecture() + arch := k8s.GetNodeArchitecture() fmt.Printf("Node architecture: %q\n", arch) if arch != IntelArch { @@ -79,29 +81,25 @@ func MakeInstallIstio() *cobra.Command { istioVer := "1.4.5" - err = addHelmRepo("istio", "https://storage.googleapis.com/istio-release/releases/"+istioVer+"/charts", helm3) - if err != nil { - return fmt.Errorf("unable to add repo %s", err) - } - updateRepo, _ := istio.Flags().GetBool("update-repo") - if updateRepo { - err = updateHelmRepos(helm3) - if err != nil { - return fmt.Errorf("unable to update repos %s", err) - } + err = helm.AddHelmRepo( + "istio", + fmt.Sprintf("https://storage.googleapis.com/istio-release/releases/%s/charts", istioVer), + updateRepo, + helm3, + ) + if err != nil { + return fmt.Errorf("unable to add repo %s", err) } - _, err = kubectlTask("create", "ns", "istio-system") + _, err = k8s.KubectlTask("create", "ns", "istio-system") if err != nil { return fmt.Errorf("unable to create namespace %s", err) } - chartPath := path.Join(os.TempDir(), "charts") - - err = fetchChart(chartPath, "istio/istio", defaultVersion, helm3) + err = helm.FetchChart("istio/istio", defaultVersion, helm3) if err != nil { return fmt.Errorf("unable fetch chart %s", err) @@ -114,11 +112,9 @@ func MakeInstallIstio() *cobra.Command { return writeErr } - outputPath := path.Join(chartPath, "istio") - if initIstio, _ := command.Flags().GetBool("init"); initIstio { // Waiting for the crds to appear - err = helm3Upgrade(outputPath, "istio/istio-init", namespace, "", defaultVersion, overrides, true) + err = helm.Helm3Upgrade("istio/istio-init", namespace, "", defaultVersion, overrides, true) if err != nil { return fmt.Errorf("unable to istio-init install chart with helm %s", err) } @@ -126,7 +122,7 @@ func MakeInstallIstio() *cobra.Command { fmt.Printf("Waiting for Istio init jobs to create CRDs\n") - _, err = kubectlTask("wait", "-n", "istio-system", "--for=condition=complete", "job", "--all") + _, err = k8s.KubectlTask("wait", "-n", "istio-system", "--for=condition=complete", "job", "--all") if err != nil { fmt.Printf("error waiting for init jobs") } @@ -145,7 +141,7 @@ func MakeInstallIstio() *cobra.Command { return err } - err = helm3Upgrade(outputPath, "istio/istio", namespace, valuesFile, defaultVersion, overrides, wait) + err = helm.Helm3Upgrade("istio/istio", namespace, valuesFile, defaultVersion, overrides, wait) if err != nil { return fmt.Errorf("unable to istio install chart with helm %s", err) } diff --git a/cmd/apps/jenkins_app.go b/cmd/apps/jenkins_app.go index 00d75719d..8a5592a49 100644 --- a/cmd/apps/jenkins_app.go +++ b/cmd/apps/jenkins_app.go @@ -11,6 +11,8 @@ import ( "strconv" "strings" + "github.com/alexellis/arkade/pkg/k8s" + "github.com/alexellis/arkade/pkg" "github.com/alexellis/arkade/pkg/config" "github.com/alexellis/arkade/pkg/env" @@ -34,7 +36,7 @@ func MakeInstallJenkins() *cobra.Command { "Use custom flags or override existing flags \n(example --set persistence.enabled=true)") jenkins.RunE = func(command *cobra.Command, args []string) error { - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() wait, _ := command.Flags().GetBool("wait") if command.Flags().Changed("kubeconfig") { @@ -44,7 +46,7 @@ func MakeInstallJenkins() *cobra.Command { fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath) - arch := getNodeArchitecture() + arch := k8s.GetNodeArchitecture() fmt.Printf("Node architecture: %q\n", arch) if arch != IntelArch { @@ -70,20 +72,12 @@ func MakeInstallJenkins() *cobra.Command { return err } - err = addHelmRepo("stable", "https://kubernetes-charts.storage.googleapis.com", true) + err = helm.AddHelmRepo("stable", "https://kubernetes-charts.storage.googleapis.com", updateRepo, true) if err != nil { return err } - if updateRepo { - err = updateHelmRepos(true) - if err != nil { - return err - } - } - - chartPath := path.Join(os.TempDir(), "charts") - err = fetchChart(chartPath, "stable/jenkins", defaultVersion, true) + err = helm.FetchChart("stable/jenkins", defaultVersion, true) if err != nil { return err @@ -103,9 +97,7 @@ func MakeInstallJenkins() *cobra.Command { return err } - outputPath := path.Join(chartPath, "jenkins") - - err = helm3Upgrade(outputPath, "stable/jenkins", ns, + err = helm.Helm3Upgrade("stable/jenkins", ns, "values.yaml", defaultVersion, overrides, @@ -122,7 +114,7 @@ func MakeInstallJenkins() *cobra.Command { return jenkins } -var JenkinsInfoMsg = `# Jenkins can take several minutes to install, check its status with: +const JenkinsInfoMsg = `# Jenkins can take several minutes to install, check its status with: kubectl rollout status deploy/jenkins --timeout 10m # Get the Jenkins credentials: diff --git a/cmd/apps/kafkaconnector_app.go b/cmd/apps/kafkaconnector_app.go index a37a4e12b..aaf48a0d1 100644 --- a/cmd/apps/kafkaconnector_app.go +++ b/cmd/apps/kafkaconnector_app.go @@ -9,6 +9,8 @@ import ( "os" "path" + "github.com/alexellis/arkade/pkg/k8s" + "github.com/alexellis/arkade/pkg" "github.com/alexellis/arkade/pkg/config" "github.com/alexellis/arkade/pkg/env" @@ -33,7 +35,7 @@ func MakeInstallKafkaConnector() *cobra.Command { "Use custom flags or override existing flags \n(example --set key=value)") command.RunE = func(command *cobra.Command, args []string) error { - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() if command.Flags().Changed("kubeconfig") { kubeConfigPath, _ = command.Flags().GetString("kubeconfig") @@ -66,20 +68,13 @@ func MakeInstallKafkaConnector() *cobra.Command { return err } - err = addHelmRepo("openfaas", "https://openfaas.github.io/faas-netes/", false) + err = helm.AddHelmRepo("openfaas", "https://openfaas.github.io/faas-netes/", updateRepo, false) if err != nil { return err } - if updateRepo { - err = updateHelmRepos(false) - if err != nil { - return err - } - } - chartPath := path.Join(os.TempDir(), "charts") - err = fetchChart(chartPath, "openfaas/kafka-connector", defaultVersion, false) + err = helm.FetchChart("openfaas/kafka-connector", defaultVersion, false) if err != nil { return err @@ -109,7 +104,7 @@ func MakeInstallKafkaConnector() *cobra.Command { return err } - arch := getNodeArchitecture() + arch := k8s.GetNodeArchitecture() fmt.Printf("Node architecture: %q\n", arch) if arch != IntelArch { return fmt.Errorf(OnlyIntelArch) @@ -120,7 +115,7 @@ func MakeInstallKafkaConnector() *cobra.Command { outputPath := path.Join(chartPath, "kafka-connector/rendered") ns := namespace - err = templateChart(chartPath, + err = helm.TemplateChart(chartPath, "kafka-connector", ns, outputPath, @@ -131,7 +126,7 @@ func MakeInstallKafkaConnector() *cobra.Command { return err } - err = kubectl("apply", "-R", "-f", outputPath) + err = k8s.Kubectl("apply", "-R", "-f", outputPath) if err != nil { return err diff --git a/cmd/apps/kube_state_metrics.go b/cmd/apps/kube_state_metrics.go index a29079b5f..a26badfe8 100644 --- a/cmd/apps/kube_state_metrics.go +++ b/cmd/apps/kube_state_metrics.go @@ -10,6 +10,8 @@ import ( "path" "strings" + "github.com/alexellis/arkade/pkg/k8s" + "github.com/alexellis/arkade/pkg" "github.com/alexellis/arkade/pkg/config" "github.com/alexellis/arkade/pkg/env" @@ -32,7 +34,7 @@ func MakeInstallKubeStateMetrics() *cobra.Command { kubeStateMetrics.RunE = func(command *cobra.Command, args []string) error { wait, _ := command.Flags().GetBool("wait") - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() if command.Flags().Changed("kubeconfig") { kubeConfigPath, _ = command.Flags().GetString("kubeconfig") @@ -46,7 +48,7 @@ func MakeInstallKubeStateMetrics() *cobra.Command { } namespace, _ := command.Flags().GetString("namespace") - arch := getNodeArchitecture() + arch := k8s.GetNodeArchitecture() fmt.Printf("Node architecture: %q\n", arch) if arch != IntelArch { @@ -69,13 +71,13 @@ func MakeInstallKubeStateMetrics() *cobra.Command { return err } - err = updateHelmRepos(helm3) + err = helm.UpdateHelmRepos(helm3) if err != nil { return err } chartPath := path.Join(os.TempDir(), "charts") - err = fetchChart(chartPath, "stable/kube-state-metrics", defaultVersion, helm3) + err = helm.FetchChart("stable/kube-state-metrics", defaultVersion, helm3) if err != nil { return err @@ -98,9 +100,7 @@ func MakeInstallKubeStateMetrics() *cobra.Command { fmt.Println("Chart path: ", chartPath) if helm3 { - outputPath := path.Join(chartPath, "kube-state-metrics") - - err := helm3Upgrade(outputPath, "stable/kube-state-metrics", namespace, + err := helm.Helm3Upgrade("stable/kube-state-metrics", namespace, "values.yaml", defaultVersion, setMap, @@ -113,7 +113,7 @@ func MakeInstallKubeStateMetrics() *cobra.Command { } else { outputPath := path.Join(chartPath, "kube-state-metrics/rendered") - err = templateChart(chartPath, + err = helm.TemplateChart(chartPath, "kube-state-metrics", namespace, outputPath, @@ -124,7 +124,7 @@ func MakeInstallKubeStateMetrics() *cobra.Command { return err } - applyRes, applyErr := kubectlTask("apply", "-n", namespace, "-R", "-f", outputPath) + applyRes, applyErr := k8s.KubectlTask("apply", "-n", namespace, "-R", "-f", outputPath) if applyErr != nil { return applyErr } diff --git a/cmd/apps/kubernetes_dashboard_app.go b/cmd/apps/kubernetes_dashboard_app.go index 5db3faf19..fe80178fb 100644 --- a/cmd/apps/kubernetes_dashboard_app.go +++ b/cmd/apps/kubernetes_dashboard_app.go @@ -6,6 +6,9 @@ package apps import ( "fmt" + "github.com/alexellis/arkade/pkg/config" + "github.com/alexellis/arkade/pkg/k8s" + "github.com/alexellis/arkade/pkg" "github.com/spf13/cobra" @@ -21,7 +24,7 @@ func MakeInstallKubernetesDashboard() *cobra.Command { } kubeDashboard.RunE = func(command *cobra.Command, args []string) error { - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() if command.Flags().Changed("kubeconfig") { kubeConfigPath, _ = command.Flags().GetString("kubeconfig") @@ -29,10 +32,10 @@ func MakeInstallKubernetesDashboard() *cobra.Command { fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath) - arch := getNodeArchitecture() + arch := k8s.GetNodeArchitecture() fmt.Printf("Node architecture: %q\n", arch) - _, err := kubectlTask("apply", "-f", + _, err := k8s.KubectlTask("apply", "-f", "https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-rc5/aio/deploy/recommended.yaml") if err != nil { return err diff --git a/cmd/apps/kubernetes_exec.go b/cmd/apps/kubernetes_exec.go deleted file mode 100644 index a7d0f0d89..000000000 --- a/cmd/apps/kubernetes_exec.go +++ /dev/null @@ -1,260 +0,0 @@ -// Copyright (c) arkade author(s) 2020. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -package apps - -import ( - "fmt" - "log" - "os" - "path" - "strings" - - "github.com/alexellis/arkade/pkg/env" - execute "github.com/alexellis/go-execute/pkg/v1" -) - -func fetchChart(path, chart, version string, helm3 bool) error { - versionStr := "" - - if len(version) > 0 { - // Issue in helm where adding a space to the command makes it think that it's another chart of " " we want to template, - // So we add the space before version here rather than on the command - versionStr = " --version " + version - } - subdir := "" - if helm3 { - subdir = "helm3" - } - - // First remove any existing folder - os.RemoveAll(path) - - mkErr := os.MkdirAll(path, 0700) - - if mkErr != nil { - return mkErr - } - - task := execute.ExecTask{ - Command: fmt.Sprintf("%s fetch %s --untar=true --untardir %s%s", env.LocalBinary("helm", subdir), chart, path, versionStr), - Env: os.Environ(), - StreamStdio: true, - } - res, err := task.Execute() - - if err != nil { - return err - } - - if res.ExitCode != 0 { - return fmt.Errorf("exit code %d", res.ExitCode) - } - return nil -} - -func getNodeArchitecture() string { - res, _ := kubectlTask("get", "nodes", `--output`, `jsonpath={range $.items[0]}{.status.nodeInfo.architecture}`) - - arch := strings.TrimSpace(string(res.Stdout)) - - return arch -} - -func helm3Upgrade(basePath, chart, namespace, values, version string, overrides map[string]string, wait bool) error { - - chartName := chart - if index := strings.Index(chartName, "/"); index > -1 { - chartName = chartName[index+1:] - } - - chartRoot := basePath - - args := []string{"upgrade", "--install", chartName, chart, "--namespace", namespace} - if len(version) > 0 { - args = append(args, "--version", version) - } - - if wait { - args = append(args, "--wait") - } - - fmt.Println("VALUES", values) - if len(values) > 0 { - args = append(args, "--values") - if !strings.HasPrefix(values, "/") { - args = append(args, path.Join(chartRoot, values)) - } else { - args = append(args, values) - } - } - - for k, v := range overrides { - args = append(args, "--set") - args = append(args, fmt.Sprintf("%s=%s", k, v)) - } - - task := execute.ExecTask{ - Command: env.LocalBinary("helm", "helm3"), - Args: args, - Env: os.Environ(), - Cwd: basePath, - StreamStdio: true, - } - - fmt.Printf("Command: %s %s\n", task.Command, task.Args) - res, err := task.Execute() - - if err != nil { - return err - } - - if res.ExitCode != 0 { - return fmt.Errorf("exit code %d, stderr: %s", res.ExitCode, res.Stderr) - } - - if len(res.Stderr) > 0 { - log.Printf("stderr: %s\n", res.Stderr) - } - - return nil -} - -func templateChart(basePath, chart, namespace, outputPath, values string, overrides map[string]string) error { - - rmErr := os.RemoveAll(outputPath) - - if rmErr != nil { - log.Printf("Error cleaning up: %s, %s\n", outputPath, rmErr.Error()) - } - - mkErr := os.MkdirAll(outputPath, 0700) - if mkErr != nil { - return mkErr - } - - overridesStr := "" - for k, v := range overrides { - overridesStr += fmt.Sprintf(" --set %s=%s", k, v) - } - - chartRoot := path.Join(basePath, chart) - - valuesStr := "" - if len(values) > 0 { - valuesStr = "--values " + path.Join(chartRoot, values) - } - - task := execute.ExecTask{ - Command: fmt.Sprintf("%s template %s --name %s --namespace %s --output-dir %s %s %s", - env.LocalBinary("helm", ""), chart, chart, namespace, outputPath, valuesStr, overridesStr), - Env: os.Environ(), - Cwd: basePath, - StreamStdio: true, - } - - res, err := task.Execute() - - if err != nil { - return err - } - - if res.ExitCode != 0 { - return fmt.Errorf("exit code %d, stderr: %s", res.ExitCode, res.Stderr) - } - - if len(res.Stderr) > 0 { - log.Printf("stderr: %s\n", res.Stderr) - } - - return nil -} - -func addHelmRepo(name, url string, helm3 bool) error { - subdir := "" - if helm3 { - subdir = "helm3" - } - - task := execute.ExecTask{ - Command: fmt.Sprintf("%s repo add %s %s", env.LocalBinary("helm", subdir), name, url), - Env: os.Environ(), - StreamStdio: true, - } - res, err := task.Execute() - - if err != nil { - return err - } - - if res.ExitCode != 0 { - return fmt.Errorf("exit code %d", res.ExitCode) - } - return nil -} - -func updateHelmRepos(helm3 bool) error { - subdir := "" - if helm3 { - subdir = "helm3" - } - task := execute.ExecTask{ - Command: fmt.Sprintf("%s repo update", env.LocalBinary("helm", subdir)), - Env: os.Environ(), - StreamStdio: true, - } - - res, err := task.Execute() - - if err != nil { - return err - } - - if res.ExitCode != 0 { - return fmt.Errorf("exit code %d", res.ExitCode) - } - return nil -} - -func kubectlTask(parts ...string) (execute.ExecResult, error) { - task := execute.ExecTask{ - Command: "kubectl", - Args: parts, - StreamStdio: false, - } - - res, err := task.Execute() - - return res, err -} - -func kubectl(parts ...string) error { - task := execute.ExecTask{ - Command: "kubectl", - Args: parts, - StreamStdio: true, - } - - res, err := task.Execute() - - if err != nil { - return err - } - - if res.ExitCode != 0 { - return fmt.Errorf("kubectl exit code %d, stderr: %s", - res.ExitCode, - res.Stderr) - } - return nil -} - -func getDefaultKubeconfig() string { - kubeConfigPath := path.Join(os.Getenv("HOME"), ".kube/config") - - if val, ok := os.LookupEnv("KUBECONFIG"); ok { - kubeConfigPath = val - } - - return kubeConfigPath -} diff --git a/cmd/apps/linkerd_app.go b/cmd/apps/linkerd_app.go index ce086e2c4..013ee7aad 100644 --- a/cmd/apps/linkerd_app.go +++ b/cmd/apps/linkerd_app.go @@ -15,6 +15,8 @@ import ( "path" "strings" + "github.com/alexellis/arkade/pkg/k8s" + "github.com/alexellis/arkade/pkg" "github.com/alexellis/arkade/pkg/config" @@ -35,14 +37,14 @@ func MakeInstallLinkerd() *cobra.Command { } linkerd.RunE = func(command *cobra.Command, args []string) error { - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() if command.Flags().Changed("kubeconfig") { kubeConfigPath, _ = command.Flags().GetString("kubeconfig") } fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath) - arch := getNodeArchitecture() + arch := k8s.GetNodeArchitecture() fmt.Printf("Node architecture: %q\n", arch) if arch != IntelArch { return fmt.Errorf(OnlyIntelArch) @@ -86,7 +88,7 @@ func MakeInstallLinkerd() *cobra.Command { } w.Flush() - err = kubectl("apply", "-R", "-f", file.Name()) + err = k8s.Kubectl("apply", "-R", "-f", file.Name()) if err != nil { return err } diff --git a/cmd/apps/loki_app.go b/cmd/apps/loki_app.go new file mode 100644 index 000000000..1c9d08de0 --- /dev/null +++ b/cmd/apps/loki_app.go @@ -0,0 +1,123 @@ +// Copyright (c) arkade author(s) 2020. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +package apps + +import ( + "log" + "os" + "path" + + "github.com/alexellis/arkade/pkg" + "github.com/alexellis/arkade/pkg/apps" + "github.com/alexellis/arkade/pkg/config" + "github.com/alexellis/arkade/pkg/env" + "github.com/alexellis/arkade/pkg/helm" + "github.com/alexellis/arkade/pkg/types" + "github.com/spf13/cobra" +) + +func MakeInstallLoki() *cobra.Command { + var lokiApp = &cobra.Command{ + Use: "loki", + Short: "Install Loki for monitoring and tracing", + Long: "Install Loki, part of the Grafana products for Logging and Tracing", + Example: "arkade install loki", + SilenceUsage: true, + } + + lokiApp.Flags().StringP("namespace", "n", "default", "The namespace to install loki (default: default") + lokiApp.Flags().Bool("update-repo", true, "Update the helm repo") + lokiApp.Flags().StringArray("set", []string{}, "Use custom flags or override existing flags \n(example --set grafana.enabled=true)") + lokiApp.Flags().Bool("grafana", false, "Install Grafana alongside Loki (default: false)") + + lokiApp.RunE = func(command *cobra.Command, args []string) error { + helm3 := true + + namespace, _ := lokiApp.Flags().GetString("namespace") + userPath, err := config.InitUserDir() + if err != nil { + return err + } + + clientArch, clientOS := env.GetClientArch() + + log.Printf("Client: %s, %s\n", clientArch, clientOS) + + log.Printf("User dir established as: %s\n", userPath) + + if err := os.Setenv("HELM_HOME", path.Join(userPath, ".helm")); err != nil { + return err + } + + installGrafana, _ := lokiApp.Flags().GetBool("grafana") + + overrides := map[string]string{} + + if installGrafana { + overrides["grafana.enabled"] = "true" + } + + customFlags, _ := command.Flags().GetStringArray("set") + + if err := config.MergeFlags(overrides, customFlags); err != nil { + return err + } + + lokiOptions := types.DefaultInstallOptions(). + WithNamespace(namespace). + WithHelmPath(path.Join(userPath, ".helm")). + WithHelmRepo("loki/loki-stack"). + WithHelmURL("https://grafana.github.io/loki/charts"). + WithOverrides(overrides) + + if command.Flags().Changed("kubeconfig") { + kubeconfigPath, _ := command.Flags().GetString("kubeconfig") + lokiOptions.WithKubeconfigPath(kubeconfigPath) + } + + os.Setenv("HELM_HOME", path.Join(userPath, ".helm")) + + _, err = helm.TryDownloadHelm(userPath, clientArch, clientOS, helm3) + if err != nil { + return err + } + + _, err = apps.MakeInstallLoki(lokiOptions) + if err != nil { + return err + } + + println(lokiInstallMsg) + return nil + } + + return lokiApp +} + +const LokiInfoMsg = `# Get started with loki here: +# https://github.com/grafana/loki/blob/master/docs/README.md + +# See how to integrate loki with Grafana here +# https://github.com/grafana/loki/blob/master/docs/getting-started/grafana.md + +# Check loki's logs with: + +kubectl logs svc/loki-stack + +kubectl logs svc/loki-stack-headless + + +# If you installed with Grafana you can access the dashboard with the username "admin" and password shown below + # To get password + kubectl get secret loki-stack-grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo + + # Forward traffic to your localhost + kubectl port-forward service/loki-stack-grafana 3000:80 + +` + +const lokiInstallMsg = `======================================================================= += loki has been installed. = +=======================================================================` + + "\n\n" + LokiInfoMsg + "\n\n" + pkg.ThanksForUsing diff --git a/cmd/apps/metricsserver_app.go b/cmd/apps/metricsserver_app.go index ff2b2bd95..5baab36b1 100644 --- a/cmd/apps/metricsserver_app.go +++ b/cmd/apps/metricsserver_app.go @@ -9,6 +9,8 @@ import ( "os" "path" + "github.com/alexellis/arkade/pkg/k8s" + "github.com/alexellis/arkade/pkg" "github.com/alexellis/arkade/pkg/config" "github.com/alexellis/arkade/pkg/env" @@ -30,7 +32,7 @@ func MakeInstallMetricsServer() *cobra.Command { metricsServer.RunE = func(command *cobra.Command, args []string) error { wait, _ := command.Flags().GetBool("wait") - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() if command.Flags().Changed("kubeconfig") { kubeConfigPath, _ = command.Flags().GetString("kubeconfig") @@ -48,7 +50,7 @@ func MakeInstallMetricsServer() *cobra.Command { return fmt.Errorf(`to override the "kube-system", install via tiller`) } - arch := getNodeArchitecture() + arch := k8s.GetNodeArchitecture() fmt.Printf("Node architecture: %q\n", arch) helm3, _ := command.Flags().GetBool("helm3") @@ -67,13 +69,13 @@ func MakeInstallMetricsServer() *cobra.Command { return err } - err = updateHelmRepos(helm3) + err = helm.UpdateHelmRepos(helm3) if err != nil { return err } chartPath := path.Join(os.TempDir(), "charts") - err = fetchChart(chartPath, "stable/metrics-server", defaultVersion, helm3) + err = helm.FetchChart("stable/metrics-server", defaultVersion, helm3) if err != nil { return err @@ -93,9 +95,7 @@ func MakeInstallMetricsServer() *cobra.Command { fmt.Println("Chart path: ", chartPath) if helm3 { - outputPath := path.Join(chartPath, "metrics-server") - - err := helm3Upgrade(outputPath, "stable/metrics-server", namespace, + err := helm.Helm3Upgrade("stable/metrics-server", namespace, "values.yaml", defaultVersion, overrides, @@ -108,7 +108,7 @@ func MakeInstallMetricsServer() *cobra.Command { } else { outputPath := path.Join(chartPath, "metrics-server/rendered") - err = templateChart(chartPath, + err = helm.TemplateChart(chartPath, "metrics-server", namespace, outputPath, @@ -119,7 +119,7 @@ func MakeInstallMetricsServer() *cobra.Command { return err } - applyRes, applyErr := kubectlTask("apply", "-n", namespace, "-R", "-f", outputPath) + applyRes, applyErr := k8s.KubectlTask("apply", "-n", namespace, "-R", "-f", outputPath) if applyErr != nil { return applyErr } diff --git a/cmd/apps/minio_app.go b/cmd/apps/minio_app.go index ffca2dfa8..6c59ee4ad 100644 --- a/cmd/apps/minio_app.go +++ b/cmd/apps/minio_app.go @@ -11,6 +11,8 @@ import ( "strconv" "strings" + "github.com/alexellis/arkade/pkg/k8s" + "github.com/alexellis/arkade/pkg" "github.com/alexellis/arkade/pkg/config" "github.com/alexellis/arkade/pkg/env" @@ -39,7 +41,7 @@ func MakeInstallMinio() *cobra.Command { "Use custom flags or override existing flags \n(example --set persistence.enabled=true)") minio.RunE = func(command *cobra.Command, args []string) error { - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() wait, _ := command.Flags().GetBool("wait") if command.Flags().Changed("kubeconfig") { @@ -54,7 +56,7 @@ func MakeInstallMinio() *cobra.Command { fmt.Println("Using helm3") } - arch := getNodeArchitecture() + arch := k8s.GetNodeArchitecture() fmt.Printf("Node architecture: %q\n", arch) if arch != IntelArch { @@ -84,20 +86,13 @@ func MakeInstallMinio() *cobra.Command { return err } - err = addHelmRepo("stable", "https://kubernetes-charts.storage.googleapis.com", helm3) + err = helm.AddHelmRepo("stable", "https://kubernetes-charts.storage.googleapis.com", updateRepo, helm3) if err != nil { return err } - if updateRepo { - err = updateHelmRepos(helm3) - if err != nil { - return err - } - } - chartPath := path.Join(os.TempDir(), "charts") - err = fetchChart(chartPath, "stable/minio", defaultVersion, helm3) + err = helm.FetchChart("stable/minio", defaultVersion, helm3) if err != nil { return err @@ -148,9 +143,7 @@ func MakeInstallMinio() *cobra.Command { } if helm3 { - outputPath := path.Join(chartPath, "minio") - - err := helm3Upgrade(outputPath, "stable/minio", ns, + err := helm.Helm3Upgrade("stable/minio", ns, "values.yaml", defaultVersion, overrides, @@ -163,7 +156,7 @@ func MakeInstallMinio() *cobra.Command { } else { outputPath := path.Join(chartPath, "minio/rendered") - err = templateChart(chartPath, + err = helm.TemplateChart(chartPath, "minio", ns, outputPath, @@ -174,7 +167,7 @@ func MakeInstallMinio() *cobra.Command { return err } - err = kubectl("apply", "-R", "-f", outputPath) + err = k8s.Kubectl("apply", "-R", "-f", outputPath) if err != nil { return err diff --git a/cmd/apps/mongodb_app.go b/cmd/apps/mongodb_app.go index 878773b10..3148debea 100644 --- a/cmd/apps/mongodb_app.go +++ b/cmd/apps/mongodb_app.go @@ -10,6 +10,8 @@ import ( "path" "strconv" + "github.com/alexellis/arkade/pkg/k8s" + "github.com/alexellis/arkade/pkg" "github.com/alexellis/arkade/pkg/config" "github.com/alexellis/arkade/pkg/env" @@ -34,7 +36,7 @@ func MakeInstallMongoDB() *cobra.Command { command.RunE = func(command *cobra.Command, args []string) error { wait, _ := command.Flags().GetBool("wait") - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() if command.Flags().Changed("kubeconfig") { kubeConfigPath, _ = command.Flags().GetString("kubeconfig") @@ -44,7 +46,7 @@ func MakeInstallMongoDB() *cobra.Command { namespace, _ := command.Flags().GetString("namespace") - arch := getNodeArchitecture() + arch := k8s.GetNodeArchitecture() fmt.Printf("Node architecture: %q\n", arch) if arch != IntelArch { @@ -73,23 +75,13 @@ func MakeInstallMongoDB() *cobra.Command { return err } - err = addHelmRepo("stable", "https://kubernetes-charts.storage.googleapis.com/", helm3) + updateRepo, _ := command.Flags().GetBool("update-repo") + err = helm.AddHelmRepo("stable", "https://kubernetes-charts.storage.googleapis.com/", updateRepo, helm3) if err != nil { return fmt.Errorf("unable to add repo %s", err) } - updateRepo, _ := command.Flags().GetBool("update-repo") - - if updateRepo { - err = updateHelmRepos(helm3) - if err != nil { - return fmt.Errorf("unable to update repos %s", err) - } - } - - chartPath := path.Join(os.TempDir(), "charts") - - err = fetchChart(chartPath, "stable/mongodb", defaultVersion, helm3) + err = helm.FetchChart("stable/mongodb", defaultVersion, helm3) if err != nil { return fmt.Errorf("unable fetch chart %s", err) @@ -99,8 +91,6 @@ func MakeInstallMongoDB() *cobra.Command { overrides["persistence.enabled"] = strconv.FormatBool(persistence) - outputPath := path.Join(chartPath, "mongodb") - customFlags, err := command.Flags().GetStringArray("set") if err != nil { return fmt.Errorf("error with --set usage: %s", err) @@ -110,7 +100,7 @@ func MakeInstallMongoDB() *cobra.Command { return err } - err = helm3Upgrade(outputPath, "stable/mongodb", + err = helm.Helm3Upgrade("stable/mongodb", namespace, "values.yaml", defaultVersion, overrides, wait) if err != nil { return fmt.Errorf("unable to mongodb chart with helm %s", err) diff --git a/cmd/apps/nginx_app.go b/cmd/apps/nginx_app.go index fb4e54c89..d149866a6 100644 --- a/cmd/apps/nginx_app.go +++ b/cmd/apps/nginx_app.go @@ -9,6 +9,8 @@ import ( "os" "path" + "github.com/alexellis/arkade/pkg/k8s" + "github.com/alexellis/arkade/pkg" "github.com/alexellis/arkade/pkg/config" "github.com/alexellis/arkade/pkg/env" @@ -34,7 +36,7 @@ flag and the ingress-nginx docs for more info`, nginx.Flags().Bool("helm3", true, "Use helm3, if set to false uses helm2") nginx.RunE = func(command *cobra.Command, args []string) error { - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() wait, _ := command.Flags().GetBool("wait") if command.Flags().Changed("kubeconfig") { @@ -72,20 +74,13 @@ flag and the ingress-nginx docs for more info`, return err } - err = addHelmRepo("ingress-nginx", "https://kubernetes.github.io/ingress-nginx", helm3) + err = helm.AddHelmRepo("ingress-nginx", "https://kubernetes.github.io/ingress-nginx", updateRepo, helm3) if err != nil { return err } - if updateRepo { - err = updateHelmRepos(helm3) - if err != nil { - return err - } - } - chartPath := path.Join(os.TempDir(), "charts") - err = fetchChart(chartPath, "ingress-nginx/ingress-nginx", defaultVersion, helm3) + err = helm.FetchChart("ingress-nginx/ingress-nginx", defaultVersion, helm3) if err != nil { return err @@ -110,9 +105,7 @@ flag and the ingress-nginx docs for more info`, ns := "default" if helm3 { - outputPath := path.Join(chartPath, "ingress-nginx") - - err := helm3Upgrade(outputPath, "ingress-nginx/ingress-nginx", ns, + err := helm.Helm3Upgrade("ingress-nginx/ingress-nginx", ns, "values.yaml", defaultVersion, overrides, @@ -124,7 +117,7 @@ flag and the ingress-nginx docs for more info`, } else { outputPath := path.Join(chartPath, "ingress-nginx/rendered") - err = templateChart(chartPath, + err = helm.TemplateChart(chartPath, "ingress-nginx", ns, outputPath, @@ -135,7 +128,7 @@ flag and the ingress-nginx docs for more info`, return err } - err = kubectl("apply", "-R", "-f", outputPath) + err = k8s.Kubectl("apply", "-R", "-f", outputPath) if err != nil { return err diff --git a/cmd/apps/openfaas_app.go b/cmd/apps/openfaas_app.go index b15893f9f..e78082940 100644 --- a/cmd/apps/openfaas_app.go +++ b/cmd/apps/openfaas_app.go @@ -11,6 +11,8 @@ import ( "strconv" "strings" + "github.com/alexellis/arkade/pkg/k8s" + "github.com/alexellis/arkade/pkg" "github.com/alexellis/arkade/pkg/config" "github.com/alexellis/arkade/pkg/env" @@ -54,7 +56,7 @@ func MakeInstallOpenFaaS() *cobra.Command { openfaas.Flags().StringArray("set", []string{}, "Use custom flags or override existing flags \n(example --set=gateway.replicas=2)") openfaas.RunE = func(command *cobra.Command, args []string) error { - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() wait, _ := command.Flags().GetBool("wait") if command.Flags().Changed("kubeconfig") { kubeConfigPath, _ = command.Flags().GetString("kubeconfig") @@ -73,7 +75,7 @@ func MakeInstallOpenFaaS() *cobra.Command { return fmt.Errorf(`to override the "openfaas", install OpenFaaS via helm manually`) } - arch := getNodeArchitecture() + arch := k8s.GetNodeArchitecture() fmt.Printf("Node architecture: %q\n", arch) valuesSuffix := getValuesSuffix(arch) @@ -93,25 +95,17 @@ func MakeInstallOpenFaaS() *cobra.Command { return err } - err = addHelmRepo("openfaas", "https://openfaas.github.io/faas-netes/", helm3) + updateRepo, _ := openfaas.Flags().GetBool("update-repo") + err = helm.AddHelmRepo("openfaas", "https://openfaas.github.io/faas-netes/", updateRepo, helm3) if err != nil { return err } - updateRepo, _ := openfaas.Flags().GetBool("update-repo") - - if updateRepo { - err = updateHelmRepos(helm3) - if err != nil { - return err - } - } - if err != nil { return err } - _, err = kubectlTask("apply", "-f", + _, err = k8s.KubectlTask("apply", "-f", "https://raw.githubusercontent.com/openfaas/faas-netes/master/namespaces.yml") if err != nil { @@ -128,7 +122,7 @@ func MakeInstallOpenFaaS() *cobra.Command { } } - res, secretErr := kubectlTask("-n", namespace, "create", "secret", "generic", + res, secretErr := k8s.KubectlTask("-n", namespace, "create", "secret", "generic", "basic-auth", "--from-literal=basic-auth-user=admin", `--from-literal=basic-auth-password=`+pass) @@ -143,7 +137,7 @@ func MakeInstallOpenFaaS() *cobra.Command { chartPath := path.Join(os.TempDir(), "charts") - err = fetchChart(chartPath, "openfaas/openfaas", defaultVersion, helm3) + err = helm.FetchChart("openfaas/openfaas", defaultVersion, helm3) if err != nil { return err } @@ -212,9 +206,7 @@ func MakeInstallOpenFaaS() *cobra.Command { } if helm3 { - outputPath := path.Join(chartPath, "openfaas") - - err := helm3Upgrade(outputPath, "openfaas/openfaas", namespace, + err := helm.Helm3Upgrade("openfaas/openfaas", namespace, "values"+valuesSuffix+".yaml", "", overrides, @@ -226,7 +218,7 @@ func MakeInstallOpenFaaS() *cobra.Command { } else { outputPath := path.Join(chartPath, "openfaas/rendered") - err = templateChart(chartPath, "openfaas", + err = helm.TemplateChart(chartPath, "openfaas", namespace, outputPath, "values"+valuesSuffix+".yaml", @@ -236,7 +228,7 @@ func MakeInstallOpenFaaS() *cobra.Command { return err } - applyRes, applyErr := kubectlTask("apply", "-R", "-f", outputPath) + applyRes, applyErr := k8s.KubectlTask("apply", "-R", "-f", outputPath) if applyErr != nil { return applyErr } diff --git a/cmd/apps/openfaas_app_test.go b/cmd/apps/openfaas_app_test.go index ff77108bd..d24d5fe5c 100644 --- a/cmd/apps/openfaas_app_test.go +++ b/cmd/apps/openfaas_app_test.go @@ -4,9 +4,6 @@ package apps import ( - "fmt" - "reflect" - "strings" "testing" ) @@ -25,98 +22,3 @@ func Test_getValuesSuffix_aarch64(t *testing.T) { t.Errorf("suffix, want: %s, got: %s", want, got) } } - -func Test_mergeFlags(t *testing.T) { - var ( - unexpectedErr = "failed to merge err: %v, existing flags: %v, set overrides: %v" - unexpectedMergeErr = "error merging flags, want: %v, got: %v" - inconsistentErrString = "inconsistent error return want: %v, got: %v" - ) - tests := []struct { - title string - flags map[string]string - overrides []string - resultExpected map[string]string - errExpected error - }{ - // positive cases: - {"Single key with numeric value and no flags", - map[string]string{}, - []string{"b=1"}, - map[string]string{"b": "1"}, - nil, - }, - {"Empty set and no flags, should not fail", - map[string]string{}, - []string{}, - map[string]string{}, - nil, - }, - {"No set key and an existing flag", - map[string]string{"a": "1"}, - []string{}, - map[string]string{"a": "1"}, - nil, - }, - {"Single key with numeric value, override the flag with numeric value", - map[string]string{"a": "1"}, - []string{"a=2"}, - map[string]string{"a": "2"}, - nil, - }, - {"Single key with numeric value and single flag key with numeric value", - map[string]string{"a": "1"}, - []string{"b=1"}, - map[string]string{"a": "1", "b": "1"}, - nil, - }, - {"Single key with numeric value, update existing key and a new key", - map[string]string{"a": "1"}, - []string{"a=2", "b=1"}, - map[string]string{"a": "2", "b": "1"}, - nil, - }, - {"Update all existing flags in the map", - map[string]string{"a": "1", "b": "2"}, - []string{"a=2", "b=3"}, - map[string]string{"a": "2", "b": "3"}, - nil, - }, - - // check errors - {"Incorrect flag format, providing : as a delimiter", - map[string]string{"a": "1"}, - []string{"a:2"}, - nil, - fmt.Errorf("incorrect format for custom flag `a:2`"), - }, - {"Incorrect flag format, providing space as a delimiter", - map[string]string{"a": "1"}, []string{"a 2"}, - nil, - fmt.Errorf("incorrect format for custom flag `a 2`"), - }, - {"Incorrect flag format, providing - as a delimiter", - map[string]string{"a": "1"}, - []string{"a-2"}, - nil, - fmt.Errorf("incorrect format for custom flag `a-2`"), - }, - } - - for _, test := range tests { - t.Run(test.title, func(t *testing.T) { - err := mergeFlags(test.flags, test.overrides) - if err != nil { - if test.errExpected == nil { - t.Errorf(unexpectedErr, err, test.flags, test.overrides) - } else if !strings.EqualFold(err.Error(), test.errExpected.Error()) { - t.Errorf(inconsistentErrString, test.errExpected, err) - } - return - } - if !reflect.DeepEqual(test.resultExpected, test.flags) { - t.Errorf(unexpectedMergeErr, test.resultExpected, test.flags) - } - }) - } -} diff --git a/cmd/apps/openfaas_ingress_app.go b/cmd/apps/openfaas_ingress_app.go index 8a8bae8ff..7ba0f5194 100644 --- a/cmd/apps/openfaas_ingress_app.go +++ b/cmd/apps/openfaas_ingress_app.go @@ -12,6 +12,9 @@ import ( "os" "path/filepath" + "github.com/alexellis/arkade/pkg/config" + "github.com/alexellis/arkade/pkg/k8s" + "text/template" "github.com/alexellis/arkade/pkg" @@ -56,7 +59,7 @@ func MakeInstallOpenFaaSIngress() *cobra.Command { return errors.New("--ingress-class must be set") } - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() if command.Flags().Changed("kubeconfig") { kubeConfigPath, _ = command.Flags().GetString("kubeconfig") @@ -78,7 +81,7 @@ func MakeInstallOpenFaaSIngress() *cobra.Command { return tempFileErr } - res, err := kubectlTask("apply", "-f", tempFile) + res, err := k8s.KubectlTask("apply", "-f", tempFile) if err != nil { log.Print(err) diff --git a/cmd/apps/portainer_app.go b/cmd/apps/portainer_app.go index e90cd6d66..b983d41a1 100644 --- a/cmd/apps/portainer_app.go +++ b/cmd/apps/portainer_app.go @@ -11,6 +11,9 @@ import ( "path" "strings" + "github.com/alexellis/arkade/pkg/config" + "github.com/alexellis/arkade/pkg/k8s" + "github.com/alexellis/arkade/pkg" "github.com/spf13/cobra" @@ -26,7 +29,7 @@ func MakeInstallPortainer() *cobra.Command { } command.RunE = func(command *cobra.Command, args []string) error { - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() if command.Flags().Changed("kubeconfig") { kubeConfigPath, _ = command.Flags().GetString("kubeconfig") @@ -34,14 +37,14 @@ func MakeInstallPortainer() *cobra.Command { fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath) - arch := getNodeArchitecture() + arch := k8s.GetNodeArchitecture() fmt.Printf("Node architecture: %q\n", arch) if arch != IntelArch && arch != "arm" { return fmt.Errorf(`only Intel and "arm" is supported for this app`) } - _, err := kubectlTask("create", "ns", + _, err := k8s.KubectlTask("create", "ns", "portainer") if err != nil { if !strings.Contains(err.Error(), "exists") { @@ -77,7 +80,7 @@ func MakeInstallPortainer() *cobra.Command { return err } - _, err = kubectlTask("apply", "-f", joined, "-n", "portainer") + _, err = k8s.KubectlTask("apply", "-f", joined, "-n", "portainer") if err != nil { return err } diff --git a/cmd/apps/postgres_app.go b/cmd/apps/postgres_app.go index 943aa79c5..5af42f185 100644 --- a/cmd/apps/postgres_app.go +++ b/cmd/apps/postgres_app.go @@ -11,6 +11,8 @@ import ( "strconv" "strings" + "github.com/alexellis/arkade/pkg/k8s" + "github.com/alexellis/arkade/pkg" "github.com/alexellis/arkade/pkg/config" "github.com/alexellis/arkade/pkg/env" @@ -36,7 +38,7 @@ func MakeInstallPostgresql() *cobra.Command { "Use custom flags or override existing flags \n(example --set persistence.enabled=true)") postgresql.RunE = func(command *cobra.Command, args []string) error { - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() if command.Flags().Changed("kubeconfig") { kubeConfigPath, _ = command.Flags().GetString("kubeconfig") @@ -45,7 +47,7 @@ func MakeInstallPostgresql() *cobra.Command { fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath) - arch := getNodeArchitecture() + arch := k8s.GetNodeArchitecture() fmt.Printf("Node architecture: %q\n", arch) if arch != IntelArch { @@ -76,14 +78,14 @@ func MakeInstallPostgresql() *cobra.Command { } if updateRepo { - err = updateHelmRepos(false) + err = helm.UpdateHelmRepos(false) if err != nil { return err } } chartPath := path.Join(os.TempDir(), "charts") - err = fetchChart(chartPath, "stable/postgresql", defaultVersion, false) + err = helm.FetchChart("stable/postgresql", defaultVersion, false) if err != nil { return err @@ -110,7 +112,7 @@ func MakeInstallPostgresql() *cobra.Command { outputPath := path.Join(chartPath, "postgresql/rendered") - err = templateChart(chartPath, + err = helm.TemplateChart(chartPath, "postgresql", ns, outputPath, @@ -121,7 +123,7 @@ func MakeInstallPostgresql() *cobra.Command { return err } - err = kubectl("apply", "-R", "-f", outputPath) + err = k8s.Kubectl("apply", "-R", "-f", outputPath) if err != nil { return err diff --git a/cmd/apps/registry_app.go b/cmd/apps/registry_app.go index fca898a3a..a5208a86b 100644 --- a/cmd/apps/registry_app.go +++ b/cmd/apps/registry_app.go @@ -9,6 +9,8 @@ import ( "os" "path" + "github.com/alexellis/arkade/pkg/k8s" + "golang.org/x/crypto/bcrypt" "github.com/alexellis/arkade/pkg" @@ -35,7 +37,7 @@ func MakeInstallRegistry() *cobra.Command { registry.Flags().StringP("password", "p", "", "Password for the registry, leave blank to generate") registry.RunE = func(command *cobra.Command, args []string) error { - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() wait, _ := command.Flags().GetBool("wait") if command.Flags().Changed("kubeconfig") { @@ -88,20 +90,13 @@ func MakeInstallRegistry() *cobra.Command { htPasswd := fmt.Sprintf("%s:%s\n", username, string(val)) - err = addHelmRepo("stable", "https://kubernetes-charts.storage.googleapis.com", helm3) + err = helm.AddHelmRepo("stable", "https://kubernetes-charts.storage.googleapis.com", updateRepo, helm3) if err != nil { return err } - if updateRepo { - err = updateHelmRepos(helm3) - if err != nil { - return err - } - } - chartPath := path.Join(os.TempDir(), "charts") - err = fetchChart(chartPath, "stable/docker-registry", defaultVersion, helm3) + err = helm.FetchChart("stable/docker-registry", defaultVersion, helm3) if err != nil { return err @@ -112,7 +107,7 @@ func MakeInstallRegistry() *cobra.Command { overrides["persistence.enabled"] = "false" overrides["secrets.htpasswd"] = string(htPasswd) - arch := getNodeArchitecture() + arch := k8s.GetNodeArchitecture() fmt.Printf("Node architecture: %q\n", arch) fmt.Println("Chart path: ", chartPath) @@ -120,9 +115,7 @@ func MakeInstallRegistry() *cobra.Command { ns := "default" if helm3 { - outputPath := path.Join(chartPath, "docker-registry") - - err := helm3Upgrade(outputPath, "stable/docker-registry", ns, + err := helm.Helm3Upgrade("stable/docker-registry", ns, "values.yaml", defaultVersion, overrides, @@ -134,7 +127,7 @@ func MakeInstallRegistry() *cobra.Command { } else { outputPath := path.Join(chartPath, "docker-registry/rendered") - err = templateChart(chartPath, + err = helm.TemplateChart(chartPath, "docker-registry", ns, outputPath, @@ -145,7 +138,7 @@ func MakeInstallRegistry() *cobra.Command { return err } - err = kubectl("apply", "-R", "-f", outputPath) + err = k8s.Kubectl("apply", "-R", "-f", outputPath) if err != nil { return err diff --git a/cmd/apps/registry_ingress_app.go b/cmd/apps/registry_ingress_app.go index 61cba553b..8e6933e1d 100644 --- a/cmd/apps/registry_ingress_app.go +++ b/cmd/apps/registry_ingress_app.go @@ -9,6 +9,9 @@ import ( "fmt" "log" + "github.com/alexellis/arkade/pkg/config" + "github.com/alexellis/arkade/pkg/k8s" + "text/template" "github.com/alexellis/arkade/pkg" @@ -57,7 +60,7 @@ to your email - this email is used by letsencrypt for domain expiry etc.`, return errors.New("--ingress-class must be set") } - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() if command.Flags().Changed("kubeconfig") { kubeConfigPath, _ = command.Flags().GetString("kubeconfig") @@ -77,7 +80,7 @@ to your email - this email is used by letsencrypt for domain expiry etc.`, return tempFileErr } - res, err := kubectlTask("apply", "-f", tempFile) + res, err := k8s.KubectlTask("apply", "-f", tempFile) if err != nil { log.Print(err) diff --git a/cmd/apps/tekton_app.go b/cmd/apps/tekton_app.go index 5ba7c7990..6ce496040 100644 --- a/cmd/apps/tekton_app.go +++ b/cmd/apps/tekton_app.go @@ -6,6 +6,9 @@ package apps import ( "fmt" + "github.com/alexellis/arkade/pkg/config" + "github.com/alexellis/arkade/pkg/k8s" + "github.com/alexellis/arkade/pkg" "github.com/spf13/cobra" @@ -21,7 +24,7 @@ func MakeInstallTekton() *cobra.Command { } tekton.RunE = func(command *cobra.Command, args []string) error { - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() if command.Flags().Changed("kubeconfig") { kubeConfigPath, _ = command.Flags().GetString("kubeconfig") @@ -29,7 +32,7 @@ func MakeInstallTekton() *cobra.Command { fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath) - arch := getNodeArchitecture() + arch := k8s.GetNodeArchitecture() fmt.Printf("Node architecture: %q\n", arch) if arch != IntelArch { @@ -37,14 +40,14 @@ func MakeInstallTekton() *cobra.Command { } fmt.Println("Installing Tekton pipelines...") - _, err := kubectlTask("apply", "-f", + _, err := k8s.KubectlTask("apply", "-f", "https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml") if err != nil { return err } fmt.Println("Installing Tekton dashboard...") - _, err = kubectlTask("apply", "-f", + _, err = k8s.KubectlTask("apply", "-f", "https://github.com/tektoncd/dashboard/releases/download/v0.5.1/tekton-dashboard-release.yaml") if err != nil { return err diff --git a/cmd/apps/traefik2_app.go b/cmd/apps/traefik2_app.go index 4d6de0ee9..b919f44e7 100644 --- a/cmd/apps/traefik2_app.go +++ b/cmd/apps/traefik2_app.go @@ -5,8 +5,6 @@ package apps import ( "fmt" - "os" - "path" "github.com/alexellis/arkade/pkg" "github.com/alexellis/arkade/pkg/config" @@ -37,7 +35,7 @@ func MakeInstallTraefik2() *cobra.Command { traefik2.RunE = func(command *cobra.Command, args []string) error { - kubeConfigPath := getDefaultKubeconfig() + kubeConfigPath := config.GetDefaultKubeconfig() if command.Flags().Changed("kubeconfig") { kubeConfigPath, _ = command.Flags().GetString("kubeconfig") } @@ -59,20 +57,12 @@ func MakeInstallTraefik2() *cobra.Command { return err } - err = addHelmRepo("traefik", "https://containous.github.io/traefik-helm-chart", helm3) + err = helm.AddHelmRepo("traefik", "https://containous.github.io/traefik-helm-chart", updateRepo, helm3) if err != nil { return fmt.Errorf("Unable to add repo %s", err) } - if updateRepo { - err = updateHelmRepos(helm3) - if err != nil { - return err - } - } - - chartPath := path.Join(os.TempDir(), "charts") - err = fetchChart(chartPath, "traefik/traefik", "", helm3) + err = helm.FetchChart("traefik/traefik", "", helm3) if err != nil { return fmt.Errorf("Unable fetch chart: %s", err) } @@ -109,8 +99,7 @@ func MakeInstallTraefik2() *cobra.Command { return err } - outputPath := path.Join(chartPath, "traefik") - err = helm3Upgrade(outputPath, "traefik/traefik", namespace, + err = helm.Helm3Upgrade("traefik/traefik", namespace, "values.yaml", "", overrides, diff --git a/cmd/info.go b/cmd/info.go index 92ec6fdc4..865db464b 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -107,6 +107,9 @@ arkade info --help`, case "jenkins": fmt.Printf("Info for app: %s\n", appName) fmt.Println(apps.JenkinsInfoMsg) + case "loki": + fmt.Printf("Info for app: %s\n", appName) + fmt.Println(apps.LokiInfoMsg) default: return fmt.Errorf("no info available for app: %s", appName) } diff --git a/cmd/install.go b/cmd/install.go index cbf14bdac..47288700c 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -66,6 +66,7 @@ command.`, command.AddCommand(apps.MakeInstallPortainer()) command.AddCommand(apps.MakeInstallTekton()) command.AddCommand(apps.MakeInstallJenkins()) + command.AddCommand(apps.MakeInstallLoki()) command.AddCommand(MakeInfo()) @@ -96,5 +97,6 @@ func getApps() []string { "grafana", "tekton", "jenkins", + "loki", } } diff --git a/pkg/apps/loki.go b/pkg/apps/loki.go new file mode 100644 index 000000000..9d7341fda --- /dev/null +++ b/pkg/apps/loki.go @@ -0,0 +1,28 @@ +package apps + +import ( + "github.com/alexellis/arkade/pkg/helm" + "github.com/alexellis/arkade/pkg/types" +) + +func MakeInstallLoki(options *types.InstallerOptions) (*types.InstallerOutput, error) { + result := &types.InstallerOutput{} + err := helm.AddHelmRepo(options.Helm.Repo.Name, options.Helm.Repo.URL, options.Helm.UpdateRepo, options.Helm.Helm3) + if err != nil { + return result, err + } + + if err := helm.FetchChart(options.Helm.Repo.Name, options.Helm.Repo.Version, options.Helm.Helm3); err != nil { + return result, err + } + + if err := helm.Helm3Upgrade(options.Helm.Repo.Name, options.Namespace, + "values.yaml", + options.Helm.Repo.Version, + options.Helm.Overrides, + options.Helm.Wait); err != nil { + return result, err + } + + return result, nil +} diff --git a/pkg/config/config.go b/pkg/config/config.go index ffac0b613..9b518536a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "path" + "strings" ) func GetUserDir() string { @@ -37,3 +38,24 @@ func InitUserDir() (string, error) { return root, nil } + +func GetDefaultKubeconfig() string { + kubeConfigPath := path.Join(os.Getenv("HOME"), ".kube/config") + + if val, ok := os.LookupEnv("KUBECONFIG"); ok { + kubeConfigPath = val + } + + return kubeConfigPath +} + +func MergeFlags(existingMap map[string]string, setOverrides []string) error { + for _, setOverride := range setOverrides { + flag := strings.Split(setOverride, "=") + if len(flag) != 2 { + return fmt.Errorf("incorrect format for custom flag `%s`", setOverride) + } + existingMap[flag[0]] = flag[1] + } + return nil +} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go new file mode 100644 index 000000000..4a6d734f8 --- /dev/null +++ b/pkg/config/config_test.go @@ -0,0 +1,103 @@ +package config + +import ( + "fmt" + "reflect" + "strings" + "testing" +) + +func Test_MergeFlags(t *testing.T) { + var ( + unexpectedErr = "failed to merge err: %v, existing flags: %v, set overrides: %v" + unexpectedMergeErr = "error merging flags, want: %v, got: %v" + inconsistentErrString = "inconsistent error return want: %v, got: %v" + ) + tests := []struct { + title string + flags map[string]string + overrides []string + resultExpected map[string]string + errExpected error + }{ + // positive cases: + {"Single key with numeric value and no flags", + map[string]string{}, + []string{"b=1"}, + map[string]string{"b": "1"}, + nil, + }, + {"Empty set and no flags, should not fail", + map[string]string{}, + []string{}, + map[string]string{}, + nil, + }, + {"No set key and an existing flag", + map[string]string{"a": "1"}, + []string{}, + map[string]string{"a": "1"}, + nil, + }, + {"Single key with numeric value, override the flag with numeric value", + map[string]string{"a": "1"}, + []string{"a=2"}, + map[string]string{"a": "2"}, + nil, + }, + {"Single key with numeric value and single flag key with numeric value", + map[string]string{"a": "1"}, + []string{"b=1"}, + map[string]string{"a": "1", "b": "1"}, + nil, + }, + {"Single key with numeric value, update existing key and a new key", + map[string]string{"a": "1"}, + []string{"a=2", "b=1"}, + map[string]string{"a": "2", "b": "1"}, + nil, + }, + {"Update all existing flags in the map", + map[string]string{"a": "1", "b": "2"}, + []string{"a=2", "b=3"}, + map[string]string{"a": "2", "b": "3"}, + nil, + }, + + // check errors + {"Incorrect flag format, providing : as a delimiter", + map[string]string{"a": "1"}, + []string{"a:2"}, + nil, + fmt.Errorf("incorrect format for custom flag `a:2`"), + }, + {"Incorrect flag format, providing space as a delimiter", + map[string]string{"a": "1"}, []string{"a 2"}, + nil, + fmt.Errorf("incorrect format for custom flag `a 2`"), + }, + {"Incorrect flag format, providing - as a delimiter", + map[string]string{"a": "1"}, + []string{"a-2"}, + nil, + fmt.Errorf("incorrect format for custom flag `a-2`"), + }, + } + + for _, test := range tests { + t.Run(test.title, func(t *testing.T) { + err := MergeFlags(test.flags, test.overrides) + if err != nil { + if test.errExpected == nil { + t.Errorf(unexpectedErr, err, test.flags, test.overrides) + } else if !strings.EqualFold(err.Error(), test.errExpected.Error()) { + t.Errorf(inconsistentErrString, test.errExpected, err) + } + return + } + if !reflect.DeepEqual(test.resultExpected, test.flags) { + t.Errorf(unexpectedMergeErr, test.resultExpected, test.flags) + } + }) + } +} diff --git a/pkg/helm/helm.go b/pkg/helm/helm.go index 434e5a65c..b9295a8be 100644 --- a/pkg/helm/helm.go +++ b/pkg/helm/helm.go @@ -6,6 +6,7 @@ package helm import ( "fmt" "io/ioutil" + "log" "net/http" "net/url" "os" @@ -29,7 +30,9 @@ func TryDownloadHelm(userPath, clientArch, clientOS string, helm3 bool) (string, helmBinaryPath := path.Join(path.Join(userPath, "bin"), helmVal) if _, statErr := os.Stat(helmBinaryPath); statErr != nil { - DownloadHelm(userPath, clientArch, clientOS, subdir, helm3) + if err := DownloadHelm(userPath, clientArch, clientOS, subdir, helm3); err != nil { + return "", err + } if !helm3 { err := HelmInit() @@ -115,3 +118,203 @@ func HelmInit() error { } return nil } + +func AddHelmRepo(name, url string, update, helm3 bool) error { + subdir := "" + if helm3 { + subdir = "helm3" + } + task := execute.ExecTask{ + Command: fmt.Sprintf("%s repo add %s %s", env.LocalBinary("helm", subdir), name, url), + Env: os.Environ(), + StreamStdio: true, + } + res, err := task.Execute() + + println(res.Stderr) + + if err != nil { + return err + } + if res.ExitCode != 0 { + return fmt.Errorf("exit code %d", res.ExitCode) + } + + if update { + return UpdateHelmRepos(helm3) + } + + return nil +} + +func UpdateHelmRepos(helm3 bool) error { + subdir := "" + if helm3 { + subdir = "helm3" + } + task := execute.ExecTask{ + Command: fmt.Sprintf("%s repo update", env.LocalBinary("helm", subdir)), + Env: os.Environ(), + StreamStdio: true, + } + + res, err := task.Execute() + + if err != nil { + return err + } + + if res.ExitCode != 0 { + return fmt.Errorf("exit code %d", res.ExitCode) + } + return nil +} + +func FetchChart(chart, version string, helm3 bool) error { + chartsPath := path.Join(os.TempDir(), "charts") + versionStr := "" + + if len(version) > 0 { + // Issue in helm where adding a space to the command makes it think that it's another chart of " " we want to template, + // So we add the space before version here rather than on the command + versionStr = " --version " + version + } + subdir := "" + if helm3 { + subdir = "helm3" + } + + // First remove any existing folder + os.RemoveAll(chartsPath) + + mkErr := os.MkdirAll(chartsPath, 0700) + + if mkErr != nil { + return mkErr + } + + task := execute.ExecTask{ + Command: fmt.Sprintf("%s fetch %s --untar=true --untardir %s%s", env.LocalBinary("helm", subdir), chart, chartsPath, versionStr), + Env: os.Environ(), + StreamStdio: true, + } + res, err := task.Execute() + + if err != nil { + return err + } + + if res.ExitCode != 0 { + return fmt.Errorf("exit code %d", res.ExitCode) + } + return nil +} + +func Helm3Upgrade(chart, namespace, values, version string, overrides map[string]string, wait bool) error { + + chartName := chart + if index := strings.Index(chartName, "/"); index > -1 { + chartName = chartName[index+1:] + } + + basePath := path.Join(os.TempDir(), "charts", chartName) + + args := []string{"upgrade", "--install", chartName, chart, "--namespace", namespace} + if len(version) > 0 { + args = append(args, "--version", version) + } + + if wait { + args = append(args, "--wait") + } + + fmt.Println("VALUES", values) + if len(values) > 0 { + args = append(args, "--values") + if !strings.HasPrefix(values, "/") { + args = append(args, path.Join(basePath, values)) + } else { + args = append(args, values) + } + } + + for k, v := range overrides { + args = append(args, "--set") + args = append(args, fmt.Sprintf("%s=%s", k, v)) + } + + task := execute.ExecTask{ + Command: env.LocalBinary("helm", "helm3"), + Args: args, + Env: os.Environ(), + Cwd: basePath, + StreamStdio: true, + } + + fmt.Printf("Command: %s %s\n", task.Command, task.Args) + res, err := task.Execute() + + if err != nil { + return err + } + + if res.ExitCode != 0 { + return fmt.Errorf("exit code %d, stderr: %s", res.ExitCode, res.Stderr) + } + + if len(res.Stderr) > 0 { + log.Printf("stderr: %s\n", res.Stderr) + } + + return nil +} + +func TemplateChart(basePath, chart, namespace, outputPath, values string, overrides map[string]string) error { + + rmErr := os.RemoveAll(outputPath) + + if rmErr != nil { + log.Printf("Error cleaning up: %s, %s\n", outputPath, rmErr.Error()) + } + + mkErr := os.MkdirAll(outputPath, 0700) + if mkErr != nil { + return mkErr + } + + overridesStr := "" + for k, v := range overrides { + overridesStr += fmt.Sprintf(" --set %s=%s", k, v) + } + + chartRoot := path.Join(basePath, chart) + + valuesStr := "" + if len(values) > 0 { + valuesStr = "--values " + path.Join(chartRoot, values) + } + + task := execute.ExecTask{ + Command: fmt.Sprintf("%s template %s --name %s --namespace %s --output-dir %s %s %s", + env.LocalBinary("helm", ""), chart, chart, namespace, outputPath, valuesStr, overridesStr), + Env: os.Environ(), + Cwd: basePath, + StreamStdio: true, + } + + res, err := task.Execute() + + if err != nil { + return err + } + + if res.ExitCode != 0 { + return fmt.Errorf("exit code %d, stderr: %s", res.ExitCode, res.Stderr) + } + + if len(res.Stderr) > 0 { + log.Printf("stderr: %s\n", res.Stderr) + } + + return nil +} diff --git a/pkg/k8s/kubernetes_exec.go b/pkg/k8s/kubernetes_exec.go new file mode 100644 index 000000000..ef4c3331d --- /dev/null +++ b/pkg/k8s/kubernetes_exec.go @@ -0,0 +1,52 @@ +// Copyright (c) arkade author(s) 2020. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +package k8s + +import ( + "fmt" + "strings" + + execute "github.com/alexellis/go-execute/pkg/v1" +) + +func GetNodeArchitecture() string { + res, _ := KubectlTask("get", "nodes", `--output`, `jsonpath={range $.items[0]}{.status.nodeInfo.architecture}`) + + arch := strings.TrimSpace(string(res.Stdout)) + + return arch +} + +func KubectlTask(parts ...string) (execute.ExecResult, error) { + task := execute.ExecTask{ + Command: "kubectl", + Args: parts, + StreamStdio: false, + } + + res, err := task.Execute() + + return res, err +} + +func Kubectl(parts ...string) error { + task := execute.ExecTask{ + Command: "kubectl", + Args: parts, + StreamStdio: true, + } + + res, err := task.Execute() + + if err != nil { + return err + } + + if res.ExitCode != 0 { + return fmt.Errorf("kubectl exit code %d, stderr: %s", + res.ExitCode, + res.Stderr) + } + return nil +} diff --git a/pkg/types/types.go b/pkg/types/types.go new file mode 100644 index 000000000..7ee97698b --- /dev/null +++ b/pkg/types/types.go @@ -0,0 +1,80 @@ +package types + +import "github.com/alexellis/arkade/pkg/config" + +type InstallerOptions struct { + Namespace string + KubeconfigPath string + NodeArch string + Helm *HelmConfig + Verbose bool +} + +type HelmConfig struct { + Repo *HelmRepo + Helm3 bool + HelmPath string + Overrides map[string]string + UpdateRepo bool + Wait bool +} + +type HelmRepo struct { + Name string + URL string + Version string +} + +type InstallerOutput struct { +} + +func (o *InstallerOptions) WithKubeconfigPath(path string) *InstallerOptions { + o.KubeconfigPath = path + return o +} + +func (o *InstallerOptions) WithNamespace(namespace string) *InstallerOptions { + o.Namespace = namespace + return o +} + +func (o *InstallerOptions) WithHelmPath(helmPath string) *InstallerOptions { + o.Helm.HelmPath = helmPath + return o +} + +func (o *InstallerOptions) WithWait(wait bool) *InstallerOptions { + o.Helm.Wait = wait + return o +} + +func (o *InstallerOptions) WithHelmRepo(s string) *InstallerOptions { + o.Helm.Repo.Name = s + return o +} + +func (o *InstallerOptions) WithHelmURL(s string) *InstallerOptions { + o.Helm.Repo.URL = s + return o +} + +func (o *InstallerOptions) WithOverrides(overrides map[string]string) *InstallerOptions { + o.Helm.Overrides = overrides + return o +} + +func DefaultInstallOptions() *InstallerOptions { + return &InstallerOptions{ + Namespace: "default", + KubeconfigPath: config.GetDefaultKubeconfig(), + NodeArch: "x86_64", + Helm: &HelmConfig{ + Repo: &HelmRepo{ + Version: "", + }, + Helm3: true, + Wait: false, + }, + Verbose: false, + } +}