guest@blog.cmj.tw: ~/posts $

Github Action


How to use Github Action to automate your workflow.

記錄一下這幾年來、使用 Github Action 的一些心得:如何透過自動/手動觸發、來執行一些自動化的工作。

Concepts

主要來說,Github Action 是一個 CI/CD 的工具:透過撰寫正確的 YAML 檔案、定義觸發流程跟執行的指令, 來自動化執行一連串的工作。整個 YAML 檔案稱為 workflow,而每個 workflow 可以包含一個或多個 job,而每個 job 可以包含一個或多個 step,另外可以使用 on 來定義觸發的事件。

以下是一個複雜的例子,完整的語法可以參考 官方文件 查詢所有可以使用的指令。

# define the human-readable name of the workflow and show it in the Actions tab
name: Your first but not last Workflow

# define how to trigger the workflow by the following events
on:
  # when push to the `main` branch or tag with pattern `v1.*`
  push:
    branches:
      - main
    tags:
      - "v1.*"
  # when create a pull request with:
  #
  #  1. the `.py` file modified
  #  2. all the files inside the `src` directory
  #  3. but not the files inside the `src/ignore` directory
  pull_request:
    paths:
      - "*.py"
      - "src/**"
      - "!src/ignore/**"
  # trigger on every day at 00:00 UTC
  schedule:
    - cron: "0 0 * * *"
  # or manually trigger on the Actions tab
  workflow_dispatch:
    # with some necessary or optional inputs
    inputs:
      # define the necessary input with the default value and description
      server:
        description: "Your first input"
        required: true
        default: "localhost"
      # define the optional input with the options value and description
      options:
        description: "Your second input"
        required: false
        default: "option_1"
        options: ["option_1", "option_2", "option_3"]

# the global environment variables and may be overridden by job env
env:
  # define the global environment variable
  DEBUG: true
  # define the environment variable from the input or use the default value
  SERVER: ${{ inputs.server || 'example.com' }}

jobs:
  your_first_job:
    name: Your First Job on ${{ matrix.os }}
    runs-on: ${{ matrix.os }}

    timeout-minutes: 60 # define the job-level timeout (60 minutes)

    env:
      DEBUG: false # override the global environment variable

    strategy:
      matrix:
        os:
          - ubuntu-latest
          - windows-latest
          - macos-latest

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v2
        with:
          python-version: "3.x"
      - name: whoami
        run: |
          whoami          
      - name: whois
        run: |
          whois ${{ env.SERVER }}          

on

on 是用來定義觸發的事件,可以是一個或多個事件,例如:pushpull_requestfork 等。

事件可以指定單一事件,像是 on: push,也可以指定多個事件,像是 on: [push, pull_request]。 如果需要更細膩的設定,可以根據不同的事件來指定更多的條件。像是當 push 到特定的分支時才觸發。 或者當 pull request 修改到特定的檔案時才觸發。

on:
  push:
    branches:
      - master
      - develop
  pull_request:
    paths:
      - "src/**"
      - "!src/ignore/**"

可以參考 官方文件 查詢所有可以使用的事件。

jobs

jobs 定義了一個或多個工作,每個工作可以包含一個或多個步驟。每個工作預設是平行執行 (parallel) 的, 如果需要串行執行,可以使用 needs 來定義依賴的工作。每一個job 都需要透過 runs-on 指定執行的環境。 這邊可以指定 Github-hosted runner 或者是 self-hosted runner 。每一個 job 都有自己的 job id, 可以透過這個 id 來指定依賴的工作或拿到特定 job 的結果。

概念上,每一個 job 都被視為是獨立的執行環境,所以每一個 job 都會有自己的環境變數、工作目錄等等。 不同的 job 之間是無法共享環境變數、工作目錄等參數。如果需要、可以透過 outputs 將結果傳遞給其他的 job,另外的 job 可以透過 needs 來拿到其他 job 的結果。

在 job 下會有一個或多個 step,可以在 job 下定義全域的環境變數等參數。job 下的 step 會繼承這些參數 ,但是可以透過 step 自己的參數來覆蓋全域的參數。

jobs:
  your_first_job:
    runs-on: ubuntu-latest

    # the job-level environment variables and may be overridden by step env
    env:
      YOUR_ENV: "YOUR_VALUE"

    # the job-level timeout and may be overridden by step timeout
    timeout-minutes: 10

    outputs:
      your_output: ${{ steps.your_step_id.outputs.your_output }}

    steps:
      - name: Your First Step
        id: your_step_id
        run: |
          echo "Hello, World!"
          echo "your_output=THE SECRET RESULT" >> $GITHUB_OUTPUT          
      - name: Your Second Step
        run: echo "Hello, World!"

  your_second_job:
    runs-on: ubuntu-latest
    needs: your_first_job

    steps:
      - name: Your Another Step
        run: echo "Hello, World!"
      - name: your_first_job Output
        run: echo "${{ needs.your_first_job.outputs.your_output }}"

strategy

如果同一個 job 會需要執行多次、每次執行的參數都不一樣,可以使用 strategy 來定義這些參數。 在 matrix 下可以定義多個變數,每個變數都代表一個維度。在 steps 下可以使用 ${{ matrix.YOUR_VARIABLE }} 來取得這些變數。以下面的例子則會執行 9 次,每次執行的環境變數都不一樣。

jobs:
  your_first_job:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node: [10, 12, 14]

    steps:
      - name: Your Step
        run: |
          echo "Run on ${{ matrix.os }} with node ${{ matrix.node }}"          

steps

steps 定義了一個或多個步驟,每個步驟都是一個指令。每個步驟需要使用 usesrun 來定義執行的指令。 uses 是用來引用其他的 action,而 run 則是直接執行指令。每個步驟都有自己的環境變數、工作目錄等參數, 沒有設定的話會繼承 job 的參數。

每個 step 可以使用 id 指定一個唯一的 id,這樣可以在其他的 step 或 job 中使用這個 id 來取得這個步驟的結果。 此時可以透過 ${{ steps.your_step_id.outputs.your_output }} 來取得這個步驟的結果。如果需要重複傳遞結果, 可以透過複寫 $GITHUB_ENV 來覆蓋全域的環境變數,其他的 job 可以透過 ${{ env.your_output }} 來取得這個結果。

jobs:
  your_first_job:
    runs-on: ubuntu-latest

    steps:
      - name: Your First Step
        id: your_step_id
        run: |
          echo "Hello, World!"
          echo "your_output=THE SECRET RESULT" >> $GITHUB_OUTPUT          
      - name: Your Second Step
        run: |
          echo "${{ steps.your_step_id.outputs.your_output }}"
          echo "your_output=THE NEW SECRET RESULT" >> $GITHUB_ENV          

uses

再重複利用的情境時、可以使用 uses 來引用其他的 action。這樣可以將重複的部分抽出來、讓 workflow 更加簡潔。 而這個 workflow 可以是同個 repository 的 workflow,也可以是其他 repository 的 workflow。

如果是同個 repository,使用./the/path/of/your/workflow.yaml@v1 來引用。如果是其他 repository 使用 your-org/your-repo/another/workflow.yaml@v1。如果需要傳遞參數,可以使用 with 來傳遞參數,而 workflow 可以透過 ${{ inputs.input_key }} 來取得這些參數。另外也可以使用 env 來傳遞環境變數。

jobs:
  your_first_job:
    uses: ./the/path/of/your/workflow.yaml@v1
  your_second_job:
    uses: your-org/your-repo/another/workflow.yaml@v1
    with:
      input_key: "YOUR INPUT VALUE"