GitHub Action で AWS S3 にアップロード

[MoAR](https://whatever.co/moar/) で使用しているモデルデータやテクスチャはコードと一緒に GitHub 上で管理されています。これらのデータは GitHub に push されるとそのタイミングで GitHub Action によって AWS の S3 へ自動的にアップロードされます。MoAR アプリは起動後に S3 からデータをロードしてコンテンツが表示されます。

workflow はこんな感じ（いぶくん (@asus4) 作

```yaml
name: Deploy to staging
on:
  push:
    branches:
      - main
    paths:
      - RemoteResources/**
  workflow_dispatch:
jobs:
  deploy:
    name: Upload to Staging AWS S3
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
      with:
        lfs: true
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ${{ secrets.AWS_REGION }}
    - name: Deploy
      run: |
        aws s3 sync ./RemoteResources s3://${{secrets.AWS_S3_BUCKET_STG}}
```

`RemoteRecources` ディレクトリ以下にモデル＆テクスチャデータが入ってるので `actions/checkout` でチェックアウトして [`aws-actions/configure-aws-credentials`](https://github.com/aws-actions/configure-aws-credentials) で credential 設定して `ubuntu-latest` にはデフォルトでインストールされてる AWS CLI で `aws s3 sync` するだけ。簡単。

# USDZ を gzip 圧縮する

MoAR のモデルデータは USDZ 形式で保存されています。（このへんのはなしは @kidapu が書いてくれるはず…

USDZ っていうくらいなので ZIP 圧縮されてると思いきや、

>A usdz package is a zero compression, unencrypted zip archive.

https://graphics.pixar.com/usd/release/spec_usdz.html#zip-constraints

無圧縮で1アーカイブにまとめられているだけ… なぜ無圧縮なのかってのは↑のドキュメントにも書いてあるんだけど展開時に新たなメモリ消費することなく直接 `mmap` でデータ読めるようにということっぽい。（テクスチャはすでに圧縮フォーマットになってるから非圧縮なのはそんなにシリアスな問題じゃないとも…

とはいえ、ランタイムにネットワーク経由でデータを読んでくる MoAR の場合ファイルサイズが小さいにこしたこたーないわけで、実際に MoAR で使ってる一番大きい USDZ ファイルを gzip 圧縮してみると
![Screen_Shot_2022-12-10_at_14_20_30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2595/60b56072-9c7b-3cdb-cf39-34ae76ac2c30.png)
**半分‼️**

ということで S3 で gzip 配信することにしました。

```yaml
name: Deploy to staging
on:
  push:
    branches:
      - main
    paths:
      - RemoteResources/**
  workflow_dispatch:
jobs:
  deploy:
    name: Upload to Staging AWS S3
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
      with:
        lfs: true
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ${{ secrets.AWS_REGION }}
    - name: Deploy other than usdz and json
      run: |
        aws s3 sync --exclude "*.usdz" --exclude "*.json" ./RemoteResources s3://${{secrets.AWS_S3_BUCKET_STG}}
    - name: Deploy usdz with gzip
      run: |
        find RemoteResources -name "*.usdz" -exec sh -c "x={}; gzip --best -c {} | aws s3 cp - s3://${{secrets.AWS_S3_BUCKET_STG}}/\${x#RemoteResources/} --content-encoding \"gzip\" --content-type \"model/vnd.usd+zip\"" \; -print
    - name: Deploy json with gzip
      run: |
        find RemoteResources -name "*.json" -exec sh -c "x={}; gzip --best -c {} | aws s3 cp - s3://${{secrets.AWS_S3_BUCKET_STG}}/\${x#RemoteResources/} --content-encoding \"gzip\" --content-type \"application/json\"" \; -print
```

1. `aws s3 sync --exclude "*.usdz"` で USDZ ファイル以外を sync。
1. 次に `find RemoteResources -name "*.usdz"` で USDZ ファイルを1つずつ処理。
1. `gzip --best -c {} | aws s3 cp -` で `gzip` の結果を標準出力に出して `aws` コマンドが標準入力から読んでアップロード。
1. と同時に `--content-encoding "gzip"` で Content-encoding 指定して、ついでに Content-type も指定。（なくてもいい？

# JSON も gzip 必要？

↑の workflow では USDZ だけじゃなくて JSON ファイルも同様に gzip 圧縮してます。
JSON ってそんな圧縮必要なほどでかくなるんか？と思うかもなのだけど [Ghost Building](https://whatever.co/moar/ja/ghost_building/) の UI アニメーションに使用している [Lottie](https://lottiefiles.com/) のアニメーションファイルはビットマップも JSON に Base64 エンコードで含められるので意外と大きくなる。
![Screen Shot 2022-12-10 at 14.37.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2595/3afdf489-a0a2-75d2-43df-c9453858ca6e.png)
**半分‼️**

:::note info
[.Lottie](https://dotlottie.io/) というフォーマットを使うと ZIP ファイルとして必要なアセットをまとめることができるので gzip 圧縮不要かも。（時間切れで実装できなかった
:::

# ステージング環境と本番環境

↑にでてきた workflow は開発時用なのでデータが push されたらステージング環境に sync されるようになっている。
アプリリリース時だけ本番環境へアップロードされるように別の workflow を用意する。

```yaml
name: Deploy to production S3
on:
  release:
    types: [created]
  workflow_dispatch:
```

トリガーを新しいリリースが作成されたタイミングにしてあとは同じ。（バケットが違うだけ

# 自動化するとらくちん

GitHub のコードを push すると自動で Xcode Cloud でビルドされて（[8日目の記事](https://zenn.dev/asus4/articles/f9b92f9ff50576)）、データを push すると自動で S3 にアップロードされるのでめっちゃ楽だしミスも発生しづらいのでおすすめ。