Skip to content

Latest commit

 

History

History
327 lines (262 loc) · 13.5 KB

README.md

File metadata and controls

327 lines (262 loc) · 13.5 KB

Awesome GHA Snippets

A collection of useful snippets, all in one place.

GitHub Actions is a great CI/CD with many possibilities that can be used to solve the most non-trivial tasks. But some features are poorly documented or come with experience. The most interesting of the features will be reflected in this repository.

📚 Contents

📌 Tips

Secrets

If your repository is in an organization (not a personal repo), always store the secret keys in the organization settings.

They are available at the URL:
https://github.com/organizations/[ORG_SLUG]/settings/secrets/actions

Avoid adding secrets to the repository settings as this will cause them to be duplicated in the future, which will "shoot" at the most inopportune moment (at the time of release).

If you add secrets in the organization, it is possible to manage accesses (Repository access –> Selected repositories) and quickly revoke/change if necessary for all repos.

Note: You can painlessly move all the secrets from the repositories to the organization level, as their names do not change (but do not forget to remove the secrets from the repository settings).

Action options

Each action from the marketplace contains an action.yml or action.yaml file at the root of its repository, which may contain directives within it:

  1. inputs – input parameters with their description and default values.
  2. outputs – names of variables through which action will return result.

Strict Error Mode (Shell)

When using the run directive in steps – try to use the strict error control setting set -euo pipefail on the first line.

If you don't like this approach, at least use shell: bash in this step, since the command shell will be used when you start it:

bash --noprofile --norc -eo pipefail {0}

If these two directives are missing – the shell will be started without full strict error control (which can lead to unpredictable results in case of problems):

shell: /usr/bin/bash -e {0}

Note: this applies to Linux distributions.

Cache Dependencies

Always use caching of dependencies with cache action – this speeds up the execution of the workflow.

Look at the usage examples for your package manager.

Installing Dependencies in Node.js

In the github workflow for projects using Node.js, always prefer install dependencies using command npm ci rather than npm install – it speeds up execution time a lot.

Together with dependency caching for large projects, you can achieve amazing acceleration. Be sure to read the documentation about npm ci.

Marketplace Actions

Try not to use actions that are not versioned and require a @master or @main version to be installed. This is at least unsafe and may cause your workflow to crash, in case of major changes.

Many actions are not optimized and run a lot of stuff inside them (especially if they use a docker), which slows down the execution process. If you need to perform some trivial tasks that fit into a few commands, prefer to use your simple implementation, or at least make sure that the action won't be slower than you are allowed by the limits.

🛠 Snippets

Notify on Error

If the workflow crashes or is cancelled, a notification will be sent to Slack channel with the name of the repository and a link to the workflow execution log.

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Notify on Error
        if: ${{ failure() || cancelled() }}
        shell: bash
        run: |
          MESSAGE="❌ ${GITHUB_REPOSITORY##*/} deploy has been failed: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
          # send message to Slack
          curl -X POST --data-urlencode "payload={\"channel\": \"#releases\", \"username\": \"GitHub Actions\", \"text\": \"${MESSAGE}\"}" https://hooks.slack.com/services/${{ secrets.SLACK_WEBHOOK_TOKEN }}

Commit History (aka Release Notes)

Sending the commit history from a published release to Slack channel along with the release notification. Commits are taken as the difference between the previous tag and the current one.

Requirements:

  1. Releases are published by setting a new tag.
  2. All tags must begin with the letter v.
  3. Tags must be named according to semver.
  4. To get tags inside the workflow, you need to set fetch-depth: 0 in the actions/checkout action.
  5. You must use a Squash merge (combine all commits from the head branch into a single commit in the base branch) in all PRs. You can disable other merge types in the repository settings with the Merge button configuration.
on:
  push:
    tags:
      - 'v*'
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          # download tags along with the repository to get the commit history
          fetch-depth: 0
      - name: Publish Commit History
        shell: bash
        run: |
          # previous release tag
          TAG_FROM=$(git tag | egrep '^v([0-9]+\.[0-9]+\.[0-9]+)$' | sort -V | tail -n 2 | head -n 1)
          HISTORY=$(git show -s --reverse --format='• %s' "${TAG_FROM}...${GITHUB_REF##*/}" | tr '\"' "'" | tr '\`' "'" | python -c 'import sys; print(sys.stdin.read().replace("\n", "\\n"))')
          MESSAGE="🎉 ${GITHUB_REPOSITORY##*/} release ${GITHUB_REF##*/} has been deployed:"
          # send message with history to Slack
          curl -X POST --data-urlencode "payload={\"channel\": \"#releases\", \"username\": \"GitHub Actions\", \"blocks\": [{\"type\": \"section\", \"text\": {\"type\": \"mrkdwn\", \"text\": \"${MESSAGE}\"}}, {\"type\": \"section\", \"text\": {\"type\": \"mrkdwn\", \"text\": \"\`\`\`${HISTORY}\`\`\`\"}}]}" https://hooks.slack.com/services/${{ secrets.SLACK_WEBHOOK_TOKEN }}

Passing Variables Between Steps

With this method you can pass (transfer) variable values (output of some command) between steps.

Note: it is better to encode the values using base64, so that if there are newlines in the output, nothing is lost.

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Create Variable
        id: create_var
        shell: bash
        run: |
          DATA="some long message received when executing the some command"
          DATA64=$(echo "${DATA}" | base64 -w 0)
          echo "::set-output name=RESULT::${DATA64}"
      - name: Get Variable
        shell: bash
        run: |
          DATA=$(echo "${{ steps.create_var.outputs.RESULT }}" | base64 -d)
          echo "${DATA}"

Publish Package on PyPI

After creating a new package release, you can automatically publish it in Python Package Index.

Requirements:

  1. To start a workflow, you need to create a new release in the repository.
  2. PYPI_USERNAME and PYPI_PASSWORD must be added in the secret keys for access to PyPI.
on:
  release:
    types: [published]
jobs:
  publish_pypi:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-python@v2
        with:
          python-version: '3.x'
      - name: Install Dependencies
        run: |
          python -m pip install --upgrade pip
          pip install setuptools wheel twine
      - name: Build and Publish
        shell: bash
        env:
          TWINE_REPOSITORY: pypi
          TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
          TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
        run: |
          python setup.py sdist bdist_wheel
          twine upload dist/*

Publish Docker Image

After creating a new release, you can automatically add the image to the docker hub (update latest tag and add new).

Requirements:

  1. To start a workflow, you need to create a new release in the repository.
  2. You need to change [USERNAME_OR_ORG] to the name of the profile or organization. For example: textdatasetcleaner for https://hub.docker.com/u/textdatasetcleaner
  3. You need to change [IMAGE_NAME] to repository (image name). For example: tdc for https://hub.docker.com/r/textdatasetcleaner/tdc
  4. DOCKER_HUB_USERNAME and DOCKER_HUB_TOKEN (see managing tokens) must be added in the secret keys for access to Docker Hub.
on:
  release:
    types: [published]
jobs:
  publish_docker:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Build the Docker Image
        shell: bash
        run: |
          docker build -t [USERNAME_OR_ORG]/[IMAGE_NAME]:${GITHUB_REF##*/} -t [USERNAME_OR_ORG]/[IMAGE_NAME]:latest .
      - name: Push Built Image Into Registry
        shell: bash
        run: |
          echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login docker.io --username "${{ secrets.DOCKER_HUB_USERNAME }}" --password-stdin
          docker push [USERNAME_OR_ORG]/[IMAGE_NAME]

Check PR Title

Fast check of PR title with reject/accept review and comment from bot using linter. Example

name: Check PR title
on:
  pull_request:
    types: [opened, edited, reopened]
jobs:
  pr-lint:
    runs-on: ubuntu-latest
    steps:
      - uses: morrisoncole/[email protected]
        with:
          title-regex: "^#\\d{1,}\\."
          on-failed-regex-fail-action: true
          on-failed-regex-request-changes: true
          on-failed-regex-create-review: true
          on-failed-regex-comment: "Incorrect PR title. Must be a regex: `%regex%` (like: `#314. Task title`)"
          repo-token: "${{ secrets.GITHUB_TOKEN }}"
Checking regex in the browser console (DevTools)
r = new RegExp("^#\\d{1,}\\.");
!r.test('#123: aa') && r.test('#1.') && !r.test('#12345678: abc') && !r.test('12345678: a') && !r.test('#12345678 a') && r.test('#12345678. a');
// Output: true

Extract Task ID from Branch Name

Check if the branch name corresponds to the rules and extract the task identifier.

Requirements:

  1. The branch name must begin with a number, followed by a dash and a short description of the task. For example: 222-bump_reqs.
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Check and Extract Task ID
        id: task_id
        run: |
          TASK_ID=$(echo "${GITHUB_HEAD_REF}" | grep -o '^[0-9]*')
          if [ "${TASK_ID}" == "" ] ; then
            >&2 echo "Incorrect branch name. Must be like: 314-task_name"
            exit 1
          fi
          echo "::set-output name=RESULT::${TASK_ID}"

Scheduled Events

You can schedule a workflow, just like you do with crontab. If you have a long build and need to have a pre-release for a prepared test stage to check the operability - you can schedule a launch at night so that everything is ready by the morning.

Note: at the beginning of each hour a lot of GitHub Action workflows are started, so it is better to set any non-zero start minutes so that there are no delays to start at peak loads.

on:
  schedule:
    - cron: '17 4 * * MON' # Runs at 04:17 UTC on Monday

Trigger Workflow Outside of GitHub

If you need to start a workflow on some event from the outside with certain input data, then a special type of event has been invented for this – repository_dispatch.

Requirements:

  1. Create a personal token with repo scopes.
  2. Replace the token [TOKEN] in the authorization header with the received token.
  3. Replace the repository owner [OWNER] in the request URL. For example: yengoteam for https://github.com/yengoteam/awesome-gha-snippets
  4. Replace the repository name [REPO] in the request URL. For example: awesome-gha-snippets for https://github.com/yengoteam/awesome-gha-snippets
on:
  repository_dispatch
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Print Payload
        shell: bash
        run: |
          echo '${{ toJson(github.event.client_payload) }}'

Example of a request to trigger:

curl -v -X POST -H "Authorization: token [TOKEN]" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/[OWNER]/[REPO]/dispatches --data '{"event_type":"manually triggered","client_payload":{"msg":"Hello, world!"}}'