Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

GitHub Actions의 workflow_call로 워크플로우 재사용하기

GitHub Actions에는 워크플로우를 재사용할 수 있는 워크플로우가 있다.

물론 어떤 워크플로우의 동작을 여러 저장소에서 쓰기를 원할 때 가장 쉽게 떠올릴 수 있는 방법은 액션을 직접 만드는 방법이다. Docker 컨테이너나 JavaScript 액션을 사용할 수 있어서 원하는 동작을 마음대로 만들 수 있다.

액션을 직접 만드는 게 가장 강력하겠지만 종종 비슷한 액션을 여러 저장소(보통 팀에서 하는 프로젝트의 환경은 어느 정도 비슷하니까)에서 쓰게 되는 경우가 많이 생긴다. 아주 간단하게는 도커 로그인/빌드/푸시하는 워크플로우라거나 배포하는 과정 등은 모든 프로젝트에서 반복적으로 사용하게 되는 경우가 많다.

용도에 따라 다르겠지만 starter 워크플로우를 사용하면 각 프로젝트에서 워크플로우를 처음 사용할 때 기본 템플릿으로 사용할 수 있다. 생성 시의 템플릿이므로 이후부터는 각 프로젝트에서 관리해야 하고 뭔가 바꿔야 한다면 사용한 곳을 모두 찾아서 변경해 주어야 한다. 직접 액션을 만들 정도로 복잡하진 않은데 반복되는 워크플로우를 한곳에서 관리하고 싶다면 재사용할 수 있는 워크플로우가 적절할 수 있다. 비슷한 용도로 사용할 수 있는 기능이 많아서 재사용할 수 있는 워크플로우의 제약과 동작 방식을 알아야 써먹을 수 있을 것 같아 테스트를 해봤다.

재사용할 수 있는 워크플로우 생성하기

사용 방법을 알기 위해 일단 재사용할 수 있는 워크플로우를 만들어 보자. 이 예제는 GitHub의 공식 문서에서 많이 참고해 왔다. 재사용 되는 워크플로우 즉 호출되는 워크플로우를 Called 워크플로우라고 부른다.

name: Reusable Workflow

on:
  push:
  workflow_call:
    inputs:
      username:
        required: true
        type: string
    secrets:
      SECRET_SEED:
        required: true

jobs:
  reusable_workflow_job:
    runs-on: ubuntu-latest
    steps:
      - run: | 
          echo ${{ inputs.username }}
          echo ${{ secrets.SECRET_SEED }}
          echo ${{ secrets.SECRET_SEED }} | sed 's/./& /g' 

워크플로우를 다른 곳에서도 호출할 수 있게 하려면(워크플로우로 재사용할 수 있게 만들려면) workflow_call를 지정해야 한다. workflow_call가 없으면 다른 워크플로우에서 호출할 수가 없다.

재사용할 수 있는 워크플로우를 공통으로 관리할 한 저장소에 모아두는 것도 가능하지만 한 저장소에서 이미 사용하고 있는 워크플로우를 다른 곳에서 호출해서 쓰는 것도 가능하므로 워크플로우가 호출되는 이벤트를 지정하는 on:push는 그대로 둔 채 workflow_call를 추가로 지정했다.

workflow_call에서는 호출할 때 값을 전달할 수 있도록 inputssecrets를 지정할 수 있다. 이는 그 아래에서 사용한 것처럼 inputs.usernamesecrets.SECRET_SEED로 전달받은 값을 참조할 수 있다. 마지막 echo ${{ secrets.SECRET_SEED }} | sed 's/./& /g' 부분은 워크플로우 실행 로그에서 시크릿은 ***로 출력되기 때문에 이후 의도대로 값이 달라지는지 테스트하기 위해 시크릿값이 출력되도록 GitHub의 검사를 우회한 것이다.(당연히 여기서는 테스트라서 그렇고 실제로 시크릿을 출력하는 건 아주 위험하다.)

액션 시크릿에 SECRET_SEED를 저장

이 저장소의 시크릿에 SECRET_SEED를 저장해두고 워크플로우를 푸시해서 실행해보자. push 이벤트가 있으므로 코드 푸시만 해도 이 저장소가 실행되고 이는 workflow_call로 실행된 것이 아니라 그냥 실행된 것이다.

워크플로우 실행 결과

inputs는 당연히 전달하지 않으므로 아무것도 출력되지 않고 시크릿으로 저장했던 hello가 출력된 것을 볼 수 있다. 그냥 출력했을 때는 ***로 출력된 거고 마스킹 처리를 우회하기 위해 각 글자 사이에 공백이 들어가서 h e l l o가 출력되었다.

재사용할 수 있는 워크플로우 호출하기

같은 저장소에서 이 워크플로우를 사용해보자. 호출하는 쪽을 Caller 워크플로우라고 부른다.

name: Caller Workflow

on:
  push:

jobs:
  call-workflow-1-in-local-repo:
    uses: outsideris/actions-test/.github/workflows/called.yaml@36f0d0aef48da832eb03a4d1d82e1705392bbfdb
    with:
      username: outsider
    secrets:
      SECRET_SEED: world
  call-workflow-2-in-local-repo:
    uses: ./.github/workflows/called.yaml
    with:
      username: mona
    secrets:
      SECRET_SEED: seed

Caller에서는 uses 키워드를 이용해서 지정하는데 위처럼 jobs.<job_id>.uses 수준에서만 사용할 수 있다. 일반적인 워크플로우처럼 잡 안에 있는 steps에서는 사용할 수 없다. 재사용한 워크플로우의 결과를 사용해야 할 때는 outputs를 이용해서 주고 받아야 한다.

uses 키워드에서는 다음 두가지 문법으로 워크플로우를 지정한다.

  • {owner}/{repo}/{path}/{filename}@{ref} : public/internal 저장소의 재사용할 수 있는 워크플로우
  • ./{path}/{filename} : 같은 저장소의 재사용할 수 있는 워크플로우

위 예시에서는 데모용이라 두가지 방법을 다 사용했다. 위에서 {ref}는 Git 커밋 SHA, 브랜치명, 태그명이 될 수 있는데 SHA는 전체 SHA를 지정해야만 한다. private 저장소의 경우에는 같은 저장소 내에서만 재사용할 수 있는 워크플로우를 호출할 수 있다. internal 저장소는 GitHub Enterprise를 사용할 때 org 밑에서만 public처럼 쓸 수 있는 내부 저장소를 의미한다.

첫번째 잡의 실행결과이다.

워크플로우 실행 결과

두번째 잡의 실행결과이다.

워크플로우 실행 결과

같은 저장소에 이미 SECRET_SEED이 시크릿으로 저장되어 있음에도 push 때와는 달리 전달한 입력값과 시크릿이 출력된 것을 확인할 수 있다.

이번엔 다른 저장소에서 호출을 해보자. 다른 저장소에서는 {owner}/{repo}/{path}/{filename}@{ref} 패턴만 써야 한다는 것을 제외하고는 위와 동일하다. 재사용할 수 있는 워크플로우에 접근만 할 수 있다면 앞에서 본 것과 다르진 않지만 그래도 해보자.

아래 파일을 다른 저장소에 워크플로우로 정의해 보자.

name: Reusable Caller Workflow

on:
  push:

jobs:
  call-workflow-in-another-repo:
    uses: outsideris/actions-test/.github/workflows/called.yaml@4189beb6cbbebde39af9a0a4f3f2695d165973cf
    with:
      username: testuser
    secrets:
      SECRET_SEED: REMOTE_SEED

이를 실행하면 아까와 동일하게 워크플로우를 재사용할 수 있는 것을 볼 수 있다.

워크플로우 실행 결과


제약사항

재사용할 수 있는 워크플로우에는 몇 가지 제약사항이 있다.

  • 재사용된 워크플로우는 재사용할 수 있는 워크플로우를 호출할 수 없다.
  • 앞에서 얘기한 대로 private 저장소의 재사용할 수 있는 워크플로우는 같은 저장소에서만 사용할 수 있다.
  • caller 워크플로우에 env 컨텍스트로 정의된 환경변수는 called 워크플로우에 전달되지 않는다.
  • 재사용할 수 있는 워크플로우를 호출하는 잡에서 strategy 프로퍼티는 지원되지 않는다.
2022/04/18 00:59 2022/04/18 00:59