google-cloud-go の使ってみた操作まとめ

前から Google Cloud Storage には興味があったのでラーメンの写真を記録するサービスを作るのに使ってみました。機能ごとにもリンクを貼りますが、ひとまず公式ドキュメントとリポジトリは以下になります。

**実行環境**
```
go: v1.18
cloud.google.com/go/storage: v1.22.0
```
https://pkg.go.dev/cloud.google.com/go/storage

https://github.com/googleapis/google-cloud-go

※ 全体を通してエラーハンドリングは省略しています。
## 事前準備：クライアントの作成
クライアントの作成に使用するため、事前に GCP のアカウント作成と鍵ファイルをダウンロードしておきます。

今回はダウンロードした鍵ファイルを引数にとり [WithCredentialsFile] を使用します。(https://pkg.go.dev/google.golang.org/api/option#WithCredentialsFile)

```
// これ以降のコードは func main 内のみ記載します
package main

import (
	"context"
	"cloud.google.com/go/storage"
)

func main() {
	ctx := context.Background()
    client, _ := storage.NewClient(ctx, option.WithCredentialsFile("./key.json"))
}
```

公式ドキュメントには

```
The client will use your default application credentials. Clients should be reused instead of created as needed. The methods of Client are safe for concurrent use by multiple goroutines.
クライアントは必要に応じて作成するのではなく、再利用することが望ましいです。Clientのメソッドは、複数のゴルーチンによる同時使用にも安全です。
```

とあるため、~~本記事では都合上毎度定義し直してますが~~ どこか一箇所で定義して使い回すのがよさそうです。
`複数のゴルーチンによる同時使用にも安全です。` とあるのは context を引数としてるからですかね。~~あまり context を理解していない~~

https://pkg.go.dev/cloud.google.com/go/storage#hdr-Creating_a_Client

## バケットの作成

バケットの作成は [BucketHandle.Create](https://pkg.go.dev/cloud.google.com/go/storage#BucketHandle.Create) を使用します。
`bucket-name` は作成したいバケット名で、 `my-project` はバケットを作りたいプロジェクト名（実在するもの）です。

```
ctx := context.Background()
client, _ := storage.NewClient(ctx)	
client.Bucket("bucket-name").Create(ctx, "my-project", nil)
```

~~余談ですが、~~ `client.Project("my-project").CreateBucket(ctx, "bucket-name")` みたいな書き方の方が個人的にはしっくりくるので、できそうでしたら教えてください🙇‍♂️

## ファイルのアップロード
ファイルアップロード（オブジェクトの作成）には [BucketHandle.Create](https://pkg.go.dev/cloud.google.com/go/storage#ObjectHandle.NewWriter
) を使用します。

```
// 適当なファイルを作成
f, _ := os.Create("sample.txt")
// file-name はオブジェクトに付けたい名前
writer := user.Client.Bucket("bucket-name").Object("file-name").NewWriter(ctx)
io.Copy(writer, e.File)
writer.Close()
```

`file-name` は `folder/file.txt` のようにパス構造にする事で階層を持ってアップロードする事ができます。

※1 **`file-name` をパス構造として階層にすることはあまりお勧めしません。**
詳しくはディレクトリトラバーサルと検索ください。

※2 オブジェクトの更新に[ObjectHandle.Update](https://pkg.go.dev/cloud.google.com/go/storage#ObjectHandle.Update) がありましたが、[BucketHandle.Create](https://pkg.go.dev/cloud.google.com/go/storage#ObjectHandle.NewWriter
) が UPSERT （同じ `file-name`　のオブジェクトなければ作成、あれば更新）のような役割をしていたため今回は使用してません。個人開発なのでオブジェクトのバージョンとかも気にする必要はありませんし。
パフォーマンスなどで明らかに優れる点がありましたら教えてください。

## オブジェクトの取得

オブジェクトの取得には [ObjectHandle.NewReader](https://pkg.go.dev/cloud.google.com/go/storage#ObjectHandle.NewReader) を使用します。

```
obj := user.Client.Bucket("bucket-name").Object("file-name")
rc, _ := obj.NewReader(ctx)
buf := new(bytes.Buffer)
io.Copy(buf, rc);
```

特定の条件のオブジェクトの情報を取得したい場合は [BucketHandle.Objects](https://pkg.go.dev/cloud.google.com/go/storage#BucketHandle.Objects) を使用するようです。
```
ctx := context.Background()
client, _ := storage.NewClient(ctx)
it := client.Bucket("my-bucket").Objects(ctx, nil)
```

`BucketHandle.Objects` の第二引数へは [Query](https://pkg.go.dev/cloud.google.com/go/storage#Query) を渡す事ができ、取得するオブジェクトを絞り込めるようです。（`nil` でバケット配下を全て取得します）
たとえば `folder` 配下のオブジェクトの情報([ObjectIterator](https://pkg.go.dev/cloud.google.com/go/storage#ObjectIterator))のみを取得する場合は以下のようになります。

```
ctx := context.Background()
client, _ := storage.NewClient(ctx)
query := &storage.Query{
    StartOffset: "folder/"
}
it := client.Bucket("my-bucket").Objects(ctx, query)
```

この `ObjectIterator` からそれぞれのオブジェクト名（[ObjectAttrs.name](https://pkg.go.dev/cloud.google.com/go/storage#ObjectAttrs) を取得して、`ObjectHandle.NewReader`を取得します。

（より良いやり方ありましたら教えていただければと思います🙇‍♂️）

以下の公式ドキュメントも参考にいただければと思います。

https://pkg.go.dev/cloud.google.com/go/storage#hdr-Listing_objects

## オブジェクトの削除

オブジェクトの削除には [BucketHandle.Delete](https://pkg.go.dev/cloud.google.com/go/storage#BucketHandle.Delete) を使用します。

```
ctx := context.Background()
client, _ := storage.NewClient(ctx)
client.Bucket("my-bucket").Delete(ctx)
```

## 最後に

今回は基本的な操作に関してのみ記載いたしました。今後気になる機能があったら追記等々行っていきたいと思います。
最後までお付き合いいただきありがとうございました🙇‍♂️

## 参考リンク

https://qiita.com/greenteabiscuit/items/727b8672b0bdd47e2682

https://dev.classmethod.jp/articles/golang-write-gcs/