Skip to content
Snippets Groups Projects
Select Git revision
  • 51ad749b6beb8b11b7c71a1c132c225fb6e154fa
  • master default protected
  • 4
  • 4.3
  • 4.3.1
  • 4.3.0
  • 4.2
  • 4.2.4
  • 4.2.3
  • 4.2.2
10 results

gitlab-ci-sonar.yml

Blame
  • gitlab-ci-sonar.yml 15.23 KiB
    # =========================================================================================
    # 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
    
    # 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
    
    variables:
      # variabilized tracking image
      TBC_TRACKING_IMAGE: "registry.gitlab.com/to-be-continuous/tools/tracking:master"
    
      # Sonar
      SONAR_SCANNER_IMAGE: "registry.hub.docker.com/sonarsource/sonar-scanner-cli:latest"
    
      # Sonar base analysis default args
      # see: https://docs.sonarqube.org/latest/analysis/analysis-parameters/
      # default uses branch analysis: https://docs.sonarqube.org/latest/branches/overview/
      SONAR_BASE_ARGS: >-
        -Dsonar.links.homepage=${CI_PROJECT_URL}
        -Dsonar.links.ci=${CI_PROJECT_URL}/-/pipelines
        -Dsonar.links.issue=${CI_PROJECT_URL}/-/issues
    
      # 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
    
    .sonar-scripts: &sonar-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 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
    
        # import in Java keystore (if keytool command found)
        if command -v keytool > /dev/null
        then
          # shellcheck disable=SC2046
          javahome=${JAVA_HOME:-$(dirname $(readlink -f $(command -v java)))/..}
          # shellcheck disable=SC2086
          keystore=${JAVA_KEYSTORE_PATH:-$(ls -1 $javahome/jre/lib/security/cacerts 2>/dev/null || ls -1 $javahome/lib/security/cacerts 2>/dev/null || echo "")}
          if [[ -f "$keystore" ]]
          then
            storepass=${JAVA_KEYSTORE_PASSWORD:-changeit}
            nb_certs=$(echo "$certs" | grep -c 'END CERTIFICATE')
            log_info "importing $nb_certs certificates in Java keystore \\e[33;1m$keystore\\e[0m..."
            for idx in $(seq 0 $((nb_certs - 1)))
            do
              # TODO: use keytool option -trustcacerts ?
              if echo "$certs" | awk "n==$idx { print }; /END CERTIFICATE/ { n++ }" | keytool -noprompt -import -alias "imported CA Cert $idx" -keystore "$keystore" -storepass "$storepass"
              then
                log_info "... CA certificate [$idx] successfully imported"
              else
                log_warn "... Failed importing CA certificate [$idx]: abort"
                return
              fi
            done
          else
            log_warn "Java keystore \\e[33;1m$keystore\\e[0m not found: could not import CA certificates"
          fi
        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
            log_warn "Couldn't get secret \\e[33;1m${name}\\e[0m: no http client found"
          fi
          ;;
        esac
      }
    
      function eval_all_secrets() {
        encoded_vars=$(env | grep -Ev '(^|.*_ENV_)scoped__' | awk -F '=' '/^[a-zA-Z0-9_]*=@(b64|hex|url)@/ {print $1}')
        for var in $encoded_vars
        do
          eval_secret "$var"
        done
      }
    
      # builds the Java proxy options from Linux env (http_proxy, https_proxy, ftp_proxy and no_proxy)
      function eval_java_proxy_args() {
        # transform no_proxy into Java stype nonProxyHosts
        nph=$(echo "${no_proxy:-$NO_PROXY}" | sed -e 's/\s*//g' -e 's/^\./*./' -e 's/,\./,*./g' -e 's/,/|/g')
        java_proxy_args="$(java_proto_proxy_args http "${http_proxy:-$HTTP_PROXY}" "$nph") $(java_proto_proxy_args https "${https_proxy:-$HTTPS_PROXY}" "$nph") $(java_proto_proxy_args ftp "${ftp_proxy:-$FTP_PROXY}" "$nph")"
        export java_proxy_args
        if [[ "$java_proxy_args" ]]
        then
          log_info "Using Java proxy options (from env):  \\e[33;1m$java_proxy_args\\e[0m"
        fi
      }
    
      function java_proto_proxy_args() {
        proto=$1
        proxy=$2
        non_proxy_hosts=$3
        if [[ "$proxy" ]]
        then
          host_port=$(echo "$proxy" | cut -d'/' -f3)
          host=$(echo "$host_port" | cut -d':' -f1)
          port=$(echo "$host_port" | cut -s -d':' -f2)
          proto_proxy_args="-D$proto.proxyHost=$host -D$proto.proxyPort=${port:-80}"
          if [[ "$non_proxy_hosts" ]]; then proto_proxy_args="$proto_proxy_args -D$proto.nonProxyHosts=\"$non_proxy_hosts\""; fi
          echo "$proto_proxy_args"
        fi
      }
    
      # determines whether the given SonarQube param is defined 
      # either in the sonar-project.properties or in the $SONAR_BASE_ARGS variable
      function has_sonar_param() {
        sonar_param="$1"
        grep -e "^sonar\.${sonar_param}[ \t]*[:=]" sonar-project.properties > /dev/null || echo "$SONAR_BASE_ARGS" | grep -e "-Dsonar\.${sonar_param}=" > /dev/null
      }
    
      unscope_variables
      eval_all_secrets
    
      # ENDSCRIPT
    
    # Sonar job
    sonar:
      stage: test
      image:
        name: $SONAR_SCANNER_IMAGE
        entrypoint: [""]
      services:
        - name: "$TBC_TRACKING_IMAGE"
          command: ["--service", "sonar", "4.1.1" ]
      variables:
        # see: https://docs.sonarqube.org/latest/analysis/gitlab-integration/#header-4
        SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache
        GIT_DEPTH: 0 # Tells git to fetch all the branches of the project, required by the analysis task
      cache:
        key: "sonar"
        paths:
          - .sonar/cache
      before_script:
        - !reference [.sonar-scripts]
        - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}"
        - eval_java_proxy_args
      script:
        - |
          if [[ -z "$SONAR_PROJECT_KEY" ]] && ! has_sonar_param projectKey
          then
            log_info "No Sonar Project Key explicitly set: use default \\e[33;1m$CI_PROJECT_PATH_SLUG\\e[0m"
            log_info "If you want another value, either set the 'sonar.projectKey' entry in your sonar-project.properties, or set it as \$SONAR_PROJECT_KEY"
            export SONAR_PROJECT_KEY="$CI_PROJECT_PATH_SLUG"
          fi
        - |
          if [[ -z "$SONAR_PROJECT_NAME" ]] && ! has_sonar_param projectName
          then
            log_info "No Sonar Project Name explicitly set: use default \\e[33;1m$CI_PROJECT_PATH\\e[0m"
            log_info "If you want another value, either set the 'sonar.projectName' entry in your sonar-project.properties, or set it as \$SONAR_PROJECT_NAME"
            export SONAR_PROJECT_NAME="$CI_PROJECT_PATH"
          fi
        - |
          if [[ "$SONAR_URL" ]] && [[ -z "$SONAR_HOST_URL" ]]
          then 
            log_warn '$SONAR_URL variable detected: use $SONAR_HOST_URL instead (see doc)'
            export SONAR_HOST_URL="$SONAR_URL"
          fi
        - |
          if [[ "$SONAR_AUTH_TOKEN" ]] && [[ -z "$SONAR_TOKEN" ]]
          then 
            log_warn '$SONAR_AUTH_TOKEN variable detected: use $SONAR_TOKEN instead (see doc)'
            export SONAR_TOKEN="$SONAR_AUTH_TOKEN"
          fi
        - >-
          sonar-scanner ${TRACE+-Dsonar.verbose=true} $java_proxy_args 
          ${SONAR_LOGIN+-Dsonar.login=$SONAR_LOGIN} 
          ${SONAR_PASSWORD+-Dsonar.password=$SONAR_PASSWORD} 
          ${SONAR_PROJECT_KEY+-Dsonar.projectKey=$SONAR_PROJECT_KEY} 
          ${SONAR_PROJECT_NAME+-Dsonar.projectName=$SONAR_PROJECT_NAME} 
          ${SONAR_QUALITY_GATE_ENABLED+-Dsonar.qualitygate.wait=$SONAR_QUALITY_GATE_ENABLED}
          $SONAR_BASE_ARGS
      rules:
        - !reference [.test-policy, rules]