reusable workflows

composite actions

reusable workflows vs composite actions

reusable workflows

어떤 클래스를 정의한 뒤 다른 클래스에서 이 클래스를 포함하여 코드 중복을 방지하면서 유지보수성을 높이듯이, 워크플로우 역시 다른 워크플로우를 포함시켜 자신의 워크플로우의 일부로 재사용할 수 있다

이 때 다른 워크플로우를 호출하는 워크플로우를 caller workflow라고 하고 호출된 워크플로우를 called 워크플로우라고 한다

caller 워크플로우에 의해 트리거된 called 워크플로우의 github context는 항상 caller 워크플로우와 관련되어 있으며 수행하는 어느 action이든 caller workflow의 일부분으로 수행된다

e.g) 다른 리포지토리에 존재하는 called 워크플로우가 actions/checkout을 수행하는 경우 호스트한 caller 워크플로우의 리포지토리 컨텐츠를 체크아웃한다

example for reusable workflows

reusable workflow 파일은 다른 workflow와 유사하게 yaml 형식으로 정의하며 .github/workflows 디렉토리에 위치한다 (하위 디렉토리 X)

reusable workflow example

on.workflow_call 이벤트를 지정하여 reusable workflow임을 명시한다

하위에 inputs와 secrets 키워드를 선언하여 caller workflow에서 정의한 입력값과 비밀변수를 사용할 수 있다 (또는 caller workflow에서 secrets: inherit을 사용하여 암묵적으로 secrets 사용)

name: reusable workflow example

on:
  workflow_call:
    # inputs와 secrets 키워드를 통해 caller workflow에서 정의한 입력값과 비밀변수를 resuable workflow 내에서 사용할 수 있다
    # 또는 caller workflow 내에서 secrets: inherit을 설정하면 아래와 같이 명시적으로 secrets를 선언하지 않아도 사용할 수 있다
    inputs:
      config-path:
        required: true
        type: string
    secrets:
      personal_access_token:
        required: true

jobs:
  reusable-workflow-job:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/labeler@v4
        with:
          repo-token: $
          configuration-path: $

caller workflow example

name: call a reusable workflow

on:
  push:
    branches:
      example-reusable-workflow

# reusable workflow를 참조하는 두 가지 방법(여러 workflow 파일을 참조하는 경우 각각 사용 가능)

# 1. public 또는 private 리포지토리의 reusable workflow 참조
# {owner}/{repo}/.github/workflows/{filename}@{ref}
# {ref}: SHA, release 태그 또는 브랜치 이름

# 2. 같은 리포지토리의 reusable workflow 참조
# ./.github/workflows/{filename}

jobs:
  call-workflow-passing-data:
    permissions:
      contents: read
    uses: ./.github/workflows/reusable-workflow.yml
    with:
      config-path: .github/labeler.yml
    secrets:
      token: $

matrix strategy

matrix 전략을 사용하면 단일 reusable workflow을 정의한 변수만큼 여러 개의 job으로 실행할 수 있다

name: using the matrix strategy call a reusable workflow

on:
  push:
    branches:
      - example-reusable-workflow

jobs:
  reusable-matrix-job-for-development:
    strategy:
      matrix:
        target: [dev, stage, prod]
    uses: ./.github/workflows/reusable-workflow.yml
    with:
      target: $

nesting reusable workflows

github actions는 최대 4개의 중첩된 워크플로우를 호출할 수 있다

top-level workflow -> nested workflow 1 -> nested workflow 2 -> nested workflow 3

중첩된 workflow는 직접적으로 호출한 workflow로부터만 secrets를 받을 수 있다

만약 top-level workflow에서 secrets: inherit으로 모든 비밀 변수를 전달했으나 nested workflow 1에서 nested workflow 2에게 특정 secrets만 전달하면 nested workflow 2는 해당 secrets에만 접근할 수 있다

중첩된 workflow에서 top-level workflow(initial caller workflow)를 접근할 수 없다면 해당 워크플로우는 실패한다

또한 GITHUB_TOKEN의 권한은 같거나 더 제한될 수 있다


on:
  workflow_call:

jobs:
  # reusable workflow에서 다른 reusable workflow 호출
  call-another-reusable:
    uses: ./.github/workflows/another-workflow.yml
    
    # 중첩된 reusable workflow에게 secrets를 전달한다
    secrets:
      repo-token: $

using outputs from a reusable workflow

on.workflow_call 레벨에서 출력할 값을 outputs 키워드로 정의한다

해당 키워드는 정의한 job 레벨의 출력 값을 기반으로 워크플로우의 출력값을 정의하며, job 레벨의 출력 값은 자신의 steps에서 출력한 값을 기반으로 정의할 수 있다

name: reusable workflow example

on:
  workflow_call:
    # job 레벨에서 정의한 출력값을 reusable workflow의 출력값으로 정의한다
    outputs:
      firstword:
        description: "the first output string"
        value: $
      secondword:
        description: "the second output string"
        value: $

jobs:
  example-output-job:
    name: generate output
    runs-on: ubuntu-latest

    # steps의 출력값을 job 레벨의 출력값로 정의한다
    outputs:
      output1: $
      output2: $
    steps:
      - id: step1
        run: echo "firstword=hello" >> $GITHUB_OUTPUT
      - id: step2
        run: echo "secondword=world" >> $GITHUB_OUTPUT

아래와 같이 동일한 워크플로우에서 reusable workflow의 출력값을 다른 job에서 사용할 수 있다

jobs:
  call-workflow-passing-data:
    permissions:
      contents: read
    uses: ./.github/workflows/reusable-workflow.yml
    with:
      config-path: ./.github/labeler.yml
    secrets:
      token: $

  # reusable workflow에서 출력한 값을 같은 워크플로우의 job에서 사용할 수 있다
  print-outputs-from-a-job:
    runs-on: ubuntu-latest
    needs: call-workflow-passing-data
    steps:
      - run: echo $ $

limitations

reusable workflows를 최대 4번 중첩할 수 있고 최상단 워크플로우 파일에서 최대 20개의 reusable workflow 파일들을 포함할 수 있다

각각의 caller workflow와 called workflow에서 정의한 env 컨텍스트는 서로에게 전파되지 않는다 -> 대신 reusable workflow의 출력을 사용할 수 있다

여러 워크플로우 파일에서 변수를 재사용하려면 organization, repository 또는 environment 레벨에서 정의하고 vars 컨텍스트를 통해 접근할 수 있다

reusable workflow은 job 레벨에서 environment 키워드를 사용할 수 있으나 on.workflow_call 레벨에선 사용할 수 없기 때문에 caller workflow로부터 enviroment secrets를 전달받을 수 없다

composite actions

reusable workflows가 클래스라면, composite actions는 메서드라고 비유할 수 있다

composite actions는 여러 steps를 결합하여 하나의 action으로 정의하는 것을 말하여 어떤 워크플로우의 특정 job 내에서 하나의 step으로써 호출되어 정의한 step들을 수행한다

주로 하나 이상의 워크플로우에서 순차적인 step들이 중복적으로 수행할 필요가 있을 때 사용한다

방대한 yaml 워크플로우 파일을 잘게 나눠 리팩토링하여 각 워크플로우 파일의 중복된 코드를 줄일 수 있다

example for composite actions

아래와 같이 action.yml에 composite action을 정의한다

name: hello world
description: greet someone

# composite action의 입력값 정의
inputs:
  who-to-greet:
    description: who to greet
    required: true
    default: 'world'

# steps 출력값을 기반으로 composite action 출력값 정의
outputs:
  random-number:
    description: the random number
    value: $

runs:
  using: composite
  steps:
    - name: set greeting
      run: echo "hello  $INPUT_WHO_TO_GREET"
      shell: bash
      env:
        INPUT_WHO_TO_GREET: $

    - name: generate random number
      id: random-number-generator
      run: echo "random-number=$(echo $RANDOM)" >> $GITHUB_OUTPUT
      shell: bash

    # goodbye.sh 파일의 위치를 지정하기 전에 action의 경로를 runner 시스템 경로에 추가한다
    - name: set github path
      run: echo "$GITHUB_ACTION_PATH" >> $GITHUB_PATH
      shell: bash
      env:
        GITHUB_ACTION_PATH: $

    - name: run goodbye.sh
      run: goodbye.sh
      shell: bash

정의한 action을 다음과 같이 workflow에서 하나의 step으로 사용할 수 있다

on: [push]

jobs:
  hello-world-job:
    runs-on: ubuntu-latest
    name: a job to say hello

    steps:
      - uses: actions/checkout@v4
      - id: foo
        uses: ./.github/action/action.yml
        with:
          who-to-greet: 'hansanhha'
      - run: echo random-number "$RANDOM_NUMBER"
        shell: bash
        env:
          RANDOM_NUMBER: $

reusable workflows vs composite actions

reference github docs

workflow jobs

composite actions는 caller workflow 내에서 단일 step으로써 동작하는 step 리스트를 구성한다

다만 reusable workflow처럼 파일 내에서 job을 포함할 수 없다

logging

composite action이 실행되면 파일 내에 정의된 각각의 step들의 실행 로그 대신 caller workflow에서 하나의 step이 실행된 것으로만 로그를 출력한다

resuable workflow는 모든 job과 step이 각각 로그로 출력된다

specifying runners

composite action과 달리 reusable workflow의 경우 job을 실행할 runner를 선택할 수 있다

passing output to steps

composite action은 workflow job내의 실행되는 step으로 다른 step들과 함께 실행할 수 있다 (composite action 실행 이전/이후 모두)

따라서 composite action 내부에서 GITHUB_ENV 환경 변수를 설정("VAR=value" >> $GITHUB_ENV 등)하면 이후 step에서도 이걸 사용할 수 있다

reusable workflow는 job 내에서 실행되는 step이 아니라 개별 job 수준에서 호출되며, 호출한 이후 같은 job 내에 새로운 step을 추가할 수 없기 때문에 reusable workflow 내부에서 설정한 환경변수는 후속 step에서 사용할 수 없다

composite actions의 환경변수 재사용

jobs:
  example-job:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Run Composite Action
        
        # my-org/my-composite-action@v1 내에서 환경변수 설정
        uses: my-org/my-composite-action@v1

        # composite action에서 설정한 환경변수 사용
      - name: Use output from Composite Action
        run: echo "The output is $MY_OUTPUT"

reusable workflow의 환경변수 재사용 (불가능)

jobs:
  call-reusable-workflow:
    uses: my-org/my-reusable-workflow/.github/workflows/reusable.yml@v1

  another-job:
    runs-on: ubuntu-latest
    needs: call-reusable-workflow
    steps:
      - name: Try to use output

        # reusable workflow 호출 이후 step을 추가할 수 없기 때문에 내부적으로 정의한 환경변수를 사용할 수 없다
        run: echo "The output is $MY_OUTPUT"

reusable workflows와 composite actions 주요 차이점

reusable workflows composite actions
standard workflow 파일과 유사한 yaml file workflow steps를 포함한 action
리포지토리의 .github/workflows 디렉토리에 위치한 단일 파일(하위 디렉토리 허용 X) action.yml 파일을 포함한 분리된 리포지토리 또는 디렉토리
특정 yaml 파일을 참조하여 호출됨 action이 정의된 특정 리포지토리 또는 디렉토리를 참조하여 호출됨
job 수준에서 호출됨 job의 step 수준에서 호출됨
파일 내에 여러 job을 포함할 수 있음 job을 포함할 수 없음
각 step의 실행이 모두 로그에 출력됨 하나의 step 실행으로만 로그에 출력됨
최대 4단계 중첩된 워크플로우 호출 하나의 워크플로우에 최대 10개의 composite action 중첩 가능
secrets 사용 가능 secrets 사용 불가능