# 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')" ```