# [ 살펴보기 ] Github Action - Jobs, Steps

workflow는 특정 작업을 수행하는 여러 개의 jobs으로 구성되고 각 job은 해당 job을 수행하기 위해 필요한 각각의 steps으로 구성된다. workflow를 이루는 여러 개의 jobs은 각각 병렬적으로 처리되며 만약 특정 job이 다른 job이 끝나고 실행되어야 한다면 needs keyword를 통해 job 사이의 의존 관계를 명시해줄 수 있다.

## Runner

Runner란 workflow에 선언한 각각의 job이 실행될 환경을 말한다. github에서 제공하는 runner를 사용할 수도 있고 또는 자체적으로 hosting 하는 runner를 사용할 수도 있다. Job을 실행하는 runner의 설정은 `runs-on` keyword를 통해 설정한다.

```yaml
name: Test Workflow

on:
  push:
    branches:
      - master

jobs:
  information_commit_id:
    runs-on: ubuntu-latest
    steps:
      ...
```

Github에서 제공하는 runner는 linux, widnows, macOS가 있으며 Github에서 제공하는 runner를 사용하는 job은 같은 종류의 runner를 사용하더라도 실행될 때 각각 새로운 runner instance에서 실행된다.

Github에서 제공하는 runner의 종류에 대한 자세한 사항은 documentation을 ( Reference - [Choosing GitHub-hosted runners](https://docs.github.com/en/actions/writing-workflows/choosing-where-your-workflow-runs/choosing-the-runner-for-a-job#choosing-github-hosted-runners) ) 통해 확인할 수 있으며 public repository, private repository에서 실행할 수 있는 사용량이 다르므로 주의하자.

## Job

다음 예제를 통해 workflow에 job과 step을 어떻게 추가하는지 살펴보자.

```yaml
name: Test Workflow

on:
  push:
    branches:
      - master

jobs:
  information_commit_id:
    runs-on: ubuntu-latest
    steps:
      - name: Get commit ID
        run: |
          echo "commit id : ${{ github.sha }}"

  information_repository:
    runs-on: ubuntu-latest
    steps:
      - name: Get repository name
        run: |
          echo "repository name : ${{ github.repository }}"
```

위의 workflow는 `information_commit_id`과 `information_repository`라는 두 개의 job으로 구성되어 있다. 그리고 `information_commit_id` job은 Get commit ID라는 name을 가진 하나의 step으로 구성되어 있고 `information_repository`job은 Get repository name이라는 name을 가진 step으로 구성되어 있다.

위에서 언급했듯이 workflow의 job은 모두 병렬적으로 처리된다. ( 동시에 실행된다 ) 만약 특정 job이 정상적으로 종료되고 다른 job을 실행하고 싶다면 다음과 같이 `needs` keyword를 사용한다.

```yaml
name: Test Workflow

on:
  push:
    branches:
      - master

jobs:
  information_commit_id:
    runs-on: ubuntu-latest
    steps:
      - name: Get commit ID
        run: |
          echo "commit id : ${{ github.sha }}"

  information_repository:
    needs: information_commit_id
    runs-on: ubuntu-latest
    steps:
      - name: Get repository name
        run: |
          echo "repository name : ${{ github.repository }}"

  information_branch_name:
    needs: information_repository
    runs-on: ubuntu-latest
    steps:
      - name: Get branch name
        run: |
          echo "branch name : ${{ github.ref }}"
```

위의 예제에서 `information_repository` job은 `information_commit_id` job이 성공적으로 종료되어야 실행되며 `information_branch_name` job은 `information_repository` job이 성공적으로 종료되어야 실행된다.

만약 needs에 선언된 job이 실패하더라도 무조건 job을 실행시켜야 한다면 다음과 같이 `always()`를 실행되어야 하는 job의 condition에 추가해준다.

```yaml
name: Test Workflow

on:
  push:
    branches:
      - master

jobs:
  information_commit_id:
    runs-on: ubuntu-latest
    steps:
      - name: Get commit ID
        run: |
          echo "commit id : ${{ github.sha }}"

  information_repository:
    if: ${{ always() }}
    needs: information_commit_id
    runs-on: ubuntu-latest
    steps:
      - name: Get repository name
        run: |
          echo "repository name : ${{ github.repository }}"
```

위의 예제에서 `information_repository`job은 `information_commit_id` job의 성공 여부와 상관없이 `information_commit_id` job의 실행이 종료되면 무조건 실행된다.

반면에 다음과 같이 `failure()` status check function을 통해 needs에 선언된 job이 실패했을 때만 job이 실행되도록 설정할 수도 있다.

```yaml
name: Test Workflow

on:
  push:
    branches:
      - master

jobs:
  information_commit_id:
    runs-on: ubuntu-latest
    steps:
      - name: Get commit ID
        run: |
          echo "commit id : ${{ github.sha }}"

  information_repository:
    if: ${{ failure() }}
    needs: information_commit_id
    runs-on: ubuntu-latest
    steps:
      - name: Get repository name
        run: |
          echo "repository name : ${{ github.repository }}"
```

위의 예제에서 `information_repository` job은 `information_commit_id` job이 실패로 종료되었을 때만 실행된다.

## 다음 job으로 data 전달하기

`output` keyword를 통해 다른 job에게 특정 data를 전달할 수 있다. 이 때 data를 전달할 수 있는 job은 `output` keyword를 통해 data를 전달하는 job을 `needs` keyword를 통해 해당 job에 의존하고 있는 job이다.

Outputs을 통해 전달할 수 있는 data의 최대 크기는 1MB다.

```yaml
name: Test Workflow

on:
  push:
    branches:
      - master
      - develop

jobs:
  information_commit_id:
    runs-on: ubuntu-latest
    outputs:
      current_commit_id: ${{ steps.store_commit_id.outputs.commit_id }}
    steps:
      - id: store_commit_id
        name: Get commit ID and store to output
        run: |
          echo "commit_id=${{ github.sha }}" >> "$GITHUB_OUTPUT"

      - id: print_commit_id
        name: Get commit ID and print
        run: |
          echo "commit id : ${{ github.sha }}"

  information_repository:
    needs: information_commit_id
    runs-on: ubuntu-latest
    steps:
      - name: Get repository name
        run: |
          echo "repository name : ${{ github.repository }}"
      
      - name: Print commit id from the previous job
        run: |
          echo "commit id : ${{ needs.information_commit_id.outputs.current_commit_id }}"
```

위의 예제에서 `information_commit_id` job의 `store_commit_id` step과 같이 `"commit_id=${{ github.sha }}" >> "$GITHUB_OUTPUT"` 구문을 통해 commit\_id 변수에 value를 할당하면 ( 변수명은 임의로 정해도 무관하다 ) `steps.store_commit_id.outputs.commit_id`를 통해 commit\_id 변수에 설정한 값에 접근할 수 있다.

`information_commit_id` job에서 `outputs` property를 통해 commit\_id 변수에 설정한 값을 `current_commit_id`라는 output property에 설정하면 `information_commit_id`job을 needs keyword를 통해 의존하고 있는 다른 job에서 해당 값을 전달 받아 사용할 수 있다.

`information_repository` job에선 `needs.information_commit_id.outputs.current_commit_id`을 통해 이전 job에서 전달한 값에 접근하고 있다.

## Conditional Job

위에서 잠시 살펴 보았듯이 `if` keyword를 통해 특정 job이 실행되는 조건을 설정할 수 있다.

```yaml
name: Test Workflow

on:
  push:
    branches:
      - master
      - develop

jobs:
  information_commit_id:
    runs-on: ubuntu-latest
    outputs:
      current_commit_id: ${{ steps.store_commit_id.outputs.commit_id }}
    steps:
      - id: store_commit_id
        name: Get commit ID and store to output
        run: |
          echo "commit_id=${{ github.sha }}" >> "$GITHUB_OUTPUT"

      - id: print_commit_id
        name: Get commit ID and print
        run: |
          echo "commit id : ${{ github.sha }}"

  information_repository:
    if: github.repository == 'test/test-action'
    needs: information_commit_id
    runs-on: ubuntu-latest
    steps:
      - name: Get repository name
        run: |
          echo "repository name : ${{ github.repository }}"
      
      - name: Print commit id from the previous job
        run: |
          echo "commit id : ${{ needs.information_commit_id.outputs.current_commit_id }}"
```

위의 예제에서 `information_repository` job은 workflow가 trigger된 github.repository context variable 값이 `test/test-action`일 때만 실행된다. 위의 예제에서 볼 수 있듯이 if 조건문에서 github.repository와 같은 context variable을 사용할 때 `${{ }}`와 같은 expression syntax를 생략할 수 있다.

하지만 if 조건문이 `!`로 시작한다면 `${{ }}` expression syntax를 사용해야 한다. ( Reference - [Using conditions to control job execution](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/using-conditions-to-control-job-execution) )

```yaml
if: ${{ ! startsWith(github.ref, 'refs/tags/') }}
```

## Steps

위의 예제에서 살펴보았듯이 workflow의 job은 한 개 혹은 다수의 steps으로 구성되어 있다. step을 통해 특정 command를 실행하거나 action을 실행한다. job과는 달리 각 job을 구성하는 step은 차례대로 실행된다. 예를 들어 아래 예제의 information job에서 name이 `Get commit ID and repository name`인 step이 실행되고 나서 name이 `Get repository name`인 step이 실행된다.

```yaml
name: Test Action

on:
  push:
    branches:
      - master
      - develop

jobs:
  information:
    runs-on: ubuntu-latest
    steps:
      - name: Get commit ID and repository name
        run: |
          echo "commit id : ${{ github.sha }}"

      - name: Get repository name
        run: |
          echo "repository name : ${{ github.repository }}"
```

그리고 workflow나 job level뿐만 아니라 step level에서도 environment variable을 설정하여 사용할 수 있다.

```yaml
name: Test Action

on:
  push:
    branches:
      - master
      - develop

jobs:
  information:
    runs-on: ubuntu-latest
    steps:
      - name: Get commit ID and repository name
        run: |
          echo "commit id : ${{ github.sha }}"

      - name: Get repository name
        env:
          GITHUB_REPO: ${{ github.repository }}
        run: |
          echo "repository name : $GITHUB_REPO"
```

job을 구성하는 step 중 특정 조건에 만족할 때만 실행되어야 하는 step이 있다면 if statement를 통해 특정 조건에만 step을 실행할 수도 있다. 예를 들어 아래 workflow에서 name이 `Get repository name`인 step은 workflow가 pull\_request event에 의해 trigger 되었을 때만 실행된다.

```yaml
name: Test Action

on:
  pull_request:
    branches:
      - master
      - develop
  push:
    branches:
      - master
      - develop

jobs:
  information:
    runs-on: ubuntu-latest
    steps:
      - name: Get commit ID and repository name
        run: |
          echo "commit id : ${{ github.sha }}"

      - name: Get repository name
        if: ${{ github.event_name == 'pull_request' }}
        run: |
          echo "repository name : ${{ github.repository }}"
```

또는 다음과 같이 이전 step이 실패 했을 때만 다음 step이 실행되도록 처리할 수도 있다.

```yaml
name: Test Action

on:
  pull_request:
    branches:
      - master
      - develop
  push:
    branches:
      - master
      - develop

jobs:
  information:
    runs-on: ubuntu-latest
    steps:
      - name: Get commit ID and repository name
        run: |
          echo "commit id : ${{ github.sha }}"

      - name: Get repository name
        if: ${{ failure() }}
        run: |
          echo "repository name : ${{ github.repository }}"
```

만약 steps 사이에서 data를 공유하고자 한다면 다음과 같이 environment variable을 활용할 수 있다. 아래 예제는 commit id를 TEST\_COMMIT\_ID라는 environment variable에 설정하고 다음 step에서 해당 environment variable value를 사용하고 있다.

```yaml
name: Test Action

on:
  push:
    branches:
      - master
      - develop

jobs:
  information:
    runs-on: ubuntu-latest
    steps:
      - name: Set the commit id in an environment variable
        run: |
          echo "TEST_COMMIT_ID=${{ github.sha }}" >> $GITHUB_ENV

      - name: Printing the commit id environment variable
        run: |
          echo "Commit id : $TEST_COMMIT_ID"
```

혹은 다음과 같이 임시 file을 통해 데이터를 저장하고 사용하는 방법도 있다.

```yaml
name: Test Action

on:
  push:
    branches:
      - master
      - develop

jobs:
  information:
    runs-on: ubuntu-latest
    steps:
      - name: Set the commit id in a file
        run: |
          echo "${{ github.sha }}" > temp_test_data.txt

      - name: Printing the commit id from a file
        run: |
          cat temp_test_data.txt
```
