My top 3 advanced features you can’t miss on GitHub Actions
Tags: github, devops, opensource
Published: 2022-01-20 09:17:26

Hi, my fellow developers!

My [last blog post](https://entando.com/page/en/get_started_with_github_actions?contentId=BLG5560&modelId=64) showed how to use GitHub Actions to create simple Continuous Integration workflows for your projects, and today I’d like to share the advanced GitHub Actions features I find most exciting.

Here are my top 3 features you should not miss!


## #1 Welcome to the matrix

No revolution here, but the matrix strategy can be explained as “Defined once, run multiple times.”Basically, it allows you to define a job structure to be run in multiple scenarios. 

According to the [official documentation](https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix): _“A matrix allows you to create multiple jobs by performing variable substitution in a single job definition. For example, you can use a matrix to create jobs for more than one supported version of a programming language, operating system, or tool. A matrix reuses the job's configuration and creates a job for each matrix you configure.”_

So when is the matrix strategy valuable? Of course, it’s hard to define all of the cases where a matrix is helpful, but here are scenarios based on my own experience:



* You want to run your job over several versions of a tool, e.g. Java 11 and 17, or different OS versions
* You want to inject variables to run jobs differently, e.g. change the path to your micro frontends
* You want to combine the two cases above for maximum flexibility

The Entando Standard Banking Demo workflow leverages the matrix strategy to ensure that micro frontend jobs use the same definition for all components. This only changes the final subfolder of a job, and that path value is defined on the fly via variable injection.

Please note, with the `include` option we can specify several variable values of a job defined by the matrix strategy. 


``` yaml
 micro-frontends:
    name: ${{ matrix.widget }} micro frontend
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        widget:
          - alert-bar-icon-react
          - dashboard-card-angular
          - dashboard-card-config
          - dashboard-card-react
          - transaction-table
        include:
          - widget: dashboard-card-angular
            test-script-name: test-ci
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2.1.4
        with:
          node-version: '14.15.0'
      - name: Run tests
        run: |
          cd $GITHUB_WORKSPACE/$PROJECT_NAME/ui/widgets/banking-widgets/${{ matrix.widget }}
          npm install
          npm run ${{ matrix.test-script-name || 'test' }}
```


If you’re interested in other examples of how the matrix strategy can be used, I suggest you check out [JHipster pipelines](https://github.com/jhipster/generator-jhipster/blob/main/.github/workflows/angular.yml#L80). In fact, JHipster offers many options combinations to generate apps and using the matrix feature addresses most of them.


## #2 Compose your jobs using composite actions

My second favorite feature of GitHub Actions is [composite actions](https://docs.github.com/en/actions/creating-actions/creating-a-composite-action). Yes, you heard it right. You can create reusable actions and compose them to create a job.

This feature is available as of summer 2021 and lets you not only use scripts or actions from the marketplace, as you could before but also reference actions defined in separate files.

Composite actions allow you to reduce duplication and improve reusability. You can create, update and maintain one action for several jobs and workflows in the same repository.

You can also share these actions across multiple repositories, but that means you need to checkout the project where the actions belong. To do that, you will need to set up a [Personal Acces Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) and inject it into your repository as an environment variable.

To summarize, using composite actions is great for streamlining your actions inside a repository, or across multiple repositories for more complex cases.

Once again, in the Standard Banking Demo I exploited composite actions to improve my pipelines. For instance, I have created one action to run each time I want to run my Spring Boot backend microservice tests.


``` yaml
name: 'Unit testing Microservice backend application'
description: 'Run unit tests for a backend Spring boot application'
inputs:
  path:
    description: the project path
    required: true
runs:
  using: 'composite'
  steps:
    - name: Run backend tests
      shell: bash
      run: |
        cd ${{inputs.path}}
        chmod +x mvnw
        ./mvnw -ntp clean test
```


Please note, we need to explicitly set the `using` value to `'composite'` and specify the `shell` we are using for each step (e.g. `bash`).

In the following action, I use one input value (a parameter) to define the path to the project. I have to set it each time I use this action.


``` yaml
name: Banking Plugin CI
on: push
env:
  PROJECT_NAME: banking-plugin
jobs:
  backend:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-java@v1
        with:
          java-version: '11.x'
      - uses: ./.github/actions/ms-unit-tests
        with:
          path: $GITHUB_WORKSPACE/$PROJECT_NAME
      - uses: ./.github/actions/ms-build
        with:
          path: $GITHUB_WORKSPACE/$PROJECT_NAME
```


Please note, to use a composite action you need to give the path to the action file (e.g. `uses: ./.github/actions/ms-unit-tests`), then pass parameters using the keyword `with:.`


## #3 Link your jobs to each other

I believe linking jobs is important for creating a synergy between several jobs when you need to handle complex use cases.

The keyword `needs` allows you to define a job need that must be completed before another can run. If `backend2` needs `backend1`, then `backend2` will launch only when `backend1` has finished successfully.


``` yaml
name: Banking Plugin CI
on: push
env:
  PROJECT_NAME: banking-plugin
jobs:
  backend1:
[...]
  backend2:
    needs: backend1
```


Or, you can decide to run the second job, even if the first one is failing.


``` yaml
name: Banking Plugin CI
on: push
env:
  PROJECT_NAME: banking-plugin
jobs:
  backend1:
[...]
  backend2:
    if: ${{ always() }}
    needs: backend1
```


<span style="text-decoration:underline;">Pro Tip</span>

The [if](https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idif) parameter allows you to run a job only when a condition is met, e.g. `always()` means that the job will always run (but still after the previous job has finished, successful or not). It is useful to have flexibility in your pipelines or fallback steps.

Below is an example of dependency between jobs from the project [JHipster-lite](https://github.com/jhipster/jhipster-lite). The matrix jobs need `tests-windows` and `tests-linux`  to run, while `codecov` needs all the matrix jobs.




![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8be85colbwu44f9qtyw7.png)



## Conclusion

In this blog post I’ve shared what I like the most about GitHub Actions in an advanced scenario. From my own experience and the different projects I’ve seen on GitHub, these three features were best suited to complete some of my use cases. However, your own experience and needs will determine what works best for you.

What are the best features you’ve used already in your GitHub Actions workflows? Feel free to share it with us on Twitter!