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: "registry.hub.docker.com/alpine/helm"
HELM_YAMLLINT_IMAGE: "registry.hub.docker.com/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: "registry.hub.docker.com/zegl/kube-score"
Pierre Smeyers
committed
# to GitLab's container registry (OCI-compliant)
HELM_PUBLISH_URL: "oci://$CI_REGISTRY/$CI_PROJECT_PATH/charts"
# to GitLab's packages repository
# HELM_PUBLISH_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/helm/release"
# HELM_PUBLISH_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/helm/release"
HELM_PUBLISH_METHOD: "auto"
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"
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# 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"
HELM_PUBLISH_ON: "prod"
PROD_REF: '/^(master|main)$/'
# default integration ref name (pattern)
INTEG_REF: '/^develop$/'
stages:
- test
- package-build
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
172
- 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"
}
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
296
# 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
}
if [[ -f "$1" ]]
then
# arg1 is a path to a Kuberconfig file
export KUBECONFIG="$CI_PROJECT_DIR/.kubeconfig"
cp -f "$1" "$KUBECONFIG"
log_info "--- using \\e[32mKUBECONFIG\\e[0m provided by env variables"
elif [[ -n "$1" ]]
then
# arg1 is a Kuberconfig file content
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/"
# 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)
Pierre Smeyers
committed
repo_name_ssc=$(echo "$repo_name" | tr '[:lower:]' '[:upper:]' | tr '[:punct:]' '_')
repo_user=$(eval echo "\$HELM_REPO_${repo_name_ssc}_USER")
repo_password=$(eval echo "\$HELM_REPO_${repo_name_ssc}_PASSWORD")
if [[ "$repo_url" =~ oci://.* ]]
then
if [[ "$repo_user" ]] && [[ "$repo_password" ]]
then
registry_host=$(echo "$repo_url" | awk -F[/:] '{print $4}')
log_info "--- login to OCI-registry \\e[32m${repo_name}\\e[0m: \\e[33;1m${registry_host}\\e[0m"
export HELM_EXPERIMENTAL_OCI=1
# shellcheck disable=SC2086
echo "$repo_password" | helm ${TRACE+--debug} registry login "$registry_host" --username "$repo_user" --password-stdin
Pierre Smeyers
committed
else
log_warn "--- OCI-registry \\e[32m${repo_name}\\e[0m (\\e[33;1m${repo_url}\\e[0m) defined, but no credentials found (\$HELM_REPO_${repo_name_ssc}_USER/\$HELM_REPO_${repo_name_ssc}_PASSWORD)"
fi
else
if [[ "$repo_user" ]] && [[ "$repo_password" ]]
then
log_info "--- add repository \\e[32m${repo_name}\\e[0m: \\e[33;1m${repo_url}\\e[0m (with user/password auth)"
# shellcheck disable=SC2086
echo "$repo_password" | helm ${TRACE+--debug} repo add "$repo_name" "$repo_url" --username "$repo_user" --password-stdin
Pierre Smeyers
committed
else
log_info "--- add repository \\e[32m${repo_name}\\e[0m: \\e[33;1m${repo_url}\\e[0m (unauthenticated)"
# shellcheck disable=SC2086
helm ${TRACE+--debug} repo add "$repo_name" "$repo_url"
Pierre Smeyers
committed
fi
fi
helm ${TRACE+--debug} repo update
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=${TRACE+--debug}
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"
_pkg=${helm_package_file:-$HELM_DEPLOY_CHART}
if [ -z "${_pkg}" ]; 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${_pkg}\\e[0m"
helm $helm_opts $HELM_DEPLOY_ARGS $environment_name $_pkg
# 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=${TRACE+--debug}
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"
Pierre Smeyers
committed
helm $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=${TRACE+--debug}
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"
Pierre Smeyers
committed
helm $helm_opts $HELM_TEST_ARGS $environment_name
Pierre Smeyers
committed
function helm_package() {
rm -f helm-package.env
# determine chart version to publish (semantic-release integration)
if [[ "${SEMREL_INFO_ON}" && "${HELM_SEMREL_RELEASE_DISABLED}" != "true" ]]
Pierre Smeyers
committed
if [[ -z "${SEMREL_INFO_NEXT_VERSION}" ]]
then
log_warn "[semantic-release] no new version to release: skip"
exit 0
else
log_info "[semantic-release] use computed next version: \\e[33;1m${SEMREL_INFO_NEXT_VERSION}\\e[0m"
base_version="${SEMREL_INFO_NEXT_VERSION}"
Pierre Smeyers
committed
fi
else
base_version=$(awk -F':' '/^version:/ {print $2}' "$HELM_CHART_DIR/Chart.yaml")
base_version=${base_version// /}
fi
chart_name=$(awk -F':' '/^name:/ {print $2}' "$HELM_CHART_DIR/Chart.yaml")
chart_name=${chart_name// /}
# also determine version label
prod_ref_expr=${PROD_REF#/}
prod_ref_expr=${prod_ref_expr%/}
if [[ "$CI_COMMIT_REF_NAME" =~ $prod_ref_expr ]]
then
version_label=""
else
version_label="-$CI_COMMIT_REF_SLUG"
Pierre Smeyers
committed
add_helm_repositories
# helm package
log_info "packaging chart with version: \\e[33;1m${base_version}${version_label}\\e[0m"
Pierre Smeyers
committed
# shellcheck disable=SC2086
helm ${TRACE+--debug} $HELM_PACKAGE_ARGS --version ${base_version}${version_label} $HELM_CHART_DIR --destination helm_packages
helm_package=$(ls -1 ./helm_packages/*.tgz 2>/dev/null || echo "")
echo -e "helm_package_file=${helm_package}\\nhelm_package_version=${base_version}${version_label}\\nhelm_package_name=${chart_name}" > helm-package.env
if [[ "$HELM_PUBLISH_SNAPSHOT_ENABLED" == "true" ]]
then
log_info "snapshot enabled: also package and publish chart with version: \\e[33;1m${base_version}${version_label}-snapshot\\e[0m"
mkdir -p /tmp/helm_snapshot
# shellcheck disable=SC2086
helm ${TRACE+--debug} $HELM_PACKAGE_ARGS --version ${base_version}${version_label}-snapshot $HELM_CHART_DIR --destination /tmp/helm_snapshot
snapshot_package=$(ls -1 /tmp/helm_snapshot/*.tgz 2>/dev/null || echo "")
helm_publish "$snapshot_package"
echo -e "helm_snapshot_package_name=${chart_name}\\nhelm_snapshot_package_version=${base_version}${version_label}-snapshot\\nhelm_snapshot_package_remote_url=${HELM_PUBLISH_URL}" >> helm-package.env
Pierre Smeyers
committed
}
function helm_publish() {
if [[ -z "$_pkg" ]]; then
Pierre Smeyers
committed
log_error "No package found to deploy"
exit 1
fi
_pkg_name=$(basename "$_pkg")
log_info "--- Publishing Helm package ${_pkg_name} to: ${HELM_PUBLISH_URL}..."
Pierre Smeyers
committed
# method to lowercase
HELM_PUBLISH_METHOD=$(echo "$HELM_PUBLISH_METHOD" | tr '[:upper:]' '[:lower:]')
# auto-detect method
if [[ "$HELM_PUBLISH_METHOD" == "auto" ]]
then
log_info "--- trying to auto detect publish method..."
pubscript="$HELM_SCRIPTS_DIR/helm-publish.sh"
if [[ -f "$pubscript" ]]
then
log_info "--- ... custom publish script (\\e[33;1m${pubscript}\\e[0m) found: will use"
Pierre Smeyers
committed
HELM_PUBLISH_METHOD=custom
elif [[ "$HELM_PUBLISH_URL" =~ oci://.* ]]
then
log_info "--- ... publish url looks like an OCI registry: will use helm push"
HELM_PUBLISH_METHOD=push
else
log_info "--- ... publish url looks like a Chart repository: will use push method (uses cm-push plugin)"
log_info "--- ... if auto-selected method is not suited, override with \$HELM_PUBLISH_METHOD or provide a custom publish script"
HELM_PUBLISH_METHOD=push
fi
fi
username="${HELM_PUBLISH_USER:-$CI_REGISTRY_USER}"
password="${HELM_PUBLISH_PASSWORD:-$CI_REGISTRY_PASSWORD}"
case "$HELM_PUBLISH_METHOD" in
push)
if [[ "$HELM_PUBLISH_URL" =~ oci://.* ]]
then
registry_host=$(echo "$HELM_PUBLISH_URL" | awk -F[/:] '{print $4}')
Pierre Smeyers
committed
# shellcheck disable=SC2086
echo "$password" | helm ${TRACE+--debug} registry login "$registry_host" --username "$username" --password-stdin
Pierre Smeyers
committed
# enable OCI support prior to v3.8.0
export HELM_EXPERIMENTAL_OCI=1
Pierre Smeyers
committed
# shellcheck disable=SC2086
helm ${TRACE+--debug} push "$_pkg" "$HELM_PUBLISH_URL"
Pierre Smeyers
committed
else
log_info "Installing cm-push plugin (version ${HELM_CM_PUSH_PLUGIN_VERSION:-latest})..."
# shellcheck disable=SC2086
helm ${TRACE+--debug} plugin install ${HELM_CM_PUSH_PLUGIN_VERSION:+--version "$HELM_CM_PUSH_PLUGIN_VERSION"} https://github.com/chartmuseum/helm-push || true
Pierre Smeyers
committed
# shellcheck disable=SC2086
helm ${TRACE+--debug} cm-push --username "$username" --password "$password" "$_pkg" "$HELM_PUBLISH_URL"
Pierre Smeyers
committed
fi
;;
post)
if ! command -v curl > /dev/null
then
log_info "--- installing curl (required to publish Helm charts)..."
apk add --no-cache curl
fi
curl --fail --request POST --form "chart=@$_pkg" --user "$username:$password" "$HELM_PUBLISH_URL"
Pierre Smeyers
committed
;;
put)
wget -v --method=PUT --user="$username" --password="$password" --body-file="$_pkg" "$HELM_PUBLISH_URL/$_pkg_name" -O -
Pierre Smeyers
committed
;;
custom)
pubscript="$HELM_SCRIPTS_DIR/helm-publish.sh"
log_info "--- run custom publish script (\\e[33;1m${pubscript}\\e[0m)"
exec_hook "$pubscript"
;;
*)
log_error "Unsupported publish method: $HELM_PUBLISH_METHOD"
exit 1
;;
esac
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", "4.1.4" ]
variables:
HELM_CACHE_HOME: $CI_PROJECT_DIR/.cache/helm
HELM_CONFIG_HOME: $CI_PROJECT_DIR/.config/helm
before_script:
- *helm-scripts
- install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}"
cache:
helm-values-lint:
extends: .helm-base
image:
name: $HELM_YAMLLINT_IMAGE
entrypoint: [""]
stage: test
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
parallel:
matrix:
- VAR_PREFIX: COMMON
- VAR_PREFIX: REVIEW
- VAR_PREFIX: INTEG
- VAR_PREFIX: STAGING
- VAR_PREFIX: PROD
script:
- values_file=$(eval echo "\$HELM_${VAR_PREFIX}_VALUES")
- awkenvsubst < "$values_file" > generated-values.yml
- yamllint -d "$HELM_YAMLLINT_CONFIG" $HELM_YAMLLINT_ARGS generated-values.yml
rules:
# exclude tags
- if: $CI_COMMIT_TAG
when: never
# exclude when $HELM_YAMLLINT_DISABLED is set
- if: '$HELM_YAMLLINT_DISABLED == "true"'
when: never
# exclude common if $HELM_COMMON_VALUES unset
- if: '$VAR_PREFIX == "COMMON" && ($HELM_COMMON_VALUES == null || $HELM_COMMON_VALUES == "")'
when: never
# exclude review if $HELM_REVIEW_VALUES unset
- if: '$VAR_PREFIX == "REVIEW" && ($HELM_REVIEW_VALUES == null || $HELM_REVIEW_VALUES == "")'
when: never
# exclude review on integration or prod branch
- if: '$VAR_PREFIX == "REVIEW" && ($CI_COMMIT_REF_NAME =~ $INTEG_REF || $CI_COMMIT_REF_NAME =~ $PROD_REF)'
when: never
# exclude integration if $HELM_INTEG_VALUES unset
- if: '$VAR_PREFIX == "INTEG" && ($HELM_INTEG_VALUES == null || $HELM_INTEG_VALUES == "")'
when: never
# exclude integration on prod branch
- if: '$VAR_PREFIX == "INTEG" && $CI_COMMIT_REF_NAME =~ $PROD_REF'
when: never
# exclude staging if $HELM_STAGING_VALUES unset
- if: '$VAR_PREFIX == "STAGING" && ($HELM_STAGING_VALUES == null || $HELM_STAGING_VALUES == "")'
when: never
# exclude production if $HELM_PROD_VALUES unset
- if: '$VAR_PREFIX == "PROD" && ($HELM_PROD_VALUES == null || $HELM_PROD_VALUES == "")'
when: never
- !reference [.test-policy, rules]
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
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
parallel:
matrix:
- ENV_TYPE: review
VAR_PREFIX: REVIEW
- ENV_TYPE: integration
VAR_PREFIX: INTEG
- ENV_TYPE: staging
VAR_PREFIX: STAGING
- ENV_TYPE: production
VAR_PREFIX: PROD
script:
- awkenvsubst < "${HELM_COMMON_VALUES:-/dev/null}" > generated-values-common.yml
- env_values=$(eval echo "\$HELM_${VAR_PREFIX}_VALUES")
- awkenvsubst < "$env_values" > generated-values-env.yml
- helm template $helm_package --values generated-values-common.yml --values generated-values-env.yml | kube-score score ${HELM_KUBE_SCORE_ARGS} -
rules:
# exclude tags
- if: $CI_COMMIT_TAG
when: never
# exclude when $HELM_SCORE_DISABLED is set
- if: '$HELM_KUBE_SCORE_DISABLED == "true"'
when: never
# exclude review if $HELM_REVIEW_VALUES unset
- if: '$ENV_TYPE == "review" && ($HELM_REVIEW_VALUES == null || $HELM_REVIEW_VALUES == "")'
when: never
# exclude review on integration or prod branch
- if: '$ENV_TYPE == "review" && ($CI_COMMIT_REF_NAME =~ $INTEG_REF || $CI_COMMIT_REF_NAME =~ $PROD_REF)'
when: never
# exclude integration if $HELM_INTEG_VALUES unset
- if: '$ENV_TYPE == "integration" && ($HELM_INTEG_VALUES == null || $HELM_INTEG_VALUES == "")'
when: never
# exclude integration on prod branch
- if: '$ENV_TYPE == "integration" && $CI_COMMIT_REF_NAME =~ $PROD_REF'
when: never
# exclude staging if $HELM_STAGING_VALUES unset
- if: '$ENV_TYPE == "staging" && ($HELM_STAGING_VALUES == null || $HELM_STAGING_VALUES == "")'
when: never
# exclude production if $HELM_PROD_VALUES unset
- if: '$ENV_TYPE == "production" && ($HELM_PROD_VALUES == null || $HELM_PROD_VALUES == "")'
when: never
- !reference [.test-policy, rules]
# ==================================================
# 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"
# ==================================================
# 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:
Pierre Smeyers
committed
- helm_package
rules:
- exists:
- "**/Chart.yaml"
artifacts:
name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
- helm_packages/
reports:
dotenv: helm-package.env
# ==================================================
# Stage: publish
# ==================================================
helm-publish:
extends: .helm-base
stage: publish
script:
- helm_publish $helm_package_file
rules:
- if: '$HELM_PUBLISH_URL == null || $HELM_PUBLISH_URL == "" || $HELM_PUBLISH_METHOD == "disabled"'
when: never
- if: '$HELM_PUBLISH_ON == "prod" && $CI_COMMIT_REF_NAME !~ $PROD_REF'
when: never
- if: '$HELM_PUBLISH_ON == "protected" && $CI_COMMIT_REF_PROTECTED != "true"'
when: never
- if: '$AUTODEPLOY_TO_PROD == "true"'
exists:
- "**/Chart.yaml"
- 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
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
# 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)
# disabled by default, enable this job by setting $HELM_REVIEW_ENABLED
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_ENABLED not set
- if: '$HELM_REVIEW_ENABLED != "true" || $CI_COMMIT_TAG'