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.
# =========================================================================================
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
spec:
inputs:
{% if cookiecutter.template_type == 'build' %}
image:
description: The Docker image used to run `{{cookiecutter.cli_tool}}`
default: registry.hub.docker.com/{{cookiecutter.project_slug}}:1.2.3
build-args:
description: Arguments used by the build job
default: build --with-default-args
lint-disabled:
description: Disable {{cookiecutter.template_PREFIX}} lint
type: boolean
default: false
lint-image:
description: The Docker image used to run the lint tool
default: registry.hub.docker.com/{{cookiecutter.project_slug}}-lint:latest
lint-args:
description: Lint [options and arguments](link-to-the-cli-options)
default: --serevity=medium
depcheck-image:
description: The Docker image used to run the dependency check tool
default: registry.hub.docker.com/{{cookiecutter.project_slug}}-depcheck:latest
depcheck-args:
description: Dependency check [options and arguments](link-to-the-cli-options)
default: ''
publish-enabled:
description: Enable Publish
type: boolean
default: false
publish-args:
description: Arguments used by the publish job
default: publish --with-default-args
{%- elif cookiecutter.template_type == 'acceptance' %}
image:
description: The Docker image used to run {{cookiecutter.template_name}}
default: registry.hub.docker.com/{{cookiecutter.project_slug}}:latest
project-dir:
description: The {{cookiecutter.template_name}} project directory (containing test scripts)
default: .
extra-args:
description: {{cookiecutter.template_name}} extra [run options](link-to-cli-options-ref)
default: ''
review-enabled:
description: Set to enable {{cookiecutter.template_name}} tests on review environments (dynamic environments instantiated on development branches)
type: boolean
default: false
{%- elif cookiecutter.template_type == 'deploy' %}
image:
description: The Docker image used to run {{cookiecutter.template_name}} CLI commands - **set the version required by your {{cookiecutter.template_name}} cluster**
default: registry.hub.docker.com/{{cookiecutter.project_slug}}:latest
api-url:
description: Default {{cookiecutter.template_name}} API url
default: ''
base-app-name:
description: Base application name
default: $CI_PROJECT_NAME
environment-url:
description: |-
The default environments url _(only define for static environment URLs declaration)_
_supports late variable expansion (ex: `https://%{environment_name}.{{cookiecutter.template_prefix}}.acme.com`)_
default: ''
scripts-dir:
description: Directory where deploy & cleanup scripts are located
default: .
review-project:
description: Project ID for `review` env
default: ''
review-app-name:
description: The application name for `review` env (only define to override default)
default: ''
review-autostop-duration:
description: The amount of time before GitLab will automatically stop `review` environments
default: 4 hours
review-environment-url:
description: The `review` environments url _(only define for static environment URLs declaration and if different from default)_
default: ''
review-api-url:
description: API url for `review` env _(only define to override default)_
default: ''
integ-project:
description: Project ID for `integration` env
default: ''
integ-app-name:
description: The application name for `integration` env (only define to override default)
default: ''
integ-environment-url:
description: The `integration` environment url _(only define for static environment URLs declaration and if different from default)_
default: ''
integ-api-url:
description: API url for `integration` env _(only define to override default)_
default: ''
staging-project:
description: Project ID for `staging` env
default: ''
staging-app-name:
description: The application name for `staging` env (only define to override default)
default: ''
staging-environment-url:
description: The `staging` environment url _(only define for static environment URLs declaration and if different from default)_
default: ''
staging-api-url:
description: API url for `staging` env _(only define to override default)_
default: ''
prod-project:
description: Project ID for `production` env
default: ''
prod-app-name:
description: The application name for `production` env (only define to override default)
default: ''
prod-environment-url:
description: The `production` environment url _(only define for static environment URLs declaration and if different from default)_
default: ''
prod-api-url:
description: API url for `production` env _(only define to override default)_
default: ''
prod-deploy-strategy:
description: Defines the deployment to production strategy.
options:
- manual
- auto
default: manual
{%- endif %}
---
# 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' %}
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
# 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
.{{ cookiecutter.template_prefix }}-scripts: &{{ cookiecutter.template_prefix }}-scripts |
# BEGSCRIPT
set -e
function log_info() {
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
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
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
420
421
422
423
424
425
426
427
428
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
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
}
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")
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
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")
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
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]
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
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
- 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"
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
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)
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
{{ 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 %}