DEV Community

Cover image for My top 3 advanced features you can’t miss on GitHub Actions
Anthony Viard 🥑 for Entando

Posted on • Originally published at entando.com

My top 3 advanced features you can’t miss on GitHub Actions

Hi, my fellow developers!

My last blog post 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: “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.

 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' }}
Enter fullscreen mode Exit fullscreen mode

If you’re interested in other examples of how the matrix strategy can be used, I suggest you check out JHipster pipelines. 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. 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 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.

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
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

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.

name: Banking Plugin CI
on: push
env:
  PROJECT_NAME: banking-plugin
jobs:
  backend1:
[...]
  backend2:
    needs: backend1
Enter fullscreen mode Exit fullscreen mode

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

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

Pro Tip

The if 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. The matrix jobs need tests-windows and tests-linux to run, while codecov needs all the matrix jobs.

Image description

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!

Top comments (0)