Skip to content
Snippets Groups Projects
gitlab-ci-golang.yml 14.3 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;
Pierre Smeyers's avatar
Pierre Smeyers committed
# 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
Pierre Smeyers's avatar
Pierre Smeyers committed
# Floor, Boston, MA  02110-1301, USA.
# =========================================================================================
Pierre Smeyers's avatar
Pierre Smeyers committed
# default workflow rules: Merge Request pipelines
workflow:
  rules:
Pierre Smeyers's avatar
Pierre Smeyers committed
    # prevent branch pipeline when an MR is open (prefer MR pipeline)
    - if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS'
      when: never
    - when: always

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

Pierre Smeyers's avatar
Pierre Smeyers committed
variables:
  # variabilized tracking image
  TBC_TRACKING_IMAGE: "$CI_REGISTRY/to-be-continuous/tools/tracking:master"

  # Default Go project root directory
  GO_PROJECT_DIR: .

Pierre Smeyers's avatar
Pierre Smeyers committed
  # Default Docker image (can be overridden)
Pierre Smeyers's avatar
Pierre Smeyers committed
  GO_IMAGE: "golang:buster"

  # Default flags for 'build' command
  GO_BUILD_FLAGS: >-
Pierre Smeyers's avatar
Pierre Smeyers committed
    -mod=readonly
  # Default flags for go build linker
  GO_BUILD_LINKER_FLAGS: "-s -w"
  # Default packages for 'build' command
  GO_BUILD_PACKAGES: >-
Pierre Smeyers's avatar
Pierre Smeyers committed
    ./...

  # Default build mode (application/modules/auto)
  GO_BUILD_MODE: auto

  # Default flags for 'test' command
  GO_TEST_FLAGS: >-
Pierre Smeyers's avatar
Pierre Smeyers committed
    -mod=readonly
    -v
    -race

  # Default packages for 'test' command
  GO_TEST_PACKAGES: >-
    ./...
Pierre Smeyers's avatar
Pierre Smeyers committed

  # Default arguments for 'list' command
  GO_LIST_ARGS : >-
    list
    -u
    -m
    -mod=readonly
    -json all

  # Default arguments for go-mod-outdated command
Pierre Smeyers's avatar
Pierre Smeyers committed
  GO_MOD_OUTDATED_ARGS: '-update -direct'
Pierre Smeyers's avatar
Pierre Smeyers committed
  # Default golangci-lint Docker image (can be overridden)
Pierre Smeyers's avatar
Pierre Smeyers committed
  GO_CI_LINT_IMAGE: "golangci/golangci-lint:latest-alpine"

  # Default arguments for golangci-lint command
  GO_CI_LINT_ARGS: '-E gosec,goimports ./...'

  # default production ref name (pattern)
  PROD_REF: '/^(master|main)$/'
Pierre Smeyers's avatar
Pierre Smeyers committed
  # default integration ref name (pattern)
  INTEG_REF: '/^develop$/'

Pierre Smeyers's avatar
Pierre Smeyers committed
# allowed stages depend on your template type (see: to-be-continuous.gitlab.io/doc/dev-guidelines/#stages)
Pierre Smeyers's avatar
Pierre Smeyers committed
stages:
  - build
  - test

.go-scripts: &go-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 output_coverage() {
Pierre Smeyers's avatar
Pierre Smeyers committed
    coverage_out=reports/go-coverage.native.out
Pierre Smeyers's avatar
Pierre Smeyers committed
    if [[ -f "$coverage_out" ]]
    then
      log_info "--- \\e[32mCoverage report(s) found\\e[0m (\\e[33;1m${coverage_out}\\e[0m): output"
      percent=$(go tool cover -func="$coverage_out" | tail -1 | awk -F" " '{print $NF}')
      echo "${percent} covered"
Pierre Smeyers's avatar
Pierre Smeyers committed

      go get github.com/boumenot/gocover-cobertura
      go run github.com/boumenot/gocover-cobertura < "$coverage_out" > reports/go-coverage.cobertura.xml
Pierre Smeyers's avatar
Pierre Smeyers committed
    else
      log_info "--- \\e[32mCoverage report(s) not found\\e[0m: skip"
    fi
  }

    case "${GO_BUILD_MODE}" in
      application)
        go_build_application
        ;;
      modules)
        go_build_modules
        ;;
      auto)
        go_build_auto
        ;;
      *)
        log_error "invalid \\e[94;1mGO_BUILD_MODE\\e[0m (expected values are \\e[96;1mapplication\\e[0m, \\e[96;1mmodules\\e[0m, \\e[96;1mauto\\e[0m)"
        exit 1
        ;;
    esac
  }

  function go_build_auto() {
    log_info "auto build mode, looking up for a \\e[33;1mmain\\e[0m package..."
    if grep -Rw --include "*.go" "^package main"
    then
      go_build_application
    fi
  }

  function go_build_application() {
    log_info "building go application"
    GO_TARGET_OS="${GO_TARGET_OS:-$GOOS}"
    GO_TARGET_ARCH="${GO_TARGET_ARCH:-$GOARCH}"
    target_dir="$GOBIN/$GO_TARGET_OS/$GO_TARGET_ARCH"
    mkdir -p "$target_dir"
    # shellcheck disable=SC2086
    GOOS="$GO_TARGET_OS" GOARCH="$GO_TARGET_ARCH" go build -ldflags="$GO_BUILD_LINKER_FLAGS" $GO_BUILD_FLAGS -o "$target_dir" $GO_BUILD_PACKAGES
  function go_build_modules() {
    log_info "building go modules"
    # shellcheck disable=SC2086
    go build -ldflags="$GO_BUILD_LINKER_FLAGS" $GO_BUILD_FLAGS $GO_BUILD_PACKAGES
  }

Pierre Smeyers's avatar
Pierre Smeyers committed
    mkdir -p -m 777 reports
    local go_text_report="reports/go-test.native.txt"
    # shellcheck disable=SC2086
Pierre Smeyers's avatar
Pierre Smeyers committed
    go test $GO_TEST_FLAGS "-coverprofile=reports/go-coverage.native.out" $GO_TEST_PACKAGES > "$go_text_report"
    test_rc=$?
    set -e

    # dump text report in the console
    cat "$go_text_report"
    # compute and dump code coverage in the console

    # produce JUnit report (for GitLab)
    install_go_junit_report
Pierre Smeyers's avatar
Pierre Smeyers committed
    "$GOBIN/go-junit-report" < "$go_text_report" > reports/go-test.xunit.xml

    # produce JSON report (for SonarQube)
Pierre Smeyers's avatar
Pierre Smeyers committed
    go tool test2json < "$go_text_report" > reports/go-test.native.json

    # maybe fail
    if [[ "$test_rc" != "0" ]]; then exit "$test_rc"; fi
Pierre Smeyers's avatar
Pierre Smeyers committed
  function install_go_junit_report() {
    cd "$(mktemp -d)"
    go mod init go-junit-report
    go install github.com/jstemmer/go-junit-report@latest
Yann D'Isanto's avatar
Yann D'Isanto committed
  function install_go_mod_outdated() {
    cd "$(mktemp -d)"
    go mod init go-mod-outdated
    go install github.com/psampaz/go-mod-outdated@latest
Pierre Smeyers's avatar
Pierre Smeyers committed
  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}__"
Cédric OLIVIER's avatar
Cédric OLIVIER committed
        _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;
          if [[ -z "$_not" ]] && [[ "$_cond_val" != "$_cmp_val"* ]]; then continue;
          elif [[ "$_not" ]] && [[ "$_cond_val" == "$_cmp_val"* ]]; then continue;
          if [[ -z "$_not" ]] && [[ "$_cond_val" != *"$_cmp_val" ]]; then continue;
          elif [[ "$_not" ]] && [[ "$_cond_val" == *"$_cmp_val" ]]; then continue;
          if [[ -z "$_not" ]] && [[ "$_cond_val" != *"$_cmp_val"* ]]; then continue;
          elif [[ "$_not" ]] && [[ "$_cond_val" == *"$_cmp_val"* ]]; then continue;
          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"
  }

  unscope_variables
Pierre Smeyers's avatar
Pierre Smeyers committed

  # ENDSCRIPT

# job prototype
# defines default default docker image, tracking probe, cache policy and tags
.go-base:
  image: $GO_IMAGE
  services:
    - name: "$TBC_TRACKING_IMAGE"
      command: ["--service", "golang", "4.0.2" ]
Pierre Smeyers's avatar
Pierre Smeyers committed
  variables:
    # The directory where 'go install' will install a command.
    GOBIN: "$CI_PROJECT_DIR/$GO_PROJECT_DIR/bin"
Pierre Smeyers's avatar
Pierre Smeyers committed
    # The directory where the go command will store cached information for reuse in future builds.
    GOCACHE: "$CI_PROJECT_DIR/$GO_PROJECT_DIR/.cache"
Pierre Smeyers's avatar
Pierre Smeyers committed
  cache:
    key: "$CI_COMMIT_REF_SLUG-golang"
    paths:
      - $GO_PROJECT_DIR/.cache/
Pierre Smeyers's avatar
Pierre Smeyers committed
  before_script:
    - *go-scripts
    - |
      if command -v git > /dev/null
      then
        git config --global url.https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}.insteadOf https://${CI_SERVER_HOST}
      else
        log_warn "If you need to use private repository, you should provide an image with git executable"
      fi
Pierre Smeyers's avatar
Pierre Smeyers committed
    - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}"
    - cd ${GO_PROJECT_DIR}
Pierre Smeyers's avatar
Pierre Smeyers committed

go-build:
  extends: .go-base
  stage: build
  script:
Pierre Smeyers's avatar
Pierre Smeyers committed
  artifacts:
    name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
    expire_in: 1 day
    paths:
      - $GO_PROJECT_DIR/bin/
Pierre Smeyers's avatar
Pierre Smeyers committed
  rules:
    # if $GO_TEST_IMAGE set
    - if: '$GO_TEST_IMAGE'

go-test:
  extends: .go-base
  image: $GO_TEST_IMAGE
  stage: build
  script:
Pierre Smeyers's avatar
Pierre Smeyers committed
  coverage: '/^(\d+.\d+\%) covered$/'
  artifacts:
    name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
    expire_in: 1 day
Pierre Smeyers's avatar
Pierre Smeyers committed
    reports:
      junit:
Pierre Smeyers's avatar
Pierre Smeyers committed
        - "$GO_PROJECT_DIR/reports/go-test.xunit.xml"
      coverage_report:
        coverage_format: cobertura
        path: "$GO_PROJECT_DIR/reports/go-coverage.cobertura.xml"
Pierre Smeyers's avatar
Pierre Smeyers committed
    paths:
Pierre Smeyers's avatar
Pierre Smeyers committed
      - "$GO_PROJECT_DIR/reports/go-test.*"
      - "$GO_PROJECT_DIR/reports/go-coverage.*"
Pierre Smeyers's avatar
Pierre Smeyers committed
  rules:
    # if $GO_TEST_IMAGE set
Pierre Smeyers's avatar
Pierre Smeyers committed
    - if: '$GO_TEST_IMAGE == null'
      when: never
    - !reference [.test-policy, rules]
Pierre Smeyers's avatar
Pierre Smeyers committed

go-build-test:
  extends: .go-base
  stage: build
  script:
Pierre Smeyers's avatar
Pierre Smeyers committed
  coverage: '/^(\d+.\d+\%) covered$/'
  artifacts:
    name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
Pierre Smeyers's avatar
Pierre Smeyers committed
    expire_in: 1 day
    reports:
      junit:
Pierre Smeyers's avatar
Pierre Smeyers committed
        - "$GO_PROJECT_DIR/reports/go-test.xunit.xml"
      coverage_report:
        coverage_format: cobertura
        path: "$GO_PROJECT_DIR/reports/go-coverage.cobertura.xml"
Pierre Smeyers's avatar
Pierre Smeyers committed
    paths:
      - $GO_PROJECT_DIR/bin/
      - $GO_PROJECT_DIR/reports/
Pierre Smeyers's avatar
Pierre Smeyers committed
      - "$GO_PROJECT_DIR/reports/go-test.*"
      - "$GO_PROJECT_DIR/reports/go-coverage.*"
Pierre Smeyers's avatar
Pierre Smeyers committed
  rules:
    # if $GO_TEST_IMAGE not set
    - if: '$GO_TEST_IMAGE == null'

go-ci-lint:
  extends: .go-base
  stage: build
  image: $GO_CI_LINT_IMAGE
  script:
Pierre Smeyers's avatar
Pierre Smeyers committed
    - mkdir -p -m 777 reports
    # produce all reports at once
Pierre Smeyers's avatar
Pierre Smeyers committed
    - golangci-lint run --out-format "colored-line-number:stdout,code-climate:reports/go-ci-lint.codeclimate.json,checkstyle:reports/go-ci-lint.checkstyle.xml" $GO_CI_LINT_ARGS
Pierre Smeyers's avatar
Pierre Smeyers committed
  artifacts:
    name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
    expire_in: 1 day
    when: always
    paths:
Pierre Smeyers's avatar
Pierre Smeyers committed
      - "$GO_PROJECT_DIR/reports/go-ci-lint.*"
    reports:
      codequality:
Pierre Smeyers's avatar
Pierre Smeyers committed
        - "$GO_PROJECT_DIR/reports/go-ci-lint.codeclimate.json"
Pierre Smeyers's avatar
Pierre Smeyers committed
  rules:
    # exclude if GO_CI_LINT_DISABLED set
    - if: '$GO_CI_LINT_DISABLED == "true"'
      when: never
Pierre Smeyers's avatar
Pierre Smeyers committed
    - !reference [.test-policy, rules]
Pierre Smeyers's avatar
Pierre Smeyers committed

go-mod-outdated:
  extends: .go-base
  stage: test
  dependencies: []
  script:
Pierre Smeyers's avatar
Pierre Smeyers committed
    - mkdir -p -m 777 reports
    # go list
    - go $GO_LIST_ARGS > reports/go-list.native.json
Pierre Smeyers's avatar
Pierre Smeyers committed
    - install_go_mod_outdated
Pierre Smeyers's avatar
Pierre Smeyers committed
    # console output (no fail)
    - $GOBIN/go-mod-outdated $GO_MOD_OUTDATED_ARGS < reports/go-list.native.json
    # text report (-ci fails)
    - $GOBIN/go-mod-outdated $GO_MOD_OUTDATED_ARGS -ci < reports/go-list.native.json > reports/go-mod-outdated.native.txt
Pierre Smeyers's avatar
Pierre Smeyers committed
  artifacts:
    name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
    expire_in: 1 day
    when: always
    paths:
Pierre Smeyers's avatar
Pierre Smeyers committed
      - "$GO_PROJECT_DIR/reports/go-list.native.json"
      - "$GO_PROJECT_DIR/reports/go-mod-outdated.native.txt"
Pierre Smeyers's avatar
Pierre Smeyers committed
  rules:
    # on schedule: auto
    - if: '$CI_PIPELINE_SOURCE == "schedule"'
      allow_failure: true
    # else manual & non-blocking
    - when: manual
      allow_failure: true