# ========================================================================================= # 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. # ========================================================================================= variables: # Default Go project root directory GO_PROJECT_DIR: . # Default Docker image (can be overridden) GO_IMAGE: "golang:buster" # Default flags for 'build' command GO_BUILD_FLAGS: >- -mod=readonly # Default packages for 'build' command GO_BUILD_PACKAGES: >- ./... # Default flags for 'test' command GO_TEST_FLAGS: >- -mod=readonly -v -race # Default packages for 'test' command GO_TEST_PACKAGES: >- ./... # Default arguments for 'list' command GO_LIST_ARGS : >- list -u -m -mod=readonly -json all # Default arguments for go-mod-outdated command GO_MOD_OUTDATED_ARGS: '-update -direct -style markdown -ci' # Default golangci-lint Docker image (can be overridden) 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)$/' # default integration ref name (pattern) INTEG_REF: '/^develop$/' # allowed stages depend on your template type (see: to-be-continuous.gitlab.io/doc/dev-guidelines/#stages) 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() { coverage_out=reports/coverage.out 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" go tool cover -html="$coverage_out" -o "reports/coverage.html" else log_info "--- \\e[32mCoverage report(s) not found\\e[0m: skip" fi } function go_build() { 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 $GO_BUILD_FLAGS -o "$target_dir" $GO_BUILD_PACKAGES } function go_test() { mkdir reports local go_tests_report="reports/tests-output.txt" local junit_tests_report="reports/junit-tests-report.xml" local coverage_report_opts=-coverprofile=reports/coverage.out # shellcheck disable=SC2086 go test $GO_TEST_FLAGS "$coverage_report_opts" $GO_TEST_PACKAGES | tee "$go_tests_report" output_coverage install_go_junit_report # shellcheck disable=SC2002 cat "$go_tests_report" | "$GOBIN/go-junit-report" > "$junit_tests_report" sonar_tests_report } function install_go_junit_report() { cd "$(mktemp -d)" go mod init go-junit-report go install github.com/jstemmer/go-junit-report@latest cd - } function sonar_tests_report() { if [[ -n "$SONAR_URL" ]] then log_info "SonarQube template detected (\$SONAR_URL): generate tests report for SonarQube (\\e[33;1mreports/sonar-tests-report.json\\e[0m)" mkdir -p reports # shellcheck disable=SC2086 go test $GO_TEST_FLAGS -json $GO_TEST_PACKAGES > reports/sonar-tests-report.json fi } function sonar_lint_report() { if [[ -n "$SONAR_URL" ]] then mkdir -p reports log_info "SonarQube template detected (\$SONAR_URL): generate golangci-lint report for SonarQube (\\e[33;1mreports/golangci-lint-report.xml\\e[0m)" # shellcheck disable=SC2086 golangci-lint run --out-format checkstyle $GO_CI_LINT_ARGS > reports/golangci-lint-report.xml fi } function install_go_mod_outdated() { cd "$(mktemp -d)" go mod init go-mod-outdated go install github.com/psampaz/go-mod-outdated@latest cd - } 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" } function get_latest_template_version() { tag_json=$(wget -T 5 -q -O - "$CI_API_V4_URL/projects/to-be-continuous%2F$1/repository/tags?per_page=1" || echo "") 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 [[ "$TEMPLATE_CHECK_UPDATE_DISABLED" != "true" ]]; then check_for_update golang "2.1.2"; fi unscope_variables # ENDSCRIPT # job prototype # defines default default docker image, tracking probe, cache policy and tags .go-base: image: $GO_IMAGE services: - name: "$CI_REGISTRY/to-be-continuous/tools/tracking:master" command: ["--service", "golang", "2.1.2" ] variables: # The directory where 'go install' will install a command. GOBIN: "$CI_PROJECT_DIR/$GO_PROJECT_DIR/bin" # The directory where the go command will store cached information for reuse in future builds. GOCACHE: "$CI_PROJECT_DIR/$GO_PROJECT_DIR/.cache" cache: key: "$CI_COMMIT_REF_SLUG-golang" paths: - $GO_PROJECT_DIR/.cache/ before_script: - *go-scripts - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}" - cd ${GO_PROJECT_DIR} go-build: extends: .go-base stage: build script: - go_build artifacts: name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG" expire_in: 1 day paths: - $GO_PROJECT_DIR/bin/ rules: # exclude merge requests - if: $CI_MERGE_REQUEST_ID when: never # if $GO_TEST_IMAGE set - if: '$GO_TEST_IMAGE' go-test: extends: .go-base image: $GO_TEST_IMAGE stage: build script: - go_test coverage: '/^(\d+.\d+\%) covered$/' artifacts: name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG" expire_in: 1 day reports: junit: - "$GO_PROJECT_DIR/reports/junit-*.xml" paths: - $GO_PROJECT_DIR/reports/ rules: # exclude merge requests - if: $CI_MERGE_REQUEST_ID when: never # if $GO_TEST_IMAGE set - if: '$GO_TEST_IMAGE' go-build-test: extends: .go-base stage: build script: - go_build - go_test coverage: '/^(\d+.\d+\%) covered$/' artifacts: name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG" expire_in: 1 day reports: junit: - "$GO_PROJECT_DIR/reports/junit-*.xml" paths: - $GO_PROJECT_DIR/bin/ - $GO_PROJECT_DIR/reports/ rules: # exclude merge requests - if: $CI_MERGE_REQUEST_ID when: never # 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: - golangci-lint run $GO_CI_LINT_ARGS - sonar_lint_report artifacts: name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG" expire_in: 1 day when: always paths: - $GO_PROJECT_DIR/reports/ rules: # exclude merge requests - if: $CI_MERGE_REQUEST_ID when: never # exclude if GO_CI_LINT_DISABLED set - if: '$GO_CI_LINT_DISABLED == "true"' when: never # on production or integration branches: auto - if: '$CI_COMMIT_REF_NAME =~ $PROD_REF || $CI_COMMIT_REF_NAME =~ $INTEG_REF' # else (development branches): non-blocking - if: $CI_COMMIT_REF_NAME # useless but prevents GitLab from warning allow_failure: true go-mod-outdated: extends: .go-base stage: test dependencies: [] script: - mkdir reports - install_go_mod_outdated - go $GO_LIST_ARGS | $GOBIN/go-mod-outdated $GO_MOD_OUTDATED_ARGS > reports/go-mod-outdated-report.md artifacts: name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG" expire_in: 1 day when: always paths: - $GO_PROJECT_DIR/reports/ rules: # exclude merge requests - if: $CI_MERGE_REQUEST_ID when: never # on schedule: auto - if: '$CI_PIPELINE_SOURCE == "schedule"' allow_failure: true # else manual & non-blocking - when: manual allow_failure: true