---
include:
  - project: funkwhale/ci
    file: /templates/pre-commit.yml
  - project: funkwhale/ci
    file: /templates/lychee.yml

variables:
  PYTHONDONTWRITEBYTECODE: "true"

  PIP_CACHE_DIR: $CI_PROJECT_DIR/.cache/pip

.shared_variables:
  # Keep the git files permissions during job setup
  keep_git_files_permissions: &keep_git_files_permissions
    GIT_STRATEGY: clone
    GIT_DEPTH: "5"
    FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR: "true"

default:
  tags:
    - docker

workflow:
  rules:
    # Run for any event on the default branches in the funkwhale namespace
    - if: >
        $CI_PROJECT_NAMESPACE == "funkwhale" &&
        (
          $CI_COMMIT_BRANCH =~ /(stable|develop)/ ||
          $CI_COMMIT_TAG
        )
    # Run for merge requests from any repo or branches
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

stages:
  - review
  - lint
  - test
  - build
  - publish

review_front:
  interruptible: true
  stage: review
  image: node:18-alpine
  when: manual
  allow_failure: true
  variables:
    BASE_URL: /-/$CI_PROJECT_NAME/-/jobs/$CI_JOB_ID/artifacts/front-review/
    VUE_APP_ROUTER_BASE_URL: /-/$CI_PROJECT_NAME/-/jobs/$CI_JOB_ID/artifacts/front-review/
    VUE_APP_INSTANCE_URL: https://demo.funkwhale.audio
    NODE_ENV: review
  before_script:
    - apk add --no-cache jq bash coreutils python3
    - rm -rf front-review
    - mkdir front-review
    - cd front
  script:
    - yarn install
    # this is to ensure we don't have any errors in the output,
    # cf https://dev.funkwhale.audio/funkwhale/funkwhale/issues/169
    - yarn run build --base ./ | tee /dev/stderr | (! grep -i 'ERROR in')
    - cp -r dist/* ../front-review
  artifacts:
    expire_in: 2 weeks
    paths:
      - front-review
  cache:
    key: "funkwhale__front_dependencies"
    paths:
      - front/node_modules
      - front/yarn.lock
  environment:
    name: review/front/$CI_COMMIT_REF_NAME
    url: http://$CI_PROJECT_NAMESPACE.pages.funkwhale.audio/-/$CI_PROJECT_NAME/-/jobs/$CI_JOB_ID/artifacts/front-review/index.html

review_docs:
  interruptible: true
  stage: review
  allow_failure: true
  image: python:3.11
  variables:
    BUILD_PATH: "../docs-review"
  before_script:
    - rm -rf docs-review
    - mkdir docs-review
    - cd docs
    - apt-get update
    - apt-get install -y graphviz git
    - pip install poetry
    - poetry install
    - git switch develop && git pull
    - git switch stable && git pull
    - git switch $CI_COMMIT_BRANCH && git pull
  script:
    - poetry run python3 -m sphinx . $BUILD_PATH
  cache:
    key: "$CI_PROJECT_ID__sphinx"
    paths:
      - "$PIP_CACHE_DIR"
  artifacts:
    expire_in: 2 weeks
    paths:
      - docs-review
  environment:
    name: review/docs/$CI_COMMIT_REF_NAME
    url: http://$CI_PROJECT_NAMESPACE.pages.funkwhale.audio/-/$CI_PROJECT_NAME/-/jobs/$CI_JOB_ID/artifacts/docs-review/index.html
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      changes:
        - docs/**/*
    - if: $CI_PIPELINE_SOURCE != "merge_request_event" && $CI_COMMIT_REF_NAME != $CI_DEFAULT_BRANCH
      when: manual

find_broken_links:
  allow_failure:
    exit_codes: 2

  extends: [.lychee]
  script:
    - >
      lychee
      --cache
      --no-progress
      --exclude-all-private
      --exclude-mail
      --exclude 'demo\.funkwhale\.audio'
      --exclude 'nginx\.com'
      --exclude-path 'docs/_templates/'
      -- . || exit $?

changelog_snippet:
  interruptible: true
  image: alpine:3.17
  stage: lint
  before_script:
    - apk add git
    - git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
  script:
    - git diff --name-only FETCH_HEAD | grep "changes/changelog.d/*"
  rules:
    - if: $CI_COMMIT_AUTHOR == 'Renovate Bot <bot@dev.funkwhale.audio>'
      when: never
    - if: $CI_MERGE_REQUEST_TITLE =~ /NOCHANGELOG/
      when: never
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'

pre-commit:
  extends: [.pre-commit]

eslint:
  interruptible: true
  image: node:18-alpine
  stage: lint
  before_script:
    - cd front
    - apk add --no-cache jq bash coreutils python3
    - yarn install
  script:
    - yarn lint --max-warnings 0
    - yarn lint:tsc
  cache:
    key: "$CI_PROJECT_ID__eslint_npm_cache"
    paths:
      - front/node_modules
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      changes:
        - front/**/*

test_api:
  interruptible: true
  services:
    - postgres:15-alpine
    - redis:7-alpine
  stage: test
  retry: 1
  cache:
    key: "$CI_PROJECT_ID__pip_cache"
    paths:
      - "$PIP_CACHE_DIR"
  variables:
    DATABASE_URL: "postgresql://postgres@postgres/postgres"
    FUNKWHALE_URL: "https://funkwhale.ci"
    DJANGO_SETTINGS_MODULE: config.settings.local
    POSTGRES_HOST_AUTH_METHOD: trust
    CACHE_URL: "redis://redis:6379/0"
  before_script:
    - cd api
    - poetry install --no-root
  script:
    - poetry run pytest --cov-report xml --cov-report term-missing:skip-covered --cov=funkwhale_api --junitxml=report.xml tests/
  artifacts:
    expire_in: 2 weeks
    reports:
      junit: api/report.xml
      coverage_report:
        coverage_format: cobertura
        path: api/coverage.xml
  parallel:
    matrix:
      - PY_VER: ["3.7", "3.8", "3.9", "3.10", "3.11"]
  image: $CI_REGISTRY/funkwhale/backend-test-docker:$PY_VER
  coverage: '/TOTAL\s*\d*\s*\d*\s*(\d*%)/'
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event" || $CI_PIPELINE_SOURCE == "push"
      changes:
        - api/**/*
    - if: $CI_COMMIT_REF_PROTECTED == "true"
      when: always

# Those tests are disabled for now since no vitest dom emulation is providing
# AudioContext, which is required for our HTML audio player
#test_front:
#  interruptible: true
#  stage: test
#  image: node:18-alpine
#  before_script:
#    - cd front
#    - apk add --no-cache jq bash coreutils python3
#  script:
#    - yarn install --check-files
#    - yarn test:unit
#  cache:
#    key: "funkwhale__front_dependencies"
#    paths:
#      - front/node_modules
#      - front/yarn.lock
#  artifacts:
#    name: "front_${CI_COMMIT_REF_NAME}"
#    paths:
#      - front/dist/
#    reports:
#      junit: front/coverage/cobertura-coverage.xml
#  tags:
#    - docker
#  rules:
#    - if: $CI_PIPELINE_SOURCE == "merge_request_event" || $CI_PIPELINE_SOURCE == "push"
#      changes:
#        - front/**/*
#    - if: $CI_COMMIT_REF_PROTECTED == "true"
#      when: always

build_openapi_schema:
  stage: build
  image: $CI_REGISTRY/funkwhale/backend-test-docker:3.11
  services:
    - postgres:15-alpine
    - redis:7-alpine
  cache:
    key: "$CI_PROJECT_ID__pip_cache"
    paths:
      - "$PIP_CACHE_DIR"
  variables:
    DATABASE_URL: "postgresql://postgres@postgres/postgres"
    FUNKWHALE_URL: "https://funkwhale.ci"
    DJANGO_SETTINGS_MODULE: config.settings.local
    POSTGRES_HOST_AUTH_METHOD: trust
    CACHE_URL: "redis://redis:6379/0"
    API_TYPE: "v1"
  before_script:
    - cd api
    - pip3 install poetry
    - poetry install
    - poetry run funkwhale-manage migrate
  script:
    - poetry run funkwhale-manage spectacular --file ../docs/schema.yml
  artifacts:
    expire_in: 2 weeks
    paths:
      - docs/schema.yml

build_documentation:
  stage: build
  image: python:3.11
  needs:
    - job: build_openapi_schema
      artifacts: true
  variables:
    BUILD_PATH: "../public"
    GIT_STRATEGY: clone
    GIT_DEPTH: 0
  before_script:
    - cd docs
    - apt-get update
    - apt-get install -y graphviz
    - pip install poetry
    - poetry install
    - git branch stable --track origin/stable || true
    - git branch develop --track origin/develop || true
  script:
    - ./build_docs.sh
  cache:
    key: "$CI_PROJECT_ID__sphinx"
    paths:
      - "$PIP_CACHE_DIR"
  artifacts:
    expire_in: 2 weeks
    paths:
      - public
  rules:
    - if: $CI_COMMIT_BRANCH == "stable" || $CI_COMMIT_BRANCH == "develop"
      when: always
    - changes:
        - docs/**/*
      when: always

build_front:
  stage: build
  image: node:18-alpine
  variables:
    <<: *keep_git_files_permissions
  before_script:
    - apk add --no-cache jq bash coreutils python3
    - cd front
  script:
    - yarn install
    # this is to ensure we don't have any errors in the output,
    # cf https://dev.funkwhale.audio/funkwhale/funkwhale/issues/169
    - yarn run build:deployment | tee /dev/stderr | (! grep -i 'ERROR in')
  artifacts:
    name: front_${CI_COMMIT_REF_NAME}
    paths:
      - front/dist/
  only:
    - tags@funkwhale/funkwhale
    - stable@funkwhale/funkwhale
    - develop@funkwhale/funkwhale

build_api:
  stage: build
  image: bash
  variables:
    <<: *keep_git_files_permissions
  script:
    - rm -rf api/tests
    - >
      if [ "$CI_COMMIT_REF_NAME" == "develop" ] || [ "$CI_COMMIT_REF_NAME" == "stable" ]; then
        ./scripts/set-api-build-metadata.sh $CI_COMMIT_SHORT_SHA;
      fi
  artifacts:
    name: api_${CI_COMMIT_REF_NAME}
    paths:
      - api
  only:
    - tags@funkwhale/funkwhale
    - stable@funkwhale/funkwhale
    - develop@funkwhale/funkwhale

deploy_documentation:
  stage: publish
  image: alpine
  needs:
    - job: build_documentation
      artifacts: true
  before_script:
    - apk add openssh-client rsync
    - mkdir -p ~/.ssh
    - echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts
    - eval `ssh-agent -s`
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
  script:
    - rsync -r -e "ssh -p 2282" $CI_PROJECT_DIR/public/ docs@docs.funkwhale.audio:/htdocs/
  only:
    - stable

.docker_publish:
  stage: publish
  image: egon0/docker-with-buildx-and-git:bash
  parallel:
    matrix:
      - COMPONENT: ["api", "front"]
  variables:
    <<: *keep_git_files_permissions

    IMAGE_NAME: funkwhale/$COMPONENT
    IMAGE: $IMAGE_NAME:$CI_COMMIT_REF_NAME
    IMAGE_LATEST: $IMAGE_NAME:latest

    DOCKER_HOST: tcp://docker:2375/
    DOCKER_DRIVER: overlay2
    DOCKER_TLS_CERTDIR: ""
    BUILD_PLATFORMS: linux/amd64,linux/arm64,linux/arm/v7
  tags:
    - multiarch
  services:
    - docker:20-dind
  before_script:
    - docker login -u $DOCKER_LOGIN -p $DOCKER_PASSWORD
  cache:
    key: docker_public_${CI_COMMIT_REF_NAME}
    paths:
      - ~/.cargo

docker_publish_stable_release:
  # Publish a docker image for releases
  extends: .docker_publish
  rules:
    - if: $CI_COMMIT_TAG && $CI_COMMIT_REF_NAME =~ /^[0-9]+(.[0-9]+){1,2}$/
  script:
    # Check if this is the latest release
    - ./docs/get-releases-json.py | scripts/is-docker-latest.py $CI_COMMIT_TAG - && export DOCKER_LATEST_TAG="-t $IMAGE_LATEST" || export DOCKER_LATEST_TAG=;
    - export major="$(echo $CI_COMMIT_REF_NAME | cut -d '.' -f 1)"
    - export minor="$(echo $CI_COMMIT_REF_NAME | cut -d '.' -f 1,2)"
    - cd $COMPONENT
    - docker buildx create --use --name A$CI_COMMIT_SHORT_SHA
    - docker buildx build --platform $BUILD_PLATFORMS --push -t $IMAGE $DOCKER_LATEST_TAG -t $IMAGE_NAME:$major -t $IMAGE_NAME:$minor .

docker_publish_unstable_release:
  # Publish a docker image for releases
  extends: .docker_publish
  rules:
    - if: $CI_COMMIT_TAG && $CI_COMMIT_REF_NAME !~ /^[0-9]+(.[0-9]+){1,2}$/
  script:
    # Check if this is the latest release
    - cd $COMPONENT
    - docker buildx create --use --name A$CI_COMMIT_SHORT_SHA
    - docker buildx build --platform $BUILD_PLATFORMS --push -t $IMAGE .

docker_publish_non-release:
  # Publish a docker image for each commit on develop
  extends: .docker_publish
  only:
    - develop@funkwhale/funkwhale
    - stable@funkwhale/funkwhale
  script:
    - cd $COMPONENT
    - docker buildx create --use --name A$CI_COMMIT_SHORT_SHA
    - docker buildx build --platform $BUILD_PLATFORMS --push -t $IMAGE .
