Github Actions are the go-to continuous integration tool for plenty of reasons - chief among them: first-party support with Github, pretty decent performance, and relatively cheap runner instances.

However, one place Github Actions has fallen short relative to other YAML-based players in the space (namely CircleCI) has been the inability to DRY up the YAML config files.

However, with a cool new 1 feature called Composite Run Steps, your repeated shell script invocations can now happily live in a separate YAML file!

Don’t read the docs

The documentation for Composite Run Steps (linked at the bottom) are great, but they make the terrible assumption that you want to create a whole different repository for your composite action.

If that’s your use-case, great! Otherwise, I’m guessing the far more common use-case is to warehouse code, workflow files, and composite action files in the same repository!

Fortunately, this is possible, the solution just is buried in a Github Community link instead of being plainly available in big bold letters at the top of the Github’s documentation.

3 Steps to DRYer YAML

  1. Go through the official docs for creating a Composite Run Step Action, except ignore everything about creating a new repository โ€” just name your action metadata file as /.github/actions/my-action-name/action.yml

    As you’ll see in a moment, the my-action-name is important, since that’s how you’ll reference your action. It can be anything that’s legal as a file-path.

  2. In your actual Workflow yaml file, replace your repeated shell scripts with the following line; for example:

    - - run: sudo apt-get install androidsdk
    - - run: alias sdkmanager="androidsdk"  
    - - name: Update Android Deps
    -   run: sdkmanager "build-tools;31.0.0-rc5" "platform-tools" "platforms;android-30" "cmdline-tools;latest" "extras;android;m2repository" "extras;google;m2repository" --verbose   
    -   env:  
    -     ANDROID_SDK_ROOT: /opt/android/sdk
    + - uses: ./.github/actions/my-action-name
    
  3. Revel in your victory; your workflow files are just a wee bit smaller! ๐ŸŽ‰

If you’re a visual person, your file heirarchy should look like this:

repo-root/
โ”œโ”€โ”€.github/
โ”‚   โ”œโ”€โ”€actions/
โ”‚   โ”‚   โ””โ”€โ”€my-android-action/
โ”‚   โ”‚       โ””โ”€โ”€action.yml
โ”‚   โ””โ”€โ”€workflows/
โ”‚       โ””โ”€โ”€workflow.yml
โ””โ”€โ”€:app

In my case, across 2 workflow files, I converted 40 lines of YAML to 14 lines, with the benefit of better maintainability moving forward.

Limitations

As of this writing (06/2021) Composite Run Step Actions don’t support importing other actions – meaning you’ll have to do anything involving a uses statement the old-fashioned way, for now2:

- name: Build App
  uses: eskatos/gradle-command-action@v1
  id: gradle
  with:
    wrapper-cache-enabled: true
    dependencies-cache-enabled: true
    configuration-cache-enabled: true
    arguments: :app:assembleDebug --scan

Composite Run Actions also don’t support conditionals, timeouts, referencing secrets, and a few other things. The Metadata Syntax doc has the full enumeration of allowable items; everything else is unsupported.

Wrap-up

What did I miss? Is there some super-secret way to reuse YAML I don’t know about?

Send me a note at @JvmName!

Links/References

  1. Github’s Composite Run Steps docs

    https://docs.github.com/en/actions/creating-actions/creating-a-composite-run-steps-action

  2. Metadata Syntax Doc:

    https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs-for-composite-run-steps-actions

  3. GitHub Composite Actions - STOP wasting your time and create reusable actions
    https://dev.to/n3wt0n/github-composite-actions-nest-actions-within-actions-3e5l

  4. Github Community

    https://github.community/t/path-to-action-in-the-same-repository-as-workflow/16952/7


  1. “New” meaning “I just discovered it yesterday”; I have no idea how long it’s been out. ↩︎

  2. It looks like Github has an Architecture Decision Record that’s been accepted…so now it’s just a matter of time? ↩︎