workflow:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_PIPELINE_SOURCE == "schedule"
    - if: $CI_COMMIT_TAG
    - if: $CI_COMMIT_REF_PROTECTED == "true"

include:
  - template: Jobs/SAST.gitlab-ci.yml
  - component: ${CI_SERVER_FQDN}/components/dependency-scanning/main@0.8.0
  - component: ${CI_SERVER_FQDN}/components/dependency-scanning/main@0.8.0
  - component: ${CI_SERVER_FQDN}/gitlab-org/components/danger-review/danger-review@2.0.0
    inputs:
      job_stage: lint
      job_allow_failure: true

  # NOTE: the two includes below are a hack to conditionally set the tags node
  # on our Go jobs. We want to use the large Ultimate runners if possible,
  # which is what we have available in the gitlab-org and gitlab-community (Community Forks)
  # groups. However, there is no easy way to conditionally set tags or even variables without
  # jeopardizing existing (complex) workflow:rules or job:rules. Thus, we resort to
  # this nasty conditionally include hack.
  - local: ".gitlab/ci/gitlab-go-runner-tags.gitlab-ci.yml"
    rules:
      - if: $CI_PROJECT_ROOT_NAMESPACE == 'gitlab-org' || $CI_PROJECT_ROOT_NAMESPACE == 'gitlab-community'
  - local: ".gitlab/ci/community-go-runner-tags.gitlab-ci.yml"
    rules:
      - if: $CI_PROJECT_ROOT_NAMESPACE != 'gitlab-org' && $CI_PROJECT_ROOT_NAMESPACE != 'gitlab-community'

  # Add Work Type Classification labels to merge requests,
  # see https://gitlab.com/gitlab-com/gl-infra/common-ci-tasks/-/blob/main/autolabels.md
  - project: "gitlab-com/gl-infra/common-ci-tasks"
    ref: v2.93.0
    file: "autolabels.yml"
    inputs:
      stage: lint


  # Add Work Type Classification labels to merge requests,
  # see https://gitlab.com/gitlab-com/gl-infra/common-ci-tasks/-/blob/main/autolabels.md
  - project: "gitlab-com/gl-infra/common-ci-tasks"
    ref: v2.93.0
    file: "autolabels.yml"
    inputs:
      stage: lint

  # Analyze commits to determine whether to cut a release
  # see https://gitlab.com/gitlab-com/gl-infra/common-ci-tasks/-/blob/main/semantic-release.md
  - project: "gitlab-com/gl-infra/common-ci-tasks"
    ref: v2.93.0
    file: "semantic-release.yml"
  - project: "gitlab-com/gl-infra/common-ci-tasks"
    ref: v2.93.0
    file: "semantic-release.yml"
    inputs:
      stage: deploy
      validate_stage: lint
      vault: access_tokens/${VAULT_SECRETS_PATH}/semantic-release/token@ci

stages:
  - lint
  - test
  - deploy

.go:versions:
  parallel:
    matrix:
      - GOLANG_IMAGE_VERSION:
        - '1.24'
        - '1.25'

.go:base:
  extends:
    - .go:runner-tags
  # From: https://docs.gitlab.com/ci/caching/#cache-go-dependencies
  variables:
    GOPATH: $CI_PROJECT_DIR/.go
    GOLANGCI_LINT_CACHE: $CI_PROJECT_DIR/.golangci-lint
    MISE_VERSION: v2025.6.1
    MISE_DATA_DIR: $CI_PROJECT_DIR/.mise/mise-data
  before_script:
    - mkdir -p "${GOPATH}" "${GOLANGCI_LINT_CACHE}"
    - export PATH="${GOPATH}/bin:$PATH"
    # Install mise
    - curl --retry 6 https://mise.run | MISE_VERSION=${MISE_VERSION} MISE_INSTALL_PATH=/usr/local/bin/mise sh
    - eval "$(mise activate bash --shims)"
    - mise install
  cache:
    - key:
        files:
          - go.sum
      paths:
        - $GOPATH/pkg/mod/
        - $GOLANGCI_LINT_CACHE/
    - key:
        prefix: mise-
        files: [".tool-versions"]
      paths:
        - $MISE_DATA_DIR

  # We only need to run Go-related jobs when actual Go files changed
  # or when running either on the default branch or for a tag.
  rules:
    - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
    - if: $CI_COMMIT_TAG
    - changes:
        - "**/*.go"
        - testdata/**
        - go.mod
        - go.sum
        - .gitlab-ci.yml
        - .gitlab/ci/*.yml
        - buf.yaml
        - buf.gen.yaml
        - buf.lock

golangci-lint:
  extends:
    - .go:base
  stage: lint
  needs: []
  variables:
    REPORT_FILENAME: "gl-code-quality-report.json"
  image: golangci/golangci-lint:v2.7.2
  script:
    - golangci-lint run
  artifacts:
    reports:
      codequality: $REPORT_FILENAME
    paths: [$REPORT_FILENAME]
    when: always

buf-lint:
  extends:
    - .go:base
  stage: lint
  needs: []
  script:
    - buf format --diff --exit-code
    - buf lint

verify-generated-code:
  extends:
    - .go:base
  stage: lint
  needs: []
  image: golang:1.25-bookworm
  script:
    - make generate
    - |
      echo "Checking git status"
      [ -z "$(git status --short)" ] || {
        echo "Error: Files should have been generated:";
        git status --short; echo "Diff:";
        git --no-pager diff HEAD;
        echo "Run \"make generate\" and try again";
        exit 1;
      }

tests:unit:
  extends:
    - .go:versions
  stage: test
  needs: []
  image: golang:$GOLANG_IMAGE_VERSION
  variables:
    # configure tooling versions
    GOTESTSUM_VERSION: 'v1.13.0'
    GOCOVER_COBERTURA_VERSION: 'v1.4.0'

    # configure artifact files
    JUNIT_FILENAME: tests.xml
    COVERPROFILE_FILENAME: coverage.out
    COVERPROFILE_XML_FILENAME: coverage.xml
  script:
    - go run gotest.tools/gotestsum@${GOTESTSUM_VERSION} --format=standard-quiet --junitfile=$JUNIT_FILENAME -- -race -coverprofile=$COVERPROFILE_FILENAME -covermode=atomic ./...
    - grep -v '_generated.go' "$COVERPROFILE_FILENAME" | grep -v '_mock.go' > "${COVERPROFILE_FILENAME}.tmp"
    - mv "${COVERPROFILE_FILENAME}.tmp" "$COVERPROFILE_FILENAME"
    - go run github.com/boumenot/gocover-cobertura@${GOCOVER_COBERTURA_VERSION} < $COVERPROFILE_FILENAME > $COVERPROFILE_XML_FILENAME
    - go tool cover -func $COVERPROFILE_FILENAME
  coverage: '/total:.+\(statements\).+\d+\.\d+/'
  artifacts:
    paths:
      - $JUNIT_FILENAME
      - $COVERPROFILE_XML_FILENAME
    reports:
      junit: $JUNIT_FILENAME
      coverage_report:
        path: $COVERPROFILE_XML_FILENAME
        coverage_format: cobertura
    when: always


tests:integration:
  stage: test
  rules:
    - if: $ENABLE_EE_ACCEPTANCE_TESTS == "true"
  needs: []
  image: golang:1.25-bookworm
  timeout: 2 hours
  services:
    - docker:29.1.3-dind
  variables:
    # Docker-in-docker configuration
    DOCKER_HOST: tcp://docker:2376
    DOCKER_TLS_VERIFY: 1
    DOCKER_TLS_CERTDIR: "/certs"
    DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"

    # GitLab test configuration
    GITLAB_BASE_URL: http://docker:8095/api/v4

    # Since we're not using the makefile, this doesn't default to the testing token
    # properly. 
    GITLAB_TOKEN: "glpat-ACCTEST1234567890123"

    # configure tooling versions
    GOTESTSUM_VERSION: 'v1.12.0'
    GOCOVER_COBERTURA_VERSION: 'v1.2.1-0.20240107185409-0818f3538137'
    MISE_VERSION: v2025.6.1

    # configure artifact files
    JUNIT_FILENAME: integration-tests.xml
    COVERPROFILE_FILENAME: integration-coverage.out
    COVERPROFILE_XML_FILENAME: integration-coverage.xml
  before_script:
    # Install docker + compose
    - curl -fsSL https://get.docker.com | sh
    # Install mise
    - curl --retry 3 https://mise.run | MISE_VERSION=${MISE_VERSION} MISE_INSTALL_PATH=/usr/local/bin/mise sh
    - eval "$(mise activate bash --shims)"
    
    # Install Go and other tools with mise
    - mise install

    # Copy the license to the correct location
    - test -f "${GITLAB_LICENSE}" && cp "${GITLAB_LICENSE}" "${CI_PROJECT_DIR}/Gitlab-license.txt"
        
    # Start GitLab instance
    - make testacc-up SERVICE=gitlab-ee
  script:
    - go run gotest.tools/gotestsum@${GOTESTSUM_VERSION} --format=standard-quiet --junitfile=$JUNIT_FILENAME -- -race -coverprofile=$COVERPROFILE_FILENAME -covermode=atomic -tags=integration ./gitlab_test/...
    - grep -v '_generated.go' "$COVERPROFILE_FILENAME" | grep -v '_mock.go' > "${COVERPROFILE_FILENAME}.tmp"
    - mv "${COVERPROFILE_FILENAME}.tmp" "$COVERPROFILE_FILENAME"
    - go run github.com/boumenot/gocover-cobertura@${GOCOVER_COBERTURA_VERSION} < $COVERPROFILE_FILENAME > $COVERPROFILE_XML_FILENAME
    - go tool cover -func $COVERPROFILE_FILENAME


# Update rules on SAST to ensure the jobs show up in the pipeline
# this prevents forks that don't have `ultimate` from skipping SAST scans
# since gitlab-advaced-sast replaces semgrep.
semgrep-sast:
  needs: []
  rules:
    - when: always

# Update rules on dependency scanning so that it only runs when on the 
# canonical project or the community fork. This ensures that when people
# use personal forks, it doesn't fail stating the forks are unlicensed.
dependency-scanning:
  rules:
    - if: $CI_PROJECT_ID == "65271576" # Canonical repository
    - if: $CI_PROJECT_ID == "65275361" # Community Fork
    - when: never
