github action을 배워보자 2일차 - 다양한 기능

0

github action

목록 보기
2/3

github action의 다양한 기능

checkout

기본적으로 github action은 docker container를 띄워 실행하기 때문에, 우리의 repo를 가져오지 않는다. 따라서, github action에서 우리의 code를 가져와서 특정 작업을 시켜주기 위해서는 repo를 github action에 다운로드 시켜주는 기능이 필요한데, 이 기능이 가능하도록 하는 action이 바로 checkout이다

github repository를 가져와 작업을 수행하는 것으로, github marketplace에 정의된 공식 action이다. 이전에 말했듯이 action을 사용하기 위해서는 job의 step 중에 run대신에 uses를 사용하면 된다.

  • .github/workflows/checkout.yaml
name: checkout
on: workflow_dispatch

jobs:
  no-checkout:
    runs-on: ubuntu-latest
    steps:
    - name: check file list
      run: cat README.md
  checkout:
    runs-on: ubuntu-latest
    steps:
    - name: user checkout action
      uses: actions/checkout@v4
    - name: check file list
      run: cat README.md

workflow_dispatch event이므로 workflow를 수동으로 실행시켜주어야 한다.

no-checkout의 경우는 README.md가 없기 때문에 에러를 반환하게 될 것이다. user checkout action의 경우에는 actions/checkout@v4이므로 repo에 모든 file들을 가져오게 된다. 우리의 경우 README.md file도 가져오게 되어 cat 실행시에 문제없이 구동된다.

echo "hell world" >> ./README.md
git add ./README.md
git commit -m "feat: update README.md"
git push origin main

이 다음 github에 들어가서 workflow를 실행시키면 첫번째 job인 no-checkout는 실패하지만 checkout은 성공하는 것을 볼 수 있다.

context

context는 workflow를 실행하는 환경에 대한 정보를 말한다.

context는 다양한 정보들을 가지는데, github context는 repository이름, owner이름, branch, tag 이름 등도 확인할 수 있다.

github context를 사용해서 dev branch에 push event가 발생하면 workflow에서 1번 job을 실행하도록 할 수 있고, master branch에서 push event가 발생하면 workflow에서 2번 job을 실행하도록 수 있다.

단, 오해하지 말것이 context"context"라는 이름 자체를 가진 객체가 아니다. 아래의 링크를 참고하면 context 객체들의 구체적인 타입들이 있다.

https://docs.github.com/ko/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs

대표적인 context들은 다음과 같다.
1. github
2. end
3. vars
4. job
5. jobs

context를 사용하기 위해서는 ${{ <context> }} 방법으로 step에서 사용하면 된다. 만약 github context를 사용하고 싶다면 ${{ github }} 이렇게 사용한다.

  • .github/workflows/context.yaml
name: context
on: workflow_dispatch

jobs:
  context:
    runs-on: ubuntu-latest
    steps:
    - name: github context
      run: echo '${{ toJSON(github) }}'

context 객체 자체는 javascript object이기 때문에 json 문자열로 바꾸어주기 위해서는 toJSON을 사용해주어야 한다. 출력 결과로 쭉 나올 것이다.

{
  "token": "***",
  "job": "context",
  ...
   "action_ref": ""
}

이 중 하나를 가지고 실행해보도록 하자.

  • .github/workflows/context.yaml
name: context
on: workflow_dispatch

jobs:
  context:
    runs-on: ubuntu-latest
    steps:
    - name: github context
      run: echo '${{ toJSON(github) }}'
    - name: check github context
      run: |
        echo ${{ github.repository }}
        echo ${{ github.event_name }}

실행 결과를 보면 github.repositorygithub.event_time의 결과가 나올 것이다.

branch filter

event가 특정 조건에 부합할 때 실행하도록 한다. 이를 통해서 workflow 실행을 더욱 효과적으로 제어가 가능하다.

branch filter는 event아래에 branch, path, tag에 적용이 가능한데 filter를 통해 특정 branch, 특정 path, 특정 tag에서 실행가능하도록 할 수 있다.

가령, 특정 branch에서 실행하는 경우인데, dev, master branch 중에서 master branch로 push해야만 실행하도록 할 수 있다.

  • .github/workflows/branch-filter.yaml
name: branch-filter
on:
  push:
    branches: ["dev"]

jobs:
  branch-filter:
    runs-on: ubuntu-latest
    steps:
    - name: echo
      run: echo hello

위는 push event에 대해서 dev branch일 때만 실행하도록 만든 것이다.

git push origin main으로 해놓으면 실행이 안되는 것을 볼 수 있다.

git push origin dev로 해보도록 하자.

git switch -c dev
git add .
git commit -m "add branch_filter"
git push origin dev

dev branch에서는 branch-filter worflow가 동작하는 것을 볼 수 있다.

path filter

특정 directory path에서 변화가 감지되어야 실행되도록 한다. 가령 my-app이라는 directory 내에 file이 update될 때만 실행된다.

먼저 새로운 directory와 file을 만들어보도록 하자.

mkdir -p foo/poo/
echo "hello" >> ./foo/poo/hello.txt
echo "SECRET_KEY=HELLO" >> ./foo/poo/.env

다음으로 foo/poo/ path에 변화가 생기면 event를 실행하도록 하는 path filter를 만들도록 하자.

  • .github/workflows/path-filter.yaml
name: path-filter
on:
  push:
    paths:
    - 'foo/poo/*'
    - '!foo/poo/.env'

jobs:
  path-filter:
    runs-on: ubuntu-latest
    steps:
    - name: echo hello
      run: echo hello

event아래에 path를 나열하면 된다. foo/poo/*는 해당 path에 대한 모든 file들의 변화가 있을 경우 실행한다는 의미이고, !foo/poo/.env.env의 변화에 대해서는 감지하지 말라는 것이다.

push하면 foo/poo/에 파일이 생겼으므로 실행된다.

다음으로 .env 파일을 수정하고 넣어보자.

echo "SECRET_KEY=WORLD" >> ./foo/poo/.env

path-filter workflow가 실행되지 않을 것이다.

tag filter

특정 tag 패턴일 때만 event를 실행한다. 가령 v1.0.0으로 태깅해야 실행하도록 할 수 있다. 단 tag filter는 push event에서만 사용이 가능하다.

  • .github/workflows/tag-filter.yaml
name: tag-filter
on:
  push:
    tags:
    - 'v[0-9]+.[0-9]+.[0-9]+' # v1.0.0 or v2.2.2, v1.0이나 1.0.0은 filter에 안걸린다.

jobs:
  tag-filter:
    runs-on: ubuntu-latest
    steps:
    - name: echo
      run: echo hello

git push로 github에 넣어둔 다음에 github action을 확인하면 별다르게 실행된 것이 없다. git tag를 통해서 tag를 만들어보도록 하자.

git tag v0.0.0
git push origin v0.0.0

workflow가 생성되어 실행되는 것을 볼 수 있다.

이번에는 pattern에 맞지 않는 tag를 만들어보도록 하자.

git tag 0.0.0
git push origin 0.0.0

github action에 가보면 workflow가 생성되어 있지 않는 것을 볼 수 있다.

timeout

특정 시간 이상 실행되면 자동으로 workflow를 중단되도록 설정하는 기능이다. 이를 통해 특정 job, 특정 step이 무한 루프에 걸리지 않도록 하여, 불필요한 자원 소모를 방지한다. default로 360분이다.

timeout이 사용할 수 있는 곳은 다음이다.
1. job-level: 이 job이 timeout 내에 실행되어야 한다.
2. step-level: 각 step마다 timeout을 두어 timeout 내에 실행되어야 한다.

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 3 # 3분 job-level 정의
    steps:
    - name: loop
      run: echo hello
      timeout-minutes: 1 # 1분 step-level
    - name: echo
      run: echo "hello world"

위와 같이 job-level, step-level의 timeout 설정이 가능하다.

이제 timeout에 대한 workflow를 하나 만들어보도록 하자.

  • .github/workflows/timeout.yaml
name: timeout
on: push

jobs:
  timeout:
    runs-on: ubuntu-latest
    timeout-minutes: 2
    steps:
    - name: loop
      run: |
        count=0
        while true; do
          echo "seconds: $count"
          count=$((count+1))
          sleep 1
        done
    - name: echo
      run: echo hello

위 timeout workflow는 첫번째 step인 loop에서 무한 반복되기 때문에, 두번째 step인 echo가 동작하지 않는다. 그래서 2분 뒤에 job-level timeout인 timeout-minutes 2분 후에 종료된다.

  • .github/workflows/timeout.yaml
name: timeout
on: push

jobs:
  timeout:
    runs-on: ubuntu-latest
    timeout-minutes: 2
    steps:
    - name: loop
      run: |
        count=0
        while true; do
          echo "seconds: $count"
          count=$((count+1))
          sleep 1
        done
      timeout-minutes: 1
    - name: echo
      run: echo hello

다음으로 loop에 대해서 timeout-minutes 1분을 주어 결과를 확인해보도록 하자.

1분 후로 넘어가면 loop step이 timeout되어 workflow가 종료된다.

cache

자주 사용되는 데이터를 빠르게 불러올 수 있도록 저장하는 action이다. 주로 의존성 설치 시간 단축에 사용되며, github marketplace에 정의된 공식 action이다.

test를 위해서 react app을 설치하도록 하자.

npx create-react-app my-app
cd my-app
npm start

npm start 명령어까지 치면 라이브러리를 모두 설치한 것이다.

다음으로 cache workflow를 만들어보도록 하자.

  • .github/workflows/cache.yaml
name: cache
on:
  push:
    paths:
    - 'my-app/**' # my-app아래의 모든 파일을 포함

jobs:
  cache:
    runs-on: ubuntu-latest
    steps:
    - name: checkout
      uses: actions/checkout@v4
    - name: setup-node
      uses: actions/setup-node@v3
      with:
        node-version: 18
    - name: Cache Node.js modules
      uses: actions/cache@v3
      with:
        path: ~/.npm
        key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
        restore-keys: |
          ${{ runner.os }}-node-
    - name: Install dependencies
      run: |
        cd my-app
        npm ci
    - name: npm build
      run: |
        cd my-app
        npm run build
  1. checkout: checkout step에서 checkout@v4 action을 사용하여 repo를 가져온다.
  2. setup-node: nodejs를 설정해준다.
  3. cache: cache action으로 with으로 input을 받을 수 있다.
  • key: runner.os로 os image 이름을 가져오고, hashFiles를 통해서 package-lock.json`을 해싱한다.
  • restore-keys: 정확히 일치하는 key값이 없을 때, 이전에 저장했던 key를 사용한다.
  1. Install dependencies: my-app에 npm ci를 실행
  2. npm build: build를 실행한다.

맨 처음에 git push를 해보면 cache를 할 것이 없어서 Cache not found가 발생하지만, 두 번쨰부터는 caching 제대로 동작하는 것을 볼 수 있다.

Artifact

workflow 실행 중 생성된 file 또는 file 모음이다. 이는 workflow가 끝나고 나온 결과물을 사용자가 얻기 위해서 artifact를 이용하는 것이다. 또한, 동일한 workflow 내에서 job 사이에 데이터 공유가 가능하고, workflow 종료된 이후에도 데이터를 유지한다. (90일)

github merketplace 공식 액션으로 다음이 있다.
1. upload-artifact
2. download-artifact

upload-artifact로 저장했다가, download-artifact로 다른 job에서 다운받으면 된다.

  • .github/workflows/artifact.yaml
name: artifact
on: push

jobs:
  upload-artifact:
    runs-on: ubuntu-latest
    steps:
    - name: echo
      run: echo hello-world > hello.txt
    - name: upload artifact
      uses: actions/upload-artifact@v3
      with:
        name: artifact-test
        path: ./hello.txt
  download-artifact:
    runs-on: ubuntu-latest
    needs: [upload-artifact]
    steps:
    - name: download artifact
      uses: actions/download-artifact@v3
      with:
        name: artifact-test
        path: ./
    - name: check
      run: cat hello.txt

upload-artifact job의 echo step으로 hello.txt를 만들고 upload artifact step으로 hello.txt file을 업로드한다. download-artifact job의 download artifact step에서 download-artifact를 사용하여 다른 job에 있는 hello.txt를 받을 수 있다. 단, 이들은 파일 생성 시점에 대한 의존 관계가 있으므로 needsupload-artifact를 먼저 실행하도록 해야한다.

github action의 결과로 artifacts가 나오며, 해당 file을 저장할 수 있다.

output

한 job에서 생성된 데이터를 동일한 job의 step 또는 다른 job에 데이터를 쉽게 공유할 수 있다. 즉, 여러 step과 job간에 데이터를 손쉽게 전달이 가능하다. 이는 artifact처럼 결과 산출물을 얻기 위함이 아니라, job과 step 사이에 데이터를 공유하기 위함이다.

artifact는 또한, 파일 또는 파일 모음으로 데이터 공유가 가능한데, output은 단순 값 형태로 데이터를 저장하며 key-value 형식을 사용한다. 사용 방법도 간단한데, 다음과 같이 사용하면 된다.

  • 저장
echo "{key}={value}" >> $GITHUB_OUTPUT

가령 keyname이고 valuegithub-actions라면 다음과 같다.

echo name=github-actions >> $GITHUB_OUTPUT

output을 사용하려면 output이 정의된 고유한 ID를 사용해야한다.

name: output
on: push

jobs:
  create_output:
    runs-on: ubuntu-latest
    steps:
    - name: echo output
      id: check-output
      run: echo "test=hello" >> "$GITHUB_OUTPUT"
    - name: check output
      run: |
        echo ${{ steps.check-output.outputs.test }}

echo output step의 test output을 사용하기 위해서는 id를 사용해야한다. 형식은 다음과 같다.

${{ steps.{step_id}.outputs.{key} }}

위는 같은 job에서 output을 사용하는 방법이었고, 다른 job에서 output을 사용하려면 needsjob-level outputs을 사용해야한다.

name: output
on: push

jobs:
  create-output:
    runs-on: ubuntu-latest
    outputs: # job level output
      test: ${{ steps.check-output.outputs.test }}
    steps:
    - name: echo output
      id: check-output
      run: | # step level output
        echo "test=hello" >> "$GITHUB_OUTPUT"
    - name: check output
      run: |
        echo ${{ steps.check-output.outputs.test }}
  get-output:
    needs: [create-output]
    runs-on: ubuntu-latest
    steps:
    - name: get output
      run: |
        echo ${{ needs.create-output.outputs.test }}

step인 echo output에서 만든 output을 job인 create-output에서 가져와 get-output에 건내줄 수 있다. 이때 종속 관계가 있어야하는데, get-outputcreate_output에 의존하고 있는 것이다.

이때 다른 job에서 해당 output을 사용하려면 다음과 같은 형식을 따라야한다.

${{ needs.{job-name}.outputs.{outputs-key} }}

단, outputs-key는 job-level의 outputs 하위에 있어야 하는 key이다.

이제 github에 push하고 결과를 확인해보도록 하자.

0개의 댓글