Newer
Older
# =========================================================================================
# 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.
# =========================================================================================
# default workflow rules: Merge Request pipelines
# prevent branch pipeline when an MR is open (prefer MR pipeline)
- if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS'
# test job prototype: implement adaptive pipeline rules
.test-policy:
rules:
# on tag: auto & failing
- if: $CI_COMMIT_TAG
# on ADAPTIVE_PIPELINE_DISABLED: auto & failing
- if: '$ADAPTIVE_PIPELINE_DISABLED == "true"'
# on production or integration branch(es): auto & failing
- if: '$CI_COMMIT_REF_NAME =~ $PROD_REF || $CI_COMMIT_REF_NAME =~ $INTEG_REF'
# early stage (dev branch, no MR): manual & non-failing
- if: '$CI_MERGE_REQUEST_ID == null && $CI_OPEN_MERGE_REQUESTS == null'
when: manual
allow_failure: true
# Draft MR: auto & non-failing
- if: '$CI_MERGE_REQUEST_TITLE =~ /^Draft:.*/'
allow_failure: true
# else (Ready MR): auto & failing
- when: on_success
# variabilized tracking image
TBC_TRACKING_IMAGE: "$CI_REGISTRY/to-be-continuous/tools/tracking:master"
# 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_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"
Pierre Smeyers
committed
HELM_ENV_VALUE_NAME: environmentType
HELM_HOSTNAME_VALUE_NAME: hostname
# Will work with gitlab Kubernetes integration (per env variables)
# KUBE_NAMESPACE: "default"
# KUBECONFIG: ""
# HELM_COMMON_VALUES: "values-common.yml"
# HELM_REVIEW_VALUES: "values-review.yml"
# HELM_REVIEW_NAMESPACE: ""
# HELM_REVIEW_KUBE_CONFIG: ""
# HELM_INTEG_VALUES: "values-review.yml"
# HELM_INTEG_NAMESPACE: ""
# HELM_INTEG_KUBE_CONFIG: ""
# 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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
- 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}__"
# 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;
if [[ -z "$_not" ]] && [[ "$_cond_val" != "$_cmp_val"* ]]; then continue;
elif [[ "$_not" ]] && [[ "$_cond_val" == "$_cmp_val"* ]]; then continue;
if [[ -z "$_not" ]] && [[ "$_cond_val" != *"$_cmp_val" ]]; then continue;
elif [[ "$_not" ]] && [[ "$_cond_val" == *"$_cmp_val" ]]; then continue;
if [[ -z "$_not" ]] && [[ "$_cond_val" != *"$_cmp_val"* ]]; then continue;
elif [[ "$_not" ]] && [[ "$_cond_val" == *"$_cmp_val"* ]]; then continue;
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"
}
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# 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}")"
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
}
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
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'
}
function exec_hook() {
if [[ ! -x "$1" ]] && ! chmod +x "$1"
then
log_warn "... could not make \\e[33;1m${1}\\e[0m executable: please do it (chmod +x)"
# fallback technique
sh "$1"
else
"$1"
fi
}
# deploy application
function deploy() {
export environment_type=$1
export environment_name=$2
namespace=$3
values_files=$4
# variables expansion in $environment_url
environment_url=$(echo "$environment_url" | awkenvsubst)
export environment_url
# extract hostname from $environment_url
hostname=$(echo "$environment_url" | awk -F[/:] '{print $4}')
log_info "--- \\e[32mdeploy\\e[0m"
log_info "--- \$namespace: \\e[33;1m${namespace}\\e[0m"
log_info "--- \$environment_type: \\e[33;1m${environment_type}\\e[0m (Helm variable '$HELM_ENV_VALUE_NAME')"
log_info "--- \$environment_name: \\e[33;1m${environment_name}\\e[0m (used as release name)"
log_info "--- \$hostname: \\e[33;1m${hostname}\\e[0m (Helm variable '$HELM_HOSTNAME_VALUE_NAME')"
# unset any upstream deployment env & artifacts
rm -f helm.env
rm -f environment_url.txt
# maybe execute pre deploy script
prescript="$HELM_SCRIPTS_DIR/helm-pre-deploy.sh"
if [[ -f "$prescript" ]]; then
log_info "--- \\e[32mpre-deploy hook\\e[0m (\\e[33;1m${prescript}\\e[0m) found: execute"
exec_hook "$prescript"
else
log_info "--- \\e[32mpre-deploy hook\\e[0m (\\e[33;1m${prescript}\\e[0m) not found: skip"
fi
helm_opts="$helm_opts --set ${HELM_ENV_VALUE_NAME}=$environment_type"
helm_opts="$helm_opts --set ${HELM_HOSTNAME_VALUE_NAME}=$hostname"
if [ -n "$HELM_COMMON_VALUES" ]; then
log_info "--- using \\e[32mcommon values\\e[0m file: \\e[33;1m${HELM_COMMON_VALUES}\\e[0m"
awkenvsubst < "$HELM_COMMON_VALUES" > generated-values-common.yml
helm_opts="$helm_opts --values generated-values-common.yml"
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_opts="$helm_opts --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_opts="$helm_opts --kubeconfig $CI_PROJECT_DIR/.kubeconfig"
fi
if [ -n "$namespace" ]; then
log_info "--- using \\e[32mnamespace\\e[0m: \\e[33;1m${namespace}\\e[0m"
helm_opts="$helm_opts --namespace $namespace"
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_DEPLOY_ARGS $environment_name $package
# maybe execute post deploy script
postscript="$HELM_SCRIPTS_DIR/helm-post-deploy.sh"
if [[ -f "$postscript" ]]; then
log_info "--- \\e[32mpost-deploy hook\\e[0m (\\e[33;1m${postscript}\\e[0m) found: execute"
exec_hook "$postscript"
else
log_info "--- \\e[32mpost-deploy hook\\e[0m (\\e[33;1m${postscript}\\e[0m) not found: skip"
fi
# persist environment url
if [[ -f environment_url.txt ]]
then
environment_url=$(cat environment_url.txt)
export environment_url
log_info "--- dynamic environment url found: (\\e[33;1m$environment_url\\e[0m)"
else
echo "$environment_url" > environment_url.txt
fi
echo -e "environment_type=$environment_type\\nenvironment_name=$environment_name\\nenvironment_url=$environment_url" > helm.env
}
# delete application (and dependencies)
function delete() {
export environment_type=$1
export environment_name=$2
log_info "--- \\e[32mdelete"
log_info "--- \$namespace: \\e[33;1m${namespace}\\e[0m"
log_info "--- \$environment_type: \\e[33;1m${environment_type}\\e[0m"
log_info "--- \$environment_name: \\e[33;1m${environment_name}\\e[0m (used as release name)"
# maybe execute pre delete script
prescript="$HELM_SCRIPTS_DIR/helm-pre-delete.sh"
if [[ -f "$prescript" ]]; then
log_info "--- \\e[32mpre-delete hook\\e[0m (\\e[33;1m${prescript}\\e[0m) found: execute"
exec_hook "$prescript"
else
log_info "--- \\e[32mpre-delete hook\\e[0m (\\e[33;1m${prescript}\\e[0m) not found: skip"
fi
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_opts="$helm_opts --kubeconfig $CI_PROJECT_DIR/.kubeconfig"
fi
if [ -n "$namespace" ]; then
log_info "--- using \\e[32mnamespace\\e[0m: \\e[33;1m${namespace}\\e[0m"
helm_opts="$helm_opts --namespace $namespace"
helm ${TRACE+--debug} $helm_opts $HELM_DELETE_ARGS $environment_name
# maybe execute post delete script
postscript="$HELM_SCRIPTS_DIR/helm-post-delete.sh"
if [[ -f "$postscript" ]]; then
log_info "--- \\e[32mpost-delete hook\\e[0m (\\e[33;1m${postscript}\\e[0m) found: execute"
exec_hook "$postscript"
else
log_info "--- \\e[32mpost-delete hook\\e[0m (\\e[33;1m${postscript}\\e[0m) not found: skip"
fi
}
# test application (and dependencies)
function test() {
export environment_type=$1
export environment_name=$2
log_info "--- \\e[32mtest\\e[0m (env: ${environment_type})"
log_info "--- \$namespace: \\e[33;1m${namespace}\\e[0m"
log_info "--- \$environment_name: \\e[33;1m${environment_name}\\e[0m"
log_info "--- \$environment_type: \\e[33;1m${environment_type}\\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_opts="$helm_opts --kubeconfig $CI_PROJECT_DIR/.kubeconfig"
fi
if [ -n "$namespace" ]; then
log_info "--- using \\e[32mnamespace\\e[0m: \\e[33;1m${namespace}\\e[0m"
helm_opts="$helm_opts --namespace $namespace"
helm ${TRACE+--debug} $helm_opts $HELM_TEST_ARGS $environment_name
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
}
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:
command: ["--service", "helm", "3.3.2" ]
before_script:
- *helm-scripts
- install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}"
cache:
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: [""]
before_script:
- *helm-scripts
- install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}"
- |
if [ -f "$HELM_CHART_DIR/Chart.yaml" ]
then
helm $HELM_DEPENDENCY_ARGS $HELM_CHART_DIR
helm_package=$HELM_CHART_DIR
elif [ ! -z "${HELM_DEPLOY_CHART}" ]
then
add_helm_repositories
helm_package=$HELM_DEPLOY_CHART
else
log_error "You need at least one Chart.yaml or external deploy chart reference"
exit 1
fi
# ==================================================
# 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:
when: never
- exists:
- "**/Chart.yaml"
# yamllint-job is used to check the syntax of the values files.
helm-values-common-lint:
extends: .helm-values-lint
script:
- awkenvsubst < "$HELM_COMMON_VALUES" > generated-values-common.yml
- yamllint -d "$HELM_YAMLLINT_CONFIG" $HELM_YAMLLINT_ARGS generated-values-common.yml
rules:
- if: '$HELM_COMMON_VALUES == null || $HELM_COMMON_VALUES == ""'
when: never
- !reference [.test-policy, rules]
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: '$HELM_REVIEW_VALUES == null || $HELM_REVIEW_VALUES == ""'
when: never
# only on non-production, non-integration branches
- if: '$CI_COMMIT_REF_NAME =~ $PROD_REF || $CI_COMMIT_REF_NAME =~ $INTEG_REF'
when: never
- !reference [.test-policy, rules]
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: '$HELM_INTEG_VALUES == null || $HELM_INTEG_VALUES == ""'
when: never
# only on non-production branches
- if: '$CI_COMMIT_REF_NAME =~ $PROD_REF'
when: never
- !reference [.test-policy, rules]
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: '$HELM_STAGING_VALUES == null || $HELM_STAGING_VALUES == ""'
when: never
- !reference [.test-policy, rules]
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: '$HELM_PROD_VALUES == null || $HELM_PROD_VALUES == ""'
when: never
- !reference [.test-policy, rules]
helm-review-score:
extends: .helm-score
script:
- if [ -z "$HELM_COMMON_VALUES" ]; then HELM_COMMON_VALUES=/dev/null; fi
- awkenvsubst < "$HELM_COMMON_VALUES" > generated-values-common.yml
- awkenvsubst < "$HELM_REVIEW_VALUES" > generated-values-review.yml
- helm template $helm_package --values generated-values-common.yml --values generated-values-review.yml | kube-score score ${HELM_KUBE_SCORE_ARGS} -
rules:
# exclude when $HELM_KUBE_SCORE_DISABLED is set
- if: '$HELM_KUBE_SCORE_DISABLED == "true"'
- if: '$HELM_REVIEW_VALUES == null || $HELM_REVIEW_VALUES == ""'
when: never
# only on non-production, non-integration branches
- if: '$CI_COMMIT_REF_NAME =~ $PROD_REF || $CI_COMMIT_REF_NAME =~ $INTEG_REF'
when: never
- !reference [.test-policy, rules]
helm-integration-score:
extends: .helm-score
script:
- if [ -z "$HELM_COMMON_VALUES" ]; then HELM_COMMON_VALUES=/dev/null; fi
- awkenvsubst < "$HELM_COMMON_VALUES" > generated-values-common.yml
- awkenvsubst < "$HELM_INTEG_VALUES" > generated-values-integration.yml
- helm template $helm_package --values generated-values-common.yml --values generated-values-integration.yml | kube-score score ${HELM_KUBE_SCORE_ARGS} -
# exclude when $HELM_SCORE_DISABLED is set
- if: '$HELM_KUBE_SCORE_DISABLED == "true"'
- if: '$HELM_INTEG_VALUES == null || $HELM_INTEG_VALUES == ""'
when: never
# only on non-production branches
- if: '$CI_COMMIT_REF_NAME =~ $PROD_REF'
when: never
- !reference [.test-policy, rules]
helm-staging-score:
extends: .helm-score
script:
- if [ -z "$HELM_COMMON_VALUES" ]; then HELM_COMMON_VALUES=/dev/null; fi
- awkenvsubst < "$HELM_COMMON_VALUES" > generated-values-common.yml
- awkenvsubst < "$HELM_STAGING_VALUES" > generated-values-staging.yml
- helm template $helm_package --values generated-values-common.yml --values generated-values-staging.yml | kube-score score ${HELM_KUBE_SCORE_ARGS} -
# exclude when $HELM_SCORE_DISABLED is set
- if: '$HELM_KUBE_SCORE_DISABLED == "true"'
- if: '$HELM_STAGING_VALUES == null || $HELM_STAGING_VALUES == ""'
when: never
- !reference [.test-policy, rules]
helm-prod-score:
extends: .helm-score
script:
- if [ -z "$HELM_COMMON_VALUES" ]; then HELM_COMMON_VALUES=/dev/null; fi
- awkenvsubst < "$HELM_COMMON_VALUES" > generated-values-common.yml
- awkenvsubst < "$HELM_PROD_VALUES" > generated-values-prod.yml
- helm template $helm_package --values generated-values-common.yml --values generated-values-prod.yml | kube-score score ${HELM_KUBE_SCORE_ARGS} -
# exclude when $HELM_SCORE_DISABLED is set
- if: '$HELM_KUBE_SCORE_DISABLED == "true"'
- if: '$HELM_PROD_VALUES == null || $HELM_PROD_VALUES == ""'
when: never
- !reference [.test-policy, rules]
# ==================================================
# 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:
- 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: $HELM_PUBLISH_URL == null || $CI_COMMIT_REF_NAME !~ $PROD_REF
when: never
- if: '$AUTODEPLOY_TO_PROD == "true"'
# 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_URL : env-specific application url
# @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" "${ENV_URL:-${HELM_ENVIRONMENT_URL:-$ENV_URL_LEGACY}}"
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
environment:
url: "$environment_url" # can be either static or dynamic
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
# 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_URL: "${HELM_REVIEW_ENVIRONMENT_URL}"
ENV_URL_LEGACY: "${HELM_REVIEW_ENVIRONMENT_SCHEME}://${CI_PROJECT_NAME}-${CI_ENVIRONMENT_SLUG}.${HELM_REVIEW_ENVIRONMENT_DOMAIN}"
ENV_KUBE_CONFIG: "$HELM_REVIEW_KUBE_CONFIG"
ENV_NAMESPACE: "$HELM_REVIEW_NAMESPACE"
ENV_VALUES: "$HELM_REVIEW_VALUES"
environment:
name: review/$CI_COMMIT_REF_NAME
on_stop: helm-cleanup-review
resource_group: review/$CI_COMMIT_REF_NAME
rules:
# exclude tags and on $HELM_REVIEW_DISABLED set
- if: '$HELM_REVIEW_DISABLED == "true" || $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 tags and on $HELM_REVIEW_DISABLED set
- if: '$HELM_REVIEW_DISABLED == "true" || $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:
- if: '$HELM_TEST_ENABLED != "true"'
when: never
- if: '$HELM_REVIEW_DISABLED == "true"'
when: never
# exclude on production or integration branch(es)
- if: '$CI_COMMIT_REF_NAME =~ $PROD_REF || $CI_COMMIT_REF_NAME =~ $INTEG_REF'
when: never
# then use common test jobs policy
- !reference [.test-policy, rules]
# ==================================================
# 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