Skip to content
Snippets Groups Projects
gitlab-ci-{{cookiecutter.project_slug}}.yml 39.8 KiB
Newer Older
Pierre Smeyers's avatar
Pierre Smeyers committed
# =========================================================================================
Pierre Smeyers's avatar
Pierre Smeyers committed
# Copyright (C) 2024 Pierre Smeyers and contributors
Pierre Smeyers's avatar
Pierre Smeyers committed
#
# 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.
# =========================================================================================
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 %}
---
Pierre Smeyers's avatar
Pierre Smeyers committed
# default workflow rules: Merge Request pipelines
workflow:
  rules:
    # prevent MR pipeline originating from production or integration branch(es)
    - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ $PROD_REF || $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ $INTEG_REF'
      when: never
    # on non-prod, non-integration branches: prefer MR pipeline over branch pipeline
    - if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_COMMIT_REF_NAME !~ $PROD_REF && $CI_COMMIT_REF_NAME !~ $INTEG_REF'
Pierre Smeyers's avatar
Pierre Smeyers committed
      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' %}
Pierre Smeyers's avatar
Pierre Smeyers committed
# 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
Pierre Smeyers's avatar
Pierre Smeyers committed

  # Default Docker image (use a public image - can be overridden)
  {{ cookiecutter.template_PREFIX }}_IMAGE: $[[ inputs.image ]]
{% if cookiecutter.template_type == 'build' %}
  {{ cookiecutter.template_PREFIX }}_BUILD_ARGS: $[[ inputs.build-args ]]
Pierre Smeyers's avatar
Pierre Smeyers committed

  {{ cookiecutter.template_PREFIX }}_PUBLISH_ENABLED: $[[ inputs.publish-enabled ]]
  {{ cookiecutter.template_PREFIX }}_PUBLISH_ARGS: $[[ inputs.publish-args ]]
Pierre Smeyers's avatar
Pierre Smeyers committed

  {{ cookiecutter.template_PREFIX }}_LINT_DISABLED: $[[ inputs.lint-disabled ]]
  {{ cookiecutter.template_PREFIX }}_LINT_IMAGE: $[[ inputs.lint-image ]]
  {{ cookiecutter.template_PREFIX }}_LINT_ARGS: $[[ inputs.lint-args ]]
Pierre Smeyers's avatar
Pierre Smeyers committed

  {{ cookiecutter.template_PREFIX }}_DEPCHECK_IMAGE: $[[ inputs.depcheck-image ]]
  {{ cookiecutter.template_PREFIX }}_DEPCHECK_ARGS: $[[ inputs.depcheck-args ]]
Pierre Smeyers's avatar
Pierre Smeyers committed
{%- elif cookiecutter.template_type == 'deploy' %}
  {{ cookiecutter.template_PREFIX }}_BASE_APP_NAME: $[[ inputs.base-app-name ]]
  {{ cookiecutter.template_PREFIX }}_API_URL: $[[ inputs.api-url ]]
  {{ cookiecutter.template_PREFIX }}_ENVIRONMENT_URL: $[[ inputs.environment-url ]]
  {{ cookiecutter.template_PREFIX }}_SCRIPTS_DIR: $[[ inputs.scripts-dir ]]

  {{ cookiecutter.template_PREFIX }}_REVIEW_PROJECT: $[[ inputs.review-project ]]
  {{ cookiecutter.template_PREFIX }}_REVIEW_APP_NAME: $[[ inputs.review-app-name ]]
  {{ cookiecutter.template_PREFIX }}_REVIEW_ENVIRONMENT_URL: $[[ inputs.review-environment-url ]]
  {{ cookiecutter.template_PREFIX }}_REVIEW_API_URL: $[[ inputs.review-api-url ]]
  {{ cookiecutter.template_PREFIX }}_REVIEW_AUTOSTOP_DURATION: $[[ inputs.review-autostop-duration ]]

  {{ cookiecutter.template_PREFIX }}_INTEG_PROJECT: $[[ inputs.integ-project ]]
  {{ cookiecutter.template_PREFIX }}_INTEG_APP_NAME: $[[ inputs.integ-app-name ]]
  {{ cookiecutter.template_PREFIX }}_INTEG_ENVIRONMENT_URL: $[[ inputs.integ-environment-url ]]
  {{ cookiecutter.template_PREFIX }}_INTEG_API_URL: $[[ inputs.integ-api-url ]]

  {{ cookiecutter.template_PREFIX }}_STAGING_PROJECT: $[[ inputs.staging-project ]]
  {{ cookiecutter.template_PREFIX }}_STAGING_APP_NAME: $[[ inputs.staging-app-name ]]
  {{ cookiecutter.template_PREFIX }}_STAGING_ENVIRONMENT_URL: $[[ inputs.staging-environment-url ]]
  {{ cookiecutter.template_PREFIX }}_STAGING_API_URL: $[[ inputs.staging-api-url ]]

  {{ cookiecutter.template_PREFIX }}_PROD_PROJECT: $[[ inputs.prod-project ]]
  {{ cookiecutter.template_PREFIX }}_PROD_APP_NAME: $[[ inputs.prod-app-name ]]
  {{ cookiecutter.template_PREFIX }}_PROD_ENVIRONMENT_URL: $[[ inputs.prod-environment-url ]]
  {{ cookiecutter.template_PREFIX }}_PROD_API_URL: $[[ inputs.prod-api-url ]]
  {{ cookiecutter.template_PREFIX }}_PROD_DEPLOY_STRATEGY: $[[ inputs.prod-deploy-strategy ]]
Pierre Smeyers's avatar
Pierre Smeyers committed
{%- elif cookiecutter.template_type == 'acceptance' %}
  # Directory where {{ cookiecutter.template_PREFIX }} tests are implemented
  {{ cookiecutter.template_PREFIX }}_PROJECT_DIR: $[[ inputs.project-dir ]]
  {{ cookiecutter.template_PREFIX }}_EXTRA_ARGS: $[[ inputs.extra-args ]]
  {{ cookiecutter.template_PREFIX }}_REVIEW_ENABLED: $[[ inputs.review-enabled ]]
Pierre Smeyers's avatar
Pierre Smeyers committed
{%- 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
Pierre Smeyers's avatar
Pierre Smeyers committed
  - deploy
  - acceptance
  - publish
  - infra-prod
  - production
Pierre Smeyers's avatar
Pierre Smeyers committed

.{{ cookiecutter.template_prefix }}-scripts: &{{ cookiecutter.template_prefix }}-scripts |
  # BEGSCRIPT
  set -e

  function log_info() {
    >&2 echo -e "[\\e[1;94mINFO\\e[0m] $*"
Pierre Smeyers's avatar
Pierre Smeyers committed
  }

  function log_warn() {
    >&2 echo -e "[\\e[1;93mWARN\\e[0m] $*"
Pierre Smeyers's avatar
Pierre Smeyers committed
  }

  function log_error() {
    >&2 echo -e "[\\e[1;91mERROR\\e[0m] $*"
Pierre Smeyers's avatar
Pierre Smeyers committed
  }

  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 maybe_exec_hook() {
    if [[ -f "$1" ]]
Pierre Smeyers's avatar
Pierre Smeyers committed
    then
      log_info "\\e[33;1m$1\\e[0m hook found: execute"
      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
Pierre Smeyers's avatar
Pierre Smeyers committed
    fi
  }
{% if cookiecutter.template_type == 'build' %}
Pierre Smeyers's avatar
Pierre Smeyers committed
  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"
  }

Pierre Smeyers's avatar
Pierre Smeyers committed
  # 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}
Pierre Smeyers's avatar
Pierre Smeyers committed
    # also export environment_name in SCREAMING_SNAKE_CASE format (may be useful with Kubernetes env variables)
    environment_name_ssc=$(to_ssc "$environment_name")
Pierre Smeyers's avatar
Pierre Smeyers committed
    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
Pierre Smeyers's avatar
Pierre Smeyers committed
  }

  # 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}}
Pierre Smeyers's avatar
Pierre Smeyers committed
    # also export environment_name in SCREAMING_SNAKE_CASE format (may be useful with Kubernetes env variables)
    environment_name_ssc=$(to_ssc "$environment_name")
Pierre Smeyers's avatar
Pierre Smeyers committed
    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' %}
Pierre Smeyers's avatar
Pierre Smeyers committed
# 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]
Pierre Smeyers's avatar
Pierre Smeyers committed
    - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}"
  variables:
    # TODO (if necessary): set cache dir variables
    XDG_CACHE_HOME: "$CI_PROJECT_DIR/.cache"
Pierre Smeyers's avatar
Pierre Smeyers committed
  cache:
    # cache shall be per branch per template
    key: "${CI_COMMIT_REF_SLUG}-{{ cookiecutter.project_slug }}"
    fallback_keys:
      - "${CI_DEFAULT_BRANCH}-{{ cookiecutter.project_slug }}"
    when: always
Pierre Smeyers's avatar
Pierre Smeyers committed
    # cache shall be per branch per template
    key: "$CI_COMMIT_REF_SLUG-{{ cookiecutter.project_slug }}"
    when: always
Pierre Smeyers's avatar
Pierre Smeyers committed
    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]
Pierre Smeyers's avatar
Pierre Smeyers committed
    # 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
Pierre Smeyers's avatar
Pierre Smeyers committed
.{{ 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]
Pierre Smeyers's avatar
Pierre Smeyers committed
    - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}"
    - {{ cookiecutter.template_prefix }}_login
Pierre Smeyers's avatar
Pierre Smeyers committed

# 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
Pierre Smeyers's avatar
Pierre Smeyers committed
.{{ cookiecutter.template_prefix }}-deploy:
  extends: .{{ cookiecutter.template_prefix }}-base
  stage: deploy
  variables:
    ENV_APP_SUFFIX: "-$CI_ENVIRONMENT_SLUG"
  script:
    - {{ cookiecutter.template_prefix }}_deploy
Pierre Smeyers's avatar
Pierre Smeyers committed
  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
Pierre Smeyers's avatar
Pierre Smeyers committed
.{{ 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
Pierre Smeyers's avatar
Pierre Smeyers committed
  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"
Pierre Smeyers's avatar
Pierre Smeyers committed
  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
    # exclude if ${{ cookiecutter.template_PREFIX }}_REVIEW_PROJECT not set
    - if: '${{ cookiecutter.template_PREFIX }}_REVIEW_PROJECT == null || ${{ cookiecutter.template_PREFIX }}_REVIEW_PROJECT == ""'
      when: never
    # only on non-production, non-integration branches
    - if: '$CI_COMMIT_REF_NAME !~ $PROD_REF && $CI_COMMIT_REF_NAME !~ $INTEG_REF'
Pierre Smeyers's avatar
Pierre Smeyers committed

# 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
    # exclude if ${{ cookiecutter.template_PREFIX }}_REVIEW_PROJECT not set
    - if: '${{ cookiecutter.template_PREFIX }}_REVIEW_PROJECT == null || ${{ cookiecutter.template_PREFIX }}_REVIEW_PROJECT == ""'
      when: never
    # only on non-production, non-integration branches
    - if: '$CI_COMMIT_REF_NAME !~ $PROD_REF && $CI_COMMIT_REF_NAME !~ $INTEG_REF'
Pierre Smeyers's avatar
Pierre Smeyers committed
      when: manual
      allow_failure: true

# deploy to `integration` env (only on develop branch)
Pierre Smeyers's avatar
Pierre Smeyers committed
{{ 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:
    # exclude if ${{ cookiecutter.template_PREFIX }}_INTEG_PROJECT not set
    - if: '${{ cookiecutter.template_PREFIX }}_INTEG_PROJECT == null || ${{ cookiecutter.template_PREFIX }}_INTEG_PROJECT == ""'
      when: never
    # only on integration branch(es)
    - if: '$CI_COMMIT_REF_NAME =~ $INTEG_REF'
Pierre Smeyers's avatar
Pierre Smeyers committed

# deploy to `staging` env (only on master branch)
Pierre Smeyers's avatar
Pierre Smeyers committed
{{ 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:
    # exclude if ${{ cookiecutter.template_PREFIX }}_STAGING_PROJECT not set
    - if: '${{ cookiecutter.template_PREFIX }}_STAGING_PROJECT == null || ${{ cookiecutter.template_PREFIX }}_STAGING_PROJECT == ""'
      when: never
    # only on production branch(es)
    - if: '$CI_COMMIT_REF_NAME =~ $PROD_REF'
Pierre Smeyers's avatar
Pierre Smeyers committed

# 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 if ${{ cookiecutter.template_PREFIX }}_PROD_PROJECT not set
    - if: '${{ cookiecutter.template_PREFIX }}_PROD_PROJECT == null || ${{ cookiecutter.template_PREFIX }}_PROD_PROJECT == ""'
      when: never
    # exclude non-production branch(es)
    - if: '$CI_COMMIT_REF_NAME !~ $PROD_REF'
      when: never
Pierre Smeyers's avatar
Pierre Smeyers committed
    - 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
  variables:
    # TODO (if necessary): set cache dir variable
    XDG_CACHE_HOME: "$CI_PROJECT_DIR/.cache"
Pierre Smeyers's avatar
Pierre Smeyers committed
  cache:
    # cache shall be per branch per template
    key: "${CI_COMMIT_REF_SLUG}-{{ cookiecutter.project_slug }}"
    fallback_keys:
      - "${CI_DEFAULT_BRANCH}-{{ cookiecutter.project_slug }}"
    when: always
Pierre Smeyers's avatar
Pierre Smeyers committed
    paths:
      - .cache/
  before_script:
    - !reference [.{{ cookiecutter.template_prefix }}-scripts]
Pierre Smeyers's avatar
Pierre Smeyers committed
    - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}"
    - eval_env_url
    - cd "${{ cookiecutter.template_PREFIX }}_PROJECT_DIR"
Pierre Smeyers's avatar
Pierre Smeyers committed
    # 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
    # maybe execute pre hook
    - maybe_exec_hook "./pre-{{ cookiecutter.project_slug }}.sh"
    # run tests
    - {{ 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 || rc=$?
    # maybe execute post hook
    - maybe_exec_hook "./post-{{ cookiecutter.project_slug }}.sh"
    # exit with return code
    - exit $rc
Pierre Smeyers's avatar
Pierre Smeyers committed
  artifacts:
    name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
    when: always
    paths:
      - ${{ cookiecutter.template_PREFIX }}_PROJECT_DIR/reports/{{ cookiecutter.project_slug }}.*
Pierre Smeyers's avatar
Pierre Smeyers committed
    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 }}_PROJECT_DIR/reports/{{ cookiecutter.project_slug }}.xunit.xml
Pierre Smeyers's avatar
Pierre Smeyers committed
    expire_in: 1 day
  rules:
    - !reference [.acceptance-policy, rules]
{%- endif %}