michimani.net

AWS SDK for Go で S3 のメタデータを取得するとキーが大文字だった

2020-06-30

AWS SDK for Go で S3 バケットの格納されたオブジェクトのメタデータを取得しようとしたところ、メタデータのキーが大文字 (先頭とハイフンの直後) になっていました。…という話です。

目次

オブジェクトの作成

まずは適当なバケットにオブジェクトを作成します。

$ cat ./sample.json
{
  "text": "This is a sample JSON."
}

$ aws s3api put-object \
--bucket s3-metadata-test-michimani \
--key sample.json \
--body ./sample.json \
--metadata custom-metadata-1=value1,custom-metadata-2=value2,Custom-Metadata-3=value3 \
--cache-control "public, max-age=1209600" \
--content-type "application/json;charset=UTF-8"
{
    "ETag": "\"2670dbe970a150ec5f0d4b0e6b9a69cc\""
}

ちなみに、 --metadata オプションで大文字、小文字を変えて指定していますが、これは無視されます。

マネジメントコンソールで確認すると、次のようになっています。

S3 object metadata

HeadObject API でオブジェクトの情報を取得

S3 の HeadObject API を使用して、オブジェクトのメタデータを取得します。
今回は SDK for Go 以外に、 AWS CLI と SDK for Python (boto3) でも試してみます。

CLI で取得してみる

まずは AWS CLI で取得してみます。

$ aws --version
aws-cli/2.0.26 Python/3.7.4 Darwin/19.5.0 botocore/2.0.0dev30

$ aws s3api head-object \
--bucket s3-metadata-test-michimani \
--key sample.json \
--query "Metadata"
{
    "custom-metadata-1": "value1",
    "custom-metadata-2": "value2",
    "custom-metadata-3": "value3"
}

SDK for Python (boto3) で取得してみる

次に、 AWS SDK for Python (boto3) で取得してみます。

$ python -V
Python 3.7.7

取得するためのコードは次のとおりです。

import boto3

target_bucket = 's3-metadata-test-michimani'
target_key = 'sample.json'
s3 = boto3.client('s3')


def print_s3_metadata(bucket, key):
    out_object = s3.head_object(
        Bucket=bucket,
        Key=key
    )

    for key in out_object['Metadata']:
        val = out_object['Metadata'][key]
        print(f'{key} : {val}')


if __name__ == '__main__':
    print_s3_metadata(target_bucket, target_key)

実行してみると

$ python python/main.py
custom-metadata-1 : value1
custom-metadata-2 : value2
custom-metadata-3 : value3

boto3 の場合はメタデータのキーは小文字です。

SDK for Go で取得してみる

最後に、 AWS SDK for Go を使って取得してみます。

$ go version
go version go1.14.4 darwin/amd64

取得するためのコードは次のとおりです。

package main

import (
	"fmt"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
)

var s3sess = s3.New(session.New(), &aws.Config{
	Region: aws.String("ap-northeast-1"),
})
var targetBucket string = "s3-metadata-test-michimani"
var targetKey string = "sample.json"

// PrintS3Metadata is a function to print S3 metadata.
func PrintS3Metadata(bucket string, key string) {
	outObject, _ := s3sess.HeadObject(&s3.HeadObjectInput{
		Bucket: aws.String(bucket),
		Key:    aws.String(key),
	})

	for key, val := range outObject.Metadata {
		fmt.Printf("%s : %s\n", key, *val)
	}
}

func main() {
	PrintS3Metadata(targetBucket, targetKey)
}

実行してみると

$ go run go/main.go
Custom-Metadata-3 : value3
Custom-Metadata-1 : value1
Custom-Metadata-2 : value2

という感じで、キーの先頭の文字、およびハイフン直後の文字が大文字になっています。これについては、ドキュメントに次のように書かれています。

By default unmarshaled keys are written as a map keys in following canonicalized format: the first letter and any letter following a hyphen will be capitalized, and the rest as lowercase.

また、次のようにも書かれていました。

Set `aws.Config.LowerCaseHeaderMaps` to `true` to write unmarshaled keys to the map as lowercase.

これに従ってコードを次のように変更しました。

  var s3sess = s3.New(session.New(), &aws.Config{
- 	Region: aws.String("ap-northeast-1"),
+ 	Region:              aws.String("ap-northeast-1"),
+ 	LowerCaseHeaderMaps: aws.Bool(true),
  })

これで再度実行してみましたが、

$ go run go/main.go
Custom-Metadata-3 : value3
Custom-Metadata-1 : value1
Custom-Metadata-2 : value2

結果は変わりませんでした。(順番が違うのは Go 言語の map の仕様です。)

この aws.Config.LowerCaseHeaderMaps の設定による挙動の違いについては、残念ながら調べてみてもそれらしい情報が得られませんでした。もしこの記事をご覧の方でご存じの方がいらっしゃればコメント等で教えていただけると幸いです。

まとめ

AWS SDK for Go で S3 バケットの格納されたオブジェクトのメタデータを取得しようとしたところ、メタデータのキーが大文字になっているという話でした。

SDK for Go でメタデータをゴニョゴニョするときに少しハマったので書きましたが、こうなっているので気をつけましょうというくらいの薄い内容です。 aws.Config.LowerCaseHeaderMaps の挙動についてはよくわからなかったので、もしご存じの方がいらっしゃればコメントいただけると幸いです!


comments powered by Disqus