NUS Hackers Wiki
NUS Hackers Wiki
  • NUS Hackers Wiki
  • Hackerschool
    • Virtual Machines and Linux
    • Beginners' Guide to the Terminal
      • Introduction to the Terminal
      • Modern Shell Tools
      • Shell Scripting
      • Real World Scripting
      • Resources
    • Self-Hosting: Three Easy Pieces
      • 1. Setting up your server
      • 2. Running Services
      • 3. Monitoring your server
    • Vim
    • Introduction to Zig
      • Language Basics
      • Error Handling
      • Memory Management
      • Working with C
      • Exploring comptime
    • CI/CD with Github Actions
      • Background
      • Basics of Github Actions
        • Target workflow
        • Running unit tests
        • Linting code
        • Deploying to Github Pages
      • Advanced use cases
        • Pollers
        • Github script
        • Executing third-party scripts
        • Reusable workflows
      • Cookbook
    • Lightning Git
      • Git Concepts
      • Getting Started with Git
      • Making your first commit
      • Branching
      • Merge Conflicts
      • Integrating remote repositories
      • Collaborative Workflows
      • Commit Manipulation and Reflog
      • Interactive rebasing
      • filter-repo
  • Orbital
    • JavaScript
      • Browser Developer Tools
      • Getting Started
      • Datatypes
      • Operators and Operations
      • Loops and Conditions
      • Functions
      • Strings
      • Arrays
      • HTML
        • Getting Started
        • Tag Attributes
        • HTML Forms
        • Browser Inspector
      • CSS
        • Selectors
        • Colors in CSS
        • Measurements in CSS
        • The Box Model
        • Adding Styles - Part 1
        • Adding Styles - Part 2
      • Working with the DOM
        • Querying the DOM - Selectors
        • Querying the DOM - Element Attributes
        • Querying the DOM - Element Styles
        • Events with JS and HTML
        • Exercise: Click Counter
        • Editing the DOM
        • Fetch Requests
        • Exercise: The NUSMods API
    • React
      • Setup
      • State
    • React Native
      • Setup
      • Intro to JSX
      • Basic Syntax
      • Handling UI
      • Props
      • State Management
    • Git
      • Setup
      • Command Glossary
      • Fundamental Concepts
        • Getting Started
        • Integrating Remote Repositories
        • Branching
        • Merge Conflicts
      • Collaborative Workflows
        • Fork and PR Workflow
        • Branch and PR Workflow
      • Advanced Concepts
        • Ignoring Files
        • Commit Message Conventions
        • Github Collaborators
        • CI/CD with Github Actions
        • Advanced Git Commands
      • FAQ
    • Telegram Bot
      • Creating a TeleBot
      • API Calls
      • Telebot Basics
      • Integrating API's
    • Relational Database
      • Database Overview
      • Database Design
      • Entity Relationship Diagram
      • SQL Basics & PostgreSQL
    • TypeScript
      • Types and Interfaces
      • Utility Types
      • Typing Component Props, Events, and Hooks
      • Why You Should Avoid Using any (and What to Do Instead)
      • TypeScript Tricks You’ll Use All the Time in React
Powered by GitBook
On this page
Edit on GitHub
Export as PDF
  1. Hackerschool
  2. CI/CD with Github Actions
  3. Advanced use cases

Reusable workflows

PreviousExecuting third-party scriptsNextCookbook

Last updated 1 month ago

Our final advanced use case we would like to cover is the use of reusable workflows.

Suppose you're in a and have the following sub-projects:

  1. web

  2. admin

  3. api

All of them require the exact same CI pipeline of running unit tests and linting that we introduced in Basics of Github Actions. If you copy-pasted the same workflow file three times, it might work, but this means that if one changes, everything needs to change. While some might argue that as the application expands, this flexibility is required to avoid a tight coupling to one type of workflow. However, for the sake of simplicity, let's suppose that this duplication is fundamentally bad for this use case. How do we go about reconciling this?

Well, this is where reusable workflows come in. They allow you to effectively define a "common workflow" that can be shared and reused by other workflows as steps. Essentially, what you're creating is custom actions that have not been properly published.

The goes into the nitty gritty of the limitations and access of reusable workflows, so we will not cover it in this section. Instead, we will focus on setting up a very rudimentary reusable workflow for the above scenario.

So let's suppose that the original workflow looks like this:

# .github/workflows/web_ci.yml
name: CI/CD Pipeline
on: [pull_request, workflow_dispatch]
jobs:
  linting:
    runs-on: ubuntu-latest
    steps:
      - name: Fetch repository
        uses: actions/checkout@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'yarn'
      - name: Install dependencies
        run: |
          yarn
      - name: Lint code
        run: |
          yarn lint

  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - name: Fetch repository
        uses: actions/checkout@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'yarn'
      - name: Install dependencies
        run: |
          yarn
      - name: Run unit tests
        run: |
          NODE_ENV=production yarn test

You realize that the job steps are exactly identical, apart from the folder that these commands are being run in. We can generalize these as inputs to the reusable workflow!

# .github/workflows/reusable-ci.yml
name: Reusable CI Workflow
on:
  workflow_call:
    inputs:
      workdir:
        description: 'Working directory'
        required: true
        type: string
jobs:
  linting:
    runs-on: ubuntu-latest
    steps:
      - name: Fetch repository
        uses: actions/checkout@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
          cache: 'yarn'
          cache-dependency-path: "${{ inputs.workdir }}/yarn.lock"
      - name: Install dependencies
        working-directory: ${{ inputs.workdir }}
        run: |
          yarn
      - name: Lint code
        working-directory: ${{ inputs.workdir }}
        run: |
          yarn lint

  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - name: Fetch repository
        uses: actions/checkout@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
          cache: 'yarn'
          cache-dependency-path: "${{ inputs.workdir }}/yarn.lock"
      - name: Install dependencies
        working-directory: ${{ inputs.workdir }}
        run: |
          yarn
      - name: Run unit tests
        working-directory: ${{ inputs.workdir }}
        run: |
          NODE_ENV=production yarn test

Essentially, the key things that had to alter were:

  1. Changing the trigger event type to workflow_call, indicating it's a reusable workflow

  2. Specifying the inputs that the reusable workflow requires, such as the workdir since that is the only thing that changes across variations of this CI workflow

  3. Specifying the cache-dependency-path in the actions/setup-node@v4 action as we need to use the yarn.lock files specific to each sub-project

  4. Specifying the working-directory of each step to point to the given sub-project directory

This is all we really need to create the reusable workflow. Then, we can update our original ci.yml with the following:

# .github/workflows/web_ci.yml
name: CI/CD Pipeline
on: [pull_request, workflow_dispatch]
jobs:
  ci:
    uses: <org name/username>/<repo name>/.github/workflows/reusable-ci.yml@main
    with:
      workdir: web

In fact, we can even inline every sub-project's CI into the same workflow:

# .github/workflows/ci.yml
name: CI/CD Pipeline
on: [pull_request, workflow_dispatch]
jobs:
  web_ci:
    uses: <org name/username>/<repo name>/.github/workflows/reusable-ci.yml@main
    with:
      workdir: web
  admin_ci:
    uses: <org name/username>/<repo name>/.github/workflows/reusable-ci.yml@main
    with:
      workdir: admin
  api_ci:
    uses: <org name/username>/<repo name>/.github/workflows/reusable-ci.yml@main
    with:
      workdir: api

Incredible, we've managed to greatly simplify our CI workflow by using reusable workflows!

monorepo
official documentation