Github Actions 워크플로우 재사용하기

개발

Github Actions 워크플로우 재사용하기

프로젝트를 진행하며 Github Actions를 사용하다 보면 비슷비슷한 형태의 workflow를 반복적으로 사용하는 경우가 많습니다.
Node.js 버전이 변경되거나, 더 나은 방법을 찾았을 때 등 상황에 repository를 돌아다니며 workflow를 수정하는 건 상당히 귀찮은 일입니다.

이때 재사용할 수 있는 workflow를 만들어 한 군데서 workflow를 관리할 수 있습니다.
예시로, 저도 tag / release 생성, pnpm 환경 구성 등 workflow들을 actions라는 한 repository에서 관리하는 중입니다.

workflow_call

Workflow 생성

name: Reusable Workflow
 
on:
    workflow_call:

onworkflow_call을 추가하면, 해당 workflow는 다른 workflow에서 호출할 수 있습니다.

등의 제약이 있지만, secret도 사용할 수 있고, 기존에 사용하시던 workflow에 workflow_call만 추가하면 바로 사용해볼 수 있습니다.

name: Reusable workflow
 
on:
    workflow_call:
        inputs:
            message:
                description: "A message to include in the greeting"
                type: string
                required: true
        secrets:
            url:
                description: "The URL to post the message to"
                required: true
 
jobs:
    run:
        name: Send greeting message
        runs-on: ubuntu-latest
        steps:
            - name: Send message
              shell: bash
              run: |
                  echo "${{ inputs.message }}" > message.txt
                  curl -X POST -d @message.txt ${{ secrets.url }}

위와 같이 input 뿐 아니라, secrets도 추가하실 수 있습니다.
yaml 파일에서 확인하실 수 있듯, 일반적인 workflow를 작성하는 것과 거의 동일합니다.

Workflow 호출

jobs:
    call-reusable-workflow:
        name: Call reusable workflow
        runs-on: ubuntu-latest
        steps:
            - name: Call reusable workflow
              uses: ./.github/workflows/reusable-workflow.yml
              with:
                  message: "Hello, world!"
              secrets:
                  url: ${{ secrets.URL }}

uses에 호출할 workflow의 경로를 입력하면 해당 workflow를 호출할 수 있습니다.
다른 repository에서 호출하실 때는 owner/repo/path/to/workflow.yml@ref 형태로 입력하시면 됩니다.

failed to run job

일례로, 저는 제가 관리하는 repository들에 CI 파이프라인을 추가해두고, 작업이 실패할 때마다 Discord로 알림이 오게 설정해두었습니다.
git push하고 얼마 지나지 않아 discord 알림이 오는 소리가 나면 식은땀이 조금 나면서 심장 박동이 빨라진다는 것만 제외하면, 정말 유용한 기능입니다.

repository_dispatch

Workflow 생성

repository_dispatch를 사용하면, 외부에서도 workflow를 호출할 수 있습니다.

name: Reusable Workflow
 
on:
    workflow_dispatch:
    repository_dispatch:
        types: [my-event-type]
 
jobs:
    echo:
        if: ${{ !github.event.client_payload.passed }}
        runs-on: ubuntu-latest
        steps:
            - env:
                  MESSAGE: ${{ github.event.client_payload.message }}
              run: echo "$MESSAGE"

기본적인 사용법은 repository_dispatch를 추가하고, types에 이벤트의 타입을 추가하면 됩니다.

입력값을 전달하는 부분에 차이가 조금 있는데, repository_dispatch에서는 client_payload 파라미터를 통해 데이터를 전달할 수 있습니다.

Workflow 호출

trigger:
    runs-on: ubuntu-latest
    steps:
        - run: |
              gh api /repos/$OWNER/$REPO/dispatches \
              --method POST \
              --field event_type=my-event-type \
              --field client_payload[passed]=false \
              --field client_payload[message]="Testing workflow"
          env:
              GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

예제로는 github api를 사용해 workflow에서 호출했지만, 외부에서 rest api 등을 사용해서도 호출할 수 있습니다(api 문서 참고).

Composite Action

workflow를 만들다 보면, 알림 발송처럼 한 단계만을 위한 job도 많지만, pnpm 환경 구성처럼 여러 단계 중 일부가 반복되는 경우도 많습니다.
하지만 workflow_call은 상술한 것처럼 steps 중간에 사용할 수 없습니다.

이때 사용할 수 있는 것이 Composite Action입니다.

Action 생성

.
├── .github
│   └── workflows
│       └── my-workflow.yml
└── my-action
    └── action.yml

기존에 .github/workflows에 workflow를 작성하던 것과는 다르게, 폴더 내에 action.yml을 추가하셔야 합니다.

name: My composite action
description: My example composite action
 
inputs:
  name:
    description: User name to greet
    required: false
    default: "Anonymous"
 
outputs:
  message:
    description: Greeting message
    value: ${{ steps.greeting.outputs.message }}
 
runs:
  using: composite
  steps:
    - name: Display greeting
      id: greeting
      shell: bash
      run: |
        message="Hello, ${{ inputs.name }}"
        echo "message=$message" >> $GITHUB_OUTPUT
    - run: echo "This script only runs if the condition is true"
      shell: bash
      if: ${{ inputs.name == 'Marshall' }}

작성 방식에 이젠 차이가 조금 생겼습니다.

다행히 조건문을 사용할 수 있긴 하지만, 하나의 job만 실행할 수 있기에 둘 이상의 job에 dependency가 있는 등 복잡한 workflow를 온전한 형태로 가져오기는 어렵습니다.

Action 호출

greeting:
  runs-on: ubuntu-latest
  steps:
    - uses: owner/repo/greeting@ref
      id: greeting
      with:
        name: "Marshall"
    - run: echo ${{ steps.greeting.outputs.message }}

다른 일반적인 action과 마찬가지로, uses에 호출할 action의 경로를 입력하면 됩니다.
workflow_call과는 다르게 step 중간에 사용할 수 있어, 훨씬 유연하게 사용할 수 있습니다.

맺으며

개인적으로 새 프로젝트를 시작할 때마다 기존에 사용하던 bash script와 workflow들 복사하고, 업데이트할 일 생기면 repository 돌아다니며 수정하는 게 상당히 귀찮았는데, 위 방법들을 사용하고부터 이런 잡무가 많이 줄었습니다.
refmaster로 고정해두고 관리하고 있어 그리 안전하진 않을 수 있는데, 실수할 사람이 당장에는 저밖에 없으니 관리하는 공수를 줄이는 것만 생각했습니다.

외부에서 호출할 일은 사실 그리 많지 않아 repository_dispatch는 한 프로젝트에밖에 사용해보지 않았고, secrets가 필요하면 reusable workflow를, 아니면 composite action을 사용하는 방식으로 구분해서 사용하고 있습니다.

불필요하게 신경 쓸 것들을 줄이고 가벼운 마음으로 프로젝트를 진행하시는 데 도움이 되셨길 바라며, 이상으로 글을 마칩니다.

Report an issue