GitHub Actions

2023年2月25日

ビルドテスト自動化ツールとしてGitHub Actionsが現在社内で利用されている。
DevOpsを学んでいく上で、Jenkinsに並んで学んでおくべきCI/CDツールの一つである。
学んだ知見を本ページに集約する。

Tips

別リポジトリのWorkflowをキックする方法

各コンポーネントが更新されたのを契機に、インテグレーションをして統合テストを行いたい場合がある。コンポーネントごとにリポジトリが分かれていてそれぞれ別のチームで管理している場合、リポジトリ間のWorkflowを連携させたい。
Jenkinsであれば容易に親JOBから子JOBを呼び出すことが可能だが、GithubActionsの場合Workflowが各リポジトリに紐づいておりリポジトリ間の連携を行うのには一工夫いる。
(上記理由で、大規模ソフトウェア開発では各JOBが一元管理されるJenkinsがツールとしては使いやすいと個人的には思う。)

では具体的にどのようにすれば良いかというと、Githubの提供するAPIを利用する。
呼び出し元リポジトリのWorkflowから、curlで呼び出し先リポジトリのAPIをたたいてトリガーする。
ただし、この方法では呼び出し元リポジトリ側では、呼び出し先のリポジトリのworkflowの結果は見えないため、呼び出し先workflowの結果を呼び出し元リポジトリに知らせるような工夫が必要などの制約がある。いずれにしてもJenkinsのように各リポジトリのワークフローの一元管理ができないため、大規模ソフトウェアの開発における管理は難しいと思う。

それでは、具体例を示す。
呼び出し元リポジトリのworkflow(.github/workflows/sampleA.yml)は以下の通りである。

.github/workflows/sampleA.yml
---
name: Trigger to another repo
on:
  push:

jobs:
  trigger-to-another-repo:
    runs-on: ubuntu-latest
    steps:
      - name: send
        run: |
          TOKEN=${{ secrets.PAT }}
          ORG=${{ secrets.ORG }}
          REPO=${{ secrets.TRIGGERREPO }}

          ## curl
          curl \
            -X POST \
            -H "Authorization: token $TOKEN" \
            -H "Accept: application/vnd.github.v3+json" \
            https://api.github.com/repos/$ORG/$REPO/dispatches \
            -d '{"event_type":"dispatch-test","client_payload":{"message": "Hello"}}'

client_payloadを利用して、呼び出し先リポジトリにmessageを渡すことが可能である。
(今回はHelloというmessageを送る。)
“https://api.github.com/repos/"を今回使用しているが、環境によっては"https://github.〇〇〇.com/api/v3/repos/"を使用する。
続いて、呼び出し先リポジトリのworkflow(.github/workflows/sampleB.yml)の例を示す。

.github/workflows/sampleB.yml

---
name: Triggered from other repo

on:
  repository_dispatch:
    types: [dispatch-test]

jobs:
  triggered-from-other-repo:
    runs-on: ubuntu-latest
    steps:
      - name: Receive
        run: |
          message=${{ github.event.client_payload.message }}
          echo "message from other repo = $message"

実行契機として、repository_dispatchを用いる。
typesには呼び出し元で指定した"event_type"を指定する。(今回はdispatch-test)
実行結果は次の通りとなる。

呼び出し元(sampleA)実行結果
呼び出し先(sampleB)実行結果

matrix構文

複数のパターンでジョブを実行したい場合、matrix構文を用いて簡易的にworkflowを記述することができる。
例えば、下記の図で示すように各コンポーネントでビルドを行った後に複数のテスト条件でテストを行い結果を通知する場合を考える。

componentA build
componentA build
componentB build
componentB build
Source Repo
Source Repo
Developper
Devel…
componentA test
componentA test
componentB test
componentB test
integ test
integ test
複数条件
複数条件
結果通知
結果通知
結果upload
結果upload
Viewer does not support full SVG 1.1

matirix構文については、こちらに文法が記載されている。
今回作成したワークフローは、以下の要求を満たすように設計されている。(ただし、実際にビルドを行ったり環境のセットアップをしているわけではないので、実践で利用する場合は該当ステップに実ビルドコマンドなどを置き換えて使用してください。)
・コンポーネントAのビルドとコンポーネントBのビルドを行う。
・上記ビルドが終了後に、コンポーネントAのテスト、コンポーネントBのテスト、インテグレーションテストをそれぞれ環境oldと環境latestで行う。
・上記テストはコンポーネントA、コンポーネントBのビルドが失敗した場合は行われない。
・テストが行われた場合、結果のOK,NGに関わらず毎回GithubにUploadされる。
・テスト結果の成果物管理ツール(例えばJFrog Artifactoryなど)へのUploadは、テスト結果がすべてOKの場合のみ行われる。
・テストが行われた場合、結果のOK,NGに関わらず毎回開発者に結果が通知される。(今回はログに表示しているだけだが、実践ではslackに通知するなどのActionを用いるとよい。)
今回作成したWorkflowは以下の通りである。

.github/workflows/matrix.yml

---
name: matrix-sample
on:
  push:
  workflow_dispatch:

jobs:
  matrix-job:
    strategy:
      matrix:
        common-settings: [old,latest]
        task: [test-build-job-A,test-build-job-B,integ-test]
        include:
          - task: test-build-job-A
            config: settings-A
          - task: test-build-job-B
            config: settings-B
          - task: integ-test
            config: settings-integ

    runs-on: ubuntu-latest
    needs: [build-job-A,build-job-B]
    steps:
      - name: common settings
        run: |
          echo "done common settings ${{ matrix.common-settings }}"

      - name: individual settings
        run: |
          echo "done config ${{ matrix.config }}"

      - name: test
        run: |
          echo "done test ${{ matrix.task }}"
          mkdir -p test_results/${{ matrix.common-settings }}_${{ matrix.task }}
          echo 'OK' > test_results/${{ matrix.common-settings }}_${{ matrix.task }}_result.txt

      - name: upload artifacts (to GitHub)
        if: ${{ always() }}
        uses: actions/upload-artifact@v2
        with:
          name: ${{ matrix.common-settings }}_${{ matrix.task }}
          path: test_results/${{ matrix.common-settings }}_${{ matrix.task }}_result.txt        

  build-job-A:
    runs-on: ubuntu-latest
    steps:
      - name: setup
        run: |
          echo "setup done"

      - name: build
        run: |
          echo "build done"

      - name: deploy
        run: |
          echo "deploy done"

  build-job-B:
    runs-on: ubuntu-latest
    steps:
      - name: setup
        run: |
          echo "setup done"

      - name: build
        run: |
          echo "build done"

      - name: deploy
        run: |
          echo "deploy done"

  upload:
    runs-on: ubuntu-latest
    needs: matrix-job
    steps:
      - name: upload
        run: |
          echo "upload to artifactory"

  notify:
    runs-on: ubuntu-latest
    if: ${{ always() }}
    needs: matrix-job
    steps:
      - name: download artifact
        uses: actions/download-artifact@v2
        with:
          path: download-artifact

      - name: notify results
        run: |
          artifact_result=(`find . -name "*result.txt"`)
          for v in "${artifact_result[@]}"
          do
            test=`echo ${v##*/}`
            result=$(
              head -n 1 "$v"
            )
            echo "Tests:${test%_*}\Results:"$result""
          done

実行結果は以下の通りとなる。

実行結果

テスト成功時のworkflow

success_workflow

テスト成功時の結果通知

success_notice

テスト失敗時のworkflow

fail_workflow

テスト失敗時の結果通知

fail_notice

matrix構文を利用することで、複数の条件でjobを回したいときにworkflowを簡易的に記載することができるので、ぜひ活用してみてください。

Github APIを使用した情報の取得

Githubを利用したソフトウェア開発の中で、PRの数やcommitしたdevelopperの人数、コードの行数などの情報から、CI/CD指標を集計したい時がある。
GithubのUIからでもある程度の情報を取得することは可能だが、複数のリポジトリを調査したい場合、常に更新されていく情報を手動で集計するのは工数がかかる。
そこで、GithubActionsを利用した、自動集計のworkflowを作成した。

ファイル構成は以下の通りである。

|.
|--.github
|  |--workflows
|  |  |--get-status.yml
|--config.json
|--README.md
|--tools
|  |--get-pr-created-date.sh

まず、get-status.ymlファイルについて説明する。
今回のworkflowは、前章で説明したmatrix構文を用いる。
リポジトリごとにstepを分けるのは非常に冗長な表現になってしまうのと、可読性や保守性が損なわれてしまう。そのため、config.jsonに記載したリポジトリを取得し、各リポジトリごとにJOBをmatrix構文にしたがって呼び出す構成にした。新たに情報を取得したいリポジトリがある場合は、config.jsonファイルのみ修正すればよい。

get-status.yml
---
name: get-status
on:
  push:
  workflow_dispatch:
env:
  date_cal: 500

jobs:
  set-config:
    runs-on: ubuntu-latest
    outputs:
      confJson: ${{ steps.set-config.outputs.confJson }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3
      - name: Set configuration
        id: set-config
        run: |
          cat config.json | tr -d '\n' | jq -c
          list=$(cat config.json | tr -d '\n' | jq -c)
          echo "confJson=${list}" >> $GITHUB_OUTPUT       

  get-status:
    needs: set-config
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        repo: ${{fromJson(needs.set-config.outputs.confJson).repository}}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3
      - name: Get pr-created-date
        run: |
          echo ${{ matrix.repo }}
          chmod +x ./tools/get-pr-created-date.sh
          ./tools/get-pr-created-date.sh ${{ env.date_cal }} ${{ matrix.repo }} ${{ secrets.GIT_PAT }}
      - name: Checkout target repository
        uses: actions/checkout@v3
        with:
          repository: ${{ matrix.repo }}
          fetch-depth: 0
          token: ${{ secrets.GIT_PAT }}
          path: target
      - name: Check developper
        run: |
          cd ${{ github.workspace }}/target
          echo "start showing developper"
          git log | git shortlog -nse
          echo "finish showing developper" 
          cd ${{ github.workspace }}
      - name: Setup-cloc
        run: |
          sudo apt-get update
          sudo apt-get install cloc
          cloc --version
      - name: Check-lines-of-code
        run: |
          cd ${{ github.workspace }}/target
          cloc . --vcs=git

config.jsonファイルは以下の通りである。

config.json
{
    "repository":[
        "${OWNER1}/${REPO1}",
        "${OWNER2}/${REPO2}"
    ]
}

過去のPR数の取得

Github APIを利用し、特定の日付以降に作成されたPRを取得する。
以下、シェルスクリプトである。

get-pr-created-date.sh
#!/bin/bash
calc_date=`date +%F --date "$1 day ago"`
echo $calc_date

pulls=$(curl \
        -H "Accept: application/vnd.github.v3+json" \
        -H "Authorization: token $3" \
        https://api.github.com/repos/$2/pulls?state=all&pre_page=100)
echo $pulls | jq --arg date $calc_date '.[] | select(.created_at >= $date) | .created_at'

developperの取得

ターゲットリポジトリのcommitした人の取得は、以下コマンドで取得する。(github apiを利用しても可能)

git log | git shortlog -nse

コード行数の取得

clocをインストールし、gitでバージョン管理されているファイルに対して集計する。

sudo apt-get update
sudo apt-get install cloc
cd ${{ github.workspace }}/target
cloc . --vcs=git

参考文献

Github Actions公式Document

GithubActions

Posted by okuribito