Denoでaws-sdk-js-v3を利用してMinIOの操作を行う

- 開発時におけるS3互換ストレージとして[MinIO](https://min.io/)を利用しています。
- 互換のため[AWS SDK](https://aws.amazon.com/jp/what-is/sdk/)を利用することも可能であり、本番時と開発時では接続情報を変えるだけで良いので非常に便利です。
- 今回はDenoで[aws-sdk-js-v3](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/index.html)を利用してMinIOの操作を行う方法を記録いたします。

## 環境
- macOS 13.0
- deno 1.34.0
- Docker version 24.0.2
    - **※MinIO構築用**
- Docker Compose version v2.18.1
    - **※MinIO構築用**

## 手順

### MinIO環境の構築
- まずファイル群を入れるためのディレクトリを任意で作成します。

```shell
mkdir ~/work/deno-minio-sample
cd ~/work/deno-minio-sample
```

- 次に必要となるディレクトリ及びファイルを作成します。

```shell
# コンテナ起動時に作成されるバケットに入れる初期データファイルを格納するディレクトリ
mkdir init_data

# コンテナ起動のための設定ファイル
touch docker-compose.yml

# MinIO情報ファイル
touch .env
```

- 作成後、以下の内容を`docker-compose.yml`に記述します。

```yaml
version: '3.8'
services:
  minio:
    image: quay.io/minio/minio:latest
    container_name: minio
    environment:
      MINIO_ROOT_USER: ${MINIO_ROOT_USER}
      MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
    ports:
      - '9000:9000'
      - '9001:9001'
    restart: unless-stopped
    command: ['server', '/data', '--console-address', ':9001']
    volumes:
      - minio_data:/data
  mc:
    image: minio/mc
    container_name: mc
    depends_on:
      - minio
    environment:
      MINIO_ROOT_USER: ${MINIO_ROOT_USER}
      MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
    entrypoint:
      - "/bin/sh"
      - "-c"
    command:
      - "mc alias set myminio http://minio:9000 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD;
         mc mb myminio/sample;
         mc cp init_data/* myminio/sample;
         exit 0;"
    volumes:
      - ./init_data:/init_data
volumes:
  minio_data:
```

- `.env`にMinIOのエンドポイントやパスワード等を記述します。
    - **※MINIO_ROOT_USERは3文字以上、MINIO_ROOT_PASSWORDは8文字以上である必要があります。**

```env
MINIO_ENDPOINT=http://localhost:9000
MINIO_ROOT_USER=test
MINIO_ROOT_PASSWORD=testpass
```

- 起動時に作成されるバケットに初期オブジェクトを流すために`init_data`ディレクトリにデータファイルを追加いたします。

```shell
# init_dataディレクトリにfirst.csvとsecond.csvを追加
$ echo "aaa" > init_data/{first,second}.txt

# ファイル一覧
$ ls init_data
first.txt   second.txt

# 中身
cat init_data/first.txt
aaa
```

- 必要なファイルが揃ったので、以下のコマンドでコンテナを起動します。

```shell
docker compose up -d
```

- 起動後、`localhost:9000`にアクセスしてMinIOのログイン画面が表示されることを確認します。

![minio_login.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336499/45704aaf-f92e-2826-55a3-baebaffe594f.png)

- ログイン画面確認後、`.env`で指定した値でログインできることを確認します。

- ログイン後、既に以下のバケットやオブジェクトが作成されていることを確認します。
    - **バケット** : sample
    - **オブジェクト** : first.txt,second.txt

![minio_init_bucket.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336499/066c287f-ba56-cde3-e29c-aee35a31831d.png)

![minio_init_object.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336499/fda303ae-9367-0adc-2dcd-c05e7aa75c80.png)

- 既に作成されている理由として、`docker-compose.yml`内で指定しているようにMinIOをコマンドラインで操作可能な[mc](https://github.com/minio/mc)が扱えるコンテナを立ち上げ、その起動時に「バケット作成やオブジェクト追加」のmcコマンドを自動実行しているからです。
    - **MinIO環境のみの立ち上げであれば、バケットやオブジェクトは存在しません。**

### Denoでの動作確認
- バケットやオブジェクトの用意が完了したので、DenoでMinIOを操作していきます。
- 今回はDenoでのAWS SDKの使い方なので、扱う操作は**オブジェクト一覧取得のみ**とします。
- 以下で`app.ts`を作成します。

```shell
touch app.ts
```

- 以下の内容を`app.ts`に記述します。

```typescript
import "https://deno.land/std@0.190.0/dotenv/load.ts";
import { S3 } from "npm:@aws-sdk/client-s3";

// クライアント生成
const minioClient = new S3({
  region: "ap-northeast-1",
  endpoint: Deno.env.get("MINIO_ENDPOINT") || "",
  forcePathStyle: true, // MinIO利用時には必要そう。
  credentials: {
    accessKeyId: Deno.env.get("MINIO_ROOT_USER") || "",
    secretAccessKey: Deno.env.get("MINIO_ROOT_PASSWORD") || "",
  },
});

// sampleバケット内のオブジェクト情報取得
const objects = await minioClient.listObjectsV2({ Bucket: "sample" });
const contents = objects.Contents;
if (!contents) {
  console.log("バケット内が空です。");
  Deno.exit(1);
}

// オブジェクト情報を一覧として整形
const outputs = contents.map((content) => {
  return {
    name: content.Key, // オブジェクト名
    size: content.Size, // オブジェクトサイズ
    lastModified: content.LastModified, // 更新日時
  };
});

// 出力
console.log(outputs);
```

- 以下のように、実行したら整形したオブジェクト一覧が正常に出力されAWS SDKの機能が利用できていることを確認します。

```shell
$ deno run --allow-read --allow-env --allow-sys app.ts

[
  {
    name: "first.txt",
    size: 4,
    lastModified: 2023-06-xxTxx:xx:xx.xxxZ
  },
  {
    name: "second.txt",
    size: 4,
    lastModified: 2023-06-xxTxx:xx:xx.xxxZ
  }
]
```

- またバージョン1.34から対応した[npmパッケージのcompileサポート](https://deno.com/blog/v1.34)も以下のように確認できます。

```shell
# コンパイル。appという実行ファイルが作成される
$ deno compile --allow-read --allow-env --allow-sys --unstable app.ts

# 実行。上記のdeno runと同様の出力
$ ./app
```

- 以上です。

## まとめ
- MinIOを利用することでローカルでS3互換操作が可能なので非常に便利です。
- Denoではnpmパッケージもサポートしており、またバージョン1.34からは[compileにも対応](https://deno.com/blog/v1.34)したので更に活用できます。

## 参考
- https://github.com/minio/minio
- https://github.com/minio/mc
- https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-s3/