Feel free to comment or share your learning.

Contents

Glossary

Style

General

Workflows

Jobs

Steps

Commands

Example

name: "Nightly Unit Tests"

on:
  push:
    branches:
      - "**/*nightly*"
  schedule:
    # Run at 2:22am early every morning Eastern time (6/7:22 UTC)
    # https://crontab.guru/#22_7_%2a_%2a_%2a
    - cron: "22 7 * * *"
  workflow_dispatch:

defaults:
  run:
    shell: bash

permissions:
  contents: read

concurrency:
  group: "${{ github.workflow }}-${{ github.ref }}"
  cancel-in-progress: true

jobs:
  tests:
    name: "Python ${{ matrix.python-version }} tests"
    runs-on: ubuntu-20.04

    strategy:
      matrix:
        python-version:
          - "3.8"
          - "3.11"

    steps:
      - name: "Check out the repo"
        uses: "actions/checkout@v3"

      - name: "Set up Python"
        uses: "actions/setup-python@v4"
        with:
          python-version: "${{ matrix.python-version }}"
          
      - name: "Do the thing"
        run: |
          python -m tox -- -rfsEX

Help

Error Checking

You can enable stricter Bash error handling by setting the default “shell” to “bash” at the top of every workflow, like so:

defaults:
  run:
    shell: bash

Note that bash is already the default interpreter for any shell code you put in your workflows, but explicitly setting “bash” in your workflow enables some extra-strict bash behavior. Notably, it enables the “pipefail” option, which means that if you write a | b, an error in a will result the entire command failing (by default, errors in a are silenced; only errors in b are raised). You can read more about the nuances here.

tl;dr: When in doubt, put this a the top of your script. It will make it less likely for errors to pass silently.

Matrix

Dynamic Matrix

To dynamically set matrix values in a maintainable way, you can utilize https://github.com/actions/github-script

jobs:
  setup-matrix:
    steps:
      - uses: actions/github-script@v6
        id: generate_matrix
        with:
          script: |
            var nodeVersions = [16, 18];
            // logic to add/remove node versions
            core.setOutput('nodeVersions', nodeVersions);
    outputs:
      node_versions: ${{ steps.generate_matrix.outputs.nodeVersions }}

  run_tests:
    needs: [setup-matrix]
    strategy:
      matrix:
        version: ${{ fromJson(needs.setup-matrix.outputs.node_versions) }}
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.version }}
      - run: |
          npm ci
          npm run test

Security

Organization

Workflows (.yml files) can contain one job or many. Here are some general guidelines on when to organize jobs into one workflow versus splitting them up:

Testing

Other advice

TODO: