How GRRR uses GitHub Actions

Over the last 6 months, GRRR migrated from Travis CI to GitHub Actions, and we happily shed light on how we arrange the workflows.

Understanding GitHub Actions vs Travis CI

It took considerable time to recognize the fundamental distinction between these platforms. While Travis CI concentrates on Continuous Integration, GitHub Actions responds to ecosystem events. GitHub Actions is a system to respond to events from the GitHub ecosystem. One such event is CI—triggered by push or pull_request actions—but numerous additional events exist.

The Workflows

For applications featuring unit tests alongside staging and production environments, GRRR creates four workflows:

  • ci.yml: executes unit tests on every push
  • deploy-staging.yml: deploys the main branch to staging
  • deploy-production.yml: deploys tags to production
  • deploy.yml: a reusable workflow preventing duplicate code in deployment workflows

ci.yml

The CI workflow incorporates multiple tools ensuring code functionality and security quality. Common tools include phpunit, jest, composer validate, php artisan migrate:rollback, phpstan, and prettier. Each tool typically receives its own job, facilitating easy identification of failed checks in the GitHub UI.

name: CI
on:
    push:
        branches:
            - "**"
        tags-ignore:
            - "*-release"

jobs:
    php-tests:
        name: PHP tests
        runs-on: ubuntu-20.04
        steps:
            - uses: actions/checkout@v3
            - uses: shivammathur/setup-php@v2
            - run: composer install --prefer-dist --no-interaction --ansi

            - name: Run PHPUnit
              run: vendor/bin/phpunit tests/

    static-analysis:
        name: Static analysis
        runs-on: ubuntu-20.04
        steps:
            - uses: actions/checkout@v3
            - uses: shivammathur/setup-php@v2
            - run: composer install --prefer-dist --no-interaction --ansi

            - name: Run PHPStan
              run: vendor/bin/phpstan analyse

    prettier:
        name: Prettier
        runs-on: ubuntu-20.04
        steps:
            - uses: actions/checkout@v3
            - uses: actions/setup-node@v3
            - run: yarn install

            - name: Run Prettier
              run: npx prettier --check .

deploy-staging.yml

The staging environment should continuously run the latest main branch code. This workflow launches following a successful CI workflow completion on main.

A particularly valued feature among developers is workflow_dispatch, adding a GitHub UI button enabling manual workflow execution on developer-selected branches. This capability allows temporary non-main branch deployment to staging, facilitating experimental code demonstration to colleagues or prototype presentation to clients.

name: Deploy staging

on:
  workflow_run:
    workflows: ['CI']
      branches: [main]
      types:
         - completed
  workflow_dispatch:

jobs:
  deploy:
    name: Deploy
    uses: organization/repo/.github/workflows/deploy.yml@main
    if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
    with:
      environment: staging
      version: ${{ github.ref_name }}
    secrets:
      SSH_KEY: ${{ secrets.SSH_KEY }}
      KNOWN_HOSTS: ${{ secrets.KNOWN_HOSTS }}

deploy-production.yml

Releases occur via tag creation: 1.2.3-release (using semver format with -release suffix). Every tag bearing this suffix deploys to production. Unlike deploy-staging, this workflow doesn't depend on CI. The commit the tag references has already undergone CI checks.

name: Deploy production

on:
    push:
        tags:
            - "*-release"

jobs:
    deploy:
        name: Deploy
        uses: organization/repo/.github/workflows/deploy.yml@main
        with:
            environment: production
            version: ${{ github.ref_name }}
        secrets:
            SSH_KEY: ${{ secrets.SSH_KEY }}
            KNOWN_HOSTS: ${{ secrets.KNOWN_HOSTS }}

deploy.yml

A reusable workflow contains on.workflow_call, supporting inputs and secrets properties. Secrets appear verbose because they repeat names—necessary since reusable workflows lack automatic secret access.

name: "Deploy app"

on:
    workflow_call:
        inputs:
            environment:
                description: "GitHub and app environment"
                required: true
                type: string
            version:
                description: "Tag or branch to deploy"
                required: true
                type: string
        secrets:
            SSH_KEY:
                description: "SSH public key"
                required: true
            KNOWN_HOSTS:
                description: "SSH known hosts"
                required: true

jobs:
    deploy-app:
        name: Deploy app
        environment: ${{ inputs.environment }}
        runs-on: ubuntu-20.04

        steps:
            - uses: actions/checkout@v3
              with:
                  ref: ${{ inputs.version }}

            - name: Install SSH key
              uses: shimataro/ssh-key-action@v2
              with:
                  key: ${{ secrets.SSH_KEY }}
                  known_hosts: ${{ secrets.KNOWN_HOSTS }}

            - uses: shivammathur/setup-php@v2

            - name: Install PHP dependencies
              run: composer install --prefer-dist --no-interaction --ansi

            - name: Deployer
              run: vendor/bin/dep deploy "${{ inputs.environment }}" --tag="${{ inputs.version }}" --log="deployer.log"

            - name: Upload logs
              uses: actions/upload-artifact@v3
              if: always()
              with:
                  name: logs
                  path: deployer.log

Originally published on norday.tech.