From a2c8ab09153bc22c2b6a25b8bbdbbe4e8b70aa2f Mon Sep 17 00:00:00 2001 From: iko Date: Wed, 29 Sep 2021 14:49:21 +0300 Subject: [PATCH 1/2] refined calling control scripts (#116) * Show errors from stdout to user in config check command * Renamed control script arguments * renamed variables --- dev/default.nix | 8 ++++---- octopod-backend/src/Octopod/Server.hs | 8 ++++---- octopod-backend/src/Octopod/Server/ControlScriptUtils.hs | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dev/default.nix b/dev/default.nix index 53ea6ce..e3cf966 100644 --- a/dev/default.nix +++ b/dev/default.nix @@ -97,9 +97,9 @@ in failScript = pkgs.writeScript "fail.sh" '' #!${pkgs.bash}/bin/bash - 1>&2 echo "You did something wrong :(" + echo "You did something wrong :(" - echo "You did something wrong, but you shouldn't see this." + 1>&2 echo "You did something wrong, but you shouldn't see this." exit 1 ''; @@ -144,9 +144,9 @@ in export CONFIG_CHECKING_COMMAND=${echoScript} export INFO_COMMAND=${infoScript} export NOTIFICATION_COMMAND=${writeScript} - export DEPLOYMENT_OVERRIDES_COMMAND=${infoScript} + export DEPLOYMENT_CONFIG_COMMAND=${infoScript} export DEPLOYMENT_KEYS_COMMAND=${infoScript} - export APPLICATION_OVERRIDES_COMMAND=${infoScript} + export APPLICATION_CONFIG_COMMAND=${infoScript} export APPLICATION_KEYS_COMMAND=${infoScript} export UNARCHIVE_COMMAND=${writeScript} export POWER_AUTHORIZATION_HEADER="123" diff --git a/octopod-backend/src/Octopod/Server.hs b/octopod-backend/src/Octopod/Server.hs index 5c38e63..6125e72 100644 --- a/octopod-backend/src/Octopod/Server.hs +++ b/octopod-backend/src/Octopod/Server.hs @@ -187,9 +187,9 @@ runOctopodServer sha = do archiveCheckingCmd <- Command . pack <$> getEnvOrDie "ARCHIVE_CHECKING_COMMAND" tagCheckingCmd <- Command . pack <$> getEnvOrDie "CONFIG_CHECKING_COMMAND" infoCmd <- Command . pack <$> getEnvOrDie "INFO_COMMAND" - dOverridesCmd <- Command . pack <$> getEnvOrDie "DEPLOYMENT_OVERRIDES_COMMAND" + dOverridesCmd <- Command . pack <$> getEnvOrDie "DEPLOYMENT_CONFIG_COMMAND" dKeysCmd <- Command . pack <$> getEnvOrDie "DEPLOYMENT_KEYS_COMMAND" - aOverridesCmd <- Command . pack <$> getEnvOrDie "APPLICATION_OVERRIDES_COMMAND" + aOverridesCmd <- Command . pack <$> getEnvOrDie "APPLICATION_CONFIG_COMMAND" aKeysCmd <- Command . pack <$> getEnvOrDie "APPLICATION_KEYS_COMMAND" unarchiveCmd <- Command . pack <$> getEnvOrDie "UNARCHIVE_COMMAND" powerAuthorizationHeader <- AuthHeader . BSC.pack <$> getEnvOrDie "POWER_AUTHORIZATION_HEADER" @@ -1109,11 +1109,11 @@ upsertDeploymentMetadatum dName dMetadata = failIfImageNotFound :: Deployment -> AppM () failIfImageNotFound dep = do cfg <- getDeploymentConfig dep - (ec, _, Stderr err, _) <- runCommandArgs configCheckingCommand =<< configCheckCommandArgs cfg dep + (ec, Stdout out, _, _) <- runCommandArgs configCheckingCommand =<< configCheckCommandArgs cfg dep case ec of ExitSuccess -> pure () ExitFailure _ -> - throwError err400 {errBody = BSL.fromStrict $ T.encodeUtf8 err} + throwError err400 {errBody = BSL.fromStrict $ T.encodeUtf8 out} -- | Helper to create an application-level error. appError :: Text -> BSL.ByteString diff --git a/octopod-backend/src/Octopod/Server/ControlScriptUtils.hs b/octopod-backend/src/Octopod/Server/ControlScriptUtils.hs index ef7229b..8ce03f8 100644 --- a/octopod-backend/src/Octopod/Server/ControlScriptUtils.hs +++ b/octopod-backend/src/Octopod/Server/ControlScriptUtils.hs @@ -255,5 +255,5 @@ overridesArgs (Config cc) = $ cc where argumentName = case knownOverrideLevel @l of - ApplicationLevel -> "--app-env-override" - DeploymentLevel -> "--deployment-override" + ApplicationLevel -> "--application-config" + DeploymentLevel -> "--deployment-config" From c8ac911f6ba6058ca18165d73860a5ab4bc34602 Mon Sep 17 00:00:00 2001 From: Alex-Sizov <45825279+Alex-Sizov@users.noreply.github.com> Date: Wed, 29 Sep 2021 17:49:36 +0300 Subject: [PATCH 2/2] New control scripts (#108) * add app and deployment overrides info scripts * overrides/keys scripts * add new scripts to the chart config * archive using scaling * archive check script + README * fix timings values + up contolscripts version * Get rid of kubectl calls * remove tags * remove status mode * rename tag_check to config_check * remove default values logic, optional name, additional checks at helm init * bump chart and app version * add keys sorting * ingress.host to ingress.hostname hardcode * add default override for wordpress * parametrize ingress host key * add gzip static + etag * config_check: logs to stderr + proper user notifications * fix bug with empty value key not being shown * rename app-env-override and deployment-override cli options * rename env configuration for octopod --- charts/octopod/Chart.yaml | 4 +- charts/octopod/templates/nginx-configmap.yaml | 10 +- .../octopod/templates/octopod-configmap.yaml | 7 +- charts/octopod/values.yaml | 11 +- helm-control-scripts/Cargo.lock | 2 +- helm-control-scripts/Cargo.toml | 26 +- helm-control-scripts/Dockerfile | 11 +- helm-control-scripts/README.md | 16 +- helm-control-scripts/src/app_keys.rs | 31 ++ helm-control-scripts/src/app_overrides.rs | 11 + helm-control-scripts/src/archive.rs | 45 +- helm-control-scripts/src/archive_check.rs | 56 +- helm-control-scripts/src/check.rs | 24 +- helm-control-scripts/src/cleanup.rs | 75 ++- .../src/{tag_check.rs => config_check.rs} | 32 +- helm-control-scripts/src/create_update.rs | 21 +- helm-control-scripts/src/deployment_keys.rs | 11 + .../src/deployment_overrides.rs | 11 + helm-control-scripts/src/info.rs | 24 +- helm-control-scripts/src/lib.rs | 477 +++++++++++++----- helm-control-scripts/src/unarchive.rs | 53 ++ 21 files changed, 715 insertions(+), 243 deletions(-) create mode 100644 helm-control-scripts/src/app_keys.rs create mode 100644 helm-control-scripts/src/app_overrides.rs rename helm-control-scripts/src/{tag_check.rs => config_check.rs} (67%) create mode 100644 helm-control-scripts/src/deployment_keys.rs create mode 100644 helm-control-scripts/src/deployment_overrides.rs create mode 100644 helm-control-scripts/src/unarchive.rs diff --git a/charts/octopod/Chart.yaml b/charts/octopod/Chart.yaml index 1c609cd..12087f7 100644 --- a/charts/octopod/Chart.yaml +++ b/charts/octopod/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: octopod description: An opensource self-hosted solution for managing multiple deployments in a Kubernetes cluster. type: application -version: 0.5.1 -appVersion: 1.3.1 +version: 0.6.0 +appVersion: 1.4 keywords: - kubernetes - octopod diff --git a/charts/octopod/templates/nginx-configmap.yaml b/charts/octopod/templates/nginx-configmap.yaml index 15f93e6..5b27f91 100644 --- a/charts/octopod/templates/nginx-configmap.yaml +++ b/charts/octopod/templates/nginx-configmap.yaml @@ -1,4 +1,5 @@ {{- $octopodAppAuthPassword := include "octopodUiAuthSecret" . -}} +{{- $etag := sha256sum (now | date "Mon Jan 2 15:04:05 MSK 2021") -}} apiVersion: v1 kind: ConfigMap metadata: @@ -14,11 +15,10 @@ data: index index.html; error_page 404 =200 /index.html; charset utf-8; - gzip on; - gzip_disable msie6; - gzip_buffers 32 4k; - gzip_comp_level 3; - gzip_types text/plain text/css application/json application/x-javascript application/javascript text/xml application/xml application/rss+xml text/javascript image/svg+xml application/vnd.ms-f ontobject application/x-font-ttf font/opentype; + location ~* \.(html|css|js|xml)$ { + gzip_static on; + add_header 'ETag' '{{ $etag }}'; + } } config.json: | { diff --git a/charts/octopod/templates/octopod-configmap.yaml b/charts/octopod/templates/octopod-configmap.yaml index 001fa7c..a64c995 100644 --- a/charts/octopod/templates/octopod-configmap.yaml +++ b/charts/octopod/templates/octopod-configmap.yaml @@ -16,8 +16,13 @@ data: CHECKING_COMMAND: {{ printf "%s/check" (include "controlScriptsPath" .) | quote }} CLEANUP_COMMAND: {{ printf "%s/cleanup" (include "controlScriptsPath" .) | quote }} ARCHIVE_CHECKING_COMMAND: {{ printf "%s/archive_check" (include "controlScriptsPath" .) | quote }} - CONFIG_CHECKING_COMMAND: {{ printf "%s/tag_check" (include "controlScriptsPath" .) | quote }} + CONFIG_CHECKING_COMMAND: {{ printf "%s/config_check" (include "controlScriptsPath" .) | quote }} INFO_COMMAND: {{ printf "%s/info" (include "controlScriptsPath" .) | quote }} + DEPLOYMENT_CONFIG_COMMAND: {{ printf "%s/deployment_overrides" (include "controlScriptsPath" .) | quote }} + DEPLOYMENT_KEYS_COMMAND: {{ printf "%s/deployment_keys" (include "controlScriptsPath" .) | quote }} + APPLICATION_CONFIG_COMMAND: {{ printf "%s/app_overrides" (include "controlScriptsPath" .) | quote }} + APPLICATION_KEYS_COMMAND: {{ printf "%s/app_keys" (include "controlScriptsPath" .) | quote }} + UNARCHIVE_COMMAND: {{ printf "%s/unarchive" (include "controlScriptsPath" .) | quote }} {{- range $name, $value := .Values.octopod.env }} {{ $name }}: {{ $value | quote }} {{- end }} diff --git a/charts/octopod/values.yaml b/charts/octopod/values.yaml index 142f103..b99e727 100644 --- a/charts/octopod/values.yaml +++ b/charts/octopod/values.yaml @@ -79,27 +79,28 @@ octopod: projectName: Octopod deploymentNamespace: octopod-deployment baseDomain: "" - statusUpdateTimeout: 600 - archiveRetention: 1209600 + statusUpdateTimeout: "600" + archiveRetention: "1209600" migrations: enabled: true env: HELM_BIN: "/utils/helm" - KUBECTL_BIN: "/utils/kubectl" DEFAULTS: | { "chart_name": "wordpress", "chart_repo_name": "bitnami", "chart_repo_url": "https://charts.bitnami.com/bitnami", "chart_version": "12.0.0", - "default_overrides": [] + "default_overrides": [ + "ingress.enabled=true" + ] } vaultEnv: {} controlScripts: image: repository: typeable/octopod-helm-control-scripts pullPolicy: IfNotPresent - tag: 0.1.0 + tag: 0.2.0 sqitch: image: repository: typeable/sqitch diff --git a/helm-control-scripts/Cargo.lock b/helm-control-scripts/Cargo.lock index 3ae85ea..6c60615 100644 --- a/helm-control-scripts/Cargo.lock +++ b/helm-control-scripts/Cargo.lock @@ -465,7 +465,7 @@ dependencies = [ [[package]] name = "helm-control-scripts" -version = "0.1.0" +version = "0.2.0" dependencies = [ "dkregistry", "env_logger", diff --git a/helm-control-scripts/Cargo.toml b/helm-control-scripts/Cargo.toml index ad7ef0f..f17efed 100644 --- a/helm-control-scripts/Cargo.toml +++ b/helm-control-scripts/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "helm-control-scripts" -version = "0.1.0" +version = "0.2.0" authors = ["Aleksei Sizov "] edition = "2018" @@ -54,5 +54,25 @@ name = "init" path = "src/init.rs" [[bin]] -name = "tag_check" -path = "src/tag_check.rs" \ No newline at end of file +name = "config_check" +path = "src/config_check.rs" + +[[bin]] +name = "app_overrides" +path = "src/app_overrides.rs" + +[[bin]] +name = "deployment_overrides" +path = "src/deployment_overrides.rs" + +[[bin]] +name = "deployment_keys" +path = "src/deployment_keys.rs" + +[[bin]] +name = "app_keys" +path = "src/app_keys.rs" + +[[bin]] +name = "unarchive" +path = "src/unarchive.rs" diff --git a/helm-control-scripts/Dockerfile b/helm-control-scripts/Dockerfile index 98b4213..b00f645 100644 --- a/helm-control-scripts/Dockerfile +++ b/helm-control-scripts/Dockerfile @@ -1,17 +1,13 @@ FROM alpine:3 ARG HELM_BIN=/utils/helm -ARG KUBECTL_BIN=/utils/kubectl ENV HELM_BIN=$HELM_BIN -ENV KUBECTL_BIN=$KUBECTL_BIN ADD https://get.helm.sh/helm-v3.6.0-linux-amd64.tar.gz /tmp/helm.tar.gz -ADD https://dl.k8s.io/release/v1.21.0/bin/linux/amd64/kubectl $KUBECTL_BIN RUN tar -xf /tmp/helm.tar.gz -C /tmp &&\ mkdir -p /utils &&\ cp /tmp/linux-amd64/helm $HELM_BIN &&\ chmod +x $HELM_BIN &&\ - chmod +x $KUBECTL_BIN &&\ rm -r /tmp/* ADD target/x86_64-unknown-linux-musl/release/archive /utils/ @@ -22,4 +18,9 @@ ADD target/x86_64-unknown-linux-musl/release/create /utils/ ADD target/x86_64-unknown-linux-musl/release/info /utils/ ADD target/x86_64-unknown-linux-musl/release/update /utils/ ADD target/x86_64-unknown-linux-musl/release/init /utils/ -ADD target/x86_64-unknown-linux-musl/release/tag_check /utils/ \ No newline at end of file +ADD target/x86_64-unknown-linux-musl/release/config_check /utils/ +ADD target/x86_64-unknown-linux-musl/release/app_overrides /utils/ +ADD target/x86_64-unknown-linux-musl/release/app_keys /utils/ +ADD target/x86_64-unknown-linux-musl/release/deployment_overrides /utils/ +ADD target/x86_64-unknown-linux-musl/release/deployment_keys /utils/ +ADD target/x86_64-unknown-linux-musl/release/unarchive /utils/ diff --git a/helm-control-scripts/README.md b/helm-control-scripts/README.md index 4ea8939..ecd63c5 100644 --- a/helm-control-scripts/README.md +++ b/helm-control-scripts/README.md @@ -7,13 +7,18 @@ You can get pre-built docker images from [hub.docker.com/r/typeable/octopod-gene - create – a script to install a helm release - update – a script to upgrade a helm release -- archive – a script to uninstall a helm release +- archive – a script to scale to zero all deployments and statefulsets +- archive_check – a script to check if all resources scaled to zero correctly +- unarchive - scale all deployments and statefulsets back (right now it will only scale to 1 and not to previous value) - info – a script to print comma-separated pairs which will be placed as links in the Octopod Web UI - cleanup – a script to delete dangling PVCs and letsencrypt certs left after `helm uninstall` command - check – a script to check if resources created by helm are healthy. Right now only deployments and statefuls sets are checked -- archive_check – a script to check if `helm uninstall` really deleted the release - init – a script to initialize something. This is the only script not run by Octopod, but as an init container. -- tag_check – a script to check that images in helm release are present in registries before invoking create script +- config_check – a script to check that configuration (such as image tag name) passed to Octopod is correct. This script is invoked before deployment creation. +- app_overrides - a scirpt which returns a list of default app overrides wichh were passed in default_overrides parameter described below. +- deployment_overrides - a scirpt which returns a list of default deployment overrides from default paramaters option. +- deployment_keys - a scripts which returns a list of the possible deployment overrides keys user can use. +- app_keys - a scripts which returns a list of the possible app overrides keys. These are parsed from an output of the `helm show values` command. ### Parameters @@ -22,10 +27,11 @@ All scipts accept common parametes which are passed by Octopod when it invokes t Also, several environment variables are used to parametrize the default behavior: - HELM_BIN – the path to the `helm` executable -- KUBECTL_BIN – the path to the `kubectl` executable - HELM_USER – the (optional) user for a private helm registry - HELM_PASS – the (optional) password for a private helm registry - DEFAUTLS – the json with the default parameters (described below) +- HELM_ON_INIT_ONLY - run helm add and update repository as a part of init script execution only. Otherwise `helm repo add` and `helm repo update` will be executed every time before any other helm command. +- INGRESS_HOST_KEY - key name which will be populated with domain name generated for the Octopod deployment. Defaults to `ingress.hostname`. #### Default parameters - default_overrides – an array with key-value pairs which will be passed as a `--set` flags for helm for each deployment @@ -34,4 +40,4 @@ Also, several environment variables are used to parametrize the default behavior - chart_version – the version of a chart you want to install - chart_name – the name of a chart you want to install -These parameters, if set up in the `DEFAULTS` variable, will be passed to every deployment unless overridden in the "deployment overrides" section of an Octopod deployment configuration. \ No newline at end of file +These parameters, if set up in the `DEFAULTS` variable, will be passed to every deployment unless overridden in the "deployment overrides" section of an Octopod deployment configuration. diff --git a/helm-control-scripts/src/app_keys.rs b/helm-control-scripts/src/app_keys.rs new file mode 100644 index 0000000..37b3917 --- /dev/null +++ b/helm-control-scripts/src/app_keys.rs @@ -0,0 +1,31 @@ +use helm_control_scripts::lib::*; + +fn main() { + let mut log_builder = Builder::from_default_env(); + log_builder.target(Target::Stderr).filter(None, LevelFilter::Info).init(); + info!("Utils version {}", env!("CARGO_PKG_VERSION")); + let envs = EnvVars::parse(); + info!("Env variables received {:?}", &envs); + let cli_opts = CliOpts::from_args(); + info!("Cli options received {:?}", &cli_opts); + let deployment_parameters = HelmDeploymentParameters::new(&cli_opts, &envs); + helm_init(&envs, &deployment_parameters); + let helm_values = HelmCmd { + name: envs.helm_bin, + mode: HelmMode::ShowValues, + release_name: String::from(""), + namespace: String::from(""), + deployment_parameters: deployment_parameters, + overrides: vec![], + }; + info!("Generated Helm args: {:?}", &helm_values.args()); + match helm_values.run_stdout() { + Ok(status) => { + print!("{}", print_keys(helm_values_as_keys(status))); + } + Err(status) => { + error!("Error during helm execution"); + panic!("{:?}", status); + } + } +} diff --git a/helm-control-scripts/src/app_overrides.rs b/helm-control-scripts/src/app_overrides.rs new file mode 100644 index 0000000..1fb041e --- /dev/null +++ b/helm-control-scripts/src/app_overrides.rs @@ -0,0 +1,11 @@ +use helm_control_scripts::lib::*; + +fn main() { + let mut log_builder = Builder::from_default_env(); + log_builder.target(Target::Stderr).filter(None, LevelFilter::Info).init(); + info!("Utils version {}", env!("CARGO_PKG_VERSION")); + let envs = EnvVars::parse(); + info!("Env variables received {:?}", &envs); + let default_values: DefaultValues = serde_json::from_str(&envs.defaults).unwrap(); + print!("{}", print_kv(default_values.app_overrides())); +} diff --git a/helm-control-scripts/src/archive.rs b/helm-control-scripts/src/archive.rs index c5f92ce..08a33a4 100644 --- a/helm-control-scripts/src/archive.rs +++ b/helm-control-scripts/src/archive.rs @@ -6,24 +6,45 @@ fn main() { info!("Utils version {}", env!("CARGO_PKG_VERSION")); let envs = EnvVars::parse(); info!("Env variables received {:?}", &envs); - let default_values: DefaultValues = serde_json::from_str(&envs.defaults).unwrap(); let cli_opts = CliOpts::from_args(); info!("Cli options received {:?}", &cli_opts); - let deployment_parameters = HelmDeploymentParameters::new(&cli_opts, &default_values, &envs); - let helm_cmd = HelmCmd { + let overrides = match overrides(&cli_opts, &envs) { + Some(inner) => inner, + None => vec![], + }; + + let deployment_parameters = HelmDeploymentParameters::new(&cli_opts, &envs); + let namespace = String::from(&cli_opts.namespace); + let release_name = match cli_opts.name { + Some(name) => name, + None => { + error!("mandatory name argument was not provided"); + panic!(); + } + }; + let helm_template = HelmCmd { name: envs.helm_bin, - mode: HelmMode::Uninstall, - release_name: cli_opts.name, - release_domain: String::from(""), + mode: HelmMode::Template, + release_name: release_name, namespace: cli_opts.namespace, deployment_parameters: deployment_parameters, - overrides: vec![], - default_values: vec![], - image_tag: String::from("") + overrides: overrides, }; - info!("Generated Helm args: {:?}", &helm_cmd.args()); - match helm_cmd.run() { - Ok(_status) => info!("Success!"), + info!("Generated Helm args: {:?}", &helm_template.args()); + match helm_template.run_stdout() { + Ok(status) => { + let (deployments, statefulsets, _ingresses, _old_ingresses) = match parse_to_k8s(status) { + Ok((deployments, statefulsets, ingresses, old_ingresses)) => (deployments, statefulsets, ingresses, old_ingresses), + Err(err) => panic!("{}", err) + }; + match scale(deployments, statefulsets, namespace, 0) { + Ok(_status) => info!("Success!"), + Err(status) => { + error!("Error checking statuses"); + panic!("{}", status); + } + } + } Err(status) => { error!("Error during helm execution"); panic!("{:?}", status); diff --git a/helm-control-scripts/src/archive_check.rs b/helm-control-scripts/src/archive_check.rs index 8408a22..eb5d05e 100644 --- a/helm-control-scripts/src/archive_check.rs +++ b/helm-control-scripts/src/archive_check.rs @@ -4,28 +4,50 @@ fn main() { let mut log_builder = Builder::from_default_env(); log_builder.target(Target::Stdout).filter(None, LevelFilter::Info).init(); info!("Utils version {}", env!("CARGO_PKG_VERSION")); - let cli_opts = CliOpts::from_args(); let envs = EnvVars::parse(); info!("Env variables received {:?}", &envs); - let default_values: DefaultValues = serde_json::from_str(&envs.defaults).unwrap(); - let deployment_parameters = HelmDeploymentParameters::new(&cli_opts, &default_values, &envs); - let helm_cmd = HelmCmd { + let cli_opts = CliOpts::from_args(); + info!("Cli options received {:?}", &cli_opts); + let overrides = match overrides(&cli_opts, &envs) { + Some(inner) => inner, + None => vec![], + }; + + let deployment_parameters = HelmDeploymentParameters::new(&cli_opts, &envs); + let namespace = String::from(&cli_opts.namespace); + let release_name = match cli_opts.name { + Some(name) => name, + None => { + error!("mandatory name argument was not provided"); + panic!(); + } + }; + let helm_template = HelmCmd { name: envs.helm_bin, - mode: HelmMode::Status, - release_name: cli_opts.name, - release_domain: String::from(""), + mode: HelmMode::Template, + release_name: release_name, namespace: cli_opts.namespace, deployment_parameters: deployment_parameters, - overrides: vec![], - default_values: vec![], - image_tag: String::from("") + overrides: overrides, }; - info!("Generated Helm args: {:?}", &helm_cmd.args()); - match helm_cmd.run() { - Ok(_status) => { - error!("Release is still present in cluster"); - panic!(); - }, - Err(_status) => info!("Success!"), + info!("Generated Helm args: {:?}", &helm_template.args()); + match helm_template.run_stdout() { + Ok(status) => { + let (deployments, statefulsets, _ingresses, _old_ingresses) = match parse_to_k8s(status) { + Ok((deployments, statefulsets, ingresses, old_ingresses)) => (deployments, statefulsets, ingresses, old_ingresses), + Err(err) => panic!("{}", err) + }; + match check_all(deployments, statefulsets, namespace) { + Ok(status) => { + error!("Deployment hasn't scaled down correctly"); + panic!("{:?}", status); + }, + Err(_status) => info!("Success!") + } + } + Err(status) => { + error!("Error during helm execution"); + panic!("{:?}", status); + } } } diff --git a/helm-control-scripts/src/check.rs b/helm-control-scripts/src/check.rs index 34b16a0..077339a 100644 --- a/helm-control-scripts/src/check.rs +++ b/helm-control-scripts/src/check.rs @@ -6,35 +6,29 @@ fn main() { info!("Utils version {}", env!("CARGO_PKG_VERSION")); let envs = EnvVars::parse(); info!("Env variables received {:?}", &envs); - let default_values: DefaultValues = serde_json::from_str(&envs.defaults).unwrap(); let cli_opts = CliOpts::from_args(); info!("Cli options received {:?}", &cli_opts); - let overrides = match overrides(&cli_opts) { + let overrides = match overrides(&cli_opts, &envs) { Some(inner) => inner, None => vec![], }; - - let domain_name = domain_name(&cli_opts); - info!("Domain generated for deployment: {}", &domain_name); - let deployment_parameters = HelmDeploymentParameters::new(&cli_opts, &default_values, &envs); - let image_tag = match cli_opts.tag { - Some(tag) => tag, + + let deployment_parameters = HelmDeploymentParameters::new(&cli_opts, &envs); + let namespace = String::from(&cli_opts.namespace); + let release_name = match cli_opts.name { + Some(name) => name, None => { - error!("mandatory tag argument was not provided"); + error!("mandatory name argument was not provided"); panic!(); } }; - let namespace = String::from(&cli_opts.namespace); let helm_template = HelmCmd { name: envs.helm_bin, mode: HelmMode::Template, - release_name: cli_opts.name, - release_domain: domain_name, + release_name: release_name, namespace: cli_opts.namespace, deployment_parameters: deployment_parameters, overrides: overrides, - default_values: default_values.default_overrides, - image_tag: image_tag }; info!("Generated Helm args: {:?}", &helm_template.args()); match helm_template.run_stdout() { @@ -47,7 +41,7 @@ fn main() { Ok(_status) => info!("Success!"), Err(status) => { error!("Error checking statuses"); - panic!("{}", status); + panic!("{:?}", status); } } } diff --git a/helm-control-scripts/src/cleanup.rs b/helm-control-scripts/src/cleanup.rs index 262e00f..07e2123 100644 --- a/helm-control-scripts/src/cleanup.rs +++ b/helm-control-scripts/src/cleanup.rs @@ -6,16 +6,77 @@ fn main() { info!("Utils version {}", env!("CARGO_PKG_VERSION")); let envs = EnvVars::parse(); info!("Env variables received {:?}", &envs); - let default_values: DefaultValues = serde_json::from_str(&envs.defaults).unwrap(); let cli_opts = CliOpts::from_args(); info!("Cli options received {:?}", &cli_opts); - let deployment_parameters = HelmDeploymentParameters::new(&cli_opts, &default_values, &envs); - let kubectl_cmd = KubectlCmd { - name: envs.kubectl_bin, - release_name: cli_opts.name, + let overrides = match overrides(&cli_opts, &envs) { + Some(inner) => inner, + None => vec![], + }; + + let deployment_parameters = HelmDeploymentParameters::new(&cli_opts, &envs); + let namespace = String::from(&cli_opts.namespace); + let release_name = match cli_opts.name { + Some(name) => name, + None => { + error!("mandatory name argument was not provided"); + panic!(); + } + }; + helm_init(&envs, &deployment_parameters); + let helm_cmd = HelmCmd { + name: envs.helm_bin.clone(), + mode: HelmMode::Uninstall, + release_name: release_name.clone(), + namespace: cli_opts.namespace.clone(), + deployment_parameters: deployment_parameters.clone(), + overrides: vec![], + }; + let helm_template = HelmCmd { + name: envs.helm_bin, + mode: HelmMode::Template, + release_name: release_name.clone(), namespace: cli_opts.namespace, deployment_parameters: deployment_parameters, + overrides: overrides, }; - kubectl_cmd.run(); - info!("Success!"); + info!("Generated Helm args: {:?}", &helm_cmd.args()); + match helm_template.run_stdout() { + Ok(status) => { + let (_deployments, _statefulsets, ingresses, old_ingresses) = match parse_to_k8s(status) { + Ok((deployments, statefulsets, ingresses, old_ingresses)) => (deployments, statefulsets, ingresses, old_ingresses), + Err(err) => panic!("{}", err) + }; + match helm_cmd.run() { + Ok(_status) => { + match delete_pvcs(&namespace, &format!("app.kubernetes.io/instance={}", release_name)) { + Ok(_status) => info!("All pvcs were deleted successfully"), + Err(err) => { + error!("Error deleting pvcs"); + panic!("{:?}", err); + } + } + match ingresses_to_secrets(ingresses, old_ingresses) { + Some(secrets) => { + for secret in secrets { + match delete_secret(&namespace, &secret) { + Ok(_status) => info!("Successfully deleted secret {}", &secret), + Err(error) => error!("Can't delete secret {}\n {}", &secret, error) + } + } + }, + None => info!("No secrets to delete") + } + info!("Success!"); + }, + Err(status) => { + error!("Error during helm uninstall"); + panic!("{:?}", status); + } + } + } + Err(status) => { + error!("Error during helm templating"); + panic!("{:?}", status); + } + } } diff --git a/helm-control-scripts/src/tag_check.rs b/helm-control-scripts/src/config_check.rs similarity index 67% rename from helm-control-scripts/src/tag_check.rs rename to helm-control-scripts/src/config_check.rs index 0d58e72..d049d79 100644 --- a/helm-control-scripts/src/tag_check.rs +++ b/helm-control-scripts/src/config_check.rs @@ -2,39 +2,33 @@ use helm_control_scripts::lib::*; fn main() { let mut log_builder = Builder::from_default_env(); - log_builder.target(Target::Stdout).filter(None, LevelFilter::Info).init(); + log_builder.target(Target::Stderr).filter(None, LevelFilter::Info).init(); info!("Utils version {}", env!("CARGO_PKG_VERSION")); let envs = EnvVars::parse(); info!("Env variables received {:?}", &envs); - let default_values: DefaultValues = serde_json::from_str(&envs.defaults).unwrap(); let cli_opts = CliOpts::from_args(); info!("Cli options received {:?}", &cli_opts); - let overrides = match overrides(&cli_opts) { + let overrides = match overrides(&cli_opts, &envs) { Some(inner) => inner, None => vec![], }; - let domain_name = domain_name(&cli_opts); - info!("Domain generated for deployment: {}", &domain_name); - let deployment_parameters = HelmDeploymentParameters::new(&cli_opts, &default_values, &envs); - let image_tag = match cli_opts.tag { - Some(tag) => tag, + let deployment_parameters = HelmDeploymentParameters::new(&cli_opts, &envs); + helm_init(&envs, &deployment_parameters); + let release_name = match cli_opts.name { + Some(name) => name, None => { - error!("mandatory tag argument was not provided"); + error!("mandatory name argument was not provided"); panic!(); } }; - helm_init(&envs, &deployment_parameters); let helm_template = HelmCmd { name: envs.helm_bin, mode: HelmMode::Template, - release_name: cli_opts.name, - release_domain: domain_name, + release_name: release_name, namespace: cli_opts.namespace, deployment_parameters: deployment_parameters, overrides: overrides, - default_values: default_values.default_overrides, - image_tag: image_tag }; match helm_template.run_stdout() { Ok(status) => { @@ -46,10 +40,16 @@ fn main() { Some(images) => { match check_images(images) { Ok(_) => info!("Success!"), - Err(err) => panic!("{}", err), + Err(err) => { + println!("Error checking images. Are they present in the registry?"); + panic!("{}", err); + } } }, - None => panic!("No images found!"), + None => { + println!("No images found in pods' declarations"); + panic!("No images found!"); + } } } Err(status) => { diff --git a/helm-control-scripts/src/create_update.rs b/helm-control-scripts/src/create_update.rs index 16e0eb0..b95ecf2 100644 --- a/helm-control-scripts/src/create_update.rs +++ b/helm-control-scripts/src/create_update.rs @@ -6,34 +6,29 @@ fn main() { info!("Utils version {}", env!("CARGO_PKG_VERSION")); let envs = EnvVars::parse(); info!("Env variables received {:?}", &envs); - let default_values: DefaultValues = serde_json::from_str(&envs.defaults).unwrap(); let cli_opts = CliOpts::from_args(); info!("Cli options received {:?}", &cli_opts); - let overrides = match overrides(&cli_opts) { + let overrides = match overrides(&cli_opts, &envs) { Some(inner) => inner, None => vec![], }; - let domain_name = domain_name(&cli_opts); - info!("Domain generated for deployment: {}", &domain_name); - let deployment_parameters = HelmDeploymentParameters::new(&cli_opts, &default_values, &envs); - let image_tag = match cli_opts.tag { - Some(tag) => tag, + + let deployment_parameters = HelmDeploymentParameters::new(&cli_opts, &envs); + helm_init(&envs, &deployment_parameters); + let release_name = match cli_opts.name { + Some(name) => name, None => { - error!("mandatory tag argument was not provided"); + error!("mandatory name argument was not provided"); panic!(); } }; - helm_init(&envs, &deployment_parameters); let helm_cmd = HelmCmd { name: envs.helm_bin, mode: HelmMode::UpgradeInstall, - release_name: cli_opts.name, - release_domain: domain_name, + release_name: release_name, namespace: cli_opts.namespace, deployment_parameters: deployment_parameters, overrides: overrides, - default_values: default_values.default_overrides, - image_tag: image_tag, }; info!("Generated Helm args: {:?}", &helm_cmd.args()); match helm_cmd.run() { diff --git a/helm-control-scripts/src/deployment_keys.rs b/helm-control-scripts/src/deployment_keys.rs new file mode 100644 index 0000000..536f002 --- /dev/null +++ b/helm-control-scripts/src/deployment_keys.rs @@ -0,0 +1,11 @@ +use helm_control_scripts::lib::*; + +fn main() { + let mut log_builder = Builder::from_default_env(); + log_builder.target(Target::Stderr).filter(None, LevelFilter::Info).init(); + info!("Utils version {}", env!("CARGO_PKG_VERSION")); + let envs = EnvVars::parse(); + info!("Env variables received {:?}", &envs); + let default_values: DefaultValues = serde_json::from_str(&envs.defaults).unwrap(); + print!("{}", print_keys(Some(default_values.deployment_keys()))); +} diff --git a/helm-control-scripts/src/deployment_overrides.rs b/helm-control-scripts/src/deployment_overrides.rs new file mode 100644 index 0000000..831419b --- /dev/null +++ b/helm-control-scripts/src/deployment_overrides.rs @@ -0,0 +1,11 @@ +use helm_control_scripts::lib::*; + +fn main() { + let mut log_builder = Builder::from_default_env(); + log_builder.target(Target::Stderr).filter(None, LevelFilter::Info).init(); + info!("Utils version {}", env!("CARGO_PKG_VERSION")); + let envs = EnvVars::parse(); + info!("Env variables received {:?}", &envs); + let default_values: DefaultValues = serde_json::from_str(&envs.defaults).unwrap(); + print!("{}", print_kv(Some(default_values.deployment_overrides()))); +} diff --git a/helm-control-scripts/src/info.rs b/helm-control-scripts/src/info.rs index 2df61ee..32006d4 100644 --- a/helm-control-scripts/src/info.rs +++ b/helm-control-scripts/src/info.rs @@ -6,36 +6,30 @@ fn main() { info!("Utils version {}", env!("CARGO_PKG_VERSION")); let envs = EnvVars::parse(); info!("Env variables received {:?}", &envs); - let default_values: DefaultValues = serde_json::from_str(&envs.defaults).unwrap(); let cli_opts = CliOpts::from_args(); info!("Cli options received {:?}", &cli_opts); - let overrides = match overrides(&cli_opts) { + let overrides = match overrides(&cli_opts, &envs) { Some(inner) => inner, None => vec![], }; - - let domain_name = domain_name(&cli_opts); - info!("Domain generated for deployment: {}", &domain_name); - let deployment_parameters = HelmDeploymentParameters::new(&cli_opts, &default_values, &envs); - let image_tag = match cli_opts.tag { - Some(tag) => tag, + + let deployment_parameters = HelmDeploymentParameters::new(&cli_opts, &envs); + helm_init(&envs, &deployment_parameters); + let release_name = match cli_opts.name { + Some(name) => name, None => { - error!("mandatory tag argument was not provided"); + error!("mandatory name argument was not provided"); panic!(); } }; - let default_name = String::from(&cli_opts.name); - helm_init(&envs, &deployment_parameters); + let default_name = String::from(&release_name); let helm_template = HelmCmd { name: envs.helm_bin, mode: HelmMode::Template, - release_name: cli_opts.name, - release_domain: domain_name, + release_name: release_name, namespace: cli_opts.namespace, deployment_parameters: deployment_parameters, overrides: overrides, - default_values: default_values.default_overrides, - image_tag: image_tag }; match helm_template.run_stdout() { Ok(status) => { diff --git a/helm-control-scripts/src/lib.rs b/helm-control-scripts/src/lib.rs index 3899a8a..28cbd32 100644 --- a/helm-control-scripts/src/lib.rs +++ b/helm-control-scripts/src/lib.rs @@ -1,24 +1,28 @@ pub mod lib { pub use std::io::prelude::*; pub use std::process::{Command, ExitStatus, Stdio}; + pub use std::error::Error; + pub use std::fmt; pub use structopt::StructOpt; pub use env_logger::{Builder, Target}; pub use log::{LevelFilter, info, warn, error}; pub use serde::{Serialize, Deserialize}; + pub use serde_json::json; pub use std::error; - pub use yaml_rust::{YamlLoader, YamlEmitter}; + pub use yaml_rust::{YamlLoader, YamlEmitter, Yaml}; pub use k8s_openapi::api::{ apps::v1::{Deployment, StatefulSet}, networking::v1::Ingress, - networking::v1beta1::Ingress as OldIngress + networking::v1beta1::Ingress as OldIngress, + core::v1::{PersistentVolumeClaim, Secret} }; pub use kube::{ - api::{Api, ListParams, ResourceExt}, + api::{Api, ListParams, ResourceExt, Patch, PatchParams, DeleteParams}, Client, }; pub use dkregistry::{ v2::Client as RegClient, - errors::Error + errors::Error as RegError, }; pub use regex::Regex; @@ -32,25 +36,25 @@ pub mod lib { #[structopt(long)] pub namespace: String, #[structopt(long)] - pub name: String, + pub name: Option, #[structopt(long)] pub tag: Option, #[structopt(long)] - pub app_env_override: Vec, + pub application_config: Vec, #[structopt(long)] - pub deployment_override: Vec, + pub deployment_config: Vec, } #[derive(Deserialize, Debug)] pub struct EnvVars { pub helm_bin: String, - pub kubectl_bin: String, pub defaults: String, //json of DefaultValues #[serde(default)] pub helm_user: String, #[serde(default)] pub helm_pass: String, pub helm_on_init_only: Option, + pub ingress_host_key: Option, } impl EnvVars { @@ -66,6 +70,12 @@ pub mod lib { } } + #[derive(Debug, Serialize, Deserialize)] + struct HelmRepo { + name: String, + url: String + } + #[derive(Deserialize, Debug)] pub struct DefaultValues { pub default_overrides: Vec, @@ -75,7 +85,42 @@ pub mod lib { pub chart_name: String, } - #[derive(Debug, Clone)] + impl DefaultValues { + pub fn app_overrides(&self) -> Option> { + if self.default_overrides.is_empty() { + None + } else { + let mut overrides: Vec<(String,String)> = Vec::new(); + for app_override in &self.default_overrides { + let split_override = app_override.split('=').collect::>(); + overrides.push((split_override.first().unwrap().to_string(), split_override.last().unwrap().to_string())); + } + Some(overrides) + } + } + pub fn deployment_overrides(&self) -> Vec<(String,String)> { + vec![ + (String::from("chart_repo_url"), self.chart_repo_url.clone()), + (String::from("chart_repo_name"), self.chart_repo_name.clone()), + (String::from("chart_version"), self.chart_version.clone()), + (String::from("chart_name"), self.chart_name.clone()), + ] + } + pub fn deployment_keys(&self) -> Vec { + let mut keys: Vec = vec![ + String::from("chart_repo_url"), + String::from("chart_repo_name"), + String::from("chart_version"), + String::from("chart_name"), + String::from("chart_repo_user"), + String::from("chart_repo_pass"), + ]; + keys.sort(); + keys + } + } + + #[derive(Debug, Clone, Default)] pub struct HelmDeploymentParameters { pub chart_repo_url: String, pub chart_repo_name: String, @@ -86,30 +131,23 @@ pub mod lib { } impl HelmDeploymentParameters { - pub fn new(cli_opts: &CliOpts, default_values: &DefaultValues, envs: &EnvVars) -> Self { - let mut default_parameters = Self { - chart_repo_url: default_values.chart_repo_url.clone(), - chart_repo_name: default_values.chart_repo_name.clone(), - chart_repo_user: envs.helm_user.clone(), - chart_repo_pass: envs.helm_pass.clone(), - chart_version: default_values.chart_version.clone(), - chart_name: default_values.chart_name.clone(), - }; - if cli_opts.deployment_override.is_empty() { - return default_parameters; - } else { - for deployment_override in cli_opts.deployment_override.clone().into_iter() { - let split_override = deployment_override.split('=').collect::>(); - match split_override.first().unwrap().as_ref() { - "chart_repo_url" => default_parameters.chart_repo_url = split_override.last().unwrap().to_string(), - "chart_repo_name" => default_parameters.chart_repo_name = split_override.last().unwrap().to_string(), - "chart_version" => default_parameters.chart_version = split_override.last().unwrap().to_string(), - "chart_name" => default_parameters.chart_name = split_override.last().unwrap().to_string(), - _ => continue, - } + pub fn new(cli_opts: &CliOpts, envs: &EnvVars) -> Self { + let mut deployment_parameters = Self::default(); + deployment_parameters.chart_repo_user = envs.helm_user.clone(); + deployment_parameters.chart_repo_pass = envs.helm_pass.clone(); + for deployment_override in cli_opts.deployment_config.clone().into_iter() { + let split_override = deployment_override.split('=').collect::>(); + match split_override.first().unwrap().as_ref() { + "chart_repo_url" => deployment_parameters.chart_repo_url = split_override.last().unwrap().to_string(), + "chart_repo_name" => deployment_parameters.chart_repo_name = split_override.last().unwrap().to_string(), + "chart_version" => deployment_parameters.chart_version = split_override.last().unwrap().to_string(), + "chart_name" => deployment_parameters.chart_name = split_override.last().unwrap().to_string(), + "chart_repo_user" => deployment_parameters.chart_repo_user = split_override.last().unwrap().to_string(), + "chart_repo_pass" => deployment_parameters.chart_repo_pass = split_override.last().unwrap().to_string(), + _ => continue, } - return default_parameters; } + return deployment_parameters; } pub fn new_env_only(default_values: &DefaultValues, envs: &EnvVars) -> Self { Self { @@ -126,12 +164,9 @@ pub mod lib { pub name: String, pub mode: HelmMode, pub release_name: String, - pub release_domain: String, pub namespace: String, pub deployment_parameters: HelmDeploymentParameters, pub overrides: Vec, - pub default_values: Vec, - pub image_tag: String, } impl HelmCmd { @@ -167,6 +202,13 @@ pub mod lib { args.extend(chart_version); args.extend(self.set_flag_values()); }, + HelmMode::ShowValues => { + args.extend(namespace); + args.push(String::from("show")); + args.push(String::from("values")); + args.push(chart_location); + args.extend(chart_version); + }, HelmMode::RepoAdd => { args.push(String::from("repo")); args.push(String::from("add")); @@ -180,12 +222,12 @@ pub mod lib { args.push(String::from("repo")); args.push(String::from("update")); }, - HelmMode::Status => { - args.extend(namespace); - args.push(String::from("status")); - args.push(String::from(&self.release_name)); + HelmMode::RepoList => { + args.push(String::from("repo")); + args.push(String::from("list")); + args.push(String::from("-o")); + args.push(String::from("json")); }, - _ => panic!("This helm mode is not expected"), } return args; @@ -193,9 +235,6 @@ pub mod lib { fn set_flag_values(&self) -> Vec { let mut values = Vec::new(); let mut set_values = Vec::new(); - values.push(format!("image.tag={}", &self.image_tag)); - values.push(format!("ingress.host={}", &self.release_domain)); - values.extend(self.default_values.clone()); values.extend(self.overrides.clone()); for value in values.into_iter() { set_values.push(String::from("--set")); @@ -236,58 +275,8 @@ pub mod lib { Template, RepoAdd, RepoUpdate, - Status, - } - - pub struct KubectlCmd { - pub name: String, - pub release_name: String, - pub namespace: String, - pub deployment_parameters: HelmDeploymentParameters, - } - - impl KubectlCmd { - pub fn args(&self, cmd_type: &str) -> Vec { - let mut args: Vec = Vec::new(); - let namespace = vec![String::from("-n"), String::from(&self.namespace),]; - let cert_secret_name = format!("{}-{}-tls", &self.release_name, &self.deployment_parameters.chart_name); - let label = vec![String::from("-l"), format!("app.kubernetes.io/instance={}", &self.release_name)]; - args.extend(namespace); - args.push(String::from("delete")); - match cmd_type { - "pvc" => { - args.push(String::from("pvc")); - args.extend(label); - return args; - }, - "cert" => { - args.push(String::from("secret")); - args.push(cert_secret_name); - return args; - }, - _ => unreachable!(), - } - } - pub fn run(&self) { - info!("Starting pvc cleanup"); - info!("kubectl args: {:?}", &self.args("pvc")); - let kubectl_pvc = Command::new(&self.name) - .args(&self.args("pvc")) - .output() - .expect("Failed to run"); - info!("kubectl stdout:\n {}", String::from_utf8(kubectl_pvc.stdout).unwrap()); - info!("kubectl stderr:\n {}", String::from_utf8(kubectl_pvc.stderr).unwrap()); - info!("Starting certificates cleanup"); - info!("kubectl args: {:?}", &self.args("pvc")); - let kubectl_cert = Command::new(&self.name) - .args(&self.args("cert")) - .output() - .expect("Failed to run"); - info!("kubectl stdout:\n {}", String::from_utf8(kubectl_cert.stdout).unwrap()); - info!("kubectl stderr:\n {}", String::from_utf8(kubectl_cert.stderr).unwrap()); - assert!(kubectl_pvc.status.success()); - assert!(kubectl_cert.status.success()); - } + RepoList, + ShowValues, } fn check_value(value: String) -> Result { @@ -298,9 +287,14 @@ pub mod lib { Err(format!("Override value {} is malformed", value)) } } - pub fn overrides(cli_opts: &CliOpts) -> Option> { + pub fn overrides(cli_opts: &CliOpts, envs: &EnvVars) -> Option> { let mut overrides_opts = Vec::new(); - overrides_opts.extend(&cli_opts.app_env_override); + overrides_opts.extend(&cli_opts.application_config); + let ingress_override = match &envs.ingress_host_key { + Some(key) => format!("{}={}", &key, domain_name(&cli_opts)), + None => format!("ingress.hostname={}", domain_name(&cli_opts)), + }; + overrides_opts.push(&ingress_override); if overrides_opts.is_empty() { None } else { @@ -311,8 +305,13 @@ pub mod lib { ) } } - pub fn domain_name(cli_opts: &CliOpts) -> String { - format!("{}.{}", &cli_opts.name, &cli_opts.base_domain) + fn domain_name(cli_opts: &CliOpts) -> String { + match &cli_opts.name { + Some(name) => { + return format!("{}.{}", name, &cli_opts.base_domain); + }, + None => panic!("No name argument provided") + } } pub fn parse_to_k8s(yaml: String) -> Result<(Vec, Vec, Vec, Vec), serde_yaml::Error> { let docs = YamlLoader::load_from_str(&yaml).unwrap(); @@ -356,8 +355,37 @@ pub mod lib { } return Ok((deployments, statefulsets, ingresses, old_ingresses)); } + + #[derive(Debug)] + pub struct NoReplicasError(String); + + impl fmt::Display for NoReplicasError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Deployment {} doesn't have any replicas", self.0) + } + } + impl Error for NoReplicasError {} + + #[derive(Debug)] + pub enum KubeError { + NoReplicasError(NoReplicasError), + KubeApiError(kube::Error), + } + + impl From for KubeError { + fn from(error: NoReplicasError) -> Self { + KubeError::NoReplicasError(error) + } + } + + impl From for KubeError { + fn from(error: kube::Error) -> Self { + KubeError::KubeApiError(error) + } + } + #[tokio::main] - async fn check_deployment(namespace: &str, name: &str) -> Result<(), kube::Error> { + async fn check_deployment(namespace: &str, name: &str) -> Result<(), KubeError> { let client = Client::try_default().await?; let api: Api = Api::namespaced(client, &namespace); let deployment = api.get(&name).await?; @@ -366,22 +394,22 @@ pub mod lib { match status.available_replicas { Some(replicas) => { if !replicas >= 1 { - panic!("Deployment {} doesn't have any replicas", &name); + return Err(KubeError::NoReplicasError(NoReplicasError(name.to_string()))); } }, None => { - panic!("Deployment {} doesn't have any replicas", &name); + return Err(KubeError::NoReplicasError(NoReplicasError(name.to_string()))); } } }, None => { - panic!("Unable to get status for deployment {}", &name); + return Err(KubeError::NoReplicasError(NoReplicasError(name.to_string()))); } } Ok(()) } #[tokio::main] - async fn check_statefulset(namespace: &str, name: &str) -> Result<(), kube::Error> { + async fn check_statefulset(namespace: &str, name: &str) -> Result<(), KubeError> { let client = Client::try_default().await?; let api: Api = Api::namespaced(client, &namespace); let statefulset = api.get(&name).await?; @@ -390,21 +418,21 @@ pub mod lib { match status.current_replicas { Some(replicas) => { if !replicas >= 1 { - panic!("StatefulSet {} doesn't have any replicas", &name); + return Err(KubeError::NoReplicasError(NoReplicasError(name.to_string()))); } }, None => { - panic!("StatefulSet {} doesn't have any replicas", &name); + return Err(KubeError::NoReplicasError(NoReplicasError(name.to_string()))); } } }, None => { - panic!("Unable to get status for StatefulSet {}", &name); + return Err(KubeError::NoReplicasError(NoReplicasError(name.to_string()))); } } Ok(()) } - pub fn check_all(deployments: Vec, statefulsets: Vec, namespace: String) -> Result<(), Box> { + pub fn check_all(deployments: Vec, statefulsets: Vec, namespace: String) -> Result<(), KubeError> { if deployments.is_empty() { info!("No deployments to check"); }else{ @@ -412,7 +440,7 @@ pub mod lib { match check_deployment(&namespace, &deploy.name()) { Ok(status) => status, Err(err) => { - return Err(Box::new(err)); + return Err(err); } } } @@ -425,7 +453,7 @@ pub mod lib { match check_statefulset(&namespace, &statefulset.name()) { Ok(status) => status, Err(err) => { - return Err(Box::new(err)); + return Err(err); } } } @@ -506,6 +534,18 @@ pub mod lib { } return out_string; } + pub fn print_keys(vals: Option>) -> String { + let mut out_string = String::from(""); + match vals { + Some(values) => { + for value in values { + out_string.push_str(&format!("{}\n", value)); + } + }, + None => return out_string + } + return out_string; + } //TODO: Generics!!! pub fn deployments_statefulsets_to_images(deployments: Vec, statefulsets: Vec) -> Option> { let mut images: Vec = Vec::new(); @@ -564,7 +604,7 @@ pub mod lib { } } #[tokio::main] - async fn check_image(registry: &str, repository: &str, tag: &str) -> Result<(), dkregistry::errors::Error> { + async fn check_image(registry: &str, repository: &str, tag: &str) -> Result<(), RegError> { let client = RegClient::configure() .insecure_registry(false) .registry(registry) @@ -598,7 +638,7 @@ pub mod lib { info!("Checking image: {} {} {}", registry, repository, tag); (registry, repository, tag) } - pub fn check_images(images: Vec) -> Result<(), dkregistry::errors::Error> { + pub fn check_images(images: Vec) -> Result<(), RegError> { for image in images { let (registry, repository, tag) = parse_image_name(&image); match check_image(®istry, &repository, &tag) { @@ -613,23 +653,17 @@ pub mod lib { name: String::from(&envs.helm_bin), mode: HelmMode::RepoAdd, release_name: String::from(""), - release_domain: String::from(""), namespace: String::from(""), deployment_parameters: deployment_parameters.clone(), overrides: vec![], - default_values: vec![], - image_tag: String::from(""), }; let helm_repo_update = HelmCmd { name: String::from(&envs.helm_bin), mode: HelmMode::RepoUpdate, release_name: String::from(""), - release_domain: String::from(""), namespace: String::from(""), deployment_parameters: deployment_parameters.clone(), overrides: vec![], - default_values: vec![], - image_tag: String::from(""), }; match helm_repo_add.run() { Ok(_status) => info!("Repo add success!"), @@ -652,14 +686,215 @@ pub mod lib { if *enabled { info!("Skipping helm initialization, it must be initialied on init"); } else { - info!("Starting helm initialization"); - helm_repo_add_update(&envs, &deployment_parameters); + if helm_repo_exists(&envs, &deployment_parameters) { + info!("Skipping helm initialization, because requested repo was already added"); + } else { + info!("Starting helm initialization"); + helm_repo_add_update(&envs, &deployment_parameters); + } } }, None => { - info!("Starting helm initialization"); - helm_repo_add_update(&envs, &deployment_parameters); + if helm_repo_exists(&envs, &deployment_parameters) { + info!("Skipping helm initialization, because requested repo was already added"); + } else { + info!("Starting helm initialization"); + helm_repo_add_update(&envs, &deployment_parameters); + } } } } + + fn helm_repo_exists(envs: &EnvVars, deployment_parameters: &HelmDeploymentParameters) -> bool { + let chart_repo_url = String::from(&deployment_parameters.chart_repo_url); + let chart_repo_name = String::from(&deployment_parameters.chart_repo_name); + let helm_repo_list = HelmCmd { + name: String::from(&envs.helm_bin), + mode: HelmMode::RepoList, + release_name: String::from(""), + namespace: String::from(""), + deployment_parameters: deployment_parameters.clone(), + overrides: vec![], + }; + match helm_repo_list.run_stdout() { + Ok(list) => { + let helm_repos: Vec = serde_json::from_str(&list).unwrap(); + for repo in helm_repos { + if repo.name == chart_repo_name && repo.url == chart_repo_url { + return true; + } else { + continue + } + } + }, + Err(_err) => { + return false; + } + } + false + } + pub fn helm_values_as_keys(yaml: String) -> Option> { + let mut keys: Vec = Vec::new(); + let docs = YamlLoader::load_from_str(&yaml).unwrap(); + let docs_hash = &docs[0].clone().into_hash().unwrap(); + for doc in docs_hash { + match doc.1.as_hash() { + Some(hash) => { + if hash.is_empty() { + keys.push(format!("{}", doc.0.as_str().unwrap())); + }else{ + for doc_2 in hash { + match doc_2.1.as_hash() { + Some(hash_2) => { + if hash_2.is_empty() { + keys.push(format!("{}.{}", doc.0.as_str().unwrap(), doc_2.0.as_str().unwrap())); + }else{ + for key in hash_2.keys() { + keys.push(format!("{}.{}.{}", doc.0.as_str().unwrap(), doc_2.0.as_str().unwrap(), key.as_str().unwrap())); + } + } + }, + None => keys.push(format!("{}.{}", doc.0.as_str().unwrap(), doc_2.0.as_str().unwrap())) + } + } + } + }, + None => keys.push(format!("{}", doc.0.as_str().unwrap())), + } + } + if keys.is_empty() { + None + }else{ + keys.sort(); + Some(keys) + } + } + #[tokio::main] + async fn scale_deployment(namespace: &str, name: &str, replicas: i32) -> Result<(), kube::Error> { + let client = Client::try_default().await?; + let api: Api = Api::namespaced(client, &namespace); + let deployment = api.get(&name).await?; + let mut spec = match deployment.spec { + Some(spec) => spec, + None => panic!("Deployment without any spec!") + }; + spec.replicas = Some(replicas); + let patched_deployment = Deployment { + metadata: deployment.metadata, + spec: Some(spec), + status: None + }; + let patch = Patch::Merge(patched_deployment); + let patch_params = PatchParams::apply("octopod"); + api.patch(&name, &patch_params, &patch).await?; + Ok(()) + } + #[tokio::main] + async fn scale_statefulset(namespace: &str, name: &str, replicas: i32) -> Result<(), kube::Error> { + let client = Client::try_default().await?; + let api: Api = Api::namespaced(client, &namespace); + let statefulset = api.get(&name).await?; + let mut spec = match statefulset.spec { + Some(spec) => spec, + None => panic!("StatefulSet without any spec!") + }; + spec.replicas = Some(replicas); + let patched_statefulset = StatefulSet { + metadata: statefulset.metadata, + spec: Some(spec), + status: None + }; + let patch = Patch::Merge(patched_statefulset); + let patch_params = PatchParams::apply("octopod"); + api.patch(&name, &patch_params, &patch).await?; + Ok(()) + } + pub fn scale(deployments: Vec, statefulsets: Vec, namespace: String, replicas: i32) -> Result<(), Box> { + if deployments.is_empty() { + info!("No deployments to check"); + }else{ + for deploy in deployments { + match scale_deployment(&namespace, &deploy.name(), replicas) { + Ok(status) => status, + Err(err) => { + return Err(Box::new(err)); + } + } + } + } + + if statefulsets.is_empty() { + info!("No statefulsets to check"); + }else{ + for statefulset in statefulsets { + match scale_statefulset(&namespace, &statefulset.name(), replicas) { + Ok(status) => status, + Err(err) => { + return Err(Box::new(err)); + } + } + } + } + Ok(()) + } + pub fn ingresses_to_secrets(new_ingresses: Vec, old_ingresses: Vec) -> Option> { + let mut secrets: Vec = Vec::new(); + if new_ingresses.is_empty() { + for ingress in old_ingresses { + match &ingress.spec { + Some(spec) => { + if spec.tls.is_empty() { + continue + }else{ + for tls in &spec.tls { + match &tls.secret_name { + Some(secret) => secrets.push(String::from(secret)), + None => continue + } + } + } + }, + None => continue + } + } + }else{ + for ingress in new_ingresses { + match &ingress.spec { + Some(spec) => { + if spec.tls.is_empty() { + continue + }else{ + for tls in &spec.tls { + match &tls.secret_name { + Some(secret) => secrets.push(String::from(secret)), + None => continue + } + } + } + }, + None => continue + } + } + } + if secrets.is_empty() { + None + }else{ + Some(secrets) + } + } + #[tokio::main] + pub async fn delete_pvcs(namespace: &str, selector: &str) -> Result<(), kube::Error> { + let client = Client::try_default().await?; + let api: Api = Api::namespaced(client, &namespace); + let pvc_list = ListParams::default().labels(selector); + api.delete_collection(&DeleteParams::default(), &pvc_list).await?; + Ok(()) + } + #[tokio::main] + pub async fn delete_secret(namespace: &str, secret: &str) -> Result<(), kube::Error> { + let client = Client::try_default().await?; + let api: Api = Api::namespaced(client, &namespace); + api.delete(&secret, &DeleteParams::default()).await?; + Ok(()) + } } diff --git a/helm-control-scripts/src/unarchive.rs b/helm-control-scripts/src/unarchive.rs new file mode 100644 index 0000000..799f0dd --- /dev/null +++ b/helm-control-scripts/src/unarchive.rs @@ -0,0 +1,53 @@ +use helm_control_scripts::lib::*; + +fn main() { + let mut log_builder = Builder::from_default_env(); + log_builder.target(Target::Stdout).filter(None, LevelFilter::Info).init(); + info!("Utils version {}", env!("CARGO_PKG_VERSION")); + let envs = EnvVars::parse(); + info!("Env variables received {:?}", &envs); + let cli_opts = CliOpts::from_args(); + info!("Cli options received {:?}", &cli_opts); + let overrides = match overrides(&cli_opts, &envs) { + Some(inner) => inner, + None => vec![], + }; + + let deployment_parameters = HelmDeploymentParameters::new(&cli_opts, &envs); + let namespace = String::from(&cli_opts.namespace); + let release_name = match cli_opts.name { + Some(name) => name, + None => { + error!("mandatory name argument was not provided"); + panic!(); + } + }; + let helm_template = HelmCmd { + name: envs.helm_bin, + mode: HelmMode::Template, + release_name: release_name, + namespace: cli_opts.namespace, + deployment_parameters: deployment_parameters, + overrides: overrides, + }; + info!("Generated Helm args: {:?}", &helm_template.args()); + match helm_template.run_stdout() { + Ok(status) => { + let (deployments, statefulsets, _ingresses, _old_ingresses) = match parse_to_k8s(status) { + Ok((deployments, statefulsets, ingresses, old_ingresses)) => (deployments, statefulsets, ingresses, old_ingresses), + Err(err) => panic!("{}", err) + }; + match scale(deployments, statefulsets, namespace, 1) { + Ok(_status) => info!("Success!"), + Err(status) => { + error!("Error checking statuses"); + panic!("{}", status); + } + } + } + Err(status) => { + error!("Error during helm execution"); + panic!("{:?}", status); + } + } +}