GitHub

General GitHub Notes & GitHub Actions concepts and setup reference


Last Updated: October 15, 2020 by Pepe Sandoval



Want to show support?

If you find the information in this page useful and want to show your support, you can make a donation

Use PayPal

This will help me to create more stuff and fix the existent content... or probably your money will be used to buy beer


Disclaimer

  • The notes and commands documented here are just personal notes that I use for quick reference, this means they can be wrong, unclear and confusing so they are NOT a reliable NOR an official source of information. I strongly suggest you to use the official GitHub documentation and never completely rely on what's written here
  • Please review the disclaimer of use and contents in our Terms and Conditions page for information about the contents in this page

GitHub

How Create a Repo on GitHub

  • 1) Click on "New Repository", 2) Give it a name, 3) set it to public/private, 4) decide if you will import a repo and 5) Click "Create Repository"

Create a Repo on GitHub

GitHub Actions

  • They are a tool to automate SW development workflows
  • an action refers to an individual task
  • a step can either be and action or command(s) from a shell
  • a job is a combination of steps
  • an event is something that affects a repo for example a push or a pull request was opened, an issue was created/closed, etc
  • a workflow defines a custom automated process that you can setup on a repo to for example build, test, package, release, deploy a project
    • workflows usually run a VM on a GitHub Server
    • A workflow can contain one or more jobs and each job runs on its own virtual machine, jobs can run in parallel or in sequence.

Events and Workflow

  • a runner is any machine with the GitHub actions runner application installed

    • It is the one responsible for running your jobs when an event happens and displaying results
    • It can be hosted by GitHub or you can host your own. The GitHub hosted runners already have some pre-installed SW like git, curl, npm, pip, etc, python, nodeJS, Android SDK
  • GitHub Workflows are written in YAML

    • In YAML you just need indentation (2 or 4 spaces) no need for brackets {}
    • NO need for quotes "" around keys or values, only needed if a value has spaces
    • Arrays can be specified with - every dash is a new element or with [] separated by commas
    • you can use the > for long values to suppress new lines or | to keep them
    • Example:
      name: Pepe
      active: True
      age: 20
      array:
        - item1
        - item2
        - key: value
        - key2: value2
          key3: value3
      arrayjsonformat: [item1, item2, key: value]
      address:
        country: Malta
        city: Valletta
      long_text: >
        this is the
        long text
      
  • An Artifact in the GitHub Actions context is a file or log generated by a job, by default a job produces the artifact called log archive

Simple GitHub Actions Workflow

  1. Create a repo on GitHub
  2. On your repo create a yaml file inside the following folder hierarchy your_repo/.github/workflows/your_file.yaml
  3. Create your workflow in the created YAML file
    1. Set a name for the workflow with key name
    2. Set event(s) that will trigger the workflow with the key on
    3. Set the jobs for the workflow with the key jobs. jobs will be key-value pairs where the key will be the name of the job and the value will be an object that sets the different options for the job, the steps option is used to specify the steps of the job, it must be a list where each element is an object with the name and run keys
  4. push the YAML file created and you should see it run on the Actions tab in GitHub
name: Shell Commands
on: [push]
jobs:
run-shell-commands:
  runs-on: ubuntu-latest
  steps:
    - name: echo a string
      run: echo "Hello World"
    - name: multiline commands
      run: |
        node -v
        npm -v
    - name: python cmd
      shell: python
      run: |
        import platform
        print(platform.processor())

Run Workflow Example

  • If you want to enable verbose during a job execution go to Settings -> Secrets click on the New secret button and set the ACTIONS_STEP_DEBUG & ACTIONS_RUNNER_DEBUG to true as mentioned in the Enabling debug logging GitHub Documentation

Secrets

  • Shells doc: Steps are executed in default shell program, for Linux default is bash for Windows is PowerShell but they can be specified with the shell option.

Adding up multiple Jobs

  • It just need to be specified a the same indentation level of the other jobs and by default they will jobs run in parallel

  • If we want to specify a sequence for the jobs you can set the needs option in the job which can be a list of the jobs (names of jobs) the current job depends on and it will only run if the jobs in these dependencies list finish successfully

name: Shell Commands

on: [push]

jobs:
  run-shell-commands:
    runs-on: ubuntu-latest
    steps:
      - name: echo a string
        run: echo "Hello World"
      - name: multiline commands
        run: |
          node -v
          npm -v
      - name: python cmd
        shell: python
        run: |
            import platform
            print(platform.processor())
  run-windows-commands:
    runs-on: windows-latest
    needs: [run-shell-commands]
    steps:
      - name: print dir PS
        run: Get-Location
      - name: print dir BASH
        run: pwd
        shell: bash
      - name: python cmd
        shell: python
        run: |
            import platform
            print(platform.processor())

Adding actions to a workflow

  • An Action is a script or code that can be written in JavaScript to perform some specific task, which then you execute in as a step in your workflow. This script is also called sometimes action file and can live in the same repo or in an external repo

    • For an external repo you need a reference in the format user/repo@branch,version or commit
  • In a step we use the uses option to specify reference to that action, the with option to specify inputs and then we will need to access the .outputs field of the step to access the outputs an action produces

name: Actions Workflow

on: [push]

jobs:
  run-github-actions:
    runs-on: ubuntu-latest
    steps:
      - name: Simple JS action
        id: greet
        uses: actions/hello-world-javascript-action@v1
        with:
          who-to-greet: Pepe
      - name: Log Greeting output
        run: echo "Pepe ${{ steps.greet.outputs.time }}"

The Checkout action / Clone repo in a workflow

  • When a workflow is run on a GitHub Runner by default a folder is set up on the virtual machine the runner created, we refer to this folder as the Working Directory, for example /home/runner/work/github-actions-test/github-actions-test but by default the runner will NOT clone the repo

  • The most common way to clone our repo in a workflow is to use the GitHub checkout action which is an official GitHub action (this just means it is an action created by GitHub), this action can take as input a reference ref that can be branch, tag or SHA of the commit we want to use

  • If you want to clone the repo without using the checkout action you will need to use environment variables and data objects that are setup automatically by GitHub

    • $GITHUB_SHA this is the commit ID of the commit that triggered the workflow
    • $GITHUB_REPOSITORY this is user name and repo name. e.x. sanpepe/github-actions-test
    • $GITHUB_WORKSPACE this is the directory create by the runner for the workflow
    • ${{ github.token }} token to auth with your repo, this will be hidden in the logs if you print it for security reasons
name: Actions Workflow

on: [push]

jobs:
  run-github-actions:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v1
      - name: List after checkout
        run: |
          pwd
          ls -al
          echo $GITHUB_SHA
          echo $GITHUB_REPOSITORY
          echo $GITHUB_WORKSPACE
          echo "Token = ${{ github.token }}"
      - name: Simple JS action
        id: greet
        uses: actions/hello-world-javascript-action@v1
        with:
          who-to-greet: Pepe
      - name: Log Greeting output
        run: echo "Pepe ${{ steps.greet.outputs.time }}"

Triggering a workflow

Trigger on Events

  • We can use the on to define we want to run a workflow on events like push, pull_request, pull_request_review, fork, etc. (Webhook events that can trigger a workflow)

  • Some events can have Activity Types which are just a more specific type of event, to set these we need to use the types array

  • Events use env variables that are set differently depending on the events that triggered the workflow, for example on a push and on a pull_request the GITHUB_REF env variable is used to know what commit will be used, for push will be the latest of the branch we are pushing and for the pull request it will be the branch that requested the pull request (the one that wants to be merged)

name: Actions Workflow

on:
  push:
  pull_request:
    types: [closed, assigned, opened, reopened]
...

Trigger on Schedule

  • To trigger on schedule we can use a cron schedule expression and the schedule option which is an array of objects with key cron and value must be a cron (expression but the minimum is every 5 minutes)

  • A cron expression consist of 5 things: minutes hours day_of_month month day_of_week each can be a number or symbol (* means any value). Examples:

    • * * * * * means run every minute of every hours of every day...
    • 1 * * * * means run on minute 1 of every hour so 12:01, 13:01, 14:01, etc
    • 0 20 * * * means run at 20:00 (8pm) every day
    • Check more examples at crontab guru and crontab guru examples
name: Actions Workflow

on:
  schedule:
    - cron: "10 * * * *"
...

Trigger Manually / Use repository_dispatch

  • the repository_dispatch is an event that can be triggered manually by sending a post request to the GitHub API URL in format https://api.github.com/repos/<owner>/<repo_name>/dispatches for example: https://api.github.com/repos/sanpepe/github-actions-test/dispatches

  • To be able to use the repository_dispatch feature you will need to send a token to auth the POST request, for that go to Settings -> Developer Settings -> Personal access tokens, click on Generate new token, give it a name on the Note section, select repo scope, finally click on the generate button and copy the token

Create token example

name: Actions Workflow

on:
  push:
  repository_dispatch:
    types: build
...

POST example using curl, you must get an 204 response:

curl -w "%{http_code}" --user sanpepe: --header "Content-Type: application/json" --request POST --data '{"event_type":"build"}' https://api.github.com/repos/sanpepe/github-actions-test/dispatches

Repo dispatch example

Trigger Workflows with filters

  • We can filter by branches, tags or paths to be more specific about when a workflow should run, for example if you only want to run when there is a push to a certain branch. To do this you need to add filter options to the events on the on option

  • You can use <filter>-ignore (branches-ignore, tags-ignore, paths-ignore) on an event to signal you want to ignore those but you cannot have both <filter> (branches, tags, paths) and <filter>-ignore if you want to ignore a filter can also use the ! with the <filter> option. So for example you cannot use branches and branches-ignore at the same time but you can have a pattern branch with ! to signal it will be ignored

  • Filters and Patterns GitHub Cheat sheet

name: Actions Workflow

on:
  push:
    branches:
      - master
      - 'feature/*' # matches feature/feat1, feature/feat2, etc
      - '!feature/featA' # ignores feature/featA
    tags:
      - v1
      - v2.*
    path:
      - '**.py' # match push changes on python files

  pull_request:
    branches:
      - master
...

Environment variables in GitHub Actions & GitHub Secrets

  • To set environment variables you can use the env option which should be an object where key is the name of the env variable and the value will be the actual value of the env variable

  • If env option is set at the top level of the YAML file the variables will be available for all jobs, if you want variables specific to a job or step they need to be set at the env key but at the job or step level

  • GitHub env variables documentation

name: Env Variables

on: push

env:
  WF_ENV: Available to all jobs

jobs:
  log-env:
    runs-on: ubuntu-latest
    env:
      JOB_ENV: Available to all steps in log-env Job
    steps:
      - name: Log env Variables
        env:
          STEP_ENV: Available to only this step
        run: |
          echo "WF_ENV: ${WF_ENV}"
          echo "JOB_ENV: ${JOB_ENV}"
          echo "STEP_ENV: ${STEP_ENV}"
      - name: Log env Variables Reloaded
        run: |
          echo "WF_ENV: ${WF_ENV}"
          echo "JOB_ENV: ${JOB_ENV}"
          echo "STEP_ENV: ${STEP_ENV}"
  log-default-env:
    runs-on: ubuntu-latest
    steps:
      - name: Log Default env Variables
        run: |
          echo "HOME: ${HOME}"
          echo "GITHUB_WORKFLOW: ${GITHUB_WORKFLOW}"
          echo "GITHUB_ACTION: ${GITHUB_ACTION}"
          echo "GITHUB_ACTIONS: ${GITHUB_ACTIONS}"
          echo "GITHUB_ACTOR: ${GITHUB_ACTOR}"
          echo "GITHUB_REPOSITORY: ${GITHUB_REPOSITORY}"
          echo "GITHUB_EVENT_NAME: ${GITHUB_EVENT_NAME}"
          echo "GITHUB_WORKSPACE: ${GITHUB_WORKSPACE}"
          echo "GITHUB_SHA: ${GITHUB_SHA}"
          echo "GITHUB_REF: ${GITHUB_REF}"
          echo "WF_ENV: ${WF_ENV}"
          echo "JOB_ENV: ${JOB_ENV}"
          echo "STEP_ENV: ${STEP_ENV}"
  • To encrypt env variables in GitHub go to Settings -> Secrets -> Add a new secret give it a Name and Value then in the YAML you can reference it using the secrets object. Example:
name: Secret Env Variables
on: push
env:
  MY_SECRET_ENV: ${{ secrets.MY_SECRET_ENV }}
  • GitHub provides the ${{ secrets.GITHUB_TOKEN }} if you want to auth with GitHub API or use it for other operations that require auth to GitHub. The most common usage of this token is to give access to third party apps, scripts or actions to your repo. Examples:
    • Using the token to commit and push to your repo from a workflow
    • Using an action that requires access to your repo like the actions/labeler which can automatically add labels to pull request but in order to do that it needs access to create labels in your repo
    • See more examples in the following link: Authentication in a workflow
name: ENV Variables
on: push
env:
  WF_ENV: Available to all jobs

jobs:
  push_random_file:
    runs-on: ubuntu-latest
    steps:
      - name: Push a random file
        run: |
          pwd
          ls -a
          git init
          git remote add origin "https://$GITHUB_ACTOR:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY.git"
          git config --global user.email "my-bot@bot.com"
          git config --global user.name "my-bot"
          git fetch
          git checkout master
          git branch --set-upstream-to=origin/master
          git pull
          ls -a
          echo $RANDOM >> random.txt
          ls -a
          git add -A
          git commit -m"Random file"
          git push
...

You cannot trigger a workflow when you are using the ${{ secrets.GITHUB_TOKEN }} this is the reason pushing from a workflow won't cause an endless loop of workflows running forever

  • Secrets in GitHub have a limit of 64kB for large files you can encrypt them with a tool (like GPG gpg --symmetric --cipher-algo AES256 my_secret.json), push the encrypted version, then create a GitHub secret with the passphrase and use it in your workflow to unencrypt the file (for example gpg --quiet --batch --yes --decrypt --passphrase="$PASSPHRASE" --output $HOME/my_secret.json my_secret.json.gpg
name: ENV Variables
on: push

jobs:
  decrypt:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - name: Decrypt
        env:
          PASSPHRASE: ${{ secrets.PASSPHRASE }}
        run: gpg --quiet --batch --yes --decrypt --passphrase="$PASSPHRASE" --output $HOME/my_secret.json my_secret.json.gpg
      - name: Print our file content
        run: cat $HOME/my_secret.json

Expressions, Context and Functions in GitHub Actions

Context and expression syntax for GitHub Actions Documentation

  • The ${{}} syntax is used to write what is called an expression in a workflow, an expression is something that needs to be evaluated, so besides access to and object's values you can have things like comparisons ("a" == "a") or functions (toJson())

  • Objects in GitHub are also called context and provide information about your workflow

name: Context workflow

on: [push]

jobs:
  dump-context:
    runs-on: ubuntu-latest
    steps:
      - name: Dump GitHub context
        env:
          GITHUB_CONTEXT: ${{ toJson(github) }}
        run: echo "$GITHUB_CONTEXT"
      - name: Dump steps context
        env:
          STEPS_CONTEXT: ${{ toJson(steps) }}
        run: echo "$STEPS_CONTEXT"
  functions:
    runs-on: ubuntu-latest
    steps:
      - name: functions examples
        run: |
          echo ${{ contains( 'hello', '11' ) }}
          echo ${{ startsWith( 'hello', 'he' ) }}
          echo ${{ endsWith( 'hello', '1o' ) }}
          echo ${{ format( 'Hello {0} {1} {2}', 'World', '!', '!' ) }}

Job status functions

  • Job status functions are functions that return status of the job and steps of the job

  • Job status functions are used with the if key to determine if a job or step should run. Anything inside the if key is treated as an expression

  • By default if a step fails the following steps in a job won't run unless we change that behavior using a job status functions

    • failure(): returns true if previous step or job fails
    • success(): returns true if previous step or job success
    • canceled(): returns true if previous step or job was canceled
    • alwayts(): always returns true used to run step or job that needs to run even if anything failed in the previous step or job
name: Context workflow

on: [push]

jobs:
  functions:
    runs-on: ubuntu-latest
    if: github.event_name == 'push'
    steps:
      - name: bad echo cmd
        run: |
          eccho "Bad echo command"
      - name: functions examples
        if: failure()
        run: |
          echo ${{ contains( 'hello', '11' ) }}
          echo ${{ startsWith( 'hello', 'he' ) }}
          echo ${{ endsWith( 'hello', '1o' ) }}
          echo ${{ format( 'Hello {0} {1} {2}', 'World', '!', '!' ) }}

Job status functions example

  • You can add the continue-on-error: true option to a step to signal all following steps should run even if the steps that has this set to true fails

  • You can also add the timeout-minutes to a job or step to set the max number of minutes before GitHub kills the process for this step or job

Strategy & Matrix in Jobs

  • strategy is a special option we have have in GitHub workflows that can be specified in job to setup and environment matrix

  • A strategy can be used to run a job on different types of environments, for example if we want to a job with multiple versions of NodeJS and multiple Operating Systems we can setup a matrix of environments using the strategy option to do this

  • To set up a strategy we use the strategy option

    • We can add the fail-fast if set to false each of the jobs will run independent of the results of other jobs
    • We can add the max-parallel to limit the number of jobs that will run in parallel, by default github will maximize this
    • We need to add the matrix key that must contain any keys and the values of those keys must be arrays with the values that will be used on the execution of the job, on the job we need to use the ${{matrix.key_name}} to reference the value that will change depending on which job is running
name: matrix

on: [push]

jobs:
  node-version:
    strategy:
      matrix:
        os: [macos-latest, ubuntu-latest, windows-latest]
        node_version: [6, 8, 10]
    runs-on: ${{matrix.os}}
    steps:
      - name: Log Node version before
        run: |
          node -v
          npm -v
      - name: use setup-node
        uses: actions/setup-node@v1
        with:
          node-version: ${{matrix.node_version}}
      - name: Log Node version after
        run: |
          node -v
          npm -v

Matrix example

  • we can exclude an specific combination of the matrix using the exclude inside the matrix, the exclude option must have an array of objects where each object must have the keys and values that we want to exclude or include

  • the include option is used to add extra values to a specific combination of the matrix

name: matrix
on: [push]
jobs:
  node-version:
    strategy:
      matrix:
        os: [macos-latest, ubuntu-latest, windows-latest]
        node_version: [6, 8, 10]
        include:
          - os: ubuntu-latest
            node_version: 8
            is_ubuntu_8: "true"
        exclude:
          - os: windows-latest
            node_version: 6
          - os: macos-latest
            node_version: 8
    runs-on: ${{matrix.os}}
...

Docker Containers on Jobs and Steps

  • We can create containers to run steps by using the container option and specifying a Docker image
name: Container
on: [push]
jobs:
  node-docker:
    runs-on: ubuntu-latest
    container:
      image: node:14.15.0-alpine3.10
    steps:
      - name: "Log Node Version"
        run: |
          node -v
          cat /etc/os-release

Containers example

Containers on Github Actions can only run on ubuntu

  • We can also do more complex stuff and specify multiple containers to run as services, for example an app that needs a DB to work can be run if we create and run a container that has the DB. For this we use the services option and inside we can follow same syntax as in docker-compose.yml file which let us specify multiple services that run on separate containers

  • we can use docker container as github action with the uses and with options in a step. E.x. uses: docker://node:14.15.0-alpine3.10

  • The power of being able to use docker images in Github actions lies in the fact that we can use the docker library of images. We can browse the library and if we find a docker image that does something we need we can use it as part of our workflow

Containers in Step example

CI/CD Workflow

  • CI (Continuous Integration) is a practice where one or more collaborators frequently commit code to a shared repository and if something breaks we want to detect the source of the error (the commit that caused the errors) as soon as possible

  • CI attempts to ensure that new code will not break any previously working tests or introduce any errors

  • CD (Continuous Deployment) is a practice where we want to be continuously deploying our application or in other words making releases of our application without impacting our clients/users

CI/CD Workflow Example Rules

CI/CD Workflow Example Rules

  1. Two protected branches (Develop and Master), meaning developers cannot push directly to these they need to open a pull request to merge something to these branches

  2. Master branch will contain deployable code, which means code that can be deployed to production. So we will never push anything into the master branch except if we want to deploy it to production. Our Develop branch on the other side will contain the latest development code that we would not like to deploy yet.

  3. When a developer needs to work on something, he/she will or create a new branch from the Develop branch (e.x called Feature1)

  4. When a developer work is done a pull request to merge into Develop branch will be opened, when this happens a workflow will run to execute tests (unit, integration & system), if test pass and no regression is detected this pull request will be in queue for a Reviewer to approve it

  5. if reviewer approves this will be merged into the Develop this merge will run another workflow to run our tests again (unit, integration & system) and deploy to a staging server (beta)

  6. When we want to make a release, we need to create pull request from the Develop branch to merge into master, this pull requests will trigger another workflow that will execute or tests again if our tests pass it will be in queue for a Reviewer to approve the release

  7. if reviewer approves the release, this will be merged into Master this merge will run another workflow to run our tests again (unit, integration & system) and deploy to production

  8. If any of the workflows fail, we should create an issue and send email.

  9. if Release is successfully send an email

Setup CI/CD in Github considerations

  • Github allow you to set a CODEOWNERS file to set who owns certain files (your_repo/.github/CODEOWNERS) you need to specify files, folders or patterns followed by Github username, this is used for pull-request to assign reviewers based on owners

      *.js @sanpepe
      *.yaml @sanpepe
  • To protect branches in Github 1) go to Settings -> Branches and Click on Add Rule 2) give the branch name and 3) Select the rules you want to enforce and click on Create (you may need to enter your password).

    • Once you have a workflow with Jobs the jobs will show up on the Status checks section so you can mark which jobs need to succeed in order to able to merge

Protect branches in Github Example:

Protect branches in Github

Common actions and steps in CI/CD workflows

  • On a CI/CD workflow usually we need to checkout our repo, install language dependencies (e.x. install python) install run dependencies (pip packages), then run some scripts or tests
name: CI
on:
  push:
    branches: [develop]
  push:
    branches: [develop]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Use Python 3.x
        uses: actions/setup-python@v1
        with:
          python-version: "3.x"
      - run: pip install --no-index -r $./requirements.txt
      - name: run unit test
        run: python -m unittest .../tests/unit/my_unit_test.py
          PYTHONPATH: ./
      - name: run integration tests
        if: github.event_name == 'push'
        run: python -m unittest .../tests/integration/my_int_test.py
      - name: Deploy to Staging
        if: github.event_name == 'push'
        run: echo "Some step to publish code"

Consider a merge to a branch needs a push so it you trigger on push that will also be executed when a merge happens

  • If you want to cache the sate of your dependencies you can use the cache action for example to cache python dependencies

    • when the cache steps runs it creates an identifier named as specified in the key option, this is save in your caches so every time it runs it first sees if that cache is found it will be retrieved and used in your workflow. And if not a new cache key will be created so that when you run your workflow again, you can use it
  • To Upload and artifact in Github we need to use the upload-artifact action and specify the path we want to upload, optionally give it an ID name to appear on Github

    • you can also download artifacts using the download-artifact action
...
      - name: Upload artifact
        uses: actions/upload-artifact@v1
        with:
            name: my_app_debug
            path: ./logs/my_app_debug.log
...
Create a release with assets in Github using semantic-release
  • Semantic Versioning

    • MAJOR version when you make incompatible API changes,
    • MINOR version when you add functionality in a backwards compatible manner, and
    • PATCH version when you make backwards compatible bug fixes. Semantic Versioning
  • Conventional commits: Have a format for the commits with special keywords to know what version to increment for the release

To Setup semantic release:

  1. Add a release.config.js file in your repo and set it up according with semantic release documentation
    module.exports = {
     branches: "master",
     repositoryUrl: "<github repo URL here>",
     plugins: [
         "@semantic-release/commit-analyzer",
         "@semantic-release/release-notes-generator",
         "@semantic-release/github"
         ]
    }
  2. Add a step in your workflow to run npx semantic release and provide the env variable GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  3. If you want to upload extra assets to your release, you need to add them in the semantics-release config file (we can only add files)

    module.exports = {
     branches: "master",
     repositoryUrl: "<github repo URL here>",
     plugins: [
         "@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator",
         ["@semantic-release/github", {
             assets: [
                 { path: "build.zip", label: "Build" },
                 { path: "coverage.zip", label: "Coverage" }
             ]
         }]
         ]
    }
Trigger other actions
  • To trigger an action after a workflow completion we usually use the release and other events in a separate workflow

  • Send a message

    name: Notify on Release
    on:
    release:
      types: [published]
    jobs:
    slack-message:
      runs-on: ubuntu-latest
      steps:
        - name: Slack Message
          run: |
            curl -X POST -H 'content-type: application/json'
             -- data '{"text":"New release ${{ github.event.release.tag_name }}
             is out, <${{ github.event.release.html_url }}|check it out now.>"}'
             ${{ secrets.SLACK_WEBHOOK }}
    

Remember you cannot trigger a workflow from inside another workflow using the Github token secrets.GITHUB_TOKEN that is automatically generated by Github, if you want to do this you need to generate a separate token from your personal account (Go to Settings -> Developer Settings -> personal access tokens)

  • To trigger an action after a something fails in a workflow we usually add a step with a failure() condition which will only run if any of the steps in a job fails
...
      - name: Open Issue
        if: failure() && github.event_name == 'pull_request'
        run: |
          curl --request POST \
          --url https://api.github.com/repos/${{ github.repository }}/issues \
          --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
          --header 'content-type: application/json' \
          --data '{
            "title": "Automated issue for commit: ${{ github.sha }}",
            "body": "This issue was automatically created by the GitHub Action workflow **${{ github.workflow }}**. \n\n The commit hash was: _${{ github.sha }}_.",
            "assignees": ["${{ github.event.pull_request.user.login }}"]
            }'
...
Add a status badge to readme file
  • Add to your README.md file the URL in format:
![](https://github.com/<github_username_here>/<repo_name_here>/workflows/<name_of_workflow_here>/badge.svg?branch=<name_branch_here>&event=<event_name_here>)
![](https://github.com/sanpepe/github-actions-test/workflows/Container/badge.svg?branch=master&event=push)

Create a custom GitHub Action

  • Github has public actions that live in the Github market place and we can reference in our workflow without actually including any code in our project

  • We can also write private actions that live in your project and reference them in the workflow

  • We can write actions using JavaScript or Docker containers

    • Actions with containers are more stable and consistent since we control the OS and dependencies, however they are slower because that setup takes time

How to create a simple GitHub action with Javascript

  1. In your repo create a new folder inside the .github folder called actions and inside that create a folder for your action
  2. Inside your action folder create action.yml file to define basic information of your action: name, author, inputs & outputs and runs to specify entry point and type of action e.x. Javascript and index.js

    inputs in a github action only accept strings

    name: Hello World Pepe Action
    author: Pepe Sandoval
    description: Some description about action here
    inputs:
    who-to-say-hello:
     description: 'Who to Say Hello'
     required: true
     default: Pepe
    outputs:
    time:
     description: 'Time of Hello'
    runs:
    using: "node12"
    main: 'dist/index.js'
    
  3. Inside your action folder create the entry point file with the same name you specified in the action.yml file (e.x. index.js), add here the code to run with your actions and use the GitHub Toolkit packages to get inputs and outputs.
    // index.js
    const core = require("@actions/core");
    const github = require("@actions/github");
    try {
     const name = core.getInput("who-to-say-hello");
     console.log(`Hello World ${name}`);
     const time = new Date();
     core.setOutput("time", time.toTimeString());
     console.log(JSON.stringify(github, null, '  '));
    } catch(error) {
     // You can call this function to make action and workflow fail
     core.setFailed(error.message)
    }
    
  4. Compile our file and dependencies into a single file for that you can use ncc
    1. Install: npm i -D @zeit/ncc or npm i -D @vercel/ncc
    2. Install dependencies used in your actions: npm install @actions/core and npm install @actions/github
    3. Build-compile giving it it path to the file to compile: npx ncc build .github/actions/hello/index.js -o .github/actions/hello/dist
    4. Update the action.yml to use the single compiled file e.x. main: dist/index.js
  5. Use your action in your workflow, reference it using relative path ./github/actions/...
    name: Test Actions
    on: [push]
    jobs:
    run-my-github-actions:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout Repo
         uses: actions/checkout@v1
       - name: Use my Action
         id: hello
         uses: ./.github/actions/hello
         with:
           who-to-say-hello: "Pepe Ch."
       - name: Log Greeting output
         run: echo "Pepe Action Time ${{ steps.hello.outputs.time }}"
    

When you do changes in the entry point JS file remember to recompile it

simple GitHub action with Javascript

How to Create GitHub action with Docker containers

  • Also known as Docker actions these won't run directly on the VM created by Github Actions, they will run a docker container
  1. In your repo create a new folder inside the .github folder called actions and inside that create a folder for your action
  2. Inside your action folder create action.yml file to define basic information of your action: name, author, inputs & outputs and runs to specify image or dockerfile, set the args inside run to pass arguments to your dockerfile
    name: Hello World Pepe Docker Action
    author: Pepe Sandoval
    description: Some description about action here
    inputs:
    who-to-say-hello:
     description: 'Who to Say Hello'
     required: true
     default: Pepe
    outputs:
    time:
     description: 'Time of Hello'
    runs:
    using: "docker"
    image: 'Dockerfile'
    args:
     - ${{ inputs.who-to-say-hello }}
    
  3. Inside your action folder create the Dockerfile. A common practice is to use a shell script and use special echo strings to set outputs

    if you use an .sh script make sure to give the executable permissions before pushing to repo. e.x. chmod +x .github/actions/hello-docker/entrypoint.sh and git add --chmod=+x -- ./.github/actions/hello-docker/entrypoint.sh

    FROM alpine:3.11
    COPY entrypoint.sh /entrypoint.sh
    ENTRYPOINT ["/entrypoint.sh"]
    
    #!/bin/sh -l
    # entrypoint.sh
    echo "::debug ::Debug Message"
    echo "Hello $1"
    time=$(date)
    echo "::set-output name=time::$time"
    echo "::group::Some expandable logs"
    echo 'Stuff 1'
    echo 'Stuff 2'
    echo 'Stuff 3'
    echo "::endgroup::"
    
  4. Use your action in your workflow, reference it using relative path ./github/actions/...
    name: Test Actions Docker
    on: [push]
    jobs:
    run-my-github-actions:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout Repo
         uses: actions/checkout@v1
       - name: Use my Action
         id: hello
         uses: ./.github/actions/hello-docker
         with:
           who-to-say-hello: "Pepe Ch."
       - name: Log Greeting output
         run: echo "Pepe Docker Action Time ${{ steps.hello.outputs.time }}"
    

if you want your action to fail you just need to exit the .sh script with an exit code != 0

simple GitHub action with Docker

Simple Github Actions Python Workflow

  • The following example would be located in your repo, for example: repo/.github/workflows/unit_test.yaml and does the minimal to run a unittest python script if this fails make sure your requirements.txt doesn't have issues and unit test python script works
name: Run Python Unit Test
on: push
jobs:
  run-py-unit-test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v2
      - name: Setup Python 3.8
        uses: actions/setup-python@v2
        with:
          python-version: '3.8'
      - name: Install Python requirements
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
      - name: Run Unit tests
        run: |
          python -m unittest tests/my_unit_test.py -v

References

Want to show support?

If you find the information in this page useful and want to show your support, you can make a donation

Use PayPal

This will help me to create more stuff and fix the existent content... or probably your money will be used to buy beer