betagouv/service-national-universel

View on GitHub
.github/workflows/deploy-custom-env.yml

Summary

Maintainability
Test Coverage
name: Build and deploy custom environment

on:
  workflow_dispatch:
  pull_request:
    branches:
      - main

jobs:
  prepare:
    runs-on: ubuntu-latest
    outputs:
      branch_name: ${{ steps.check.outputs.branch_name }}
      env_name: ${{ steps.prepare.outputs.env_name }}
      api_image_tag: ${{ steps.prepare.outputs.api_image_tag }}
      admin_image_tag: ${{ steps.prepare.outputs.admin_image_tag }}
      app_image_tag: ${{ steps.prepare.outputs.app_image_tag }}

    steps:
      - name: Check branch name
        id: check
        run: |
          if [[ ${{ github.event_name }} == 'pull_request' ]] ; then
            branch_name=${{ github.head_ref }}
          else
            branch_name=${{ github.ref_name }}
          fi
          if [[ $branch_name == 'main' ]] || [[ $branch_name == 'production' ]] ; then
            echo "This action is not available on the branch $branch_name"
            exit 1
          fi
          echo "branch_name=$branch_name" >> $GITHUB_OUTPUT

      - uses: actions/checkout@v4
        with:
          ref: ${{ steps.check.outputs.branch_name }}
          fetch-depth: 0

      - name: Prepare
        id: prepare
        run: |
          env_name=$(.github/scripts/get_custom_env_name.sh ${{ steps.check.outputs.branch_name }})
          echo "env_name: $env_name"
          echo "env_name=$env_name" >> $GITHUB_OUTPUT

          api_image_tag=$(git log --max-count=1 --oneline -- api packages package-lock.json | cut -d " " -f 1)
          echo "api_image_tag: $api_image_tag"
          echo "api_image_tag=$api_image_tag" >> $GITHUB_OUTPUT

          admin_image_tag=$(git log --max-count=1 --oneline -- admin packages package-lock.json | cut -d " " -f 1)
          echo "admin_image_tag: $admin_image_tag"
          echo "admin_image_tag=$admin_image_tag" >> $GITHUB_OUTPUT

          app_image_tag=$(git log --max-count=1 --oneline -- app packages package-lock.json | cut -d " " -f 1)
          echo "app_image_tag: $app_image_tag"
          echo "app_image_tag=$app_image_tag" >> $GITHUB_OUTPUT

  run_tests_api:
    needs: prepare
    uses: ./.github/workflows/run-tests-api-pr.yml
    with:
      branch_name: ${{ needs.prepare.outputs.branch_name }}
    secrets: inherit

  run_tests_front:
    needs: prepare
    uses: ./.github/workflows/run-tests-front.yml
    with:
      branch_name: ${{ needs.prepare.outputs.branch_name }}
    secrets: inherit

  run_tests_lib:
    needs: prepare
    uses: ./.github/workflows/run-tests-lib.yml
    with:
      branch_name: ${{ needs.prepare.outputs.branch_name }}
    secrets: inherit

  build:
    if: |
      github.event_name == 'workflow_dispatch' ||
      (github.event_name == 'pull_request' &&
        !(contains(github.head_ref, '-no-ci-') ||
        startsWith(github.head_ref, 'no-ci-') ||
        endsWith(github.head_ref, 'no-ci') ||
        contains(github.head_ref, '_no_ci_') ||
        startsWith(github.head_ref, 'no_ci_') ||
        endsWith(github.head_ref, '_no_ci'))
      )
    needs: prepare
    runs-on: ubuntu-latest
    strategy:
      matrix:
        app:
          [
            {
              name: api,
              path: api,
              tag: "${{needs.prepare.outputs.api_image_tag}}",
            },
            {
              name: app,
              path: app,
              tag: "${{needs.prepare.outputs.app_image_tag}}",
            },
            {
              name: admin,
              path: admin,
              tag: "${{needs.prepare.outputs.admin_image_tag}}",
            },
          ]

    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ needs.prepare.outputs.branch_name }}

      - name: Check if image exists
        id: check
        uses: ./.github/actions/check_docker_image_tag
        with:
          registry: ${{ secrets.SCW_CI_IMAGE_REGISTRY }}
          image_name: ${{matrix.app.name}}
          image_tag: ${{matrix.app.tag}}
          secret_key: ${{ secrets.SCW_CI_DEPLOY_SECRET_KEY }}

      - name: Docker Build & Publish
        uses: ./.github/actions/build_docker_image
        if: steps.check.outputs.tag_exists == 0
        with:
          username: nologin
          password: ${{ secrets.SCW_CI_DEPLOY_SECRET_KEY }}
          registry: ${{ secrets.SCW_CI_IMAGE_REGISTRY }}
          image_name: ${{matrix.app.name}}
          dockerfile_path: ${{matrix.app.path}}/Dockerfile
          image_tag: ${{matrix.app.tag}}
          sentry_auth_token: ${{ secrets.SENTRY_AUTH_TOKEN }}

  deploy:
    runs-on: ubuntu-latest
    needs: [prepare, build]
    concurrency:
      group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }}
      cancel-in-progress: false
    env:
      SCW_ACCESS_KEY: ${{ secrets.SCW_CI_DEPLOY_ACCESS_KEY }}
      SCW_SECRET_KEY: ${{ secrets.SCW_CI_DEPLOY_SECRET_KEY }}
      PG_CONN_STR: ${{ secrets.SCW_CI_BACKEND_CONN_STR }}
      PGUSER: ${{ secrets.SCW_CI_BACKEND_USER }}
      PGPASSWORD: ${{ secrets.SCW_CI_BACKEND_PASSWORD }}
      custom_directory: ./devops/terraform/environments/ci/custom

    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ needs.prepare.outputs.branch_name }}

      - uses: hashicorp/setup-terraform@v3

      - name: Create custom env stack
        working-directory: ${{ env.custom_directory }}
        run: sed -i.bak "s|###___ENV_NAME___###|${{ needs.prepare.outputs.env_name }}|g" main.tf
        # the -i option replaces main.tf in-place creating a .bak backup file

      - name: Init terraform
        working-directory: ${{ env.custom_directory }}
        run: terraform init

      - name: Validate terraform
        working-directory: ${{ env.custom_directory }}
        run: terraform validate -no-color

      - name: Terraform plan
        working-directory: ${{ env.custom_directory }}
        # add -lock-timeout option until https://github.com/hashicorp/terraform/issues/33217 is fixed
        run: |
          terraform plan -no-color -input=false -lock-timeout=15m \
            -var="api_image_tag=${{ needs.prepare.outputs.api_image_tag }}" \
            -var="admin_image_tag=${{ needs.prepare.outputs.admin_image_tag }}" \
            -var="app_image_tag=${{ needs.prepare.outputs.app_image_tag }}"

      - name: Terraform auto-apply
        working-directory: ${{ env.custom_directory }}
        run: |
          terraform apply -no-color -input=false -auto-approve -lock-timeout=15m \
            -var="api_image_tag=${{ needs.prepare.outputs.api_image_tag }}" \
            -var="admin_image_tag=${{ needs.prepare.outputs.admin_image_tag }}" \
            -var="app_image_tag=${{ needs.prepare.outputs.app_image_tag }}"

      - name: Add comment to PR
        if: github.event_name == 'pull_request' && contains(fromJSON('["opened", "reopened"]'), github.event.action)
        working-directory: ${{ env.custom_directory }}
        env:
          URL: ${{ github.event.pull_request.comments_url }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          api_endpoint=$(terraform output -raw api_endpoint)
          app_endpoint=$(terraform output -raw app_endpoint)
          admin_endpoint=$(terraform output -raw admin_endpoint)
          curl \
            -X POST \
            $URL \
            -H "Content-Type: application/json" \
            -H "Authorization: token $GITHUB_TOKEN" \
            --data "{ \"body\": \"Application endpoints :\n\n- $api_endpoint\n- $app_endpoint\n- $admin_endpoint\" }"

      - name: Healthchecks
        shell: bash
        working-directory: ${{ env.custom_directory }}
        run: |
          # Sleep because status can still be an error if the previous deployement failed
          sleep 10s

          failure=false
          while read name
          do
            status=$(terraform output -raw ${name}_container_status)
            echo $name $status
            if [[ $status != "ready" ]]
            then
                echo "Container ${name} is on '$status' state" 1>&2
                failure=true
            fi
          done <<< 'app
          admin
          api'

          if $failure
          then
            exit 1
          fi