---
# System prepended metadata

title: GitHub Action Examples

---

# GitHub Action Examples

## Set up of the CI

This includes:
1. Set triggering rules for the job, example triggering rules could be based on:
    - schedule (cron job);
    - new PR/issue created;
    - new commit to certain branch;
    - a special comment posted into a issue/PR;
    - a manual trigger (dispatch).
2. Defining input variables for manual triggers. This is a nice thing which GitLab doesn't provide.

```yaml=
name: Name of the workflow

# job triggers
on:
  # on cron
  schedule:
    - cron: "0 3 * * *"
    
 push:
    branches:
      - production/v4
      - patch/*
      - prep-release/*
    paths-ignore:
      - 'docs/**'
      - '.github/**'
  pull_request:
    branches: [ production/v4 ]
    
  tag:
      - 'v*'
  
  # enable manual dispatch with input variables
  workflow_dispatch:
    inputs:
      tag-prefix:
        description: 'nightly tag prefix'
        default: ''
      feature-branch:
        description: 'feature branch to be used for the build.'
        default: develop
      cvmfs-deployment:
        description: 'whether to deploy the release to cvmfs'
        default: 'no'
```


## Matrix builds

You can have parallel jobs running inside different containers running different Linux distros.

```yaml=
  integration_tests:
    name: run_tests
    runs-on: daq
    strategy:
        matrix:
            test_name: ["t1", "t2", "t3"]
    defaults:
      run:
        shell: bash

    steps:

    - name: setup release and run tests
      run: |
          echo ${{ matrix.test_name }}
```

## Flow control

Making a job dependent on another job

```yaml=
jobs:
  job_1:
    name: job 1
    runs-on: ubuntu-latest
    defaults:
      run:
        shell: bash
    steps:
      - id: job_1_step_1
        run: |
          echo "step 1 in job 1"

  job_2:
    name: job 2
    runs-on: ubuntu-latest
    needs: job_1
    defaults:
      run:
        shell: bash
    steps:
      - id: job_2_step_1
        run: |
          echo "step 1 in job 2"
        
```

Marking a job step always run regardless results from previous job step.


```yaml=
      # for a job step
      - name: Clean up
        if: always()
        run: |
            docker image prune -f
            docker container prune -f
            docker network prune -f
            docker volume prune -f
```


## Dealing with build artifacts

Save build artifacts in a job.

```yaml=
    - name: upload a tarball
      uses: actions/upload-artifact@v3
      with:
        name: candidates_coredaq
        path: ${{ github.workspace }}/tarballs_for_upload/coredaq-${{ github.event.inputs.base-release }}-rc${{ github.event.inputs.build-number }}-a9.tar.gz
```

Use the tarball in another job.

```yaml=
      - name: Download the tarball
        uses: actions/download-artifact@v3
        with:
          name: candidates_coredaq
          path: ${{ github.workspace }}/docker-build
```

Download the artifact via GitHub client from command line

```bash=
runid=`gh run -R <repo_name> list|grep Nightly|grep schedule|grep success|head -n 1|egrep -o '[[:digit:]]{10}'`

gh run download $run_id -D <output_dir> -n <artifact-1_name> -n <artifact-2_name>
```


## using cvmfs in GitHub Action

```yaml=
    # For each job needing cvmfs access, add the following step.
    steps:
    - uses: cvmfs-contrib/github-action-cvmfs@v3
```

The host OS `ubuntu-latest` will have `cvmfs` installed after this job step. If you use containers in your job, you can use cvmfs like the following:

```yaml=
    - name: start docker container with bind mount cvmfs
      run: |
        docker run --rm -v /cvmfs:/cvmfs:shared -v $GITHUB_WORKSPACE/scratch:/scratch ghcr.io/dune-daq/sl7-spack:latest /scratch/build_pkg.sh
```

## Caching files to speed up frequent builds

This is useful if your job constantly downloads a set of files. In the case of cvmfs, simply saving the cached files into the GitHub Action's cache would be helpful. The cache persists after the CI job completes. It can be loaded in the future CI runs before job starts. All you need is just adding one step in your job as below. The cache will be automatically updated if it's changed during your job.

```yaml=
    - name: Cache cvmfs cache
      id: cvmfs_cache
      uses: actions/cache@v3
      with:
        path: /var/lib/cvmfs/shared
        key: cachecvmfs

```


## Build/tag/push container images

You should be able to use GitHub's image registry with the default GitHub token associated with the CI job. But you would need to save your credentials in secrets if you want to use other registeries.

```yaml=
jobs:

  push_to_registries:
    name: Push Docker image to multiple registries
    runs-on: ubuntu-latest
    permissions:
      packages: write
      contents: read
    steps:
      - name: Check out the repo
        uses: actions/checkout@v3

      - name: Log in to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Log in to the Container registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@v4
        with:
          images: |
            dunedaq/sl7-minimal
            ghcr.io/DUNE-DAQ/sl7-minimal
          tags: |
            type=raw,value=latest
            type=ref,event=branch
            type=ref,event=tag

      - name: Build and push Docker images
        uses: docker/build-push-action@v3
        with:
          context: ./sl7-minimal
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
~
```


## Setting up self-hosted runner


## Other useful tricks

### Save a varaible from one job for use by other job steps

The following job step `make_nightly_tag` saves "nightly_tag" into the special env `GITHUB_OUTPUT`.

```yaml=
jobs:
  make_nightly_tag:
    name: create nightly tag
    runs-on: ubuntu-latest
    outputs:
      tag: ${{ steps.create_nightly_tag.outputs.nightly_tag }}
    defaults:
      run:
        shell: bash
    steps:
      - id: create_nightly_tag
        run: |
          date_tag=$(date +%y%m%d)
          echo "nightly_tag=${{ github.event.inputs.tag-prefix }}_DEV_${date_tag}_C8"  >>  "$GITHUB_OUTPUT"
          cat "$GITHUB_OUTPUT"
```

This is how to use it in another job.

```yaml=
  build_the_release:
    name: build_dev_release
    runs-on: ubuntu-latest
    needs: make_nightly_tag
    defaults:
      run:
        shell: bash

    steps:
    - name: printout_nightly_tag
      env:
        NIGHTLY_TAG: ${{needs.make_nightly_tag.outputs.tag}}
      run: |
          echo $NIGHTLY_TAG

```

### Adding badges to README.md for a certain CI workflow

Go to the "Actions" tab of the repository, and click one CI run of a specific CI workflow. Click the three dots icon beside the "Re-run all jobs" button, and choose "create status badge". A pop-up window will appear with the markdown string for the badge. You can copy and paste it into your README.md file.


### Adding new PRs/issues to a GitHub project

You will need to find out the project ID, and also have a token with proper scope of permissions (adding issues to a project etc) first.

```yaml=
name: Add issue to project
on:
  issues:
    types:
      - opened

jobs:
  track_issue:
    runs-on: ubuntu-latest
    steps:
      - name: Get project data
        env:
          GITHUB_TOKEN: ${{ secrets.BOT_CI_ISSUES }}
          ORGANIZATION: DUNE-DAQ
          PROJECT_NUMBER: 5
        run: |
          gh api graphql -f query='
            query($org: String!, $number: Int!) {
              organization(login: $org){
                projectV2(number: $number) {
                  id
                }
              }
            }' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json

          echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV

      - name: Add issue to project
        env:
          GITHUB_TOKEN: ${{ secrets.BOT_CI_ISSUES }}
          ISSUE_ID: ${{ github.event.issue.node_id }}
        run: |
          item_id="$( gh api graphql -f query='
            mutation($project:ID!, $issue:ID!) {
              addProjectV2ItemById(input: {projectId: $project, contentId: $issue}) {
                item {
                  id
                }
              }
            }' -f project=$PROJECT_ID -f issue=$ISSUE_ID --jq '.data.addProjectV2ItemById.item.id')"
```

```yaml=
name: Add pull request to project
on:
  pull_request:
    types:
      - opened
jobs:
  track_issue:
    runs-on: ubuntu-latest
    steps:
      - name: Get project data
        env:
          GITHUB_TOKEN: ${{ secrets.BOT_CI_ISSUES }}
          ORGANIZATION: DUNE-DAQ
          PROJECT_NUMBER: 5
        run: |
          gh api graphql -f query='
            query($org: String!, $number: Int!) {
              organization(login: $org){
                projectV2(number: $number) {
                  id
                }
              }
            }' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json

          echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV

      - name: Add issue to project
        env:
          GITHUB_TOKEN: ${{ secrets.BOT_CI_ISSUES }}
        run: |
            PR_NUMBER=${{ github.event.pull_request.number }}
            node_id=`curl -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/${{ github.repository }}/pulls/${PR_NUMBER} | jq '.node_id'`
            item_id="$( gh api graphql -f query='
            mutation($project:ID!, $issue:ID!) {
              addProjectV2ItemById(input: {projectId: $project, contentId: $issue}) {
                item {
                  id
                }
              }
            }' -f project=$PROJECT_ID -f issue=$node_id --jq '.data.addProjectV2ItemById.item.id')"
```