GitHub Actions - cacheとartifact

# 内容

GitHub Actionsのcache([`actions/cache`](https://github.com/actions/cache))とartifact([`actions/upload-artifact`](https://github.com/actions/upload-artifact), [`actions/download-artifact`](https://github.com/actions/download-artifact))の用途、有効範囲について記載する

# cache

[`actions/cache`](https://github.com/actions/cache)1個でcacheのsave/restoreする方法と、  
`actions/cache/save`, `actions/cache/restore`でcacheのsave/restoreする方法がある  
本記事では前者について記載する  

:::note info
cacheのパラメータpath, keyや、`actions/cache/save`, `actions/cache/restore`を使った実装例は[GitHub Actions - cache](https://qiita.com/takmot/items/3972d053322dbf58b4a9)参照
:::

## cacheの用途

job、workflowはそれぞれ実行環境が異なるため、毎回依存関係をインストールする必要がある  
cacheによりインストールした依存関係の再利用することができる

## ツールのcache

npm、pip等、代表的な各種ツールのcache例は[Examples](https://github.com/actions/cache/blob/main/examples.md)参照  

その他、例えばpoetryをインストールする[`snok/install-poetry`](https://github.com/snok/install-poetry)は、ubuntuでのcache方法が[Caching the Poetry installation](https://github.com/snok/install-poetry?tab=readme-ov-file#caching-the-poetry-installation)に記載されている  

また、ツールのインストールactionにcache機能が備わっているものもある  
例えば、taskをインストールする[`arduino/setup-task`](https://github.com/arduino/setup-task)で、Readmeでは特に触れられていないが、  
実装を見ると、[`@actions/tool-cache`を使ったcache処理](https://github.com/arduino/setup-task/blob/main/src/installer.ts#L192)が実装されていることがわかる

```typescript
  // look if the binary is cached
  let toolPath: string;
  toolPath = tc.find("task", targetVersion);

  // if not: download, extract and cache
  if (!toolPath) {
    toolPath = await downloadRelease(targetVersion);
    core.debug(`Task cached under ${toolPath}`);
  }
```

### `setup-*` actionによるツールのcache

Github管理の[`setup-node`](https://github.com/actions/setup-node#caching-global-packages-data)等のツールセットアップActionにはcache機能がある  
以下のように[ワークフローの依存関係のキャッシングについて](https://docs.github.com/ja/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows#about-caching-workflow-dependencies)に記載されているだけの種類がある  

![2025-01-02-14-37-46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/92359d51-9a75-3c3d-931f-821b604fddfc.png)

以下のように`cache`オプションを指定することでcacheが有効になる  
※ `node_modules`フォルダがcacheされるわけではない

```yaml
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
  with:
    node-version: 20
    cache: 'npm'
```

## cacheのsaveタイミングと有効範囲

主なcacheの使用用途は[ツールのcache](#ツールのcache)と思われるが、  
以下のファイルをcacheする例を使って、cacheのsaveタイミングと有効範囲を確認する

```yaml
name: cache save test

on:
  workflow_call:
jobs:
  cache_save:
    name: cache save
    runs-on: ubuntu-latest
    env:
      cache_key: cache_2025_01_01  # ※ 本来このような適当なkeyは使用しない
      cache_dir: cache_test
    steps:
      - name: Cache restore/save
        id: cache_restore
        uses: actions/cache@v4
        with:
          path: ${{ env.cache_dir }}
          key: ${{ env.cache_key }}

      - name: Create cache file  # cacheするファイルの作成
        if: steps.cache_restore.outputs.cache-hit != 'true'  # cacheがなかった(restoreに失敗した)場合に実行
        uses: actions/github-script@v7
        with:
          result-encoding: string
          script: |
            const fs = require('fs');
            fs.mkdirSync("${{ env.cache_dir }}");
            fs.writeFileSync( "${{ env.cache_dir }}/cache_test_1.txt", "123456789" );
            fs.writeFileSync( "${{ env.cache_dir }}/cache_test_2.txt", "987654321" );

      - name: Read cache file  # cacheファイルの読込
        uses: actions/github-script@v7
        with:
          result-encoding: string
          script: |
            const fs = require('fs');
            const messages = [];
            const caches = fs.readdirSync("${{ env.cache_dir }}");
            console.log(`caches: ${caches}`);

            for (const cache of caches) {
              messages.push(fs.readFileSync(`${{ env.cache_dir }}/${cache}`, 'utf8'));
            }
            console.log(messages.join("\n"));
```

以下、初回実行時の結果  
`Cache restore/save`でcacheをrestoreするが、cacheがないため`Create cache file`でcacheするファイルを作成、  
`Read cache file`で作成したファイルの読み出し、全てのstep終了後に`Post Cache restore/save`でcacheがsaveされていることがわかる

![2025-01-02-14-25-15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/1449b224-6a0e-5ac6-d94d-664cbea1fbc3.png)

別途同じworkflowを実行すると、以下のようにcacheのrestoreに成功し、`Create cache file`はスキップされ、  
`Read cache file`で作成したファイルの読み出し、すでにcacheが存在するためsaveは実行されない

![2025-01-02-14-25-48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/0e1dfb1c-c36e-1025-22f9-247c9285ba3b.png)

:::note info
cacheはworkflowを超えて参照することができるため、それを考慮してcacheの`key`は設定する必要がある  
[キャッシュキーの作成](https://github.com/actions/cache?tab=readme-ov-file#creating-a-cache-key)にあるように`hashFiles('**/lockfiles')`や、`github.sha`を使用する
:::

# artifact

[`actions/upload-artifact`](https://github.com/actions/upload-artifact)、[`actions/download-artifact`](https://github.com/actions/download-artifact)を使って、artifactをアップロード/ダウンロードすることができる  

## artifactの用途

artifactとしてアップロードすることで、workflow内のjob間でデータを共有することができる  
また、そのartifactはworkflow終了後にユーザによってダウンロードすることができる

## artifactの有効範囲

以下の複数artifactをアップロード/ダウンロードする例を使って、artifactの有効範囲を確認する

```yaml:./.github/workflows/reuse_artifact_create.yml
name: create artifact  # 指定した文字列からテキストファイルのartifactを作成するreusable-workflow

on:
  workflow_call:
    inputs:
      name:  # artifactの名前
        required: true
        type: string
      contents:  # テキストファイルのartifactに書き込む内容
        required: true
        type: string
jobs:
  create_artifact:
    name: create artifact
    runs-on: ubuntu-latest
    steps:
      - name: Create artifact
        uses: actions/github-script@v7
        with:
          result-encoding: string
          script: |
            const fs = require('fs');
            const contents = "${{ inputs.contents }}";
            fs.writeFileSync( "${{ inputs.name }}.txt", contents );
  
      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: ${{ inputs.name }}
          path: ${{ inputs.name }}.txt
          retention-days: 1
```

```yaml
name: artifact test

on:
  workflow_call:
    inputs:
      artifact_prefix:
        required: false
        type: string
        default: test-artifact
jobs:
  create_artifact_1:  # 適当なartifactをアップロードする
    name: create artifact 1
    uses: ./.github/workflows/reuse_artifact_create.yml
    secrets: inherit
    with:
      name: ${{ inputs.artifact_prefix }}-1
      contents: "ABCDE"

  create_artifact_2:  # 適当なartifactをアップロードする
    name: create artifact 2
    uses: ./.github/workflows/reuse_artifact_create.yml
    secrets: inherit
    with:
      name: ${{ inputs.artifact_prefix }}-2
      contents: "12345"

  download_artifact:  # artifactをダウンロードする
    name: download artifact
    runs-on: ubuntu-latest
    needs: [create_artifact_1, create_artifact_2]
    env:
      artifacts_path: artifacts
    steps:
      - name: Download All Artifacts
        uses: actions/download-artifact@v4
        with:
          path: ${{ env.artifacts_path }}  # artifactのダウンロード先
          pattern: ${{ inputs.artifact_prefix }}*  # フィルターで複数artifactを指定する
          merge-multiple: true

      - name: test result
        uses: actions/github-script@v7
        with:
          result-encoding: string
          script: |
            const fs = require('fs');
            const messages = [];
            const artifacts = fs.readdirSync("${{ env.artifacts_path }}");
            console.log(`artifacts: ${artifacts}`);

            for (const artifact of artifacts) {
              messages.push(fs.readFileSync(`${{ env.artifacts_path }}/${artifact}`, 'utf8'));
            }
            console.log(messages.join("\n"));
```

以下、実行結果

![2025-01-02-14-31-53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/68785ce0-af0a-7256-3ae0-c3ae0c9ba585.png)

以下のようにworkflowのSummaryにArtifactsとして表示されていることがわかる、ダウンロードも可能  
この結果からartifactはworkflowに紐づいていることがわかる  

ただし、[パラメータ](https://github.com/actions/download-artifact?tab=readme-ov-file#inputs)`repository`、`run-id`を使うことでrepository、workflowを超えて参照することもできる  

:::note info
artifactは基本的にworkflow内で参照するもののため、artifactの`name`はworkflow内で一意であればよい  
同名のartifactのアップロードは上書きとなる
:::

![2025-01-02-16-58-49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/a2bb2d2f-7d69-2f17-cc62-289d6f7bed95.png)

ちなみにworkflowをRe-runすると、ArtifactsはLatestのみとなる

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/bc6e5cbc-26ae-629a-6151-ba6757c3ccae.png)

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/8361dcd7-f3f4-66f6-d497-96ca5242f88b.png)

別途artifactのダウンロードのみを行うworkflowを実行しても以下のようにダウンロードはできない

![2025-01-02-14-30-29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/ae9ecebb-ced7-7799-25e1-87c2a222cd93.png)