name: Elixir on: workflow_call: jobs: unit-test: runs-on: ubuntu-22.04 defaults: run: working-directory: ./elixir permissions: checks: write env: MIX_ENV: test POSTGRES_HOST: localhost GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} services: postgres: image: postgres:15.2 ports: - 5432:5432 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v4 - name: Tool Versions id: versions uses: marocchino/tool-versions-action@v1.1.1 - uses: erlef/setup-beam@v1 id: setup-beam with: otp-version: ${{ steps.versions.outputs.erlang }} elixir-version: ${{ steps.versions.outputs.elixir }} - uses: actions/cache/restore@v3 name: Restore Elixir Deps Cache id: cache env: cache-name: elixir-deps-${{ env.MIX_ENV }} with: path: | elixir/deps elixir/_build/${{ env.MIX_ENV }} key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ env.cache-name }}-${{ hashFiles('elixir/mix.lock') }} - name: Install Dependencies if: ${{ steps.cache.outputs.cache-hit != 'true' }} run: mix deps.get --only $MIX_ENV - name: Compile Dependencies if: ${{ steps.cache.outputs.cache-hit != 'true' }} run: mix deps.compile --skip-umbrella-children - uses: actions/cache/save@v3 name: Save Elixir Deps Cache if: ${{ steps.cache.outputs.cache-hit != 'true' }} env: cache-name: elixir-deps-${{ env.MIX_ENV }} with: path: | elixir/deps elixir/_build/${{ env.MIX_ENV }} key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ env.cache-name }}-${{ hashFiles('elixir/mix.lock') }} - name: Compile Application run: mix compile --warnings-as-errors - name: Setup Database run: | mix ecto.create mix ecto.migrate - name: Run Tests env: E2E_MAX_WAIT_SECONDS: 20 run: | mix test --warnings-as-errors - name: Test Report uses: dorny/test-reporter@v1 if: ${{ github.event.pull_request.head.repo.full_name == github.repository && (success() || failure()) }} with: name: Elixir Unit Test Report path: elixir/_build/test/lib/*/test-junit-report.xml reporter: java-junit type-check: runs-on: ubuntu-22.04 defaults: run: working-directory: ./elixir env: # We need to set MIX_ENV to dev to make sure that we won't type-check our test helpers MIX_ENV: dev GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v4 - name: Tool Versions id: versions uses: marocchino/tool-versions-action@v1.1.1 - uses: erlef/setup-beam@v1 id: setup-beam with: otp-version: ${{ steps.versions.outputs.erlang }} elixir-version: ${{ steps.versions.outputs.elixir }} - uses: actions/cache/restore@v3 name: Restore Elixir Deps Cache id: cache env: cache-name: elixir-deps-${{ env.MIX_ENV }} with: path: | elixir/deps elixir/_build/${{ env.MIX_ENV }} key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ env.cache-name }}-${{ hashFiles('elixir/mix.lock') }} - name: Install Dependencies if: ${{ steps.cache.outputs.cache-hit != 'true' }} run: mix deps.get --only $MIX_ENV - name: Compile Dependencies if: ${{ steps.cache.outputs.cache-hit != 'true' }} run: mix deps.compile --skip-umbrella-children - uses: actions/cache/save@v3 name: Save Elixir Deps Cache if: ${{ steps.cache.outputs.cache-hit != 'true' }} env: cache-name: elixir-deps-${{ env.MIX_ENV }} with: path: | elixir/deps elixir/_build/${{ env.MIX_ENV }} key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ env.cache-name }}-${{ hashFiles('elixir/mix.lock') }} - name: Compile Application run: mix compile --warnings-as-errors - uses: actions/cache/restore@v3 name: Restore PLT cache id: plt_cache with: path: elixir/priv/plts key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ hashFiles('elixir/mix.lock') }} # This will make sure that we can incrementally build the PLT from older cache and save it under a new key restore-keys: | ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}- - name: Create PLTs if: ${{ steps.plt_cache.outputs.cache-hit != 'true' }} run: mix dialyzer --plt - uses: actions/cache/save@v3 if: ${{ steps.plt_cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/main' }} name: Save PLT cache with: key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ hashFiles('elixir/mix.lock') }} path: elixir/priv/plts - name: Run Dialyzer run: mix dialyzer --format dialyxir static-analysis: runs-on: ubuntu-22.04 defaults: run: working-directory: ./elixir env: MIX_ENV: test GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v4 - name: Tool Versions id: versions uses: marocchino/tool-versions-action@v1.1.1 - uses: erlef/setup-beam@v1 id: setup-beam with: otp-version: ${{ steps.versions.outputs.erlang }} elixir-version: ${{ steps.versions.outputs.elixir }} - uses: actions/cache/restore@v3 name: Restore Elixir Deps Cache id: cache env: cache-name: elixir-deps-${{ env.MIX_ENV }} with: path: | elixir/deps elixir/_build/${{ env.MIX_ENV }} key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ env.cache-name }}-${{ hashFiles('elixir/mix.lock') }} - name: Install Dependencies if: ${{ steps.cache.outputs.cache-hit != 'true' }} run: mix deps.get --only $MIX_ENV - name: Compile Dependencies if: ${{ steps.cache.outputs.cache-hit != 'true' }} run: mix deps.compile --skip-umbrella-children - uses: actions/cache/save@v3 name: Save Elixir Deps Cache if: ${{ steps.cache.outputs.cache-hit != 'true' }} env: cache-name: elixir-deps-${{ env.MIX_ENV }} with: path: | elixir/deps elixir/_build/${{ env.MIX_ENV }} key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ env.cache-name }}-${{ hashFiles('elixir/mix.lock') }} - name: Compile Application run: mix compile --force --warnings-as-errors - name: Check Formatting run: mix format --check-formatted - name: Check For Retired Packages run: mix hex.audit - name: Check For Vulnerable Packages run: mix deps.audit - name: Run Sobelow vulnerability scanner for web app working-directory: ./elixir/apps/web run: mix sobelow --skip - name: Run Credo run: mix credo --strict - name: Check for unused deps run: mix deps.unlock --check-unused migrations-and-seed-test: runs-on: ubuntu-22.04 defaults: run: working-directory: ./elixir env: MIX_ENV: dev POSTGRES_HOST: localhost GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} MAIN_BRANCH: main services: postgres: image: postgres:15.2 ports: - 5432:5432 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v4 - name: Install package dependencies run: | sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' wget -qO- https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo tee /etc/apt/trusted.gpg.d/pgdg.asc &>/dev/null sudo apt update sudo apt-get install -q -y postgresql-client - name: Tool Versions id: versions uses: marocchino/tool-versions-action@v1.1.1 - uses: erlef/setup-beam@v1 id: setup-beam with: otp-version: ${{ steps.versions.outputs.erlang }} elixir-version: ${{ steps.versions.outputs.elixir }} - uses: actions/cache/restore@v3 name: Restore Elixir Deps Cache id: cache env: cache-name: elixir-deps-${{ env.MIX_ENV }} with: path: | elixir/deps elixir/_build/${{ env.MIX_ENV }} key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ env.cache-name }}-${{ hashFiles('elixir/mix.lock') }} - name: Install Dependencies if: ${{ steps.cache.outputs.cache-hit != 'true' }} run: mix deps.get --only $MIX_ENV - name: Compile Dependencies if: ${{ steps.cache.outputs.cache-hit != 'true' }} run: mix deps.compile --skip-umbrella-children - uses: actions/cache/save@v3 name: Save Elixir Deps Cache if: ${{ steps.cache.outputs.cache-hit != 'true' }} env: cache-name: elixir-deps-${{ env.MIX_ENV }} with: path: | elixir/deps elixir/_build/${{ env.MIX_ENV }} key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ env.cache-name }}-${{ hashFiles('elixir/mix.lock') }} - name: Compile run: mix compile --warnings-as-errors - name: Download main branch DB dump id: download-artifact if: ${{ !contains(github.ref, env.MAIN_BRANCH) }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -xe REPO="${{ github.repository }}" WORKFLOW="cd.yml" ARTIFACT_NAME="db-dump" DESTINATION="apps/domain/priv/repo" ARTIFACTS_URL=$( gh api \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "/repos/${REPO}/actions/workflows/${WORKFLOW}/runs?event=push&branch=${{ env.MAIN_BRANCH }}&status=success&per_page=1" \ --jq ".workflow_runs[0].artifacts_url" ) DOWNLOAD_URL=$( gh api \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "${ARTIFACTS_URL}" \ --jq '.artifacts[] | select(.name == "'${ARTIFACT_NAME}'") | .archive_download_url' ) set +x curl -H "Accept: application/vnd.github+json" -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -L -o "${DESTINATION}.zip" "$DOWNLOAD_URL" set -x unzip "${DESTINATION}.zip" -d "${DESTINATION}" rm "${DESTINATION}.zip" - name: Create Database run: | mix ecto.create - name: Restore DB dump if: ${{ !contains(github.ref, env.MAIN_BRANCH) }} env: PGPASSWORD: postgres run: | mix ecto.load - name: Run new migrations run: | mix ecto.migrate - name: Dump DB if: contains(github.ref, env.MAIN_BRANCH) env: PGPASSWORD: postgres run: | pg_dump firezone_dev \ -U postgres -h localhost \ --file apps/domain/priv/repo/structure.sql \ --no-acl \ --no-owner - name: Upload main branch DB dump if: contains(github.ref, env.MAIN_BRANCH) uses: actions/upload-artifact@v4 with: name: db-dump path: elixir/apps/domain/priv/repo/structure.sql - name: Run Seed run: mix ecto.seed acceptance-test: permissions: checks: write runs-on: ubuntu-22.04 defaults: run: working-directory: ./elixir env: MIX_ENV: test POSTGRES_HOST: localhost GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} MIX_TEST_PARTITIONS: 1 strategy: fail-fast: false matrix: MIX_TEST_PARTITION: [1] services: postgres: image: postgres:15.2 ports: - 5432:5432 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 vault: image: vault:1.12.2 env: VAULT_ADDR: "http://127.0.0.1:8200" VAULT_DEV_ROOT_TOKEN_ID: "firezone" ports: - 8200:8200/tcp options: --cap-add=IPC_LOCK steps: - uses: actions/checkout@v4 - uses: nanasess/setup-chromedriver@v2 - run: | export DISPLAY=:99 chromedriver --url-base=/wd/hub & sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & - name: Tool Versions id: versions uses: marocchino/tool-versions-action@v1.1.1 - uses: erlef/setup-beam@v1 id: setup-beam with: otp-version: ${{ steps.versions.outputs.erlang }} elixir-version: ${{ steps.versions.outputs.elixir }} - uses: actions/setup-node@v4 with: node-version: ${{ steps.versions.outputs.nodejs }} - uses: pnpm/action-setup@v2 with: version: 8 # Elixir cache - uses: actions/cache/restore@v3 name: Restore Elixir Deps Cache id: cache env: cache-name: elixir-deps-${{ env.MIX_ENV }} with: path: | elixir/deps elixir/_build/${{ env.MIX_ENV }} key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ env.cache-name }}-${{ hashFiles('elixir/mix.lock') }} - name: Install Dependencies if: ${{ steps.cache.outputs.cache-hit != 'true' }} run: mix deps.get --only $MIX_ENV - name: Compile Dependencies if: ${{ steps.cache.outputs.cache-hit != 'true' }} run: mix deps.compile --skip-umbrella-children - uses: actions/cache/save@v3 name: Save Elixir Deps Cache if: ${{ steps.cache.outputs.cache-hit != 'true' }} env: cache-name: elixir-deps-${{ env.MIX_ENV }} with: path: | elixir/deps elixir/_build/${{ env.MIX_ENV }} key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ env.cache-name }}-${{ hashFiles('elixir/mix.lock') }} - name: Compile Application run: mix compile --warnings-as-errors # Front-End deps cache - uses: actions/cache/restore@v3 name: pnpm Web Deps Cache id: pnpm-web-deps-cache env: cache-name: pnpm-deps-web with: path: | elixir/apps/web/assets/node_modules elixir/esbuild-* elixir/tailwind-* key: ubuntu-22.04-${{ runner.arch }}-${{ env.cache-name }}-${{ hashFiles('elixir/apps/web/assets/pnpm-lock.yaml') }} - name: Install Front-End Dependencies if: ${{ steps.pnpm-web-deps-cache.outputs.cache-hit != 'true' }} run: | cd apps/web mix assets.setup - uses: actions/cache/save@v3 name: Save pnpm Deps Cache if: ${{ steps.pnpm-web-deps-cache.outputs.cache-hit != 'true' }} env: cache-name: pnpm-deps-web with: path: | elixir/apps/web/assets/node_modules elixir/esbuild-* elixir/tailwind-* key: ubuntu-22.04-${{ runner.arch }}-${{ env.cache-name }}-${{ hashFiles('elixir/apps/web/assets/pnpm-lock.yaml') }} # Front-End build cache, it rarely changes so we cache it agressively too - uses: actions/cache/restore@v3 name: Web Assets Cache id: pnpm-web-build-cache env: cache-name: pnpm-build-web with: path: | elixir/apps/web/assets/tmp elixir/apps/web/priv/static key: ubuntu-22.04-${{ runner.arch }}-${{ env.cache-name }}-${{ hashFiles('elixir/apps/web/assets/**') }} - name: Build Web Assets if: ${{ steps.pnpm-web-build-cache.outputs.cache-hit != 'true' }} run: | cd apps/web mix assets.build - uses: actions/cache/save@v3 name: Save Web Assets Cache if: ${{ steps.pnpm-web-build-cache.outputs.cache-hit != 'true' }} env: cache-name: pnpm-build-web with: path: | elixir/apps/web/assets/tmp elixir/apps/web/priv/static key: ubuntu-22.04-${{ runner.arch }}-${{ env.cache-name }}-${{ hashFiles('elixir/apps/web/assets/**') }} # Run tests - name: Setup Database run: | mix ecto.create mix ecto.migrate - name: Run Acceptance Tests env: MIX_TEST_PARTITION: ${{ matrix.MIX_TEST_PARTITION }} E2E_MAX_WAIT_SECONDS: 5 run: | mix test --only acceptance:true \ --partitions=${{ env.MIX_TEST_PARTITIONS }} \ --no-compile \ --no-archives-check \ --no-deps-check \ || pkill -f chromedriver \ || mix test --only acceptance:true --failed \ || pkill -f chromedriver \ || mix test --only acceptance:true --failed - name: Save Screenshots if: ${{ github.event.pull_request.head.repo.full_name == github.repository && always() }} uses: actions/upload-artifact@v4 with: name: screenshots-${{ matrix.MIX_TEST_PARTITION }} path: elixir/apps/web/screenshots - name: Test Report uses: dorny/test-reporter@v1 if: ${{ github.event.pull_request.head.repo.full_name == github.repository && (success() || failure()) }} with: name: Elixir Acceptance Test Report path: elixir/_build/test/lib/*/test-junit-report.xml reporter: java-junit