📝

GitHub Actionsのアーティファクトは上書きできる

2023/04/30に公開

同一のアーティファクトに複数回上書きできる

タイトルのとおりです。これはactions/upload-artifactのREADMEにも書かれているのですが、自分は全然知りませんでした。
https://github.com/actions/upload-artifact#uploading-to-the-same-artifact

今回はこの上書きの挙動をいろいろ確認してみようと思います。

同一ジョブ内で上書きする

まずは同一ジョブ内で同一アーティファクトを複数回上書きしてみます。以下はactions/upload-artifactのREADMEにある例にアーティファクトの中身を確認するジョブを付け足したものです。

ちなみに、actions/upload-artifactではnameパラメータを省略するとartifactという名前にフォールバックされますが、actions/download-artifactでnameパラメータを省略すると全てのアーティファクトをダウンロードするという意味になります。注意しましょう。

name: Example workflow
on: push
jobs:
  overwrite-artifact:
    runs-on: ubuntu-latest
    steps:
      - run: echo hi > world.txt
      - uses: actions/upload-artifact@v3
        with:
          path: world.txt
      - run: echo howdy > extra-file.txt
      - uses: actions/upload-artifact@v3
        with:
          path: extra-file.txt
      - run: echo hello > world.txt
      - uses: actions/upload-artifact@v3
        with:
          path: world.txt
  check-artifact:
    needs: overwrite-artifact
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v3
        with:
          name: artifact
      - run: |
          echo "$ ls"
          ls
          echo "$ cat world.txt"
          cat world.txt
          echo "$ cat extra-file.txt"
          cat extra-file.txt

check-artifactジョブの最後のステップの実行結果は以下のようになります。

$ ls
extra-file.txt
world.txt
$ cat world.txt
hello
$ cat extra-file.txt
howdy

ご覧の通り、別々のステップでアップロードしたextra-file.txt, world.txtが両方とも入っていて、world.txtは後からアップロードされた内容で上書きされているのがわかります。

複数ジョブから上書きする

では、複数ジョブから上書きするとどうなるでしょうか。READMEには以下のように書かれています。

Warning: Be careful when uploading to the same artifact via multiple jobs as artifacts may become corrupted. When uploading a file with an identical name and path in multiple jobs, uploads may fail with 503 errors due to conflicting uploads happening at the same time. Ensure uploads to identical locations to not interfere with each other.

要約すると、複数のジョブから同名ファイルを上書きすると競合が発生し、503エラーでアップロードが失敗する可能性があるので注意せよということのようです。
逆に言うと、同名のファイルを書き込まないよう注意すれば複数ジョブからアーティファクトを上書きできるということに見えます。やってみましょう。

以下のようなワークフローを用意しました。

name: Example workflow
on: push
jobs:
  job-a:
    runs-on: ubuntu-latest
    steps:
      - run: echo hello > world.txt
      - uses: actions/upload-artifact@v3
        with:
          path: world.txt
  job-b:
    runs-on: ubuntu-latest
    steps:
      - run: echo howdy > extra-file.txt
      - uses: actions/upload-artifact@v3
        with:
          path: extra-file.txt
  check-artifact:
    needs:
      - job-a
      - job-b
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v3
        with:
          name: artifact
      - run: |
          echo "$ ls"
          ls
          echo "$ cat world.txt"
          cat world.txt
          echo "$ cat extra-file.txt"
          cat extra-file.txt

実行結果は以下のようになります。

$ ls
extra-file.txt
world.txt
$ cat world.txt
hello
$ cat extra-file.txt
howdy

マトリクスジョブから上書きする

なお、マトリクスジョブでも同様に上書きできます。

name: Example workflow
on: push
jobs:
  overwrite-artifact:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        name: [a, b]
    steps:
      - run: echo ${{ matrix.name }} > ${{ matrix.name }}.txt
      - uses: actions/upload-artifact@v3
        with:
          path: ${{ matrix.name }}.txt
  check-artifact:
    needs: overwrite-artifact
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v3
        with:
          name: artifact
      - run: |
          echo "$ ls"
          ls

結果は以下のとおりです。

$ ls
a.txt
b.txt

おまけ: ディレクトリ構成に注意

さて、アーティファクトを上書きできるとなると、例えば以下のようなことをやりたくなるのではないでしょうか。

name: Example workflow
on: push
jobs:
  output-node-version:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [14, 16, 18]
    steps:
      - uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      - run: |
          mkdir node-${{ matrix.node-version }}
          node -v > node-${{ matrix.node-version }}/version.txt
      - uses: actions/upload-artifact@v3
        with:
          path: node-${{ matrix.node-version }}/version.txt
  check-artifact:
    needs: output-node-version
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v3
        with:
          name: artifact
      - run: |
          echo "$ ls"
          ls

このワークフローはnode-14/version.txt, node-16/version.txt, node-18/version.txtという3つのファイルがアーティファクトに保存されることを期待しています。
しかし、実際には以下のとおりversion.txtという一つのファイルしか保存されていません。(あるいはタイミング次第で503エラーが発生する可能性もあります。)

$ ls
version.txt

これはactions/upload-artifactが指定された全ファイルの最も深い共通の親ディレクトリをルートとしてファイルをアップロードするからです。
これを回避するには、以下のようにルートとしたいディレクトリにダミーファイルを置く方法があります。

name: Example workflow
on: push
jobs:
  output-node-version:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [14, 16, 18]
    steps:
      - uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      - run: |
          mkdir node-${{ matrix.node-version }}
          node -v > node-${{ matrix.node-version }}/version.txt
          touch .dummy-node-${{ matrix.node-version }}
      - uses: actions/upload-artifact@v3
        with:
          path: |
            node-${{ matrix.node-version }}/version.txt
            .dummy-node-${{ matrix.node-version }}
  check-artifact:
    needs: output-node-version
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v3
        with:
          name: artifact
      - run: |
          echo "$ ls -a"
          ls -a

結果は以下のとおりです。

$ ls -a
.
..
.dummy-node-14
.dummy-node-16
.dummy-node-18
node-14
node-16
node-18

正直、あまり綺麗なやり方ではないですね。
以下のissueでroot-directoryパラメータを使って任意のディレクトリをルートとして指定できる機能の要望が出ているので、素直にそちらが使えるようになってくれると嬉しいです。

https://github.com/actions/upload-artifact/issues/344

まとめ

actions/upload-artifactでアーティファクトを上書きする際の挙動をいろいろと確認してみました。
あまり多用すると思わぬ不具合を引き起こしそうな機能ではありますが、大量のアーティファクトが作られるようなワークフローではアーティファクト一覧を整理するためにこの機能を使っていくつかのアーティファクトをまとめてみると便利かもしれません。

また、現在はactions/download-artifactで単一 or 全ての2パターンしかダウンロード対象のアーティファクトを指定する方法が無いため、後続のジョブで使うアーティファクトを一つにまとめておくとactions/download-artifactを複数ステップ呼ばなくて済むので便利です。
ただし、こちらも複数アーティファクトを指定できるようにする機能要望が出ているので、早くそちらが実装されてほしいですね。

https://github.com/actions/download-artifact/issues/6

Discussion