Github Actionsに入門してみた

# 概要

データ分析のためのプログラムをシステマチックにテスト/デリバリ/デプロイする技術を学ぼうと、『CI/CD』を調べてました。CI/CDのためのツールをいくつか見つけ、Github Actionsを使うことにしたのですが、躓きポイントがあったので、備忘として。

＜見つけたCI/CDツール＞

- Github actions：https://github.com/features/actions
- CircleCI：https://circleci.com/
- Jenkins： https://www.jenkins.io/

どれも良さそうだったのですが、筆者がGithubを利用していることと、無償で利用できることの利点がある Github Actionsを使うことにしました。


# 今回実行すること


1. 2個のpythonファイルを作成する。
2. 1個目のpythonで2個の乱数をテキストファイルとして出力する。
3. 2個目のpythonは、1個目のpythonで出力したテキストファイルを読み込み、足し合わせてその計算結果をprintする。


筆者がここで苦労したのは、「3.」のところで、Github Actions上でファイルを読み込ませるところです。
Github Actionsをやってる人からすればすぐわかると思うかもしれないですが、「2つのジョブ間でファイルを共有すること」「ステップの順番によっては、ダウンロードしたファイルが消えてしまう」ということがあり、それを筆者は知らないまま作成していたため、ドツボにハマっていました（※もちろん解決済みです）。

図で描くと、以下のように単純な実行フローです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/687015/ae490fca-3865-f285-d79f-7fadc30e65a9.png)


そしてこれらのファイルの関係性を図にすると以下です。
```generate_array.py```が```output.txt```を出力し、それをinputとして利用する```sum_array.py```という関係性です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/687015/02803c87-9b2e-4352-fe14-3937992a63ae.png)



# 実際に作る

まずはディレクトリを作ります。


``` text
root/
　├ .github/
　│　└ workflows/
　│　　└ python.yml
　├ src/
　│　　└ generate_array.py
　│　　└ sum_array.py
　├ requirements.txt
```

各ファイルはそれぞれ以下のような内容です。

- ```python.yml```
    - ここにGithubActionsのジョブを定義します。
    - ファイル名はなんでも良いです。今回は、「python.yml」としています。
    - 注意点
        - GithubActionsを使うには、このジョブ設定ファイルの保存場所が決まっており、```.github/workiflows/```に保存する必要があります。
- ```generate_array.py```
    - これが1個目のpythonファイルです。
    - ファイルの保存場所は特に指定はないですが、今回は、```src```ディレクトリの配下に入れることとしました。
    - 2個の乱数を生成し、それをテキストファイルとして保存します。
- ```sum_array.py```
    - これが2個目のpythonファイルです。
    - ファイルの保存場所は特に指定はないですが、今回は、```src```ディレクトリの配下に入れることとしました。
    - ```generate_array.py```の出力テキストファイルを入力して、それらを足し合わせます。
- ```requirements.txt```
    - 今回はnumpyを使うので、numpyをインストールできるようにするためにrequirements.txtを用意しておきます。


## ```python.yml```の中身

Github Actionsでどんな処理をどういう順番で実行させるか、というものを定義するためのファイルです。

``` yaml
name: Python Workflow

on: [push]

jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.10'

      - name: Install dependencies
        run: pip install -r requirements.txt

      - name: Generate random array
        run: python src/generate_array.py

      - name: Upload output.txt
        uses: actions/upload-artifact@v4
        with:
          name: output-file
          path: src/output.txt

  sum:
    runs-on: ubuntu-latest
    needs: generate # このジョブはgenerateジョブが成功しなければ実行されない
    steps:      
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.10'

      - name: Download output.txt # python環境のセットアップ（actions/setup-python@v4）が完了してから、ダウンロードする必要がある.
        uses: actions/download-artifact@v4
        with:
          name: output-file
          path: src # `src/`にoutput-file（output.txt）を保存する

      - name: Calculate sum
        run: python src/sum_array.py
```

### 中身の解説

#### 基本情報

- `name: Python Workflow`
  - ワークフローの名前です。

- `on: [push]`
  - このワークフローは、リポジトリに対してプッシュされたときにトリガーされます。

#### ジョブの定義

このYAMLファイルには2つのジョブ（`generate`と`sum`）があります。

#### 1. ジョブ `generate`

- `runs-on: ubuntu-latest`
  - このジョブは最新のUbuntu環境で実行されます。

- `steps`
  - ジョブ内の各ステップを定義しています。
  
  - **Checkout code**
    ```yaml
    - name: Checkout code
      uses: actions/checkout@v4
    ```
    - リポジトリのコードをチェックアウトします。

  - **Set up Python**
    ```yaml
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.10'
    ```
    - Pythonのバージョン3.10をセットアップします。

  - **Install dependencies**
    ```yaml
    - name: Install dependencies
      run: pip install -r requirements.txt
    ```
    - 必要な依存関係を`requirements.txt`からインストールします。

  - **Generate random array**
    ```yaml
    - name: Generate random array
      run: python src/generate_array.py
    ```
    - `src/generate_array.py`を実行してランダムな配列を生成します。

  - **Upload output.txt**
    ```yaml
    - name: Upload output.txt
      uses: actions/upload-artifact@v4
      with:
        name: output-file
        path: src/output.txt
    ```
    - 生成された`output.txt`ファイルをアップロードします。ファイルは`output-file`という名前で保存されます。
    - ちなみに、```actions/upload-artifact@v4```という指示は、自動でファイルを圧縮してアップロードします。

#### 2. ジョブ `sum`

- `runs-on: ubuntu-latest`
  - このジョブも最新のUbuntu環境で実行されます。

- `needs: generate`
  - このジョブは、`generate`ジョブが成功した後にのみ実行されます。

- `steps`
  - ジョブ内の各ステップを定義しています。

  - **Checkout code**
    ```yaml
    - name: Checkout code
      uses: actions/checkout@v4
    ```
    - リポジトリのコードを再度チェックアウトします。

  - **Set up Python**
    ```yaml
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.10'
    ```
    - 再度Pythonのバージョン3.10をセットアップします。

  - **Download output.txt**
    ```yaml
    - name: Download output.txt
      uses: actions/download-artifact@v4
      with:
        name: output-file
        path: src
    ```
    - `generate`ジョブでアップロードされた`output.txt`をダウンロードします。
    - このステップは、python環境のセットアップの後にする必要があります。前にしてしまうと、python環境の構築時にファイルが上書きされてしまい、せっかくダウンロードしたファイルが消えてしまいます。
    - ```path```のパラメータは保存先のディレクトリを指定しておきます。今回作成するpythonファイルを```src```ディレクトリに入れているため、srcを指定しておきます。
    - ちなみに、この```actions/download-artifact@v4```は、zipファイルを自動で解凍してくれます。ので、1個目のジョブで実行した```actions/upload-artifact@v4```がzip圧縮していたとしても問題ないのです。

  - **Calculate sum**
    ```yaml
    - name: Calculate sum
      run: python src/sum_array.py
    ```
    - `src/sum_array.py`を実行して、配列の合計を計算します。




## ```generate_array.py```の中身

1個目のpythonファイルの中身です。
numpyで2個の整数をランダムに作成し、それをテキストファイルで出力します。


```python
import numpy as np
import os

# 環境変数から出力ファイル名を取得
file_name = os.getenv('OUTPUT_FILE', './src/output.txt')

# ランダムな整数を2つ持つ配列を生成
random_integers = np.random.randint(1, 100, size=2)

# 配列を.txtファイルに出力
with open(file_name, 'w') as f:
    f.write(','.join(map(str, random_integers)))

```


## ```sum_array.py```の中身

2個目のpythonファイルです。
1個目の出力テキストを読み込んで、足し合わせる処理です。
その結果をprint()します。


```python
import os

# 環境変数から出力ファイル名を取得
file_name = os.getenv('INPUT_FILE', './src/output.txt')

# output.txtファイルを読み取る
with open(file_name, 'r') as f:
    data = f.read()

# 整数をリストに変換
numbers = list(map(int, data.split(',')))

# 合計を計算
total = sum(numbers)

# 合計を表示
print(f'The sum of the integers is: {total}')

```




## ```requirements.txt```の中身

Github Actionsでは、pythonのピュア環境（特にライブラリをインストールされていない）であるため、numpyのようなサードパーティライブラリはrequirements.txtに書き込んで、インストールさせておく必要があります。

今回は、numpyだけ必要なので、numpyだけ記載しておきます。

```text
numpy
```

# Github Actionsを使う

↑の設定や必要なファイルを作成したので、実際にGithub Actionsを使ってみます。

使い方は簡単です。自分のリポジトリに単純にpushするだけです（今回はGithubへのプッシュ方法については解説しないです）。

pushしたあとは、以下のように自分のリポジトリの「Actions」を開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/687015/d7fa72d7-b8c9-60f8-c699-d3df39b56b48.png)

そして、そのActionsページを見ると（Actionsの動作が完了していれば）、以下のように動作実行結果を確認することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/687015/04db4c8c-a215-7007-aa30-ca8c43571fc9.png)

この図では、何回もプッシュしてテストをしていたため、何個も画面に出てきてしまっていますが、失敗した時と成功した時とでのアイコンの違いを確認できます（赤の「バツ」と緑の「チェックマーク」でわかりやすいですね）。


# まとめ

以下の2点を実施することができた。

- Github Actionsで2個のpythoファイルを別のジョブとして実行する。
- ジョブ間でファイルの共有をする。