Test and deploy an OpenAPI with Github Actions

It is time to go full circle with the search-to-rent OpenAPI demo by adding a Github Actions workflow for CI / CD.

March 02, 2024

You may want to get up to speed by taking a peek at the previous two episodes of this series Real estate API for a search-to-rent application and Clerk authentication, authorization, Upstash limiter, Axiom logging.

What is covered

Code repository

Online resources

A short recap of repository testers

I am using a combination of recommended tests by Bun and Hono Docs and a custom test runner as a Bun CLI that spawns children's processes and streams their stdout to sniff out if everything is all right.

  1. /src/routes/__tests__/featureToPropertyRoutes.test.ts a sample of classic Bun tests of Hono's endpoints
  2. /src/routes/__tests__/startAppAndRunPostmanTests.ts custom test runner for Postman CLI child process in charge of endpoints' collection contract tests
  3. /src/services/taskWorkers/__tests__/startAppAndReadStdout.ts custom test runner for the behind-the-scenes workers keeping the Algolia index in sync

Adding secrets and variables to the Github repo

Github offers an elegant and safe way to store secrets and variables (environment vars that are not secrets) required by any actions workflow. You may either use the Github web application or the multi-tallented Github Actions VS Code extension.

Github Actions web application

Github Actions VS Code extension

Using Github Actions VS Code extension

Test job

The Github Actions workflow /.github/workflows/fly-test-deploy.yml is triggered on each push to the main branch at any of the paths defined here:

name: Fly Test and Deploy

on:
  push:
    branches:
      - main
    paths:
      - 'src/**'
      - 'package.json'
      - 'bun.lockb'
      - 'fly.toml'
      - 'Dockerfile'

The test job uses a Linux ephemeral instance to load all the environment variables, secrets, or otherwise required by the three test runners. After checking out the repo and setting up Bun, it installs all project dependencies. It runs all tests in sequence, overriding the BUN_ENV environment variable value when it is needed.

jobs:
  test:
    name: Run Tests
    runs-on: ubuntu-latest
    env:
      ALGOLIA_ADMIN_API_KEY: ${{ secrets.ALGOLIA_ADMIN_API_KEY }}
      ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
      AXIOM_API_TOKEN: ${{ secrets.AXIOM_API_TOKEN }}
      CLERK_PUBLISHABLE_KEY: ${{ secrets.CLERK_PUBLISHABLE_KEY }}
      CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }}
      CLOUDAMQP_URL: ${{ secrets.CLOUDAMQP_URL }}
      DATABASE_NAME: ${{ vars.DATABASE_NAME }}
      DATABASE_HOST: ${{ vars.DATABASE_HOST }}
      DATABASE_PASSWORD: ${{ secrets.DATABASE_PASSWORD }}
      DATABASE_USERNAME: ${{ secrets.DATABASE_USERNAME }}
      GCLOUD_API_KEY: ${{ secrets.GCLOUD_API_KEY }}
      UPSTASH_REDIS_REST_TOKEN: ${{ secrets.UPSTASH_REDIS_REST_TOKEN }}
      UPSTASH_REDIS_REST_URL: ${{ secrets.UPSTASH_REDIS_REST_URL }}
      SERVER_TIMEZONE: ${{ vars.SERVER_TIMEZONE }}
      DATABASE_SEED_BLOCKED: ${{ vars.DATABASE_SEED_BLOCKED }}
      AXIOM_DATASET: ${{ vars.AXIOM_DATASET }}
      POSTMAN_API_KEY: ${{ secrets.POSTMAN_API_KEY }}
      CLERK_JWT_TEST: ${{ secrets.CLERK_JWT_TEST }}
      BUN_ENV: dev
    concurrency: deploy-group
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v1
        with:
          bun-version: '1.0.29'
      - run: bun install
      - run: bun run postman:test:cleanup:actions
      - run: bun run test:bun:actions
        env:
          BUN_ENV: test
      - run: bun run postman:test:cleanup:actions
      - run: bun run workers:test:utils:actions
        env:
          BUN_ENV: algolia
          CICD: true
      - name: Install Postman CLI
        run: |
          curl -o- "https://dl-cli.pstmn.io/install/linux64.sh" | sh
      - run: bun run postman:test:cleanup:actions
      - run: bun run test:postman:actions
        env:
          BUN_ENV: postman
          CICD: true

Deploy job

The deploy job starts anew with another Linux instance to make sure none of the previous secrets spill into the build process. Of course, it needs the test job to be successful; otherwise, it is aborted. The previous steps are common, and they are cached. Another set of variables is required; none of them are secret. Deploying to Fly.io first installs superfly CLI tools to be able to load the build and push the Docker image.

deploy:
    name: Deploy to Fly
    runs-on: ubuntu-latest
    needs: test
    concurrency: deploy-group
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v1
        with:
          bun-version: '1.0.29'
      - run: bun install
      - run: bun run build:actions
        env:
          BUN_ENV: production
          DATABASE_NAME: ${{ vars.DATABASE_NAME }}
          DATABASE_HOST: ${{ vars.DATABASE_HOST }}
          SERVER_TIMEZONE: ${{ vars.SERVER_TIMEZONE }}
          DATABASE_SEED_BLOCKED: ${{ vars.DATABASE_SEED_BLOCKED }}
          AXIOM_DATASET: ${{ vars.AXIOM_DATASET }}
      - uses: superfly/flyctl-actions/setup-flyctl@master
      - run: flyctl deploy --remote-only
        env:
          FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

Accessing Github actions jobs output

First, if you don't succeed, try again. Of course, I initially had two failed attempts to run the workflow. The first was due to wrong permissions, and the second was because I forgot to install Postman CLI before the related step. The good thing is that both the Github web application and the VS Code extension provide you with detailed workflow output.

Github Actions dashboard

Github Actions steps

Github Actions output

Fly.io Github deployed