# ========================================================================================= # 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' %} # 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: "$CI_REGISTRY/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 - deploy - acceptance - publish - infra-prod - production .{{ 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() { awk '!/# *nosubst/{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' } # 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" } # application deployment function 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") 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 } # environment cleanup function 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") 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: - *{{ cookiecutter.template_prefix }}-scripts - 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: - *{{ 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: - *{{ 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" 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: - *{{ 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 %}