Skip to content
Snippets Groups Projects
gitlab-ci-openshift.yml 29.1 KiB
Newer Older
Pierre Smeyers's avatar
Pierre Smeyers committed
# =========================================================================================
Pierre Smeyers's avatar
Pierre Smeyers committed
# Copyright (C) 2021 Orange & 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.
# =========================================================================================
variables:
Pierre Smeyers's avatar
Pierre Smeyers committed
  # Docker Image with OC CLI tool (can be overridden)
Pierre Smeyers's avatar
Pierre Smeyers committed
  OS_CLI_IMAGE: "openshift/origin-cli:latest"

  # [mandatory] OS_URL              : the OpenShift API url
  # [mandatory] OS_TOKEN            : the OpenShift authentication token

  # [optional] OS_REVIEW_PROJECT    : the OpenShift project for review (defaults to $OS_DEFAULT_PROJECT)
  # [optional] OS_STAGING_PROJECT   : the OpenShift project for staging (defaults to $OS_DEFAULT_PROJECT)
  # [optional] OS_PROD_PROJECT      : the OpenShift project for production (defaults to $OS_DEFAULT_PROJECT)

  # [optional]  OS_BASE_APP_NAME    : the base application name (defaults to $CI_PROJECT_NAME)
  # [optional]  OS_REVIEW_APP_NAME  : specific OpenShift application name in review env (defaults to $OS_BASE_APP_NAME-$CI_COMMIT_REF_SLUG)
  # [optional]  OS_STAGING_APP_NAME : specific OpenShift application name in staging env (defaults to $OS_BASE_APP_NAME-staging)
  # [optional]  OS_PROD_APP_NAME    : specific OpenShift application name in production env (defaults to $OS_BASE_APP_NAME)

  OS_BASE_APP_NAME: "$CI_PROJECT_NAME"
  OS_SCRIPTS_DIR: "."
  OS_BASE_TEMPLATE_NAME: "openshift"
  OS_REVIEW_ENVIRONMENT_SCHEME: "https"
  OS_CLEANUP_OBJECT_TYPES: "all,pvc,configmap,secret"
  # default production ref name (pattern)
  PROD_REF: '/^master$/'
  # default integration ref name (pattern)
  INTEG_REF: '/^develop$/'

stages:
  - deploy
  - production

.os-scripts: &os-scripts |
  # BEGSCRIPT
  set -e

  function log_info() {
      echo -e "[\\e[1;94mINFO\\e[0m] $*"
  }

  function log_warn() {
      echo -e "[\\e[1;93mWARN\\e[0m] $*"
  }

  function log_error() {
      echo -e "[\\e[1;91mERROR\\e[0m] $*"
  }

  function fail() {
    log_error "$*"
    exit 1
  }

  function assert_defined() {
    if [[ -z "$1" ]]
    then
      log_error "$2"
      exit 1
    fi
  }

  function install_ca_certs() {
    certs=$1
    if [[ -z "$certs" ]]
    then
      return
    fi

    # import in system
    if echo "$certs" >> /etc/ssl/certs/ca-certificates.crt
    then
      log_info "CA certificates imported in \\e[33;1m/etc/ssl/certs/ca-certificates.crt\\e[0m"
    fi
    if echo "$certs" >> /etc/ssl/cert.pem
    then
      log_info "CA certificates imported in \\e[33;1m/etc/ssl/cert.pem\\e[0m"
    fi
  }

  function unscope_variables() {
    _scoped_vars=$(env | awk -F '=' "/^scoped__[a-zA-Z0-9_]+=/ {print \$1}" | sort)
    if [[ -z "$_scoped_vars" ]]; then return; fi
    log_info "Processing scoped variables..."
    for _scoped_var in $_scoped_vars
    do
      _fields=${_scoped_var//__/:}
      _condition=$(echo "$_fields" | cut -d: -f3)
      case "$_condition" in
      if) _not="";;
      ifnot) _not=1;;
      *)
        log_warn "... unrecognized condition \\e[1;91m$_condition\\e[0m in \\e[33;1m${_scoped_var}\\e[0m"
        continue
      ;;
      esac
      _target_var=$(echo "$_fields" | cut -d: -f2)
      _cond_var=$(echo "$_fields" | cut -d: -f4)
      _cond_val=$(eval echo "\$${_cond_var}")
      _test_op=$(echo "$_fields" | cut -d: -f5)
      case "$_test_op" in
      defined)
        if [[ -z "$_not" ]] && [[ -z "$_cond_val" ]]; then continue; 
        elif [[ "$_not" ]] && [[ "$_cond_val" ]]; then continue; 
        fi
        ;;
      equals|startswith|endswith|contains|in|equals_ic|startswith_ic|endswith_ic|contains_ic|in_ic)
        # comparison operator
        # sluggify actual value
        _cond_val=$(echo "$_cond_val" | tr '[:punct:]' '_')
        # retrieve comparison value
        _cmp_val_prefix="scoped__${_target_var}__${_condition}__${_cond_var}__${_test_op}__"
        _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"
  }

Pierre Smeyers's avatar
Pierre Smeyers committed
  # 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
          fail "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
          fail "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}')
Pierre Smeyers's avatar
Pierre Smeyers committed
    for var in $encoded_vars
    do
      eval_secret "$var"
    done
  }
  
  # Converts a string to SCREAMING_SNAKE_CASE
  function to_ssc() {
    echo "$1" | tr '[:lower:]' '[:upper:]' | tr '[:punct:]' '_'
  }

  function pre_apply() {
    # maybe execute pre apply script
    prescript="$OS_SCRIPTS_DIR/os-pre-apply.sh"
    if [[ -f "$prescript" ]]
    then
      log_info "--- \\e[32mpre-apply hook\\e[0m (\\e[33;1m${prescript}\\e[0m) found: execute"
      bash "$prescript"
    else
      log_info "--- \\e[32mpre-apply hook\\e[0m (\\e[33;1m${prescript}\\e[0m) not found: skip"
    fi
  }

  # extracts parameters from template and builds the param arguments string for each param found in:
  # 1) specific dotenv file (param 2)
  # 2) global dotenv file (param 1)
  # 3) env
  # The returned string is of the form: --param=param1="$param1" --param=param2="$param2" ...
  # /!\ this string has to be eval'd but allows using multiline parameters (a TLS certificate for instance)
  function build_template_param_args() {
    global_env="$1"
    spec_env="$2"
    oc process --parameters -f - | awk 'NR > 1{print $1}' | while read -r param_name
    do
      # 1: look for param into specific dotenv file
      if grep -e "^$param_name=" "$spec_env" >/dev/null 2>&1
      then
        # force double-quote value
        val=$(grep -e "^$param_name=" "$spec_env" | cut -d'=' -f2- | sed -e 's/^ *//g' -e 's/ *$//g')
        if [[ "$val" != \"*\" ]]; then val="\"$val\""; fi
        echo -n "--param ${param_name}=${val} "
      # 2: look for param into global dotenv file
      elif grep -e "^$param_name=" "$global_env" >/dev/null 2>&1
      then
        # force double-quote value
        val=$(grep -e "^$param_name=" "$global_env" | cut -d'=' -f2- | sed -e 's/^ *//g' -e 's/ *$//g')
        if [[ "$val" != \"*\" ]]; then val="\"$val\""; fi
        echo -n "--param ${param_name}=${val} "
      # 3: look for param into environment
      elif [[ -n "${!param_name}" ]]
      then
        echo -n "--param ${param_name}=\"\$${param_name}\" "
      fi
    done
  }

  # $1: templatefile
  function apply() {
    export templatefile=$1
    # oc apply with params substitution
    log_info "--- \\e[32moc apply\\e[0m"
    log_info "--- template: \\e[33;1m${templatefile}\\e[0m"

    # evaluate template parameters from environment and dotenv files
    param_args=$(build_template_param_args "${OS_SCRIPTS_DIR}/${OS_BASE_TEMPLATE_NAME}.env" "${OS_SCRIPTS_DIR}/${OS_BASE_TEMPLATE_NAME}-${env}.env" < "$templatefile")

    # set label 'app' and 'env' on all created objects ($OS_STAGE_LABEL for backwards compatibility)
    echo "oc process --labels \"${OS_ENV_LABEL:-${OS_STAGE_LABEL:-env}}=$env,${OS_APP_LABEL:-app}=$appname\" $param_args -f $templatefile"
    eval oc process --labels "${OS_ENV_LABEL:-${OS_STAGE_LABEL:-env}}=$env,${OS_APP_LABEL:-app}=$appname" "$param_args" -f "$templatefile" | oc ${TRACE+--loglevel=6} apply -f -
  }

  function check_readiness() {
    # maybe execute readiness check script
    readycheck="$OS_SCRIPTS_DIR/os-readiness-check.sh"
    if [[ -f "$readycheck" ]]
    then
      log_info "--- \\e[32mreadiness-check hook\\e[0m (\\e[33;1m${readycheck}\\e[0m) found: execute"
      bash "$readycheck"
    else
      log_info "--- \\e[32mreadiness-check hook\\e[0m (\\e[33;1m${readycheck}\\e[0m) not found: assume app is ready"
    fi
  }

  function post_apply() {
    # maybe execute post apply script
    postscript="$OS_SCRIPTS_DIR/os-post-apply.sh"
    if [[ -f "$postscript" ]]
    then
      log_info "--- \\e[32mpost-apply hook\\e[0m (\\e[33;1m${postscript}\\e[0m) found: execute"
      bash "$postscript"
    else
      log_info "--- \\e[32mpost-apply hook\\e[0m (\\e[33;1m${postscript}\\e[0m) not found: skip"
    fi
  }

  function deploy() {
    # export project as it may be usefull to build image name based on internal registry (ex: docker-registry.default.svc:5000/${project}/${DOCKER_IMAGE_NAME}:${DOCKER_IMAGE_LABEL} )
    project=$(oc project -q)
    export project
    export env=$1
    # for backward compatibility
    export stage=$1
    export appname=$2
    # also export appname in SCREAMING_SNAKE_CASE format (may be useful with OpenShift env variables)
    appname_ssc=$(to_ssc "$2")
    export appname_ssc
    # extract hostname from $CI_ENVIRONMENT_URL
    hostname=$(echo "$CI_ENVIRONMENT_URL" | awk -F[/:] '{print $4}')
    export hostname

    log_info "--- \\e[32mdeploy\\e[0m (env: \\e[33;1m${env}\\e[0m)"
    log_info "--- looking for OS scripts in directory: \\e[33;1m${OS_SCRIPTS_DIR}\\e[0m"
    log_info "--- project: \\e[33;1m${project}\\e[0m"
    log_info "--- appname: \\e[33;1m${appname}\\e[0m"
    log_info "--- appname_ssc: \\e[33;1m${appname_ssc}\\e[0m"
    log_info "--- env: \\e[33;1m${env}\\e[0m"
    log_info "--- hostname: \\e[33;1m${hostname}\\e[0m"

    # maybe execute deploy script
    deployscript=$(ls -1 "$OS_SCRIPTS_DIR/os-deploy-${env}.sh" 2>/dev/null || ls -1 "$OS_SCRIPTS_DIR/os-deploy.sh" 2>/dev/null || echo "")
    if [[ -f "$deployscript" ]]
    then
      log_info "--- deploy script (\\e[33;1m${deployscript}\\e[0m) found: execute"
      bash "$deployscript"
    else
      log_info "--- no deploy script found: run template-based deployment"

      # find template
      templatefile=$(ls -1 "$OS_SCRIPTS_DIR/${OS_BASE_TEMPLATE_NAME}-${env}.yml" 2>/dev/null || ls -1 "$OS_SCRIPTS_DIR/${OS_BASE_TEMPLATE_NAME}.yml" 2>/dev/null || echo "")
      if [[ -z "$templatefile" ]]
      then
        log_error "--- no template"
        exit 1
      fi

      pre_apply

      apply "$templatefile"

      post_apply
    fi

    check_readiness

    # finally persist environment url
    echo "$CI_ENVIRONMENT_URL" > environment_url.txt
    echo -e "environment_type=$env\\nenvironment_name=$appname\\nenvironment_url=$CI_ENVIRONMENT_URL" > openshift.env
  }

  # $1 deployment name
  function last_rollout_status() {
    oc rollout history "dc/$1" | sed  -e '1,2d' -e '$d' | tail -1 | awk -F" " '{print tolower($2)}'
  }

  # $1 deployment name
  function force_rollout() {
    last_status=$(last_rollout_status "$1")
    if [ "$last_status" != "running" ] && [ "$last_status" != "pending" ]
    then
      log_info "Force rollout as last status is $last_status"
      oc rollout latest "$1"
    else
      log_info "A rollout is currently processing with status $last_status"
    fi
  }

  # $1 deployment name
  # $2 timeout (default is 2 minutes)
  function poll_last_rollout() {
    deployment_name=$1
    timeout=${2:-120}
    sleep_time=10
    waiting_time=0
    still_running=1
    status="running"
    log_info "Waiting for $deployment_name to finish its rollout. It will poll the last rollout status every $sleep_time seconds until $timeout seconds"
    while [ $waiting_time -lt "$timeout" ] && [ $still_running -eq 1 ]
    do
      log_info "$((timeout - waiting_time)) seconds remaining"
      status=$(last_rollout_status "$deployment_name")
      if [ "$status" == "running" ] || [ "$status" == "pending" ]
      then
        log_info "Last status is $status. Sleep $sleep_time...."
        sleep $sleep_time
        waiting_time=$((waiting_time + sleep_time))
      else
        log_info "Rollout ended with status $status"
        still_running=0
      fi
    done

    case "$status" in
    complete)
      log_info "Rollout complete"
      ;;
    failed)
      log_error "Rollout failed"
      exit 1
      ;;
    running|pending)
      log_error "Rollout still $status after $waiting_time seconds. Increase timeout"
      exit 1
      ;;
    *)
      log_error "Unknown rollout status: $status"
      exit 1
      ;;
    esac
  }

  function delete() {
    export env=$1
    # for backward compatibility
    export stage=$1
    export appname=$2
    # also export appname in SCREAMING_SNAKE_CASE format (may be useful with OpenShift env variables)
    appname_ssc=$(to_ssc "$2")
    export appname_ssc

    log_info "--- \\e[32mdelete\\e[0m (env: ${env})"
    log_info "--- appname: \\e[33;1m${appname}\\e[0m"
    log_info "--- appname_ssc: \\e[33;1m${appname_ssc}\\e[0m"
    log_info "--- env: \\e[33;1m${env}\\e[0m"

    # maybe execute cleanup script
    cleanupscript=$(ls -1 "$OS_SCRIPTS_DIR/os-cleanup-${env}.sh" 2>/dev/null || ls -1 "$OS_SCRIPTS_DIR/os-cleanup.sh" 2>/dev/null || echo "")
    if [[ -f "$cleanupscript" ]]
    then
      log_info "--- cleanup script (\\e[33;1m${cleanupscript}\\e[0m) found: execute"
      bash "$cleanupscript"
    else
      log_info "--- no cleanup script found: proceed with template-based delete"

      # maybe execute pre cleanup script
      prescript="$OS_SCRIPTS_DIR/os-pre-cleanup.sh"
      if [[ -f "$prescript" ]]
      then
        log_info "--- \\e[32mpre-cleanup hook\\e[0m (\\e[33;1m${prescript}\\e[0m) found: execute"
        bash "$prescript"
      else
        log_info "--- \\e[32mpre-cleanup hook\\e[0m (\\e[33;1m${prescript}\\e[0m) not found: skip"
      fi

      # delete app
      log_info "--- \\e[32moc delete\\e[0m"
      # delete all objects with label 'app=$appname'
      oc ${TRACE+--loglevel=6} delete "${OS_CLEANUP_OBJECT_TYPES}" --selector "${OS_APP_LABEL:-app}=$appname"

      # maybe execute post cleanup script
      postscript="$OS_SCRIPTS_DIR/os-post-cleanup.sh"
      if [[ -f "$postscript" ]]
      then
        log_info "--- \\e[32mpost-cleanup hook\\e[0m (\\e[33;1m${postscript}\\e[0m) found: execute"
        bash "$postscript"
      else
        log_info "--- \\e[32mpost-cleanup hook\\e[0m (\\e[33;1m${postscript}\\e[0m) not found: skip"
      fi
    fi
  }

  function delete_all() {
    export env=$1
    appnameproto=$2
    # make appname regex by replacing $CI_COMMIT_REF_SLUG with .*
    appnameregex=$(echo "$appnameproto" | sed -r "s/$CI_COMMIT_REF_SLUG/.*/g")
    # list services | remove 1st line | pick 7th column (selector) | filter services with label "app=$regex"
    matchingselectors=$(oc get svc -o wide | tail -n +2 | awk '{print $7}' | awk "/${OS_APP_LABEL:-app}=$appnameregex/ {print \$1}")
    matchcount=$(echo "$matchingselectors" | wc -w)

    log_info "--- \\e[32mdelete all\\e[0m (env: \\e[33;1m${env}\\e[0m, appname matcher: \\e[33;1m${appnameregex}\\e[0m): \\e[33;1m${matchcount}\\e[0m apps found"

    rc=0
    for appselector in $matchingselectors
    do
      # extract review appname from selector (can be 'app=name,env=env,foo=bar')
      matchingapp=$(echo "$appselector" | sed -E "s/${OS_APP_LABEL:-app}=([^,]*).*/\\1/")
      echo -e "\\e[1;93m-------------------------------------------------------------------------------\\e[0m"
      if ! delete "$env" "$matchingapp"
      then
        log_warn "... failed deleting review app \\e[33;1m${matchingapp}\\e[0m (see logs)"
        rc=1
      fi
    done
    return $rc
  }

  function extract_oldest_tags_from_image_stream_definition() {
    # $1 the number of oldest image to extract
    # $2: optional: file to extract tags . If not specified will take input from stdin
    # shellcheck disable=SC2016
    jq -c -r --argjson limit "$1"  '[ stderr | .status.tags[] | { tag: .tag, created: .items | max_by(.created ) | .created } ] | sort_by(.created) | reverse | .[$limit:] | .[].tag'  < "${2:-/dev/stdin}"
  }

  function purge_old_image_tags() {
    # $1 image name
    # $2 number of image tags to keep
    log_info "Keeping last $2 images"
    oc get -o json "is/$1" | extract_oldest_tags_from_image_stream_definition "$2" | while read -r tag
    do
      log_info "Removing image stream tag $1:$tag"
      oc delete "istag/$1:$tag"
    done
  }

  function get_latest_template_version() {
Pierre Smeyers's avatar
Pierre Smeyers committed
    tag_json=$(wget -T 5 -q -O - "$CI_API_V4_URL/projects/to-be-continuous%2F$1/repository/tags?per_page=1" || echo "")
Pierre Smeyers's avatar
Pierre Smeyers committed
    echo "$tag_json" | sed -rn 's/^.*"name":"([^"]*)".*$/\1/p'
  }

  function check_for_update() {
    template="$1"
    actual="$2"
    latest=$(get_latest_template_version "$template")
    if [[ -n "$latest" ]] && [[ "$latest" != "$actual" ]]
    then
      log_warn "\\e[1;93m=======================================================================================================\\e[0m"
      log_warn "\\e[93mThe template \\e[32m$template\\e[93m:\\e[33m$actual\\e[93m you're using is not up-to-date: consider upgrading to version \\e[32m$latest\\e[0m"
      log_warn "\\e[93m(set \$TEMPLATE_CHECK_UPDATE_DISABLED to disable this message)\\e[0m"
      log_warn "\\e[1;93m=======================================================================================================\\e[0m"
    fi
  }

  if [[ -z "$TEMPLATE_CHECK_UPDATE_DISABLED" ]]; then check_for_update openshift "1.1.0"; fi
Pierre Smeyers's avatar
Pierre Smeyers committed

  # export tool functions (might be used in after_script)
  export -f log_info log_warn log_error assert_defined last_rollout_status force_rollout poll_last_rollout extract_oldest_tags_from_image_stream_definition purge_old_image_tags

  unscope_variables
Pierre Smeyers's avatar
Pierre Smeyers committed
  eval_all_secrets

  # ENDSCRIPT

# Generic OS job
.os-base:
  image: $OS_CLI_IMAGE
  services:
Pierre Smeyers's avatar
Pierre Smeyers committed
    - name: "$CI_REGISTRY/to-be-continuous/tools/tracking:master"
      command: ["--service", "openshift", "1.1.0" ]
Pierre Smeyers's avatar
Pierre Smeyers committed
  before_script:
    - *os-scripts
    - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}"

# Deploy job prototype
# Can be extended to define a concrete environment
#
# @arg ENV_TYPE      : environment type
# @arg ENV_APP_NAME  : env-specific application name
# @arg ENV_APP_SUFFIX: env-specific application suffix
# @arg ENV_URL       : env-specific OpenShift API url
# @arg ENV_TOKEN     : env-specific OpenShift API token
# @arg ENV_PROJECT   : env-specific OpenShift project name
.os-deploy:
  extends: .os-base
  stage: deploy
  variables:
    ENV_APP_SUFFIX: "-$CI_ENVIRONMENT_SLUG"
  before_script:
    - *os-scripts
    - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}"
    - assert_defined "${ENV_URL:-$OS_URL}" 'Missing required OpenShift url'
    - assert_defined "${ENV_TOKEN:-$OS_TOKEN}" 'Missing required OpenShift token'
    - assert_defined "$ENV_PROJECT" 'Missing required OpenShift project'
    - oc login ${ENV_URL:-$OS_URL} --token=${ENV_TOKEN:-$OS_TOKEN} -n $ENV_PROJECT
  script:
    - deploy "$ENV_TYPE" "${ENV_APP_NAME:-${OS_BASE_APP_NAME}${ENV_APP_SUFFIX}}"
  artifacts:
    name: "$ENV_TYPE env url for $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
    paths:
      - environment_url.txt
    reports:
      dotenv: openshift.env

# Cleanup job prototype
# Can be extended for each deletable environment
#
# @arg ENV_TYPE      : environment type
# @arg ENV_APP_NAME  : env-specific application name
# @arg ENV_APP_SUFFIX: env-specific application suffix
# @arg ENV_URL       : env-specific OpenShift API url
# @arg ENV_TOKEN     : env-specific OpenShift API token
# @arg ENV_PROJECT   : env-specific OpenShift project name
.os-cleanup:
  extends: .os-base
  stage: deploy
  # force no dependencies
  dependencies: []
  variables:
    ENV_APP_SUFFIX: "-$CI_ENVIRONMENT_SLUG"
  before_script:
    - *os-scripts
    - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}"
    - assert_defined "${ENV_URL:-$OS_URL}" 'Missing required OpenShift url'
    - assert_defined "${ENV_TOKEN:-$OS_TOKEN}" 'Missing required OpenShift token'
    - assert_defined "$ENV_PROJECT" 'Missing required OpenShift project'
    - oc login ${ENV_URL:-$OS_URL} --token=${ENV_TOKEN:-$OS_TOKEN} -n $ENV_PROJECT
  script:
    - delete "$ENV_TYPE" "${ENV_APP_NAME:-${OS_BASE_APP_NAME}${ENV_APP_SUFFIX}}"
  environment:
    action: stop


# deploy to review env (only for branches)
# disabled by default, enable this job by setting
# $OS_REVIEW_PROJECT.
os-review:
  extends: .os-deploy
  variables:
    ENV_TYPE: review
    ENV_APP_NAME: "$OS_REVIEW_APP_NAME"
    ENV_URL: "$OS_REVIEW_URL"
    ENV_TOKEN: "$OS_REVIEW_TOKEN"
    ENV_PROJECT: "$OS_REVIEW_PROJECT"
  environment:
    name: review/$CI_COMMIT_REF_NAME
    url: "${OS_REVIEW_ENVIRONMENT_SCHEME}://${CI_PROJECT_NAME}-${CI_ENVIRONMENT_SLUG}.${OS_REVIEW_ENVIRONMENT_DOMAIN}"
    on_stop: os-cleanup-review
  resource_group: review/$CI_COMMIT_REF_NAME
  rules:
    # exclude merge requests
    - if: $CI_MERGE_REQUEST_ID
      when: never
    # 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 $OS_REVIEW_PROJECT set
    - if: '$OS_REVIEW_PROJECT && $CI_COMMIT_REF_NAME !~ $PROD_REF && $CI_COMMIT_REF_NAME !~ $INTEG_REF'

# stop review env (automatically triggered once branches are deleted)
os-cleanup-review:
  extends: .os-cleanup
  variables:
    ENV_TYPE: review
    ENV_APP_NAME: "$OS_REVIEW_APP_NAME"
    ENV_URL: "$OS_REVIEW_URL"
    ENV_TOKEN: "$OS_REVIEW_TOKEN"
    ENV_PROJECT: "$OS_REVIEW_PROJECT"
  environment:
    name: review/$CI_COMMIT_REF_NAME
    action: stop
  resource_group: review/$CI_COMMIT_REF_NAME
  rules:
    # exclude merge requests
    - if: $CI_MERGE_REQUEST_ID
      when: never
    # 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 $OS_REVIEW_PROJECT set
    - if: '$OS_REVIEW_PROJECT && $CI_COMMIT_REF_NAME !~ $PROD_REF && $CI_COMMIT_REF_NAME !~ $INTEG_REF'
      when: manual
      allow_failure: true

# stop all review envs (manual job on master branch)
os-cleanup-all-review:
  extends: .os-base
  stage: deploy
  dependencies: []
  before_script:
    - *os-scripts
    - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}"
    - assert_defined "${OS_REVIEW_URL:-$OS_URL}" 'Missing required env $OS_REVIEW_URL or $OS_URL'
    - assert_defined "${OS_REVIEW_TOKEN:-$OS_TOKEN}" 'Missing required env $OS_REVIEW_TOKEN or $OS_TOKEN'
    - oc login ${OS_REVIEW_URL:-$OS_URL} --token=${OS_REVIEW_TOKEN:-$OS_TOKEN} -n $OS_REVIEW_PROJECT
  script:
    - delete_all review "${OS_REVIEW_APP_NAME:-${OS_BASE_APP_NAME}-review-.*}"
  rules:
    # exclude merge requests
    - if: $CI_MERGE_REQUEST_ID
      when: never
    # exclude tags
    - if: $CI_COMMIT_TAG
      when: never
    # exclude if $OS_REVIEW_PROJECT not set
    - if: '$OS_REVIEW_PROJECT == null'
      when: never
    # on any branch with $CLEANUP_ALL_REVIEW set to 'force': auto, always (doesn't depend on upstream pipeline succeeds)
    - if: '$CLEANUP_ALL_REVIEW == "force"'
      when: always
      allow_failure: true
    # on production: manual
    - if: '$CLEANUP_ALL_REVIEW && $CI_COMMIT_REF_NAME =~ $PROD_REF'
      when: manual
      allow_failure: true

os-integration:
  extends: .os-deploy
  variables:
    ENV_TYPE: integration
    ENV_APP_NAME: "$OS_INTEG_APP_NAME"
    ENV_URL: "$OS_INTEG_URL"
    ENV_TOKEN: "$OS_INTEG_TOKEN"
    ENV_PROJECT: "$OS_INTEG_PROJECT"
  environment:
    name: integration
    url: "${OS_INTEG_ENVIRONMENT_URL}"
  resource_group: integration
  rules:
    # exclude merge requests
    - if: $CI_MERGE_REQUEST_ID
      when: never
    # only on integration branch(es), with $OS_INTEG_PROJECT set
    - if: '$OS_INTEG_PROJECT && $CI_COMMIT_REF_NAME =~ $INTEG_REF'


# Staging deploys are disabled by default since
# continuous deployment to production is enabled by default
# If you prefer to automatically deploy to staging and
# only manually promote to production, enable this job by setting
# $OS_STAGING_PROJECT.
os-staging:
  extends: .os-deploy
  variables:
    ENV_TYPE: staging
    ENV_APP_NAME: "$OS_STAGING_APP_NAME"
    ENV_URL: "$OS_STAGING_URL"
    ENV_TOKEN: "$OS_STAGING_TOKEN"
    ENV_PROJECT: "$OS_STAGING_PROJECT"
  environment:
    name: staging
    url: "${OS_STAGING_ENVIRONMENT_URL}"
  resource_group: staging
  rules:
    # exclude merge requests
    - if: $CI_MERGE_REQUEST_ID
      when: never
    # only on production branch(es), with $OS_STAGING_PROJECT set
    - if: '$OS_STAGING_PROJECT && $CI_COMMIT_REF_NAME =~ $PROD_REF'

# Deploy to production if on branch master and variable OS_PROD_PROJECT defined and AUTODEPLOY_TO_PROD is set
os-production:
  extends: .os-deploy
  stage: production
  variables:
    ENV_TYPE: production
    ENV_APP_SUFFIX: "" # no suffix for prod
    ENV_APP_NAME: "$OS_PROD_APP_NAME"
    ENV_URL: "$OS_PROD_URL"
    ENV_TOKEN: "$OS_PROD_TOKEN"
    ENV_PROJECT: "$OS_PROD_PROJECT"
  environment:
    name: production
    url: "${OS_PROD_ENVIRONMENT_URL}"
  resource_group: production
  rules:
    # exclude merge requests
    - if: $CI_MERGE_REQUEST_ID
      when: never
    # exclude non-production branches
    - if: '$CI_COMMIT_REF_NAME !~ $PROD_REF'
      when: never
    # exclude if $OS_PROD_PROJECT not set
    - if: '$OS_PROD_PROJECT == null || $OS_PROD_PROJECT == ""'
      when: never
    # if $AUTODEPLOY_TO_PROD: auto
    - if: $AUTODEPLOY_TO_PROD
    # else if PUBLISH_ON_PROD enabled: auto (because the publish job was blocking)
    - if: '$PUBLISH_ON_PROD == "true" || $PUBLISH_ON_PROD == "yes"'
    # else: manual, blocking
    - if: $OS_PROD_PROJECT # useless test, just to prevent GitLab warning
      when: manual