Search code examples
githubcontinuous-integrationgithub-actionsdevops

Avoid action/cache setup repetition in GitHub Action


I've got the following GitHub ci.yml workflow where I have created separate jobs to handle the pipeline for my project:

enter image description here

This is the YAML file

name: CI
on:
  pull_request:
    branches:
      - develop
  push:
    branches:
      - develop

jobs:
  valid-title:
    name: Validate Title
    runs-on: ubuntu-latest
    steps:
      - uses: amannn/action-semantic-pull-request@v5
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  setup:
    name: Setup
    needs: [valid-title]
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16]
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: "npm"

      - uses: actions/cache@v3
        id: npm-cache
        with:
          path: "**/node_modules"
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

      - name: Install Dependencies
        if: steps.npm-cache.outputs.cache-hit != 'true'
        run: npm ci

  lint:
    name: Static Analysis
    needs: [setup]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: "npm"

      - uses: actions/cache@v3
        id: npm-cache
        with:
          path: ./node_modules
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

      - name: Install Dependencies
        if: steps.npm-cache.outputs.cache-hit != 'true'
        run: npm ci

      - name: Static analysis
        run: npm run lint

  test:
    name: Unit Testing
    needs: [setup]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: "npm"

      - uses: actions/cache@v3
        id: npm-cache
        with:
          path: ./node_modules
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

      - name: Install Dependencies
        if: steps.npm-cache.outputs.cache-hit != 'true'
        run: npm ci
      - name: Unit Tests
        run: npm run test:unit

  build:
    name: Build
    needs: [setup]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: "npm"

      - uses: actions/cache@v3
        id: npm-cache
        with:
          path: ./node_modules
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

      - name: Install Dependencies
        if: steps.npm-cache.outputs.cache-hit != 'true'
        run: npm ci
      - name: Build Library
        run: npm run build

  integration:
    name: Integration Tests
    if: github.event.pull_request.draft == false
    needs: [lint, test, build]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: "npm"

      - uses: actions/cache@v3
        id: npm-cache
        with:
          path: ./node_modules
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

      - name: Install Dependencies
        if: steps.npm-cache.outputs.cache-hit != 'true'
        run: npm ci

      - name: Integration Tests
        run: echo "placeholder for integration run"
        env:
          PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


As you can see, for each job I am forced to copy and paste the following setup

- uses: actions/checkout@v3

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: "npm"

      - uses: actions/cache@v3
        id: npm-cache
        with:
          path: ./node_modules
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

      - name: Install Dependencies
        if: steps.npm-cache.outputs.cache-hit != 'true'
        run: npm ci

How can I extract it and reuse it to avoid this duplication/copy-pasting?

I don't want to setup an external repo just to create a composite, but I could not leverage Reusable Workflow as part of existing steps of a job.

So I am kinda lost/


Solution

  • You can create "local" composite actions in the same repository, as the documentation specifies:

    If you're building an action that you don't plan to make available to others, you can store the action's files in any location in your repository. If you plan to combine action, workflow, and application code in a single repository, we recommend storing actions in the .github directory. For example, .github/actions/action-a and .github/actions/action-b.

    So, you'd have a local path in your repository as

    .github
    ├── actions
    │   └── setup-node
    │       └── action.yaml
    └── workflows
        └── pr.yaml
    

    Then in your workflow just refer to it with:

    jobs:
      foo:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v3
          - name: Setup Node.js
            uses: ./.github/actions/setup-node
    

    Note that you should use actions/checkout@v3 before you use the local composite action