From 00b138ce7c659bac3c61da1cee84e6544dfd863a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=81nis=20Bebr=C4=ABtis?= Date: Wed, 4 Sep 2024 19:17:04 +0300 Subject: [PATCH 1/2] release diff; es init container removal script --- cmd/ciReleaseDiff.go | 373 +++++++++++++++++++++++++++++++++++ cmd/ciScripts.go | 19 ++ cmd/ciScriptsEsInitRemove.go | 103 ++++++++++ 3 files changed, 495 insertions(+) create mode 100644 cmd/ciReleaseDiff.go create mode 100644 cmd/ciScripts.go create mode 100644 cmd/ciScriptsEsInitRemove.go diff --git a/cmd/ciReleaseDiff.go b/cmd/ciReleaseDiff.go new file mode 100644 index 0000000..6f60f1e --- /dev/null +++ b/cmd/ciReleaseDiff.go @@ -0,0 +1,373 @@ +package cmd + +import ( + "fmt" + "log" + "os" + "os/exec" + "strings" + + "github.com/spf13/cobra" + "github.com/wunderio/silta-cli/internal/common" +) + +// ciReleaseDiffCmd represents the ciReleaseDiff command +var ciReleaseDiffCmd = &cobra.Command{ + Use: "diff", + Short: "Diff release resources", + Long: `Release diff command is used to compare the resources of a release with the current state of the cluster. + + * Chart allows prepending extra configuration (to helm --values line) via + "SILTA__CONFIG_VALUES" environment variable. It has to be a + base64 encoded string of a silta configuration yaml file. + `, + Run: func(cmd *cobra.Command, args []string) { + + releaseName, _ := cmd.Flags().GetString("release-name") + releaseSuffix, _ := cmd.Flags().GetString("release-suffix") + namespace, _ := cmd.Flags().GetString("namespace") + siltaEnvironmentName, _ := cmd.Flags().GetString("silta-environment-name") + branchname, _ := cmd.Flags().GetString("branchname") + dbRootPass, _ := cmd.Flags().GetString("db-root-pass") + dbUserPass, _ := cmd.Flags().GetString("db-user-pass") + vpnIP, _ := cmd.Flags().GetString("vpn-ip") + vpcNative, _ := cmd.Flags().GetString("vpc-native") + clusterType, _ := cmd.Flags().GetString("cluster-type") + chartVersion, _ := cmd.Flags().GetString("chart-version") + phpImageUrl, _ := cmd.Flags().GetString("php-image-url") + nginxImageUrl, _ := cmd.Flags().GetString("nginx-image-url") + shellImageUrl, _ := cmd.Flags().GetString("shell-image-url") + repositoryUrl, _ := cmd.Flags().GetString("repository-url") + gitAuthUsername, _ := cmd.Flags().GetString("gitauth-username") + gitAuthPassword, _ := cmd.Flags().GetString("gitauth-password") + clusterDomain, _ := cmd.Flags().GetString("cluster-domain") + chartName, _ := cmd.Flags().GetString("chart-name") + chartRepository, _ := cmd.Flags().GetString("chart-repository") + siltaConfig, _ := cmd.Flags().GetString("silta-config") + helmFlags, _ := cmd.Flags().GetString("helm-flags") + + // Use environment variables as fallback + if useEnv { + if len(dbRootPass) == 0 { + dbRootPass = os.Getenv("DB_ROOT_PASS") + } + if len(dbUserPass) == 0 { + dbUserPass = os.Getenv("DB_USER_PASS") + } + if len(vpnIP) == 0 { + vpnIP = os.Getenv("VPN_IP") + } + if len(vpcNative) == 0 { + vpcNative = os.Getenv("VPC_NATIVE") + } + if len(clusterType) == 0 { + clusterType = os.Getenv("CLUSTER_TYPE") + } + if len(siltaEnvironmentName) == 0 { + siltaEnvironmentName = os.Getenv("SILTA_ENVIRONMENT_NAME") + } + if len(siltaEnvironmentName) == 0 { + siltaEnvironmentName = common.SiltaEnvironmentName(branchname, releaseSuffix) + } + if len(repositoryUrl) == 0 { + repositoryUrl = os.Getenv("CIRCLE_REPOSITORY_URL") + } + if len(gitAuthUsername) == 0 { + gitAuthUsername = os.Getenv("GITAUTH_USERNAME") + } + if len(gitAuthPassword) == 0 { + gitAuthPassword = os.Getenv("GITAUTH_PASSWORD") + } + if len(clusterDomain) == 0 { + clusterDomain = os.Getenv("CLUSTER_DOMAIN") + } + } + + // Uses PrependChartConfigOverrides from "SILTA__CONFIG_VALUES" + // environment variable and prepends it to configuration + chartOverrideFile := common.CreateChartConfigurationFile(chartName) + if chartOverrideFile != "" { + defer os.Remove(chartOverrideFile) + siltaConfig = common.PrependChartConfigOverrides(chartOverrideFile, siltaConfig) + } + + // Chart value overrides + + // Override Database credentials if specified + dbRootPassOverride := "" + if len(dbRootPass) > 0 { + dbRootPassOverride = fmt.Sprintf("--set mariadb.rootUser.password='%s'", dbRootPass) + } + dbUserPassOverride := "" + if len(dbUserPass) > 0 { + dbUserPassOverride = fmt.Sprintf("--set mariadb.db.password='%s'", dbUserPass) + } + + // Skip basic auth for internal VPN if defined in environment + extraNoAuthIPs := "" + if len(vpnIP) > 0 { + extraNoAuthIPs = fmt.Sprintf("--set nginx.noauthips.vpn='%s/32'", vpnIP) + } + + // Pass VPC-native setting if defined in environment + vpcNativeOverride := "" + if len(vpcNative) > 0 { + vpcNativeOverride = fmt.Sprintf("--set cluster.vpcNative='%s'", vpcNative) + } + + // Add cluster type if defined in environment + extraClusterType := "" + if len(clusterType) > 0 { + extraClusterType = fmt.Sprintf("--set cluster.type='%s'", clusterType) + } + + // Allow pinning a specific chart version + chartVersionOverride := "" + if len(chartVersion) > 0 { + chartVersionOverride = fmt.Sprintf("--version '%s'", chartVersion) + } + + // TODO: Create namespace if it doesn't exist + // & tag the namespace if it isn't already tagged. + // TODO: Rewrite + + if !debug { + // Add helm repositories + command := fmt.Sprintf("helm repo add '%s' '%s'", "wunderio", chartRepository) + exec.Command("bash", "-c", command).Run() + + // Make sure repositories are up to date + command = "helm repo update" + exec.Command("bash", "-c", command).Run() + } + + if chartName == "simple" || strings.HasSuffix(chartName, "/simple") { + + if len(nginxImageUrl) == 0 { + log.Fatal("Nginx image url required (nginx-image-url)") + } + + _, errDir := os.Stat(common.ExtendedFolder + "/simple") + if !os.IsNotExist(errDir) { + chartName = common.ExtendedFolder + "/simple" + } + + fmt.Printf("Diffing %s helm release %s in %s namespace\n", chartName, releaseName, namespace) + + // helm release + command := fmt.Sprintf(` + set -Eeuo pipefail + + RELEASE_NAME='%s' + CHART_NAME='%s' + CHART_REPOSITORY='%s' + EXTRA_CHART_VERSION='%s' + SILTA_ENVIRONMENT_NAME='%s' + BRANCHNAME='%s' + NGINX_IMAGE_URL='%s' + CLUSTER_DOMAIN='%s' + EXTRA_NOAUTHIPS='%s' + EXTRA_VPCNATIVE='%s' + EXTRA_CLUSTERTYPE='%s' + NAMESPACE='%s' + SILTA_CONFIG='%s' + EXTRA_HELM_FLAGS='%s' + + helm diff upgrade --install "${RELEASE_NAME}" "${CHART_NAME}" \ + --repo "${CHART_REPOSITORY}" \ + ${EXTRA_CHART_VERSION} \ + --set environmentName="${SILTA_ENVIRONMENT_NAME}" \ + --set silta-release.branchName="${BRANCHNAME}" \ + --set nginx.image="${NGINX_IMAGE_URL}" \ + --set clusterDomain="${CLUSTER_DOMAIN}" \ + ${EXTRA_NOAUTHIPS} \ + ${EXTRA_VPCNATIVE} \ + ${EXTRA_CLUSTERTYPE} \ + --namespace="${NAMESPACE}" \ + --values "${SILTA_CONFIG}" \ + ${EXTRA_HELM_FLAGS}`, + releaseName, chartName, chartRepository, chartVersionOverride, + siltaEnvironmentName, branchname, nginxImageUrl, + clusterDomain, extraNoAuthIPs, vpcNativeOverride, extraClusterType, + namespace, siltaConfig, helmFlags) + pipedExec(command, "", "ERROR: ", debug) + + } else if chartName == "frontend" || strings.HasSuffix(chartName, "/frontend") { + + fmt.Printf("Diffing %s helm release %s in %s namespace\n", chartName, releaseName, namespace) + + _, errDir := os.Stat(common.ExtendedFolder + "/frontend") + if !os.IsNotExist(errDir) { + chartName = common.ExtendedFolder + "/frontend" + } + + // helm release + command := fmt.Sprintf(` + set -Eeuo pipefail + + RELEASE_NAME='%s' + CHART_NAME='%s' + CHART_REPOSITORY='%s' + EXTRA_CHART_VERSION='%s' + SILTA_ENVIRONMENT_NAME='%s' + BRANCHNAME='%s' + GIT_REPOSITORY_URL='%s' + GITAUTH_USERNAME='%s' + GITAUTH_PASSWORD='%s' + CLUSTER_DOMAIN='%s' + NAMESPACE='%s' + EXTRA_NOAUTHIPS='%s' + EXTRA_VPCNATIVE='%s' + EXTRA_CLUSTERTYPE='%s' + EXTRA_DB_ROOT_PASS='%s' + EXTRA_DB_USER_PASS='%s' + SILTA_CONFIG='%s' + EXTRA_HELM_FLAGS='%s' + + helm diff upgrade --install "${RELEASE_NAME}" "${CHART_NAME}" \ + --repo "${CHART_REPOSITORY}" \ + ${EXTRA_CHART_VERSION} \ + --set environmentName="${SILTA_ENVIRONMENT_NAME}" \ + --set silta-release.branchName="${BRANCHNAME}" \ + --set shell.gitAuth.repositoryUrl="${GIT_REPOSITORY_URL}" \ + --set shell.gitAuth.keyserver.username="${GITAUTH_USERNAME}" \ + --set shell.gitAuth.keyserver.password="${GITAUTH_PASSWORD}" \ + --set clusterDomain="${CLUSTER_DOMAIN}" \ + --namespace="${NAMESPACE}" \ + ${EXTRA_NOAUTHIPS} \ + ${EXTRA_VPCNATIVE} \ + ${EXTRA_CLUSTERTYPE} \ + ${EXTRA_DB_ROOT_PASS} \ + ${EXTRA_DB_USER_PASS} \ + --values "${SILTA_CONFIG}" \ + ${EXTRA_HELM_FLAGS}`, + releaseName, chartName, chartRepository, chartVersionOverride, + siltaEnvironmentName, branchname, + repositoryUrl, gitAuthUsername, gitAuthPassword, + clusterDomain, namespace, + extraNoAuthIPs, vpcNativeOverride, extraClusterType, + dbRootPassOverride, dbUserPassOverride, + siltaConfig, helmFlags) + pipedExec(command, "", "ERROR: ", debug) + + } else if chartName == "drupal" || strings.HasSuffix(chartName, "/drupal") { + + if len(phpImageUrl) == 0 { + log.Fatal("PHP image url required (php-image-url)") + } + if len(nginxImageUrl) == 0 { + log.Fatal("Nginx image url required (nginx-image-url)") + } + if len(shellImageUrl) == 0 { + log.Fatal("Shell image url required (shell-image-url)") + } + + _, errDir := os.Stat(common.ExtendedFolder + "/drupal") + if os.IsNotExist(errDir) == false { + chartName = common.ExtendedFolder + "/drupal" + } + + // Disable reference data if the required volume is not present. + referenceDataOverride := "" + if debug == false { + command := fmt.Sprintf("kubectl get persistentvolume | grep --extended-regexp '%s/.*-reference-data'", namespace) + err := exec.Command("bash", "-c", command).Run() + if err != nil { + referenceDataOverride = "--set referenceData.skipMount=true" + } + } + + fmt.Printf("Diffing %s helm release %s in %s namespace\n", chartName, releaseName, namespace) + + command := fmt.Sprintf(` + set -Eeuo pipefail + + RELEASE_NAME='%s' + CHART_NAME='%s' + CHART_REPOSITORY='%s' + EXTRA_CHART_VERSION='%s' + SILTA_ENVIRONMENT_NAME='%s' + BRANCHNAME='%s' + PHP_IMAGE_URL='%s' + NGINX_IMAGE_URL='%s' + SHELL_IMAGE_URL='%s' + GIT_REPOSITORY_URL='%s' + GITAUTH_USERNAME='%s' + GITAUTH_PASSWORD='%s' + CLUSTER_DOMAIN='%s' + EXTRA_NOAUTHIPS='%s' + EXTRA_VPCNATIVE='%s' + EXTRA_CLUSTERTYPE='%s' + EXTRA_DB_ROOT_PASS='%s' + EXTRA_DB_USER_PASS='%s' + EXTRA_REFERENCE_DATA='%s' + NAMESPACE='%s' + SILTA_CONFIG='%s' + EXTRA_HELM_FLAGS='%s' + + helm diff upgrade --install "${RELEASE_NAME}" "${CHART_NAME}" \ + --repo "${CHART_REPOSITORY}" \ + ${EXTRA_CHART_VERSION} \ + --set environmentName="${SILTA_ENVIRONMENT_NAME}" \ + --set silta-release.branchName="${BRANCHNAME}" \ + --set php.image="${PHP_IMAGE_URL}" \ + --set nginx.image="${NGINX_IMAGE_URL}" \ + --set shell.image="${SHELL_IMAGE_URL}" \ + --set shell.gitAuth.repositoryUrl="${GIT_REPOSITORY_URL}" \ + --set shell.gitAuth.keyserver.username="${GITAUTH_USERNAME}" \ + --set shell.gitAuth.keyserver.password="${GITAUTH_PASSWORD}" \ + --set clusterDomain="${CLUSTER_DOMAIN}" \ + ${EXTRA_NOAUTHIPS} \ + ${EXTRA_VPCNATIVE} \ + ${EXTRA_CLUSTERTYPE} \ + ${EXTRA_DB_ROOT_PASS} \ + ${EXTRA_DB_USER_PASS} \ + ${EXTRA_REFERENCE_DATA} \ + --namespace="${NAMESPACE}" \ + --values "${SILTA_CONFIG}" \ + ${EXTRA_HELM_FLAGS}`, + releaseName, chartName, chartRepository, chartVersionOverride, + siltaEnvironmentName, branchname, + phpImageUrl, nginxImageUrl, shellImageUrl, + repositoryUrl, gitAuthUsername, gitAuthPassword, + clusterDomain, extraNoAuthIPs, vpcNativeOverride, extraClusterType, + dbRootPassOverride, dbUserPassOverride, referenceDataOverride, namespace, + siltaConfig, helmFlags) + pipedExec(command, "", "ERROR: ", debug) + + } else { + fmt.Printf("Chart name %s does not match preselected names (drupal, frontend, simple), helm diff step was skipped\n", chartName) + } + }, +} + +func init() { + ciReleaseCmd.AddCommand(ciReleaseDiffCmd) + + ciReleaseDiffCmd.Flags().String("release-name", "", "Release name") + ciReleaseDiffCmd.Flags().String("release-suffix", "", "Release name suffix for environment name creation") + ciReleaseDiffCmd.Flags().String("namespace", "", "Project name (namespace, i.e. \"drupal-project\")") + ciReleaseDiffCmd.Flags().String("silta-environment-name", "", "Environment name override based on branchname and release-suffix. Used in some helm charts.") + ciReleaseDiffCmd.Flags().String("branchname", "", "Repository branchname that will be used for release name and environment name creation") + ciReleaseDiffCmd.Flags().String("db-root-pass", "", "Database password for root account") + ciReleaseDiffCmd.Flags().String("db-user-pass", "", "Database password for user account") + ciReleaseDiffCmd.Flags().String("vpn-ip", "", "VPN IP for basic auth allow list") + ciReleaseDiffCmd.Flags().String("vpc-native", "", "VPC-native cluster (GKE specific)") + ciReleaseDiffCmd.Flags().String("cluster-type", "", "Cluster type (i.e. gke, aws, aks, other)") + ciReleaseDiffCmd.Flags().String("chart-version", "", "Diff a specific chart version") + ciReleaseDiffCmd.Flags().String("php-image-url", "", "PHP image url") + ciReleaseDiffCmd.Flags().String("nginx-image-url", "", "PHP image url") + ciReleaseDiffCmd.Flags().String("shell-image-url", "", "PHP image url") + ciReleaseDiffCmd.Flags().String("repository-url", "", "Repository url (i.e. git@github.com:wunderio/silta.git)") + ciReleaseDiffCmd.Flags().String("gitauth-username", "", "Gitauth server username") + ciReleaseDiffCmd.Flags().String("gitauth-password", "", "Gitauth server password") + ciReleaseDiffCmd.Flags().String("cluster-domain", "", "Base domain for cluster urls (i.e. dev.example.com)") + ciReleaseDiffCmd.Flags().String("chart-name", "", "Chart name") + ciReleaseDiffCmd.Flags().String("chart-repository", "https://storage.googleapis.com/charts.wdr.io", "Chart repository") + ciReleaseDiffCmd.Flags().String("silta-config", "", "Silta release helm chart values") + ciReleaseDiffCmd.Flags().String("helm-flags", "", "Extra flags for helm release") + + ciReleaseDiffCmd.MarkFlagRequired("release-name") + ciReleaseDiffCmd.MarkFlagRequired("namespace") + ciReleaseDiffCmd.MarkFlagRequired("chart-name") +} diff --git a/cmd/ciScripts.go b/cmd/ciScripts.go new file mode 100644 index 0000000..034a9a5 --- /dev/null +++ b/cmd/ciScripts.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var ciScriptsCmd = &cobra.Command{ + Use: "scripts", + Short: "Convenience scripts for silta", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(cmd.Usage()) + }, +} + +func init() { + rootCmd.AddCommand(ciScriptsCmd) +} diff --git a/cmd/ciScriptsEsInitRemove.go b/cmd/ciScriptsEsInitRemove.go new file mode 100644 index 0000000..9bbeda9 --- /dev/null +++ b/cmd/ciScriptsEsInitRemove.go @@ -0,0 +1,103 @@ +package cmd + +import ( + "context" + "fmt" + "log" + "os" + + "github.com/spf13/cobra" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" +) + +var ciScriptsEsinitRemoveCmd = &cobra.Command{ + Use: "elasticsearch-initcontainer-remove", + Short: "es-init-remove", + Long: `Elasticsearch init container removal from all statefulsets in the cluster.`, + Run: func(cmd *cobra.Command, args []string) { + + namespace, _ := cmd.Flags().GetString("namespace") + dryRun, _ := cmd.Flags().GetBool("dry-run") + + fmt.Printf("Dry run: %t\n", dryRun) + + if namespace == "" { + fmt.Printf("Namespace: all namespaces\n") + } else { + fmt.Printf("Namespace: %s\n", namespace) + } + + homeDir, err := os.UserHomeDir() + if err != nil { + log.Fatalf("cannot read user home dir") + } + kubeConfigPath := homeDir + "/.kube/config" + + //k8s go client init logic + config, err := clientcmd.BuildConfigFromFlags("", kubeConfigPath) + if err != nil { + log.Fatalf("cannot read kubeConfig from path: %s", err) + } + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + log.Fatalf("cannot initialize k8s client: %s", err) + } + + // Sanity check - query if daemonset with es-init is installed. It has a specific name, silta-cluster-ds in silta-cluster namespace + _, err = clientset.AppsV1().DaemonSets("silta-cluster").Get(context.TODO(), "silta-cluster-ds", v1.GetOptions{}) + if err != nil { + log.Fatal("Elasticsearch init value daemonset is not created, ", err) + } + + // Select all statefulsets in the namespace + listOptions := v1.ListOptions{ + LabelSelector: "chart=elasticsearch", + } + statefulsets, err := clientset.AppsV1().StatefulSets(namespace).List(context.TODO(), listOptions) + if err != nil { + log.Fatalf("cannot get statefulsets: %s", err) + } + + fmt.Printf("Elasticsearch statefulsets: %d\n", len(statefulsets.Items)) + + patchCounter := 0 + + // Loop through all statefulsets and remove es-init container + for _, statefulset := range statefulsets.Items { + + // If statefulset has configure-sysctl initContainer, remove it + for i, container := range statefulset.Spec.Template.Spec.InitContainers { + if container.Name == "configure-sysctl" { + fmt.Printf("Removing %s/%s/configure-sysctl ... ", statefulset.Namespace, statefulset.Name) + + if dryRun { + fmt.Printf("skipping, dry-run is enabled\n") + } else { + + // Patch statefulset, apply removal of initContainer + patch := []byte(fmt.Sprintf(`[{"op": "remove", "path": "/spec/template/spec/initContainers/%d"}]`, i)) + _, err = clientset.AppsV1().StatefulSets(statefulset.Namespace).Patch(context.TODO(), statefulset.Name, types.JSONPatchType, patch, v1.PatchOptions{}) + if err != nil { + log.Printf("cannot patch statefulset, %s", err) + } + + fmt.Printf("removed\n") + } + + patchCounter++ + } + } + } + fmt.Printf("Total statefulsets patched: %d\n", patchCounter) + }, +} + +func init() { + ciScriptsEsinitRemoveCmd.Flags().String("namespace", "", "Namespace (optional)") + ciScriptsEsinitRemoveCmd.Flags().Bool("dry-run", true, "Dry run") + + ciScriptsCmd.AddCommand(ciScriptsEsinitRemoveCmd) +} From 85ef2a57a29ff3f10842f343ebbeadc4e6b79479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=81nis=20Bebr=C4=ABtis?= Date: Wed, 4 Sep 2024 20:57:28 +0300 Subject: [PATCH 2/2] counter adjustments --- cmd/ciScriptsEsInitRemove.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/ciScriptsEsInitRemove.go b/cmd/ciScriptsEsInitRemove.go index 9bbeda9..f92de68 100644 --- a/cmd/ciScriptsEsInitRemove.go +++ b/cmd/ciScriptsEsInitRemove.go @@ -63,6 +63,7 @@ var ciScriptsEsinitRemoveCmd = &cobra.Command{ fmt.Printf("Elasticsearch statefulsets: %d\n", len(statefulsets.Items)) + matchCounter := 0 patchCounter := 0 // Loop through all statefulsets and remove es-init container @@ -84,13 +85,16 @@ var ciScriptsEsinitRemoveCmd = &cobra.Command{ log.Printf("cannot patch statefulset, %s", err) } + patchCounter++ + fmt.Printf("removed\n") } - patchCounter++ + matchCounter++ } } } + fmt.Printf("Total statefulsets matched: %d\n", matchCounter) fmt.Printf("Total statefulsets patched: %d\n", patchCounter) }, }