Newer
Older
# =========================================================================================
# 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.
# =========================================================================================
# default workflow rules: Merge Request pipelines
workflow:
rules:
# prevent branch pipeline when an MR is open (prefer MR pipeline)
- if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS'
when: never
- if: '$CI_COMMIT_MESSAGE =~ "/\[(ci skip|skip ci) on ([^],]*,)*tag(,[^],]*)*\]/" && $CI_COMMIT_TAG'
when: never
- if: '$CI_COMMIT_MESSAGE =~ "/\[(ci skip|skip ci) on ([^],]*,)*branch(,[^],]*)*\]/" && $CI_COMMIT_BRANCH'
when: never
- if: '$CI_COMMIT_MESSAGE =~ "/\[(ci skip|skip ci) on ([^],]*,)*mr(,[^],]*)*\]/" && $CI_MERGE_REQUEST_ID'
when: never
- if: '$CI_COMMIT_MESSAGE =~ "/\[(ci skip|skip ci) on ([^],]*,)*default(,[^],]*)*\]/" && $CI_COMMIT_REF_NAME =~ $CI_DEFAULT_BRANCH'
when: never
- if: '$CI_COMMIT_MESSAGE =~ "/\[(ci skip|skip ci) on ([^],]*,)*prod(,[^],]*)*\]/" && $CI_COMMIT_REF_NAME =~ $PROD_REF'
when: never
- if: '$CI_COMMIT_MESSAGE =~ "/\[(ci skip|skip ci) on ([^],]*,)*integ(,[^],]*)*\]/" && $CI_COMMIT_REF_NAME =~ $INTEG_REF'
when: never
- if: '$CI_COMMIT_MESSAGE =~ "/\[(ci skip|skip ci) on ([^],]*,)*dev(,[^],]*)*\]/" && $CI_COMMIT_REF_NAME !~ $PROD_REF && $CI_COMMIT_REF_NAME !~ $INTEG_REF'
when: never
- when: always
{% if cookiecutter.template_type == 'build' %}
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# 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
{%- elif cookiecutter.template_type == 'acceptance' %}
# acceptance job prototype: implement adaptive pipeline rules
.acceptance-policy:
rules:
# exclude tags
- if: $CI_COMMIT_TAG
when: never
# on production or integration branch(es): auto & failing
- if: '$CI_COMMIT_REF_NAME =~ $PROD_REF || $CI_COMMIT_REF_NAME =~ $INTEG_REF'
# disable if no review environment
- if: '$REVIEW_ENABLED != "true"'
when: never
# on ADAPTIVE_PIPELINE_DISABLED: auto & failing
- if: '$ADAPTIVE_PIPELINE_DISABLED == "true"'
# 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
{%- endif %}
variables:
# variabilized tracking image
TBC_TRACKING_IMAGE: "registry.gitlab.com/to-be-continuous/tools/tracking:master"
# Default Docker image (use a public image - can be overridden)
{{ cookiecutter.template_PREFIX }}_IMAGE: "registry.hub.docker.com/{{ cookiecutter.project_slug }}:latest"
{% if cookiecutter.template_type == 'build' %}
# Default arguments for 'build' command
{{ cookiecutter.template_PREFIX }}_BUILD_ARGS: "build --with-default-args"
# Default arguments for 'publish' command
{{ cookiecutter.template_PREFIX }}_PUBLISH_ARGS: "publish --with-default-args"
{{ cookiecutter.template_PREFIX }}_LINT_IMAGE: "registry.hub.docker.com/{{ cookiecutter.project_slug }}-lint:latest"
{{ cookiecutter.template_PREFIX }}_LINT_ARGS: "--serevity=medium"
{{ cookiecutter.template_PREFIX }}_DEPCHECK_IMAGE: "registry.hub.docker.com/{{ cookiecutter.project_slug }}-depcheck:latest"
{%- elif cookiecutter.template_type == 'deploy' %}
{{ cookiecutter.template_PREFIX }}_BASE_APP_NAME: "$CI_PROJECT_NAME"
{{ cookiecutter.template_PREFIX }}_REVIEW_AUTOSTOP_DURATION: "4 hours"
# default: one-click deploy
{{ cookiecutter.template_PREFIX }}_PROD_DEPLOY_STRATEGY: manual
{%- elif cookiecutter.template_type == 'acceptance' %}
# Directory where {{ cookiecutter.template_PREFIX }} tests are implemented
{{ cookiecutter.template_PREFIX }}_ROOT_DIR: "."
{%- endif %}
# default production ref name (pattern)
PROD_REF: '/^(master|main)$/'
# default integration ref name (pattern)
INTEG_REF: '/^develop$/'
stages:
- build
- test
- package-build
- package-test
- infra
- publish
- infra-prod
- production
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
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
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
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
.{{ cookiecutter.template_prefix }}-scripts: &{{ cookiecutter.template_prefix }}-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
# List of typical bundles
bundles="/etc/ssl/certs/ca-certificates.crt" # Debian/Ubuntu/Gentoo etc.
bundles="${bundles} /etc/ssl/cert.pem" # Alpine Linux
bundles="${bundles} /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem" # CentOS/RHEL 7
bundles="${bundles} /etc/pki/tls/certs/ca-bundle.crt" # Fedora/RHEL 6
bundles="${bundles} /etc/ssl/ca-bundle.pem" # OpenSUSE
bundles="${bundles} /etc/pki/tls/cacert.pem" # OpenELEC
# Try to find the right bundle to update it with custom CA certificates
for bundle in ${bundles}
do
# import if bundle exists
if [[ -f "${bundle}" ]]
then
# Import certificates in bundle
echo "${certs}" | tr -d '\r' >> "${bundle}"
log_info "Custom CA certificates imported in \\e[33;1m${bundle}\\e[0m"
ca_imported=1
break
fi
done
if [[ -z "$ca_imported" ]]
then
log_warn "Could not import custom CA certificates !"
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
fail "Couldn't get secret \\e[33;1m${name}\\e[0m: no http client found"
fi
;;
esac
}
function eval_all_secrets() {
encoded_vars=$(env | grep -v '^scoped__' | awk -F '=' '/^[a-zA-Z0-9_]*=@(b64|hex|url)@/ {print $1}')
for var in $encoded_vars
do
eval_secret "$var"
done
}
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
}
{% if cookiecutter.template_type == 'build' %}
function output_coverage() {
echo "[TODO]: compute and output global coverage result"
echo "11% covered"
}
{%- elif cookiecutter.template_type == 'deploy' %}
# Converts a string to SCREAMING_SNAKE_CASE
function to_ssc() {
echo "$1" | tr '[:lower:]' '[:upper:]' | tr '[:punct:]' '_'
}
function awkenvsubst() {
# escapes '&' char in variables for gsub
awk '{while(match($0,"[$%]{[^}]*}")) {var=substr($0,RSTART+2,RLENGTH-3);val=ENVIRON[var];gsub("&","\\\\&",val);gsub("[$%]{"var"}",val)}}1'
# login to the hosting platform
function {{ cookiecutter.template_prefix }}_login() {
api_url=${ENV_API_URL:-${{ cookiecutter.template_PREFIX }}_API_URL}
api_token=${ENV_API_TOKEN:-${{ cookiecutter.template_PREFIX }}_API_TOKEN}
project=$ENV_PROJECT
assert_defined "$api_url" 'Missing required API url'
assert_defined "$api_token" 'Missing required API token'
assert_defined "$project" 'Missing required project'
{{ cookiecutter.cli_tool }} login --url="$api_url" --token="$api_token" --project="$project"
}
function {{ cookiecutter.template_prefix }}_deploy() {
export environment_type=$ENV_TYPE
export environment_name=${ENV_APP_NAME:-{{ '${' }}{{ cookiecutter.template_PREFIX }}_BASE_APP_NAME}${ENV_APP_SUFFIX}}
environment_url=${ENV_URL:-${{ cookiecutter.template_PREFIX }}_ENVIRONMENT_URL}
# also export environment_name in SCREAMING_SNAKE_CASE format (may be useful with Kubernetes env variables)
environment_name_ssc=$(to_ssc "$environment_name")
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
export environment_name_ssc
# 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}')
export hostname
log_info "--- \\e[32mdeploy\\e[0m"
log_info "--- \$environment_type: \\e[33;1m${environment_type}\\e[0m"
log_info "--- \$environment_name: \\e[33;1m${environment_name}\\e[0m"
log_info "--- \$environment_name_ssc: \\e[33;1m${environment_name_ssc}\\e[0m"
log_info "--- \$hostname: \\e[33;1m${hostname}\\e[0m"
# unset any upstream deployment env & artifacts
rm -f {{ cookiecutter.project_slug }}.env
rm -f environment_url.txt
# TODO: implement the deployment here
# 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" >> {{ cookiecutter.project_slug }}.env
function {{ cookiecutter.template_prefix }}_delete() {
export environment_type=$ENV_TYPE
export environment_name=${ENV_APP_NAME:-{{ '${' }}{{ cookiecutter.template_PREFIX }}_BASE_APP_NAME}${ENV_APP_SUFFIX}}
# also export environment_name in SCREAMING_SNAKE_CASE format (may be useful with Kubernetes env variables)
environment_name_ssc=$(to_ssc "$environment_name")
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
export environment_name_ssc
log_info "--- \\e[32mcleanup\\e[0m"
log_info "--- \$environment_type: \\e[33;1m${environment_type}\\e[0m"
log_info "--- \$environment_name: \\e[33;1m${environment_name}\\e[0m"
log_info "--- \$environment_name_ssc: \\e[33;1m${environment_name_ssc}\\e[0m"
# TODO: implement the cleanup here
}
{%- elif cookiecutter.template_type == 'acceptance' %}
# retrieve server url to test from upstream artifacts ($environment_url variable or 'environment_url.txt' file)
function eval_env_url() {
# shellcheck disable=SC2154
if [[ -n "$environment_url" ]]
then
{{ cookiecutter.template_PREFIX }}_BASE_URL="$environment_url"
export {{ cookiecutter.template_PREFIX }}_BASE_URL
log_info "Upstream \$environment_url variable set: use base url \\e[33;1m${{ cookiecutter.template_PREFIX }}_BASE_URL\\e[0m"
elif [[ -f environment_url.txt ]]
then
{{ cookiecutter.template_PREFIX }}_BASE_URL=$(cat environment_url.txt)
export {{ cookiecutter.template_PREFIX }}_BASE_URL
log_info "Upstream environment_url.txt file found: use base url \\e[33;1m${{ cookiecutter.template_PREFIX }}_BASE_URL\\e[0m"
else
log_info "No upstream environment url found: leave default"
fi
}
{%- endif %}
unscope_variables
eval_all_secrets
# ENDSCRIPT
{% if cookiecutter.template_type == 'build' %}
# job prototype
# defines default Docker image, tracking probe, cache policy and tags
.{{ cookiecutter.template_prefix }}-base:
image: ${{ cookiecutter.template_PREFIX }}_IMAGE
services:
- name: "$TBC_TRACKING_IMAGE"
command: ["--service", "{{ cookiecutter.project_slug }}", "1.0.0"]
before_script:
- !reference [.{{ cookiecutter.template_prefix }}-scripts]
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
- install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}"
# Cache downloaded dependencies and plugins between builds.
# To keep cache across branches add 'key: "$CI_JOB_NAME"'
# TODO (if necessary): define cache policy here
cache:
# cache shall be per branch per template
key: "$CI_COMMIT_REF_SLUG-{{ cookiecutter.project_slug }}"
paths:
- .cache/
# (example) build & test job
{{ cookiecutter.template_prefix }}-build:
extends: .{{ cookiecutter.template_prefix }}-base
stage: build
script:
- mkdir -p -m 777 reports
# TODO (if possible): $TRACE set enables debug logs on the tool
# TODO (if possible): force test tool to produce JUnit report(s)
# TODO (if possible): force test tool to compute code coverage with report
- {{ cookiecutter.cli_tool }} ${TRACE+--verbose} --coverage --junit --output=reports/{{ cookiecutter.template_prefix }}-test.xunit.xml ${{ cookiecutter.template_PREFIX }}_BUILD_ARGS
- output_coverage
# TODO: code coverage support and GitLab integration (see: https://docs.gitlab.com/ee/ci/yaml/#coverage)
coverage: '/^(\d+.\d+\%) covered$/'
# keep build artifacts and test reports (see: https://docs.gitlab.com/ee/ci/yaml/#artifactsreportsjunit)
artifacts:
name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
expire_in: 1 day
reports:
# TODO: Unit tests use JUnit format and GitLab integration (see: https://docs.gitlab.com/ee/ci/yaml/#artifactsreports)
junit:
- reports/{{ cookiecutter.template_prefix }}-test.xunit.xml
paths:
- build/
- reports/
# (example) linter job
{{ cookiecutter.template_prefix }}-lint:
extends: .{{ cookiecutter.template_prefix }}-base
stage: build
image: ${{ cookiecutter.template_PREFIX }}_LINT_IMAGE
# force no dependency
dependencies: []
script:
- mkdir -p -m 777 reports
- {{ cookiecutter.cli_tool }}_lint ${TRACE+--verbose} --output=reports/{{ cookiecutter.template_prefix }}-lint.native.json ${{ cookiecutter.template_PREFIX }}_LINT_ARGS
artifacts:
name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
expire_in: 1 day
when: always
paths:
- reports/{{ cookiecutter.template_prefix }}-lint.*
rules:
# exclude if ${{ cookiecutter.template_PREFIX }}_LINT_DISABLED
- if: '${{ cookiecutter.template_PREFIX }}_LINT_DISABLED == "true"'
when: never
# .test-policy rules
- !reference [.test-policy, rules]
# (example) dependency check job
{{ cookiecutter.template_prefix }}-depcheck:
extends: .{{ cookiecutter.template_prefix }}-base
stage: test
image: ${{ cookiecutter.template_PREFIX }}_DEPCHECK_IMAGE
# force no dependency
dependencies: []
script:
- {{ cookiecutter.cli_tool }}_depcheck ${TRACE+--verbose} ${{ cookiecutter.template_PREFIX }}_DEPCHECK_ARGS
rules:
# on schedule: auto
- if: '$CI_PIPELINE_SOURCE == "schedule"'
allow_failure: true
when: always
# all other cases: manual & non-blocking
- when: manual
allow_failure: true
# (example) publish job activated on env (${{ cookiecutter.template_PREFIX }}_PUBLISH_ENABLED), with required ${{ cookiecutter.template_PREFIX }}_PUBLISH_LOGIN and ${{ cookiecutter.template_PREFIX }}_PUBLISH_PASSWORD env verification
{{ cookiecutter.template_prefix }}-publish:
extends: .{{ cookiecutter.template_prefix }}-base
stage: publish
before_script:
- !reference [.{{ cookiecutter.template_prefix }}-scripts]
# verify ${{ cookiecutter.template_PREFIX }}_PUBLISH_LOGIN and ${{ cookiecutter.template_PREFIX }}_PUBLISH_PASSWORD are set
- assert_defined "${{ cookiecutter.template_PREFIX }}_PUBLISH_LOGIN" 'Missing required env ${{ cookiecutter.template_PREFIX }}_PUBLISH_LOGIN'
- assert_defined "${{ cookiecutter.template_PREFIX }}_PUBLISH_PASSWORD" 'Missing required env ${{ cookiecutter.template_PREFIX }}_PUBLISH_PASSWORD'
- {{ cookiecutter.cli_tool }} login --login=${{ cookiecutter.template_PREFIX }}_PUBLISH_LOGIN --password=${{ cookiecutter.template_PREFIX }}_PUBLISH_PASSWORD
script:
- {{ cookiecutter.cli_tool }} ${{ cookiecutter.template_PREFIX }}_PUBLISH_ARGS
rules:
# exclude if ${{ cookiecutter.template_PREFIX }}_PUBLISH_ENABLED unset
- if: '${{ cookiecutter.template_PREFIX }}_PUBLISH_ENABLED != "true"'
when: never
# on integration or production branch(es): manual & non-blocking
- if: '$CI_COMMIT_REF_NAME =~ $INTEG_REF || $CI_COMMIT_REF_NAME =~ $PROD_REF'
when: manual
allow_failure: true
{%- elif cookiecutter.template_type == 'deploy' %}
# job prototype
# defines default Docker image, tracking probe, cache policy and tags
# Required vars for login:
# @var ENV_API_URL : env-specific {{ cookiecutter.template_name }} API url
# @var ENV_API_TOKEN : env-specific {{ cookiecutter.template_name }} API token
# @var ENV_PROJECT : env-specific project name
.{{ cookiecutter.template_prefix }}-base:
image: ${{ cookiecutter.template_PREFIX }}_IMAGE
services:
- name: "$TBC_TRACKING_IMAGE"
command: ["--service", "{{ cookiecutter.project_slug }}", "1.0.0"]
before_script:
- !reference [.{{ cookiecutter.template_prefix }}-scripts]
- install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}"
- {{ cookiecutter.template_prefix }}_login
# Deploy job prototype
# Can be extended to define a concrete environment
#
# @var ENV_TYPE : environment type
# @var ENV_APP_NAME : env-specific application name
# @var ENV_APP_SUFFIX: env-specific application suffix
# @var ENV_URL : env-specific application url
.{{ cookiecutter.template_prefix }}-deploy:
extends: .{{ cookiecutter.template_prefix }}-base
stage: deploy
variables:
ENV_APP_SUFFIX: "-$CI_ENVIRONMENT_SLUG"
script:
- {{ cookiecutter.template_prefix }}_deploy
artifacts:
name: "$ENV_TYPE env url for $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
# TODO: propagate deployed env url in a environment_url.txt file
paths:
- environment_url.txt
reports:
# TODO: propagate deployed env info in a dotenv artifact
dotenv: {{ cookiecutter.project_slug }}.env
environment:
url: "$environment_url" # can be either static or dynamic
# Cleanup job prototype
# Can be extended for each deletable environment
#
# @var ENV_TYPE : environment type
# @var ENV_APP_NAME : env-specific application name
# @var ENV_APP_SUFFIX: env-specific application suffix
.{{ cookiecutter.template_prefix }}-cleanup:
extends: .{{ cookiecutter.template_prefix }}-base
stage: deploy
# force no dependencies
dependencies: []
variables:
ENV_APP_SUFFIX: "-$CI_ENVIRONMENT_SLUG"
script:
- {{ cookiecutter.template_prefix }}_delete
environment:
action: stop
# deploy to review env (only on feature branches)
# disabled by default, enable this job by setting ${{ cookiecutter.template_PREFIX }}_REVIEW_PROJECT.
{{ cookiecutter.template_prefix }}-review:
extends: .{{ cookiecutter.template_prefix }}-deploy
variables:
ENV_TYPE: review
ENV_APP_NAME: "${{ cookiecutter.template_PREFIX }}_REVIEW_APP_NAME"
ENV_URL: "${{ cookiecutter.template_PREFIX }}_REVIEW_ENVIRONMENT_URL"
ENV_API_URL: "${{ cookiecutter.template_PREFIX }}_REVIEW_API_URL"
ENV_API_TOKEN: "${{ cookiecutter.template_PREFIX }}_REVIEW_API_TOKEN"
ENV_PROJECT: "${{ cookiecutter.template_PREFIX }}_REVIEW_PROJECT"
environment:
name: review/$CI_COMMIT_REF_NAME
on_stop: {{ cookiecutter.template_prefix }}-cleanup-review
auto_stop_in: "${{ cookiecutter.template_PREFIX }}_REVIEW_AUTOSTOP_DURATION"
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
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
resource_group: review/$CI_COMMIT_REF_NAME
rules:
# exclude tags
- if: $CI_COMMIT_TAG
when: never
# exclude if $CLEANUP_ALL_REVIEW set to 'force'
- if: '$CLEANUP_ALL_REVIEW == "force"'
when: never
# only on non-production, non-integration branches, with ${{ cookiecutter.template_PREFIX }}_REVIEW_PROJECT set
- if: '${{ cookiecutter.template_PREFIX }}_REVIEW_PROJECT && $CI_COMMIT_REF_NAME !~ $PROD_REF && $CI_COMMIT_REF_NAME !~ $INTEG_REF'
# cleanup review env (automatically triggered once branches are deleted)
{{ cookiecutter.template_prefix }}-cleanup-review:
extends: .{{ cookiecutter.template_prefix }}-cleanup
variables:
ENV_TYPE: review
ENV_APP_NAME: "${{ cookiecutter.template_PREFIX }}_REVIEW_APP_NAME"
ENV_API_URL: "${{ cookiecutter.template_PREFIX }}_REVIEW_API_URL"
ENV_API_TOKEN: "${{ cookiecutter.template_PREFIX }}_REVIEW_API_TOKEN"
ENV_PROJECT: "${{ cookiecutter.template_PREFIX }}_REVIEW_PROJECT"
environment:
name: review/$CI_COMMIT_REF_NAME
action: stop
# TODO: use resource group
resource_group: review/$CI_COMMIT_REF_NAME
rules:
# exclude tags
- if: $CI_COMMIT_TAG
when: never
# only on non-production, non-integration branches, with ${{ cookiecutter.template_PREFIX }}_REVIEW_PROJECT set
- if: '${{ cookiecutter.template_PREFIX }}_REVIEW_PROJECT && $CI_COMMIT_REF_NAME !~ $PROD_REF && $CI_COMMIT_REF_NAME !~ $INTEG_REF'
when: manual
allow_failure: true
# deploy to integration env (only on develop branch)
{{ cookiecutter.template_prefix }}-integration:
extends: .{{ cookiecutter.template_prefix }}-deploy
variables:
ENV_TYPE: integration
ENV_APP_NAME: "${{ cookiecutter.template_PREFIX }}_INTEG_APP_NAME"
ENV_URL: "${{ cookiecutter.template_PREFIX }}_INTEG_ENVIRONMENT_URL"
ENV_API_URL: "${{ cookiecutter.template_PREFIX }}_INTEG_API_URL"
ENV_API_TOKEN: "${{ cookiecutter.template_PREFIX }}_INTEG_API_TOKEN"
ENV_PROJECT: "${{ cookiecutter.template_PREFIX }}_INTEG_PROJECT"
environment:
name: integration
# TODO: use resource group
resource_group: integration
rules:
# only on integration branch(es), with ${{ cookiecutter.template_PREFIX }}_INTEG_PROJECT set
- if: '${{ cookiecutter.template_PREFIX }}_INTEG_PROJECT && $CI_COMMIT_REF_NAME =~ $INTEG_REF'
# deploy to staging env (only on master branch)
{{ cookiecutter.template_prefix }}-staging:
extends: .{{ cookiecutter.template_prefix }}-deploy
variables:
ENV_TYPE: staging
ENV_APP_NAME: "${{ cookiecutter.template_PREFIX }}_STAGING_APP_NAME"
ENV_URL: "${{ cookiecutter.template_PREFIX }}_STAGING_ENVIRONMENT_URL"
ENV_API_URL: "${{ cookiecutter.template_PREFIX }}_STAGING_API_URL"
ENV_API_TOKEN: "${{ cookiecutter.template_PREFIX }}_STAGING_API_TOKEN"
ENV_PROJECT: "${{ cookiecutter.template_PREFIX }}_STAGING_PROJECT"
environment:
name: staging
# TODO: use resource group
resource_group: staging
rules:
# only on production branch(es), with ${{ cookiecutter.template_PREFIX }}_STAGING_PROJECT set
- if: '${{ cookiecutter.template_PREFIX }}_STAGING_PROJECT && $CI_COMMIT_REF_NAME =~ $PROD_REF'
# Deploy to production if on branch master and variable {{ cookiecutter.template_PREFIX }}_PROD_PROJECT defined and AUTODEPLOY_TO_PROD is set
{{ cookiecutter.template_prefix }}-production:
extends: .{{ cookiecutter.template_prefix }}-deploy
stage: production
variables:
ENV_TYPE: production
ENV_APP_SUFFIX: "" # no suffix for prod
ENV_APP_NAME: "${{ cookiecutter.template_PREFIX }}_PROD_APP_NAME"
ENV_URL: "${{ cookiecutter.template_PREFIX }}_PROD_ENVIRONMENT_URL"
ENV_API_URL: "${{ cookiecutter.template_PREFIX }}_PROD_API_URL"
ENV_API_TOKEN: "${{ cookiecutter.template_PREFIX }}_PROD_API_TOKEN"
ENV_PROJECT: "${{ cookiecutter.template_PREFIX }}_PROD_PROJECT"
environment:
name: production
# TODO: use resource group
resource_group: production
rules:
# exclude non-production branches
- if: '$CI_COMMIT_REF_NAME !~ $PROD_REF'
when: never
# exclude if ${{ cookiecutter.template_PREFIX }}_PROD_PROJECT not set
- if: '${{ cookiecutter.template_PREFIX }}_PROD_PROJECT == null || ${{ cookiecutter.template_PREFIX }}_PROD_PROJECT == ""'
when: never
- if: '${{ cookiecutter.template_PREFIX }}_PROD_DEPLOY_STRATEGY == "manual"'
when: manual
- if: '${{ cookiecutter.template_PREFIX }}_PROD_DEPLOY_STRATEGY == "auto"'
{%- elif cookiecutter.template_type == 'acceptance' %}
# The main job that starts acceptance tests tool
# on master branch: automatically started after staging deployment
# on non-master branch: manually started after review env deployment (requires $REVIEW_ENABLED to be set)
{{ cookiecutter.project_slug }}:
image: ${{ cookiecutter.template_PREFIX }}_IMAGE
services:
- name: "$TBC_TRACKING_IMAGE"
command: ["--service", "{{ cookiecutter.project_slug }}", "1.0.0"]
stage: acceptance
# TODO (if necessary): define cache policy here
cache:
# cache shall be per branch per template
key: "${CI_COMMIT_REF_SLUG}-{{ cookiecutter.project_slug }}"
paths:
- .cache/
before_script:
- !reference [.{{ cookiecutter.template_prefix }}-scripts]
- install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}"
- eval_env_url
- cd "${{ cookiecutter.template_PREFIX }}_ROOT_DIR"
# TODO (if necessary): do setup stuff here
script:
# TODO: run {{ cookiecutter.cli_tool }} tests
# TODO (if possible): $TRACE set enables debug logs on the tool
- mkdir -p -m 777 reports
- {{ cookiecutter.cli_tool }} run ${TRACE+--verbose} --env BASE_URL=${{ cookiecutter.template_PREFIX }}_BASE_URL --junit --output=reports/{{ cookiecutter.project_slug }}.xunit.xml ${{ cookiecutter.template_PREFIX }}_EXTRA_ARGS
artifacts:
name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
when: always
paths:
- ${{ cookiecutter.template_PREFIX }}_ROOT_DIR/reports/{{ cookiecutter.project_slug }}.*
reports:
# TODO (if possible): Acceptance tests use JUnit format and GitLab integration (see: https://docs.gitlab.com/ee/ci/yaml/#artifactsreports)
junit:
- ${{ cookiecutter.template_PREFIX }}_ROOT_DIR/reports/{{ cookiecutter.project_slug }}.xunit.xml
expire_in: 1 day
rules:
- !reference [.acceptance-policy, rules]
{%- endif %}