IBM Cloud Functions を使って IBM Cloud Object Storage (ICOS) オブジェクトの Content-Type を自動修正する


# 概要
ICOS にオブジェクトをアップロードすると、特に何もしなければ `Content-Type` が `application/octet-stream` に設定され、ただのバイナリ扱いになります。
今回はサーバーレスサービスを使って、アップロードされるオブジェクトの拡張子から適切な `Content-Type` に自動修正します。
この操作を行うことで、ブラウザからアクセスするときに画像や HTML などが正しく認識され、コンテンツが正しく表示されます。

`Content-Type` の修正には、「[Object operations - Copy an object](https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-object-operations#object-operations-copy)」（ICOS サーバーサイドでのコピー処理）を活用し、Functions （クライアント）に一度ダウンロードして再アップロードするといった動作はさせません
また、Functions からは ICOS プライベートエンドポイントを経由してオペレーションがおこなえます。

![Kobito.gTVsxZ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/b41f0724-a269-c049-0edf-247ef05b8216.png "Kobito.gTVsxZ.png")


# 事前準備

## CLI プラグインインストール

```shell:

ibmcloud plugin install cloud-functions
ibmcloud plugin show cloud-functions
```

## CLI ログイン

```shell:

export REGION="jp-tok"
export RESOURCE_GROUP="khayama-rg"
ibmcloud login -a cloud.ibm.com -r $REGION -g $RESOURCE_GROUP
```
## 環境設定

```shell:

export FN_NAMESPACE="khayama-fn"
export COS_INSTANCE_NAME="khayama-cos" # 既存の ICOS インスタンスを使います
```

## Functions Namespace 設定
`namespace` を作成してプロパティをセットします。

```shell:

ibmcloud fn namespace create $FN_NAMESPACE
ibmcloud fn namespace target $FN_NAMESPACE
```

## Functions から ICOS へのアクセスを許可
ICOS の変更通知を受信してトリガーできるように以下の許可設定をおこないます。

- [Using authorizations to grant access between services](https://cloud.ibm.com/docs/iam?topic=iam-serviceauth#auth-cli)

```shell:

ibmcloud iam authorization-policy-create \
functions \
--source-service-instance-name $FN_NAMESPACE \
cloud-object-storage \
--target-service-instance-name $COS_INSTANCE_NAME \
"Notifications Manager" 
```

## Functions ServiceID に Writer 権限を付与
`namespace` を作成すると、ServiceID (API Key) が自動で発行されます。
Functions では、`__OW_IAM_NAMESPACE_API_KEY` の環境変数にセットされるので、これを使って他のサービス認証をおこなうことができます。 

- [Managing namespaces](https://cloud.ibm.com/docs/openwhisk?topic=openwhisk-namespaces#what-happens-when-i-create-a-namespace-)

その ServiceID (API Key) に Writer 権限を付与して Functions からオブジェクトを書き込めるようにします。

- [Bucket permissions - Granting access to a service ID](https://cloud.ibm.com/docs/cloud-object-storage/iam?topic=cloud-object-storage-iam-bucket-permissions&locale=en#iam-service-id)

```shell:

export COS_INSTANCE_ID=$(ibmcloud resource service-instance --output JSON $COS_INSTANCE_NAME | jq -r '.[].guid')
echo $COS_INSTANCE_ID
```

```shell:

ibmcloud iam service-id-unlock $FN_NAMESPACE -f

ibmcloud iam service-policy-create $FN_NAMESPACE \
      --roles "Writer" \
      --service-name cloud-object-storage \
      --service-instance $COS_INSTANCE_ID 

ibmcloud iam service-id-lock $FN_NAMESPACE -f
```

## GitHub からダウンロード
GitHub にあるファイルをダウンロードします。

- [khayama/fn-setCosContentType](https://github.com/khayama/fn-setCosContentType)

```shell:

git clone https://github.com/khayama/fn-setCosContentType.git
cd fn-setCosContentType
```

今回の Functions デプロイ用 yaml では以下のように定義しています。
Functions 実行パラメータは、環境変数の値を取得して埋め込む形で定義しています。

```yaml:setCosContentType.yml
packages:
  setCosContentType:
    version: 1.0
    license: Apache-2.0
    actions:
      setCosContentType:
        version: 1.0
        description: replace with reference content type
        docker: openwhisk/dockerskeleton
        function: setCosContentType.sh
        inputs:

    triggers:
      cos-object-write:
        feed: /whisk.system/cos/changes
        inputs:
          bucket: $MY_BUCKET # The COS bucket must be regional and exist in the same location as the namespace
          event_types: write # https://cloud.ibm.com/docs/openwhisk?topic=openwhisk-pkg_obstorage#pkg_obstorage_ev_ch_ref_trig

    rules:
      cos-object-write-setCosContentType:
        description: Action is triggered by object-write operation
        action: setCosContentType
        trigger: cos-object-write
```

# Functions デプロイ

## 環境変数セット
以下のように環境変数をセットします。

```shell:

export MY_BUCKET=khayama-mime
```

## yaml ファイルを指定してデプロイ

```shell:

ibmcloud fn deploy --manifest setCosContentType.yml
# 削除するには ibmcloud fn undeploy --manifest setCosContentType.yml
```

# 動作確認
実際に ICOS にファイルアップロードをおこない、Functions がトリガーされたかは以下の `Monitor` 画面で確認できます。

- 1回のオブジェクトアップロードで2回トリガーが起動したことが確認できます。
- アクションのスクリプト内で、 `REF_CONTENT_TYPE` と照合し、修正済みかどうかを判断した上で処理が実行されます。
- そのため、1 度目のアクションで `Content-Type` が修正され、その書き込みによって 2 度目が起動しますが、処理は実行されず終了します。
- `REF_CONTENT_TYPE` に存在しない拡張子については、スクリプト内で何も実行されません。

https://cloud.ibm.com/functions/dashboard
![貼り付けた画像_2020_09_24_10_17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/68e4f45c-dece-af8d-060e-aca680135e56.png "貼り付けた画像_2020_09_24_10_17.png")


# Content-Type を確認する
AWS CLI を使ってオブジェクトとコンテンツタイプの一覧を確認できます。

```shell:

export BUCKET_NAME=khayama-mime
export ENDPOINT=s3.jp-tok.cloud-object-storage.appdomain.cloud

for KEY in $(aws --endpoint-url https://$ENDPOINT s3api list-objects \
  --bucket $BUCKET_NAME \
  --query "Contents[].[Key]" \
  --output text)
do
  aws --endpoint-url https://$ENDPOINT s3api head-object \
    --bucket $BUCKET_NAME \
    --key $KEY \
    --query "[\`$KEY\`,ContentType]" \
    --output text
done
```

```shell:result

5MB.zip application/zip
ibmcloud_logo.png       image/png
index.html      text/html
```

# 参考

- [file-extension-to-mime-types.json](https://gist.github.com/AshHeskes/6038140)
- [【小ネタ】S3オブジェクトのContent-TypeをAWS CLIで一括で変更する | Developers.IO](https://dev.classmethod.jp/articles/change-content-type-by-aws-cli/)