mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
Firezone's control plane is a realtime, distributed system that relies on a broadcast/subscribe system to function. In many cases, these events are broadcasted whenever relevant data in the DB changes, such as an actor losing access to a policy, a membership being deleted, and so forth. Today, this is handled in the application layer, typically happening at the place where the relevant DB call is made (i.e. in an `after_commit`). While this approach has worked thus far, it has several issues: 1. We have no guarantee that the DB change will issue a broadcast. If the application is deployed or the process crashes after the DB changes are made but before the broadcast happens, we will have potentially failed to update any connected clients or gateways with the changes. 2. We have no guarantee that the order of DB updates will be maintained in order for broadcasts. In other words, app server A could win its DB operation against app server B, but then proceed to lose being the first to broadcast. 3. If the cluster is in a bad state where broadcasts may return an error (i.e. https://github.com/firezone/firezone/issues/8660), we will never retry the broadcast. To fix the above issues, we introduce a WAL logical decoder that process the event stream one message at a time and performs any needed work. Serializability is guaranteed since we only process the WAL in a single, cluster-global process, `ReplicationConnection`. Durability is also guaranteed since we only ACK WAL segments after we've successfully ingested the event. This means we will only advance the position of our WAL stream after successfully broadcasting the event. This PR only introduces the WAL stream processing system but does not introduce any changes to our current broadcasting behavior - that's saved for another PR.
285 lines
10 KiB
YAML
285 lines
10 KiB
YAML
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 }}
|
|
steps:
|
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
- uses: ./.github/actions/setup-postgres
|
|
with:
|
|
version: 15
|
|
- uses: ./.github/actions/setup-elixir
|
|
with:
|
|
mix_env: ${{ env.MIX_ENV }}
|
|
- name: Compile Application
|
|
run: mix compile --warnings-as-errors
|
|
- name: Setup Database
|
|
run: |
|
|
mix ecto.create
|
|
mix ecto.migrate
|
|
- name: Run Tests
|
|
env:
|
|
E2E_DEFAULT_WAIT_SECONDS: 20
|
|
CI_ASSERT_RECEIVE_TIMEOUT_MS: 250
|
|
run: |
|
|
mix_test="mix test --warnings-as-errors --exclude flaky:true --exclude acceptance:true"
|
|
$mix_test || $mix_test --failed
|
|
- name: Test Report
|
|
uses: dorny/test-reporter@6e6a65b7a0bd2c9197df7d0ae36ac5cee784230c # v2.0.0
|
|
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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
- uses: ./.github/actions/setup-elixir
|
|
id: setup-beam
|
|
with:
|
|
mix_env: ${{ env.MIX_ENV }}
|
|
- name: Compile Application
|
|
run: mix compile --warnings-as-errors
|
|
- uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
|
name: Restore PLT cache
|
|
id: plt_cache
|
|
with:
|
|
path: elixir/priv/plts
|
|
key: dialyzer-ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ steps.setup-beam.outputs.erlang-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: |
|
|
dialyzer-ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ steps.setup-beam.outputs.erlang-version }}-
|
|
- name: Create PLTs
|
|
if: ${{ steps.plt_cache.outputs.cache-hit != 'true' }}
|
|
run: mix dialyzer --plt
|
|
- uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
|
if: ${{ github.ref_name == 'main' }}
|
|
name: Save PLT cache
|
|
with:
|
|
key: ${{ steps.plt_cache.outputs.cache-primary-key }}
|
|
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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
- uses: ./.github/actions/setup-elixir
|
|
with:
|
|
mix_env: ${{ env.MIX_ENV }}
|
|
- 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 }}
|
|
steps:
|
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
- uses: ./.github/actions/setup-postgres
|
|
with:
|
|
version: 15
|
|
- uses: ./.github/actions/setup-elixir
|
|
with:
|
|
mix_env: ${{ env.MIX_ENV }}
|
|
- name: Compile
|
|
run: mix compile --warnings-as-errors
|
|
- name: Create Database
|
|
run: mix ecto.create
|
|
- name: Migrate DB to base ref and seed
|
|
run: |
|
|
git fetch --depth=1 origin ${{ github.base_ref }}
|
|
git checkout ${{ github.base_ref }}
|
|
mix deps.get
|
|
mix ecto.migrate
|
|
mix ecto.seed
|
|
# Then checkout current ref and rerun migrations
|
|
- name: Run new migrations
|
|
run: |
|
|
git checkout ${{ github.sha }}
|
|
mix deps.get
|
|
mix ecto.migrate
|
|
mix ecto.reset
|
|
mix ecto.migrate
|
|
mix ecto.seed
|
|
|
|
acceptance-test:
|
|
name: acceptance-test-${{ matrix.MIX_TEST_PARTITION }}
|
|
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:
|
|
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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
- uses: ./.github/actions/setup-postgres
|
|
with:
|
|
version: 15
|
|
- uses: nanasess/setup-chromedriver@e93e57b843c0c92788f22483f1a31af8ee48db25 # v2.3.0
|
|
- run: |
|
|
export DISPLAY=:99
|
|
chromedriver --url-base=/wd/hub &
|
|
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
|
|
- uses: ./.github/actions/setup-elixir
|
|
with:
|
|
mix_env: ${{ env.MIX_ENV }}
|
|
- uses: ./.github/actions/setup-node
|
|
- name: Compile Application
|
|
run: mix compile --warnings-as-errors
|
|
# Front-End deps cache
|
|
- uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
|
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@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
|
name: Save pnpm Deps Cache
|
|
if: ${{ github.ref_name == 'main' }}
|
|
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 aggressively too
|
|
- uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
|
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@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
|
name: Save Web Assets Cache
|
|
if: ${{ github.ref_name == 'main' }}
|
|
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_DEFAULT_WAIT_SECONDS: 20
|
|
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@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
with:
|
|
name: screenshots-${{ matrix.MIX_TEST_PARTITION }}
|
|
path: elixir/apps/web/screenshots
|
|
- name: Test Report
|
|
uses: dorny/test-reporter@6e6a65b7a0bd2c9197df7d0ae36ac5cee784230c # v2.0.0
|
|
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
|