image: tcitworld/mobilizon-ci

stages:
  - install
  - check
  - build-js
  - sentry
  - test
  - docker
  - package
  - upload
  - deploy

variables:
  MIX_ENV: "test"
  YARN_CACHE_FOLDER: "js/.yarn"
  # DB Variables for Postgres / Postgis
  POSTGRES_DB: mobilizon_test
  POSTGRES_USER: postgres
  POSTGRES_PASSWORD: postgres
  POSTGRES_HOST: postgres
  # DB Variables for Mobilizon
  MOBILIZON_DATABASE_USERNAME: $POSTGRES_USER
  MOBILIZON_DATABASE_PASSWORD: $POSTGRES_PASSWORD
  MOBILIZON_DATABASE_DBNAME: $POSTGRES_DB
  MOBILIZON_DATABASE_HOST: $POSTGRES_HOST
  GEOLITE_CITIES_PATH: "/usr/share/GeoIP/GeoLite2-City.mmdb"
  MOBILIZON_INSTANCE_REGISTRATIONS_OPEN: "true"
  # Release elements
  PACKAGE_REGISTRY_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${CI_PROJECT_NAME}"
  ARCH: "amd64"
  EXPORT_FORMATS: "csv,ods,pdf"
  APP_VERSION: "${CI_COMMIT_REF_NAME}"
  APP_ASSET: "${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_${ARCH}.tar.gz"

cache:
  key: "${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}"
  paths:
    - deps/
    - _build/
    - js/node_modules
    - js/.yarn

# Installed dependencies are cached across the pipeline
# So there is no need to reinstall them all the time
# It saves minutes during a pipeline build time
install:
  stage: install
  script:
    - yarn --cwd "js" install --frozen-lockfile
    - mix deps.get
    - mix compile

lint-elixir:
  stage: check
  before_script:
    - mix deps.get
  script:
    - export EXITVALUE=0
    - git fetch origin ${CI_DEFAULT_BRANCH}
    - TARGET_SHA1=$(git show-ref -s ${CI_DEFAULT_BRANCH})
    - echo "$TARGET_SHA1"
    - mix format --check-formatted --dry-run || export EXITVALUE=1
    - mix credo diff --from-git-merge-base $TARGET_SHA1 --strict -a || export EXITVALUE=1
    - mix sobelow --config || export EXITVALUE=1
    - exit $EXITVALUE
  artifacts:
    reports:
      codequality: codeclimate.json


lint-front:
  image: node:16
  stage: check
  before_script:
    - export EXITVALUE=0
    - yarn --cwd "js" install --frozen-lockfile
  script:
    - yarn --cwd "js" run lint || export EXITVALUE=1
    - yarn --cwd "js" run prettier -c . || export EXITVALUE=1
    - exit $EXITVALUE

build-frontend:
  stage: build-js
  image: node:16
  before_script:
    - apt update
    - apt install -y --no-install-recommends python build-essential webp imagemagick gifsicle jpegoptim optipng pngquant
  script:
    - yarn --cwd "js" install --frozen-lockfile
    - yarn --cwd "js" run build
  artifacts:
    expire_in: 5 days
    paths:
      - priv/static
  needs:
    - lint-front

sentry-commit:
  stage: sentry
  image: getsentry/sentry-cli
  script:
    - echo "Create a new release $CI_COMMIT_TAG"
    - sentry-cli releases new $CI_COMMIT_TAG
    - sentry-cli releases set-commits $CI_COMMIT_TAG --auto
    - sentry-cli releases files $CI_COMMIT_TAG upload-sourcemaps priv/static/assets/
    - sentry-cli releases finalize $CI_COMMIT_TAG
    - echo "Finalized release for $CI_COMMIT_TAG"
  needs:
    - build-frontend
  only:
    - tags@framasoft/mobilizon

deps:
  stage: check
  before_script:
    - mix deps.get
  script:
    - export EXITVALUE=0
    - mix hex.outdated || export EXITVALUE=1
    - yarn --cwd "js" outdated || export EXITVALUE=1
    - exit $EXITVALUE
  allow_failure: true
  needs:
    - install

exunit:
  stage: test
  services:
    - name: postgis/postgis:14-3.2
      alias: postgres
  variables:
    MIX_ENV: test
  before_script:
    - mix deps.get
    - mix compile
    - mix tz_world.update
    - mix ecto.create
    - mix ecto.migrate
  script:
    - mix coveralls
  artifacts:
    when: always
    reports:
      junit:
        - test-junit-report.xml
    expire_in: 30 days

vitest:
  stage: test
  needs:
    - lint-front
  before_script:
    - yarn --cwd "js" install --frozen-lockfile
  script:
    - yarn --cwd "js" run coverage --reporter=default --reporter=junit --outputFile.junit=./junit.xml
  artifacts:
    when: always
    paths:
      - js/coverage
    reports:
      junit:
        - js/junit.xml
    expire_in: 30 days

e2e:
  stage: test
  services:
    - name: postgis/postgis:14-3.2
      alias: postgres
  variables:
    MIX_ENV: "e2e"
  before_script:
    - mix deps.get
    - mix ecto.create
    - mix ecto.migrate
    - mix run priv/repo/e2e.seed.exs
    - cd js && yarn install && yarn run build && npx playwright install && cd ../
    - mix phx.digest
  script:
    - mix phx.server &
    - cd js
    - npx wait-on http://localhost:4000
    - npx playwright test --project $BROWSER
  parallel:
    matrix:
      - BROWSER: ['firefox', 'chromium']
  artifacts:
    expire_in: 2 days
    paths:
      - js/playwright-report/
      - js/test-results/

pages:
  stage: deploy
  script:
    - mkdir public
    - mix deps.get
    - mix docs
    - mv doc public/backend
  #     #- yarn run --cwd "js" styleguide:build
  #     #- mv js/styleguide public/frontend
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
  artifacts:
    expire_in: 1 hour
    paths:
      - public

.docker: &docker
  stage: docker
  image: docker:24
  variables:
    DOCKER_TLS_CERTDIR: "/certs"
    DOCKER_HOST: tcp://docker:2376
    DOCKER_TLS_VERIFY: 1
    DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"
    DOCKER_DRIVER: overlay2
  services:
    - docker:24-dind
  cache: {}
  before_script:
    # Install buildx
    - wget https://github.com/docker/buildx/releases/download/v0.11.2/buildx-v0.11.2.linux-amd64
    - mkdir -p ~/.docker/cli-plugins/
    - mv buildx-v0.11.2.linux-amd64 ~/.docker/cli-plugins/docker-buildx
    - chmod a+x ~/.docker/cli-plugins/docker-buildx
    # Create env
    - docker context create tls-environment
    - docker buildx create --use tls-environment
    # Install qemu/binfmt
    - docker pull tonistiigi/binfmt:latest
    - docker run --rm --privileged tonistiigi/binfmt:latest --install all
    # Login to DockerHub
    - mkdir -p ~/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"auth\":\"$CI_REGISTRY_AUTH\",\"email\":\"$CI_REGISTRY_EMAIL\"}}}" > ~/.docker/config.json
  tags:
    - "privileged"

build-docker-main:
  <<: *docker
  rules:
    - if: '$CI_PROJECT_NAMESPACE != "framasoft"'
      when: never
    - if: '$CI_PIPELINE_SOURCE == "schedule" || $CI_PIPELINE_TRIGGERED == "true"'
  script:
    - docker buildx build --push --platform linux/amd64 -t framasoft/mobilizon:main -f docker/production/Dockerfile .

build-and-push-to-latest-docker-tag:
  <<: *docker
  rules: &release-tag-rules
    - if: '$CI_PROJECT_NAMESPACE != "framasoft"'
      when: never
    - if: $CI_COMMIT_TAG != null && $CI_COMMIT_TAG !~ /alpha|beta|rc/
      when: on_success
  timeout: 3 hours
  script:
    - >
      docker buildx build
      --push
      --platform linux/${ARCH}
      --build-arg="${ERL_FLAGS}"
      -t framasoft/mobilizon:$CI_COMMIT_TAG
      -t framasoft/mobilizon:latest
      -f docker/production/Dockerfile .
  parallel:
    matrix:
      - ARCH: ["amd64"]
        ERL_FLAGS: ["ERL_FLAGS="]
      - ARCH: ["arm64"]
        ERL_FLAGS: ["ERL_FLAGS=+JMsingle true"]


# Don't push to latest when building beta/rc tags
build-and-push-docker-tag:
  <<: *docker
  rules: &pre-release-tag-rules
    - if: '$CI_PROJECT_NAMESPACE != "framasoft"'
      when: never
    - if: $CI_COMMIT_TAG =~ /alpha|beta|rc/
      when: on_success
  timeout: 3 hours
  script:
    - >
      docker buildx build
      --push
      --platform linux/amd64
      -t framasoft/mobilizon:$CI_COMMIT_TAG
      -f docker/production/Dockerfile .

# Packaging app for amd64
package-app:
  image: mobilizon/buildpack:1.15.5-erlang-26.0.2-debian-buster
  stage: package
  variables: &release-variables
    MIX_ENV: "prod"
    DEBIAN_FRONTEND: noninteractive
    TZ: Etc/UTC
    APP_ASSET: "${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_${ARCH}.tar.gz"
  script: &release-script
    - mix local.hex --force
    - mix local.rebar --force
    - mix deps.get --only-prod
    - mix compile
    - mix phx.digest.clean --all && mix phx.digest
    - mix release --path release/mobilizon
    - cd release/mobilizon && ln -s lib/mobilizon-*/priv priv && cd ../../
    - du -sh release/
    - 'echo "Artifact: ${APP_ASSET}"'
    - tar czf ${APP_ASSET} -C release mobilizon
    - du -sh ${APP_ASSET}
  only:
    - tags@framasoft/mobilizon
  artifacts:
    expire_in: 2 days
    paths:
      - ${APP_ASSET}

package-app-dev:
  stage: package
  variables: *release-variables
  script: *release-script
  except:
    - tags@framasoft/mobilizon
  artifacts:
    expire_in: 2 days
    paths:
      - ${APP_ASSET}

# Packaging app for multi-arch
multi-arch-release:
  stage: package
  image: docker:24
  variables:
    DOCKER_TLS_CERTDIR: "/certs"
    DOCKER_HOST: tcp://docker:2376
    DOCKER_TLS_VERIFY: 1
    DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"
    DOCKER_DRIVER: overlay2
    APP_ASSET: "${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_${ARCH}.tar.gz"
    OS: debian-buster
  services:
    - docker:24-dind
  cache: {}
  before_script:
    # Install buildx
    - wget https://github.com/docker/buildx/releases/download/v0.11.2/buildx-v0.11.2.linux-amd64
    - mkdir -p ~/.docker/cli-plugins/
    - mv buildx-v0.11.2.linux-amd64 ~/.docker/cli-plugins/docker-buildx
    - chmod a+x ~/.docker/cli-plugins/docker-buildx
    # Create env
    - docker context create tls-environment
    - docker buildx create --use tls-environment
    # Install qemu/binfmt
    - docker pull tonistiigi/binfmt:latest
    - docker run --rm --privileged tonistiigi/binfmt:latest --install all
  script:
    - docker buildx build --platform linux/${ARCH} --output type=local,dest=releases --build-arg="ERL_FLAGS=+JMsingle true" --build-arg APP_ASSET=${APP_ASSET} -f docker/multiarch/Dockerfile .
    - ls -alh releases/mobilizon/
    - du -sh releases/mobilizon/${APP_ASSET}
    - mv releases/mobilizon/${APP_ASSET} .
  tags:
    - "privileged"
  artifacts:
    expire_in: 2 days
    paths:
      - ${APP_ASSET}
      - erl_crash.dump # if there's a memory issue
  parallel:
    matrix:
      - ARCH: ["arm64"]
  rules:
    - if: '$CI_COMMIT_TAG != null || $CI_PIPELINE_SOURCE == "schedule" || $CI_PIPELINE_TRIGGERED == "true"'
  timeout: 3h
  allow_failure: true

# Release
release-upload:
  stage: upload
  image: framasoft/upload-packages:latest
  variables:
    APP_ASSET: "${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_${ARCH}.tar.gz"
  rules:
    - if: '$CI_PROJECT_NAMESPACE != "framasoft"'
      when: never
  script:
    - eval `ssh-agent -s`
    - ssh-add <(echo "${DEPLOYEMENT_KEY}" | base64 --decode -i)
    - echo "put -r ${APP_ASSET}" | sftp -o "VerifyHostKeyDNS yes" ${DEPLOYEMENT_USER}@${DEPLOYEMENT_HOST}:public/
  artifacts:
    expire_in: 1 day
    when: on_success
    paths:
      - mobilizon_*.tar.gz
  parallel:
    matrix:
      - ARCH: ["amd64", "arm", "arm64"]
  allow_failure: true

release-create:
  stage: deploy
  image: registry.gitlab.com/gitlab-org/release-cli:latest
  rules:
    - if: '$CI_PROJECT_NAMESPACE != "framasoft"'
      when: never
  variables:
    APP_ASSET_AMD64: "${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_amd64.tar.gz"
    APP_ASSET_ARM: "${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_arm.tar.gz"
    APP_ASSET_ARM64: "${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_arm64.tar.gz"
  before_script:
    - apk --no-cache add gawk sed grep
  script: |
    CHANGELOG=$(awk -v version="$APP_VERSION" '/^## / { printit = $2 == version }; printit' CHANGELOG.md | grep -v "## $APP_VERSION" | sed '1{/^$/d}')
    ENDPOINT="https://packages.joinmobilizon.org"

    release-cli create  --name "$CI_COMMIT_TAG" \
                        --description "$CHANGELOG" \
                        --tag-name "$CI_COMMIT_TAG" \
                        --assets-link "{\"name\":\"${APP_ASSET_AMD64}\",\"url\":\"${ENDPOINT}/${CI_COMMIT_REF_NAME}/${APP_ASSET_AMD64}\"}" \
                        --assets-link "{\"name\":\"${APP_ASSET_ARM}\",\"url\":\"${ENDPOINT}/${CI_COMMIT_REF_NAME}/${APP_ASSET_ARM}\"}" \
                        --assets-link "{\"name\":\"${APP_ASSET_ARM64}\",\"url\":\"${ENDPOINT}/${CI_COMMIT_REF_NAME}/${APP_ASSET_ARM64}\"}"
