# ========================================================================================= # Copyright (C) 2021 Orange & contributors # # This program is free software; you can redistribute it and/or modify it under the terms # of the GNU Lesser General Public License as published by the Free Software Foundation; # either version 3 of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License along with this # program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth # Floor, Boston, MA 02110-1301, USA. # ========================================================================================= variables: # Docker Image with Helm CLI tool (can be overridden) HELM_CLI_IMAGE: "alpine/helm" HELM_YAMLLINT_IMAGE: "cytopia/yamllint" # HELM_LINT_DISABLED: "true" # HELM_YAMLLINT_DISABLED: "true" HELM_YAMLLINT_CONFIG: "{extends: relaxed, rules: {line-length: {max: 160}}}" HELM_YAMLLINT_ARGS: "-f colored --strict" HELM_LINT_ARGS: "lint --strict" HELM_DEPENDENCY_ARGS: "dependency update" HELM_KUBE_SCORE_IMAGE: "zegl/kube-score:latest-helm3" HELM_CHART_DIR: "." HELM_PACKAGE_ARGS: "package --dependency-update" HELM_PUBLISH_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/helm/api/release/charts" HELM_PUBLISH_SNAPSHOT_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/helm/api/snapshot/charts" HELM_REPO_PUBLISH_METHOD: "POST" HELM_REPOS: "stable@https://charts.helm.sh/stable bitnami@https://charts.bitnami.com/bitnami" HELM_ENV_VALUE_NAME: env HELM_HOSTNAME_VALUE_NAME: hostname # Will work with gitlab Kubernetes integration (per env variables) # KUBE_NAMESPACE: "default" # KUBECONFIG: "" # HELM_REVIEW_DISABLED: "true" # HELM_REVIEW_VALUES: "values-review.yml" # HELM_REVIEW_NAMESPACE: "" # HELM_REVIEW_KUBE_CONFIG: "" # HELM_INTEG_DISABLED: "true" # HELM_INTEG_VALUES: "values-review.yml" # HELM_INTEG_NAMESPACE: "" # HELM_INTEG_KUBE_CONFIG: "" # HELM_STAGING_DISABLED: "true" # HELM_STAGING_VALUES: "values-staging.yml" # HELM_STAGING_NAMESPACE: "" # HELM_STAGING_KUBE_CONFIG: "" # HELM_PROD_VALUES: "values-prod.yml" # HELM_PROD_NAMESPACE: "" # HELM_PROD_KUBE_CONFIG: "" HELM_DEPLOY_ARGS: "upgrade --install --atomic --timeout 120s" HELM_DELETE_ARGS: "uninstall" HELM_TEST_ARGS: "test" # HELM_DEPLOY_CHART: "" # HELM_DEPLOY_REPO_NAME: "my-repo-name" # HELM_DEPLOY_REPO_URL: "https://my.repo.com" # [optional] HELM_BASE_APP_NAME : the base application name (defaults to $CI_PROJECT_NAME) # [optional] HELM_REVIEW_APP_NAME : specific Helm application name in review env (defaults to $HELM_BASE_APP_NAME-$CI_COMMIT_REF_SLUG) # [optional] HELM_STAGING_APP_NAME : specific Helm application name in staging env (defaults to $HELM_BASE_APP_NAME-staging) # [optional] HELM_PROD_APP_NAME : specific Helm application name in production env (defaults to $HELM_BASE_APP_NAME) HELM_BASE_APP_NAME: "$CI_PROJECT_NAME" HELM_REVIEW_ENVIRONMENT_SCHEME: "https" # default production ref name (pattern) PROD_REF: '/^(master|main)$/' # default integration ref name (pattern) INTEG_REF: '/^develop$/' stages: - test - package-build - deploy - publish - production - acceptance .helm-scripts: &helm-scripts | # BEGSCRIPT set -e function log_info() { echo -e "[\\e[1;94mINFO\\e[0m] $*" } function log_warn() { echo -e "[\\e[1;93mWARN\\e[0m] $*" } function log_error() { echo -e "[\\e[1;91mERROR\\e[0m] $*" } function fail() { log_error "$*" exit 1 } function assert_defined() { if [[ -z "$1" ]] then log_error "$2" exit 1 fi } function install_ca_certs() { certs=$1 if [[ -z "$certs" ]] then return fi # import in system if echo "$certs" >> /etc/ssl/certs/ca-certificates.crt then log_info "CA certificates imported in \\e[33;1m/etc/ssl/certs/ca-certificates.crt\\e[0m" fi if echo "$certs" >> /etc/ssl/cert.pem then log_info "CA certificates imported in \\e[33;1m/etc/ssl/cert.pem\\e[0m" fi } function unscope_variables() { _scoped_vars=$(env | awk -F '=' "/^scoped__[a-zA-Z0-9_]+=/ {print \$1}" | sort) if [[ -z "$_scoped_vars" ]]; then return; fi log_info "Processing scoped variables..." for _scoped_var in $_scoped_vars do _fields=${_scoped_var//__/:} _condition=$(echo "$_fields" | cut -d: -f3) case "$_condition" in if) _not="";; ifnot) _not=1;; *) log_warn "... unrecognized condition \\e[1;91m$_condition\\e[0m in \\e[33;1m${_scoped_var}\\e[0m" continue ;; esac _target_var=$(echo "$_fields" | cut -d: -f2) _cond_var=$(echo "$_fields" | cut -d: -f4) _cond_val=$(eval echo "\$${_cond_var}") _test_op=$(echo "$_fields" | cut -d: -f5) case "$_test_op" in defined) if [[ -z "$_not" ]] && [[ -z "$_cond_val" ]]; then continue; elif [[ "$_not" ]] && [[ "$_cond_val" ]]; then continue; fi ;; equals|startswith|endswith|contains|in|equals_ic|startswith_ic|endswith_ic|contains_ic|in_ic) # comparison operator # sluggify actual value _cond_val=$(echo "$_cond_val" | tr '[:punct:]' '_') # retrieve comparison value _cmp_val_prefix="scoped__${_target_var}__${_condition}__${_cond_var}__${_test_op}__" _cmp_val=${_scoped_var#"$_cmp_val_prefix"} # manage 'ignore case' if [[ "$_test_op" == *_ic ]] then # lowercase everything _cond_val=$(echo "$_cond_val" | tr '[:upper:]' '[:lower:]') _cmp_val=$(echo "$_cmp_val" | tr '[:upper:]' '[:lower:]') fi case "$_test_op" in equals*) if [[ -z "$_not" ]] && [[ "$_cond_val" != "$_cmp_val" ]]; then continue; elif [[ "$_not" ]] && [[ "$_cond_val" == "$_cmp_val" ]]; then continue; fi ;; startswith*) if [[ -z "$_not" ]] && [[ "$_cond_val" != "$_cmp_val"* ]]; then continue; elif [[ "$_not" ]] && [[ "$_cond_val" == "$_cmp_val"* ]]; then continue; fi ;; endswith*) if [[ -z "$_not" ]] && [[ "$_cond_val" != *"$_cmp_val" ]]; then continue; elif [[ "$_not" ]] && [[ "$_cond_val" == *"$_cmp_val" ]]; then continue; fi ;; contains*) if [[ -z "$_not" ]] && [[ "$_cond_val" != *"$_cmp_val"* ]]; then continue; elif [[ "$_not" ]] && [[ "$_cond_val" == *"$_cmp_val"* ]]; then continue; fi ;; in*) if [[ -z "$_not" ]] && [[ "__${_cmp_val}__" != *"__${_cond_val}__"* ]]; then continue; elif [[ "$_not" ]] && [[ "__${_cmp_val}__" == *"__${_cond_val}__"* ]]; then continue; fi ;; esac ;; *) log_warn "... unrecognized test operator \\e[1;91m${_test_op}\\e[0m in \\e[33;1m${_scoped_var}\\e[0m" continue ;; esac # matches _val=$(eval echo "\$${_target_var}") log_info "... apply \\e[32m${_target_var}\\e[0m from \\e[32m\$${_scoped_var}\\e[0m${_val:+ (\\e[33;1moverwrite\\e[0m)}" _val=$(eval echo "\$${_scoped_var}") export "${_target_var}"="${_val}" done log_info "... done" } # evaluate and export a secret # - $1: secret variable name function eval_secret() { name=$1 value=$(eval echo "\$${name}") case "$value" in @b64@*) decoded=$(mktemp) errors=$(mktemp) if echo "$value" | cut -c6- | base64 -d > "${decoded}" 2> "${errors}" then # shellcheck disable=SC2086 export ${name}="$(cat ${decoded})" log_info "Successfully decoded base64 secret \\e[33;1m${name}\\e[0m" else fail "Failed decoding base64 secret \\e[33;1m${name}\\e[0m:\\n$(sed 's/^/... /g' "${errors}")" fi ;; @hex@*) decoded=$(mktemp) errors=$(mktemp) if echo "$value" | cut -c6- | sed 's/\([0-9A-F]\{2\}\)/\\\\x\1/gI' | xargs printf > "${decoded}" 2> "${errors}" then # shellcheck disable=SC2086 export ${name}="$(cat ${decoded})" log_info "Successfully decoded hexadecimal secret \\e[33;1m${name}\\e[0m" else fail "Failed decoding hexadecimal secret \\e[33;1m${name}\\e[0m:\\n$(sed 's/^/... /g' "${errors}")" fi ;; @url@*) url=$(echo "$value" | cut -c6-) if command -v curl > /dev/null then decoded=$(mktemp) errors=$(mktemp) if curl -s -S -f --connect-timeout 5 -o "${decoded}" "$url" 2> "${errors}" then # shellcheck disable=SC2086 export ${name}="$(cat ${decoded})" log_info "Successfully curl'd secret \\e[33;1m${name}\\e[0m" else log_warn "Failed getting secret \\e[33;1m${name}\\e[0m:\\n$(sed 's/^/... /g' "${errors}")" fi elif command -v wget > /dev/null then decoded=$(mktemp) errors=$(mktemp) if wget -T 5 -O "${decoded}" "$url" 2> "${errors}" then # shellcheck disable=SC2086 export ${name}="$(cat ${decoded})" log_info "Successfully wget'd secret \\e[33;1m${name}\\e[0m" else log_warn "Failed getting secret \\e[33;1m${name}\\e[0m:\\n$(sed 's/^/... /g' "${errors}")" fi else log_warn "Couldn't get secret \\e[33;1m${name}\\e[0m: no http client found" fi ;; esac } function eval_all_secrets() { encoded_vars=$(env | grep -Ev '(^|.*_ENV_)scoped__' | awk -F '=' '/^[a-zA-Z0-9_]*=@(b64|hex|url)@/ {print $1}') for var in $encoded_vars do eval_secret "$var" done } function get_helm_config_opt() { echo -n "--registry-config $CI_PROJECT_DIR/.config/helm/registry.json " echo -n "--repository-cache $CI_PROJECT_DIR/.cache/helm/repository " echo -n "--repository-config $CI_PROJECT_DIR/.config/helm/repositories.yaml " } function setup_kubeconfig() { if [ -n "$1" ]; then export KUBECONFIG="$CI_PROJECT_DIR/.kubeconfig" echo "$1" > "$KUBECONFIG" log_info "--- using \\e[32mKUBECONFIG\\e[0m provided by env variables" elif [ -n "$KUBECONFIG" ]; then log_info "--- using \\e[32mKUBECONFIG\\e[0m provided by GitLab" else log_warn "No \\e[32mKUBECONFIG\\e[0m configuration found!" fi } function add_helm_repositories() { # Use cacheable folders mkdir -p "$CI_PROJECT_DIR/.config/helm/" mkdir -p "$CI_PROJECT_DIR/.cache/helm/repository/" ln -s "$CI_PROJECT_DIR/.cache" ~/.cache ln -s "$CI_PROJECT_DIR/.config" ~/.config helm_opts=$(get_helm_config_opt) # Install helm repositories for repo in $HELM_REPOS do repo_name=$(echo "$repo" | cut -d@ -f 1) repo_url=$(echo "$repo" | cut -d@ -f 2) log_info "--- add repository \\e[32m${repo_name}\\e[0m: \\e[33;1m${repo_url}\\e[0m" # shellcheck disable=SC2086 helm $helm_opts repo add "$repo_name" "$repo_url" done # shellcheck disable=SC2086 helm $helm_opts repo update } function awkenvsubst() { awk '{while(match($0,"[$]{[^}]*}")) {var=substr($0,RSTART+2,RLENGTH -3);val=ENVIRON[var];gsub(/["\\]/,"\\\\&", val);gsub("\n", "\\n", val);gsub("\r", "\\r", val);gsub("[$]{"var"}",val)}}1' } # deploy application function deploy() { export env=$1 export appname=$2 # extract hostname from $CI_ENVIRONMENT_URL hostname=$(echo "$CI_ENVIRONMENT_URL" | awk -F[/:] '{print $4}') export hostname namespace=$3 values_files=$4 log_info "--- \\e[32mdeploy\\e[0m (env: \\e[33;1m${env}\\e[0m)" log_info "--- appname: \\e[33;1m${appname}\\e[0m" log_info "--- env: \\e[33;1m${env}\\e[0m" log_info "--- hostname: \\e[33;1m${hostname}\\e[0m" helm_opts=$(get_helm_config_opt) if [ -n "$values_files" ]; then log_info "--- using \\e[32mvalues\\e[0m file: \\e[33;1m${values_files}\\e[0m" awkenvsubst < "$values_files" > generated-values.yml helm_values_opt="--values generated-values.yml" fi if [ -f "$CI_PROJECT_DIR/.kubeconfig" ]; then log_info "--- using \\e[32mkubeconfig\\e[0m: \\e[33;1m$CI_PROJECT_DIR/.kubeconfig\\e[0m" helm_namespace_opt="--kubeconfig $CI_PROJECT_DIR/.kubeconfig" fi if [ -n "$namespace" ]; then log_info "--- using \\e[32mnamespace\\e[0m: \\e[33;1m${namespace}\\e[0m" helm_namespace_opt="$helm_namespace_opt --namespace $namespace" fi package=$(ls -1 ./helm_packages/*.tgz 2>/dev/null || echo "") package=${package:-$HELM_DEPLOY_CHART} if [ -z "${package}" ]; then log_error "No Chart to deploy! Please use \\e[32m\$HELM_DEPLOY_CHART\\e[0m to deploy a chart from a repository" log_error "Or check the provided variables to package your own chart!" exit 1 fi log_info "--- using \\e[32mpackage\\e[0m: \\e[33;1m${package}\\e[0m" # shellcheck disable=SC2086 helm ${TRACE+--debug} $helm_opts $helm_namespace_opt $helm_values_opt --set "${HELM_ENV_VALUE_NAME}=$env,${HELM_HOSTNAME_VALUE_NAME}=$hostname" $HELM_DEPLOY_ARGS $appname $package # finally persist environment url echo "$CI_ENVIRONMENT_URL" > environment_url.txt echo -e "environment_type=$env\\nenvironment_name=$appname\\nenvironment_url=$CI_ENVIRONMENT_URL" > helm.env } # delete application (and dependencies) function delete() { export env=$1 export appname=$2 namespace=$3 log_info "--- \\e[32mdelete\\e[0m (env: ${env})" log_info "--- appname: \\e[33;1m${appname}\\e[0m" log_info "--- env: \\e[33;1m${env}\\e[0m" helm_opts=$(get_helm_config_opt) if [ -f "$CI_PROJECT_DIR/.kubeconfig" ]; then log_info "--- using \\e[32mkubeconfig\\e[0m: \\e[33;1m$CI_PROJECT_DIR/.kubeconfig\\e[0m" helm_namespace_opt="--kubeconfig $CI_PROJECT_DIR/.kubeconfig" fi if [ -n "$namespace" ]; then log_info "--- using \\e[32mnamespace\\e[0m: \\e[33;1m${namespace}\\e[0m" helm_namespace_opt="--namespace $namespace" fi # shellcheck disable=SC2086 helm ${TRACE+--debug} $helm_opts $helm_namespace_opt $HELM_DELETE_ARGS $appname } # test application (and dependencies) function test() { export env=$1 export appname=$2 namespace=$3 log_info "--- \\e[32mtest\\e[0m (env: ${env})" log_info "--- appname: \\e[33;1m${appname}\\e[0m" log_info "--- env: \\e[33;1m${env}\\e[0m" helm_opts=$(get_helm_config_opt) if [ -f "$CI_PROJECT_DIR/.kubeconfig" ]; then log_info "--- using \\e[32mkubeconfig\\e[0m: \\e[33;1m$CI_PROJECT_DIR/.kubeconfig\\e[0m" helm_namespace_opt="--kubeconfig $CI_PROJECT_DIR/.kubeconfig" fi if [ -n "$namespace" ]; then log_info "--- using \\e[32mnamespace\\e[0m: \\e[33;1m${namespace}\\e[0m" helm_namespace_opt="--namespace $namespace" fi # shellcheck disable=SC2086 helm ${TRACE+--debug} $helm_opts $helm_namespace_opt $HELM_TEST_ARGS $appname } function maybe_install_curl() { if ! command -v curl > /dev/null then log_info "--- installing curl (required to publish Helm charts)..." apk add --no-cache curl fi } function get_latest_template_version() { tag_json=$(wget -T 5 -q -O - "$CI_API_V4_URL/projects/to-be-continuous%2F$1/repository/tags?per_page=1" || echo "") echo "$tag_json" | sed -rn 's/^.*"name":"([^"]*)".*$/\1/p' } function check_for_update() { template="$1" actual="$2" latest=$(get_latest_template_version "$template") if [[ -n "$latest" ]] && [[ "$latest" != "$actual" ]] then log_warn "\\e[1;93m=======================================================================================================\\e[0m" log_warn "\\e[93mThe template \\e[32m$template\\e[93m:\\e[33m$actual\\e[93m you're using is not up-to-date: consider upgrading to version \\e[32m$latest\\e[0m" log_warn "\\e[93m(set \$TEMPLATE_CHECK_UPDATE_DISABLED to disable this message)\\e[0m" log_warn "\\e[1;93m=======================================================================================================\\e[0m" fi } if [[ "$TEMPLATE_CHECK_UPDATE_DISABLED" != "true" ]]; then check_for_update helm "2.1.0"; fi unscope_variables eval_all_secrets # ENDSCRIPT # job prototype # defines default Docker image, tracking probe, cache policy and tags .helm-base: image: name: $HELM_CLI_IMAGE entrypoint: [""] services: - name: "$CI_REGISTRY/to-be-continuous/tools/tracking:master" command: ["--service", "helm", "2.1.0" ] before_script: - *helm-scripts - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}" cache: key: "$CI_COMMIT_REF_SLUG-helm" paths: - .cache - .config .helm-values-lint: extends: .helm-base image: name: $HELM_YAMLLINT_IMAGE entrypoint: [""] stage: test .helm-score: extends: .helm-base image: name: $HELM_KUBE_SCORE_IMAGE entrypoint: [""] stage: test before_script: - *helm-scripts - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}" - helm $HELM_DEPENDENCY_ARGS $HELM_CHART_DIR # ================================================== # Stage: check # ================================================== # lint-job is used to check the syntax of the Helm Chart for best practices. helm-lint: extends: .helm-base stage: test before_script: - *helm-scripts - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}" - add_helm_repositories script: - helm $HELM_DEPENDENCY_ARGS $HELM_CHART_DIR - helm ${TRACE+--debug} $HELM_LINT_ARGS $HELM_CHART_DIR rules: - if: '$CI_MERGE_REQUEST_ID || $HELM_LINT_DISABLED == "true"' when: never - exists: - "**/Chart.yaml" # yamllint-job is used to check the syntax of the values files. helm-values-review-lint: extends: .helm-values-lint script: - awkenvsubst < "$HELM_REVIEW_VALUES" > generated-values-review.yml - yamllint -d "$HELM_YAMLLINT_CONFIG" $HELM_YAMLLINT_ARGS generated-values-review.yml rules: - if: '$CI_MERGE_REQUEST_ID || $HELM_YAMLLINT_DISABLED == "true"' when: never # only on non-production, non-integration branches - if: '$HELM_REVIEW_VALUES && $CI_COMMIT_REF_NAME !~ $PROD_REF && $CI_COMMIT_REF_NAME !~ $INTEG_REF' helm-values-integration-lint: extends: .helm-values-lint script: - awkenvsubst < "$HELM_INTEG_VALUES" > generated-values-integration.yml - yamllint -d "$HELM_YAMLLINT_CONFIG" $HELM_YAMLLINT_ARGS generated-values-integration.yml rules: - if: '$CI_MERGE_REQUEST_ID || $HELM_YAMLLINT_DISABLED == "true"' when: never # only on integration branch(es) - if: '$HELM_INTEG_VALUES && $CI_COMMIT_REF_NAME =~ $INTEG_REF' helm-values-staging-lint: extends: .helm-values-lint script: - awkenvsubst < "$HELM_STAGING_VALUES" > generated-values-staging.yml - yamllint -d "$HELM_YAMLLINT_CONFIG" $HELM_YAMLLINT_ARGS generated-values-staging.yml rules: - if: '$CI_MERGE_REQUEST_ID || $HELM_YAMLLINT_DISABLED == "true"' when: never # only on production branch(es) - if: '$HELM_STAGING_VALUES && $CI_COMMIT_REF_NAME =~ $PROD_REF' helm-values-prod-lint: extends: .helm-values-lint script: - awkenvsubst < "$HELM_PROD_VALUES" > generated-values-prod.yml - yamllint -d "$HELM_YAMLLINT_CONFIG" $HELM_YAMLLINT_ARGS generated-values-prod.yml rules: - if: '$CI_MERGE_REQUEST_ID || $HELM_YAMLLINT_DISABLED == "true"' when: never # only on production branch(es) - if: '$HELM_PROD_VALUES && $CI_COMMIT_REF_NAME =~ $PROD_REF' helm-review-score: extends: .helm-score script: - awkenvsubst < "$HELM_REVIEW_VALUES" > generated-values-review.yml - helm template $HELM_CHART_DIR --values generated-values-review.yml | kube-score score ${HELM_KUBE_SCORE_ARGS} - rules: # exclude merge requests - if: $CI_MERGE_REQUEST_ID when: never # exclude when $HELM_KUBE_SCORE_DISABLED is set - if: '$HELM_KUBE_SCORE_DISABLED == "true"' when: never # else: only on non-production, non-integration branches + allow failure - if: $HELM_REVIEW_VALUES && $CI_COMMIT_REF_NAME !~ $PROD_REF && $CI_COMMIT_REF_NAME !~ $INTEG_REF exists: - "**/Chart.yaml" allow_failure: true helm-integration-score: extends: .helm-score script: - awkenvsubst < "$HELM_INTEG_VALUES" > generated-values-integration.yml - helm template $HELM_CHART_DIR --values generated-values-integration.yml | kube-score score ${HELM_KUBE_SCORE_ARGS} - rules: # exclude merge requests - if: $CI_MERGE_REQUEST_ID when: never # exclude when $K8S_SCORE_DISABLED is set - if: '$HELM_KUBE_SCORE_DISABLED == "true"' when: never # else: only on integration branch(es) + allow failure - if: $HELM_INTEG_VALUES && $CI_COMMIT_REF_NAME =~ $INTEG_REF exists: - "**/Chart.yaml" allow_failure: true helm-staging-score: extends: .helm-score script: - awkenvsubst < "$HELM_STAGING_VALUES" > generated-values-staging.yml - helm template $HELM_CHART_DIR --values generated-values-staging.yml | kube-score score ${HELM_KUBE_SCORE_ARGS} - rules: # exclude merge requests - if: $CI_MERGE_REQUEST_ID when: never # exclude when $K8S_SCORE_DISABLED is set - if: '$HELM_KUBE_SCORE_DISABLED == "true"' when: never # else: only on production branch(es) + allow failure - if: $HELM_STAGING_VALUES && $CI_COMMIT_REF_NAME =~ $PROD_REF exists: - "**/Chart.yaml" allow_failure: true helm-prod-score: extends: .helm-score script: - awkenvsubst < "$HELM_PROD_VALUES" > generated-values-prod.yml - helm template $HELM_CHART_DIR --values generated-values-prod.yml | kube-score score ${HELM_KUBE_SCORE_ARGS} - rules: # exclude merge requests - if: $CI_MERGE_REQUEST_ID when: never # exclude when $K8S_SCORE_DISABLED is set - if: '$HELM_KUBE_SCORE_DISABLED == "true"' when: never # else: only on production branch(es) + allow failure - if: $HELM_PROD_VALUES && $CI_COMMIT_REF_NAME =~ $PROD_REF exists: - "**/Chart.yaml" allow_failure: true # ================================================== # Stage: package-build # ================================================== helm-package: extends: .helm-base stage: package-build before_script: - *helm-scripts - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}" - add_helm_repositories script: - | if [[ "$SEMREL_INFO_ON" ]] && [[ "$SEMREL_INFO_NEXT_VERSION" ]] && [[ "$HELM_SEMREL_RELEASE_DISABLED" != "true" ]] then log_info "semantic-release info is activated, using computed next version for release: \\e[1;94m${SEMREL_INFO_NEXT_VERSION}\\e[0m" helm_version_opts="--app-version ${SEMREL_INFO_NEXT_VERSION} --version ${SEMREL_INFO_NEXT_VERSION}" fi - helm $HELM_PACKAGE_ARGS ${TRACE+--debug} $helm_version_opts $HELM_CHART_DIR --destination helm_packages - | package=$(ls -1 ./helm_packages/*.tgz 2>/dev/null || echo "") if [ -n "$HELM_PUBLISH_SNAPSHOT_URL" ] && [ -n "${package}" ] then package_file=$(basename ${package}) log_info "publishing helm chart ${package_file} to snapshot url: ${HELM_PUBLISH_SNAPSHOT_URL}" username="${HELM_REPO_SNAPSHOT_USER:-${HELM_REPO_USER:-$CI_REGISTRY_USER}}" password="${HELM_REPO_SNAPSHOT_PASSWORD:-${HELM_REPO_PASSWORD:-$CI_REGISTRY_PASSWORD}}" method="${HELM_REPO_SNAPSHOT_PUBLISH_METHOD:-$HELM_REPO_PUBLISH_METHOD}" if [[ "$method" == "POST" ]] then maybe_install_curl curl --fail --request POST --form "chart=@${package}" --user "$username:$password" $HELM_PUBLISH_SNAPSHOT_URL else wget -v --method=PUT --user="$username" --password="$password" --body-file="${package}" "$HELM_PUBLISH_SNAPSHOT_URL/${package_file}" -O - fi fi rules: - if: $CI_MERGE_REQUEST_ID when: never - exists: - "**/Chart.yaml" artifacts: expire_in: 1 week paths: - helm_packages/ # ================================================== # Stage: publish # ================================================== helm-publish: extends: .helm-base stage: publish script: - | package=$(ls -1 ./helm_packages/*.tgz 2>/dev/null || echo "") if [ -n "$HELM_PUBLISH_URL" ] && [ -n "${package}" ] then package_file=$(basename ${package}) log_info "publishing helm chart ${package_file} to release url: ${HELM_PUBLISH_URL}" username="${HELM_REPO_RELEASE_USER:-${HELM_REPO_USER:-$CI_REGISTRY_USER}}" password="${HELM_REPO_RELEASE_PASSWORD:-${HELM_REPO_PASSWORD:-$CI_REGISTRY_PASSWORD}}" method="${HELM_REPO_RELEASE_PUBLISH_METHOD:-$HELM_REPO_PUBLISH_METHOD}" if [[ "$method" == "POST" ]] then maybe_install_curl curl --fail --request POST --form "chart=@${package}" --user "$username:$password" $HELM_PUBLISH_URL else wget -v --method=PUT --user="$username" --password="$password" --body-file="${package}" "$HELM_PUBLISH_URL/${package_file}" -O - fi else log_error "No Chart to deploy! url is: $HELM_PUBLISH_URL, and package found is: ${package}" fi rules: - if: $CI_MERGE_REQUEST_ID || $HELM_PUBLISH_URL == null || $CI_COMMIT_REF_NAME !~ $PROD_REF when: never - if: $AUTODEPLOY_TO_PROD # else: manual + blocking - if: $CI_COMMIT_REF_NAME # this 'if' is useless but only prevents GitLab warning :( when: manual # /!\ variables can't be used in rules:exists - exists: - "**/Chart.yaml" # Deploy job prototype # Can be extended to define a concrete environment # # @arg ENV_TYPE : environment type # @arg ENV_APP_NAME : env-specific application name # @arg ENV_APP_SUFFIX: env-specific application suffix # @arg ENV_KUBE_CONFIG: env-specific Kubeconfig # @arg ENV_NAMESPACE : env-specific Kubernetes namespace # @arg ENV_VALUES : env-specific Helm values .helm-deploy: extends: .helm-base stage: deploy variables: ENV_APP_SUFFIX: "-$CI_ENVIRONMENT_SLUG" before_script: - *helm-scripts - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}" - assert_defined "${ENV_KUBE_CONFIG:-${HELM_DEFAULT_KUBE_CONFIG:-${KUBECONFIG}}}" 'Missing required env $ENV_KUBE_CONFIG or $HELM_DEFAULT_KUBE_CONFIG' - add_helm_repositories - setup_kubeconfig "${ENV_KUBE_CONFIG:-${HELM_DEFAULT_KUBE_CONFIG}}" script: - deploy $ENV_TYPE "${ENV_APP_NAME:-${HELM_BASE_APP_NAME}${ENV_APP_SUFFIX}}" "${ENV_NAMESPACE:-${KUBE_NAMESPACE}}" "$ENV_VALUES" artifacts: name: "$ENV_TYPE env url for $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG" paths: - environment_url.txt reports: dotenv: helm.env resource_group: $CI_ENVIRONMENT_NAME # Cleanup job prototype # Can be extended for each deletable environment # # @arg ENV_TYPE : environment type # @arg ENV_APP_NAME : env-specific application name # @arg ENV_APP_SUFFIX: env-specific application suffix # @arg ENV_KUBE_CONFIG: env-specific Kubeconfig # @arg ENV_NAMESPACE : env-specific Kubernetes namespace .helm-cleanup: extends: .helm-base stage: deploy # force no dependencies dependencies: [] variables: ENV_APP_SUFFIX: "-$CI_ENVIRONMENT_SLUG" before_script: - *helm-scripts - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}" - assert_defined "${ENV_KUBE_CONFIG:-${HELM_DEFAULT_KUBE_CONFIG:-${KUBECONFIG}}}" 'Missing required Kubeconfig' - setup_kubeconfig "${ENV_KUBE_CONFIG:-${HELM_DEFAULT_KUBE_CONFIG}}" script: - delete $ENV_TYPE "${ENV_APP_NAME:-${HELM_BASE_APP_NAME}${ENV_APP_SUFFIX}}" "${ENV_NAMESPACE:-${KUBE_NAMESPACE}}" environment: action: stop resource_group: $CI_ENVIRONMENT_NAME # Test job prototype # Can be extended to define a concrete environment # # @arg ENV_TYPE : environment type # @arg ENV_KUBE_CONFIG: env-specific Kubeconfig # @arg ENV_NAMESPACE : env-specific Kubernetes namespace .helm-test: extends: .helm-base stage: acceptance before_script: - *helm-scripts - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}" - assert_defined "${ENV_KUBE_CONFIG:-${HELM_DEFAULT_KUBE_CONFIG:-${KUBECONFIG}}}" 'Missing required Kubeconfig' - setup_kubeconfig "${ENV_KUBE_CONFIG:-${HELM_DEFAULT_KUBE_CONFIG}}" script: - test "$environment_type" "$environment_name" "${ENV_NAMESPACE:-${KUBE_NAMESPACE}}" # ================================================== # Stage: review # ================================================== # deploy to review env (only for feature branches) # enabled by default, disable this job by setting $HELM_REVIEW_DISABLED helm-review: extends: .helm-deploy variables: ENV_TYPE: review ENV_APP_NAME: "$HELM_REVIEW_APP_NAME" ENV_KUBE_CONFIG: "$HELM_REVIEW_KUBE_CONFIG" ENV_NAMESPACE: "$HELM_REVIEW_NAMESPACE" ENV_VALUES: "$HELM_REVIEW_VALUES" environment: name: review/$CI_COMMIT_REF_NAME url: "${HELM_REVIEW_ENVIRONMENT_SCHEME}://${CI_PROJECT_NAME}-${CI_ENVIRONMENT_SLUG}.${HELM_REVIEW_ENVIRONMENT_DOMAIN}" on_stop: helm-cleanup-review resource_group: review/$CI_COMMIT_REF_NAME rules: # exclude merge requests, tags and on $HELM_REVIEW_DISABLED set - if: '$HELM_REVIEW_DISABLED == "true" || $CI_MERGE_REQUEST_ID || $CI_COMMIT_TAG' when: never # only on non-production, non-integration branches - if: '$CI_COMMIT_REF_NAME !~ $PROD_REF && $CI_COMMIT_REF_NAME !~ $INTEG_REF' # stop review env (automatically triggered once branches are deleted) helm-cleanup-review: extends: .helm-cleanup variables: ENV_TYPE: review ENV_APP_NAME: "$HELM_REVIEW_APP_NAME" ENV_KUBE_CONFIG: "$HELM_REVIEW_KUBE_CONFIG" ENV_NAMESPACE: "$HELM_REVIEW_NAMESPACE" environment: name: review/$CI_COMMIT_REF_NAME action: stop resource_group: review/$CI_COMMIT_REF_NAME rules: # exclude merge requests, tags and on $HELM_REVIEW_DISABLED set - if: '$HELM_REVIEW_DISABLED == "true" || $CI_MERGE_REQUEST_ID || $CI_COMMIT_TAG' when: never # only on non-production, non-integration branches - if: '$CI_COMMIT_REF_NAME !~ $PROD_REF && $CI_COMMIT_REF_NAME !~ $INTEG_REF' when: manual allow_failure: true # test to review env (only for feature branches) # enabled by default, disable this job by setting $HELM_REVIEW_DISABLED helm-test-review: extends: .helm-test variables: ENV_TYPE: review ENV_KUBE_CONFIG: "$HELM_REVIEW_KUBE_CONFIG" ENV_NAMESPACE: "$HELM_REVIEW_NAMESPACE" rules: # exclude merge requests, tags and on $HELM_REVIEW_DISABLED set - if: '$HELM_REVIEW_DISABLED == "true" || $CI_MERGE_REQUEST_ID || $CI_COMMIT_TAG' when: never # only on non-production, non-integration branches - if: '$CI_COMMIT_REF_NAME !~ $PROD_REF && $CI_COMMIT_REF_NAME !~ $INTEG_REF && $HELM_TEST_ENABLED == "true"' # ================================================== # Stage: integration # ================================================== # deploy to integration env (only for integration branches) # enabled by default, disable this job by setting $HELM_INTEG_DISABLED helm-integration: extends: .helm-deploy variables: ENV_TYPE: integration ENV_APP_NAME: "$HELM_INTEG_APP_NAME" ENV_KUBE_CONFIG: "$HELM_INTEG_KUBE_CONFIG" ENV_NAMESPACE: "$HELM_INTEG_NAMESPACE" ENV_VALUES: "$HELM_INTEG_VALUES" environment: name: integration url: "${HELM_INTEG_ENVIRONMENT_URL}" on_stop: helm-cleanup-integration resource_group: integration rules: # exclude merge requests and on $HELM_INTEG_DISABLED set - if: '$HELM_INTEG_DISABLED == "true" || $CI_MERGE_REQUEST_ID' when: never # only on integration branch(es) - if: '$CI_COMMIT_REF_NAME =~ $INTEG_REF' # stop integration env (automatically triggered once branches are deleted) helm-cleanup-integration: extends: .helm-cleanup variables: ENV_TYPE: integration ENV_APP_NAME: "$HELM_INTEG_APP_NAME" ENV_KUBE_CONFIG: "$HELM_INTEG_KUBE_CONFIG" ENV_NAMESPACE: "$HELM_INTEG_NAMESPACE" environment: name: integration action: stop resource_group: integration rules: # exclude merge requests and on $HELM_INTEG_DISABLED set - if: '$HELM_INTEG_DISABLED == "true" || $CI_MERGE_REQUEST_ID' when: never # only on integration branch(es) - if: '$CI_COMMIT_REF_NAME =~ $INTEG_REF' when: manual allow_failure: true # test to integration env (only for integration branches) # enabled by default, disable this job by setting $HELM_INTEG_DISABLED helm-test-integration: extends: .helm-test variables: ENV_TYPE: integration ENV_KUBE_CONFIG: "$HELM_INTEG_KUBE_CONFIG" ENV_NAMESPACE: "$HELM_INTEG_NAMESPACE" ENV_VALUES: "$HELM_INTEG_VALUES" rules: # exclude merge requests and on $HELM_INTEG_DISABLED set - if: '$HELM_INTEG_DISABLED == "true" || $CI_MERGE_REQUEST_ID' when: never # only on integration branch(es) - if: '$CI_COMMIT_REF_NAME =~ $INTEG_REF && $HELM_TEST_ENABLED == "true"' # ================================================== # Stage: staging # ================================================== helm-staging: extends: .helm-deploy variables: ENV_TYPE: staging ENV_APP_NAME: "$HELM_STAGING_APP_NAME" ENV_KUBE_CONFIG: "$HELM_STAGING_KUBE_CONFIG" ENV_NAMESPACE: "$HELM_STAGING_NAMESPACE" ENV_VALUES: "$HELM_STAGING_VALUES" environment: name: staging url: "${HELM_STAGING_ENVIRONMENT_URL}" on_stop: helm-cleanup-staging resource_group: staging rules: # exclude merge requests and on $HELM_STAGING_DISABLED set - if: '$HELM_STAGING_DISABLED == "true" || $CI_MERGE_REQUEST_ID' when: never # only on production branch(es) - if: '$CI_COMMIT_REF_NAME =~ $PROD_REF' # stop staging env (automatically triggered once branches are deleted) helm-cleanup-staging: extends: .helm-cleanup variables: ENV_TYPE: staging ENV_APP_NAME: "$HELM_STAGING_APP_NAME" ENV_KUBE_CONFIG: "$HELM_STAGING_KUBE_CONFIG" ENV_NAMESPACE: "$HELM_STAGING_NAMESPACE" environment: name: staging action: stop resource_group: staging rules: # exclude merge requests and on $HELM_STAGING_DISABLED set - if: '$HELM_STAGING_DISABLED == "true" || $CI_MERGE_REQUEST_ID' when: never # only on production branch(es) - if: '$CI_COMMIT_REF_NAME =~ $PROD_REF' when: manual allow_failure: true helm-test-staging: extends: .helm-test variables: ENV_TYPE: staging ENV_KUBE_CONFIG: "$HELM_STAGING_KUBE_CONFIG" ENV_NAMESPACE: "$HELM_STAGING_NAMESPACE" ENV_VALUES: "$HELM_STAGING_VALUES" rules: # exclude merge requests and on $HELM_STAGING_DISABLED set - if: '$HELM_STAGING_DISABLED == "true" || $CI_MERGE_REQUEST_ID' when: never # only on production branch(es) - if: '$CI_COMMIT_REF_NAME =~ $PROD_REF && $HELM_TEST_ENABLED == "true" ' # ================================================== # Stage: production # ================================================== helm-production: extends: .helm-deploy stage: production variables: ENV_TYPE: production ENV_APP_NAME: "$HELM_PROD_APP_NAME" ENV_APP_SUFFIX: "" ENV_KUBE_CONFIG: "$HELM_PROD_KUBE_CONFIG" ENV_NAMESPACE: "$HELM_PROD_NAMESPACE" ENV_VALUES: "$HELM_PROD_VALUES" environment: name: production url: "${HELM_PROD_ENVIRONMENT_URL}" resource_group: production rules: # exclude merge requests - if: $CI_MERGE_REQUEST_ID when: never # exclude non-production branches - if: '$CI_COMMIT_REF_NAME !~ $PROD_REF' when: never # exclude if $HELM_PROD_DISABLED set - if: '$HELM_PROD_DISABLED == "true"' when: never # if $AUTODEPLOY_TO_PROD: auto - if: $AUTODEPLOY_TO_PROD # else if PUBLISH_ON_PROD enabled: auto (because the publish job was blocking) - if: '$PUBLISH_ON_PROD == "true" || $PUBLISH_ON_PROD == "yes"' # else: manual, blocking - if: $CI_COMMIT_REF_NAME # useless test, just to prevent GitLab warning when: manual