新しい Google Cloud Client Libraries で Storage にファイルをアップロードする

Google Cloud Platform はいろいろな API が提供されています。そんな中 [ユーザビリティが向上した新しい Cloud Client Libraries](https://cloudplatform-jp.googleblog.com/2016/12/cloud-client-libraries.html) という記事がありました。

従来のライブラリは、REST JSON API をそのまま薄くラップしたものでしたが、新しいライブラリはもうちょっと高レベルになっているようです。

今回は、ファイルを Google Cloud Storage に一般公開でアップロードして、ブラウザで閲覧できるようにするまで Go 言語でやってみます。

Go のライブラリのリポジトリはこちら:
[github.com/GoogleCloudPlatform/google-cloud-go](https://github.com/GoogleCloudPlatform/google-cloud-go)

## Storageのバケットを作成

Webのコンソールから作ります。

<img width="593" src="https://qiita-image-store.s3.amazonaws.com/0/21341/b5bc92f5-5441-69e9-b441-173a115e35dd.png">

asia-northeast1 がありますね！

## Cloud Toolsのインストール

Cloud Tools はなくともライブラリは使えるのですが、今回は認可の情報をセットアップするために使います。

こちらを参考に、Cloud Toolsをインストールします。
https://cloud.google.com/sdk/docs/

インストール後の `init` でブラウザが表示されるので、自分自身のGoogleアカウントにログインし、認可します。

<img width="526" src="https://qiita-image-store.s3.amazonaws.com/0/21341/d8a21155-8236-997d-18f5-e2b9ec8243b8.png">

許可するとマシンに認可の情報が保存されるようです。

この記事を書いている時点でのツールのバージョンは以下の通り。

```
> google-cloud-sdk/bin/gcloud -v
Google Cloud SDK 138.0.0
bq 2.0.24
bq-nix 2.0.24
core 2016.12.09
core-nix 2016.11.07
gcloud
gsutil 4.22
gsutil-nix 4.20
```

## 認可

`gcloud` コマンドでの認可ができていれば、そのマシン内の所定の位置に認可の情報が保存されています。(おそらく `$HOME/.config/gcloud` ディレクトリ内にある)

## Client

コード上で認可に関して特に指定しなかった場合、ライブラリはマシン上の認可の情報を使ってアクセスしてくれます。単純にClientを作ればOK。

```go
ctx := context.Background()
client, err := storage.NewClient(ctx)
```

## Writer

今回は書き込みなので、Bucket > Object > Writer という流れでWriterを取得します。

```go
bucket := client.Bucket(your_bucket_name)
object := bucket.Object(your_file_name)
writer := object.NewWriter(ctx)
```

ブラウザで表示するために、`ContentType: text/plain` に、またACLを「すべてのユーザー」が「読み取り」できるように設定します。ついでにキャッシュなしにしています。

```go
writer.ObjectAttrs.ContentType = "text/plain"
writer.ObjectAttrs.CacheControl = "no-cache"
writer.ObjectAttrs.ACL = []storage.ACLRule{
	storage.ACLRule{
		Entity: storage.AllUsers,
		Role:   storage.RoleReader,
	},
}
```

## 書き込む

ファイルを読み込んで、それを `Writer` に書き込みます。標準の `io.Writer` なので、普通に書き込むだけです。ここでは `io.Copy` でファイルの内容をそのままコピーしています。

```go
// アップロードしたいファイルを開く
f, err := os.Open("main.go") // 今回は自分自身
if err != nil {
	return err
}
defer f.Close()

// コピーで writer に対して書き込む
if _, err = io.Copy(writer, f); err != nil {
	return err
}
```

## アップロード

Write中にアップロードは開始されており、特に何かすることはありません。Closeするとアップロードが完了します。

```go
if err := writer.Close(); err != nil {
	return err
}
```

ライブラリの内部を見ると、初回の `Write` 時に API のアップロードを別の goroutine で開始しています。この時 `io.Pipe` が使われていて、アプリ側の Writer が API アップローダー の Reader へ接続されています。

## ブラウザで見てみる

`https://storage.googleapis.com/{yoru-bucket-name}/{yoru-file-name}` にアクセスして閲覧できます。

## コード全体

```go
package main

import (
	"io"
	"log"
	"os"

	"cloud.google.com/go/storage"
	"golang.org/x/net/context"
)

func main() {
	ctx := context.Background()
	client, err := storage.NewClient(ctx)
	if err != nil {
		log.Fatal(err)
	}

	f, err := os.Open("main.go")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	bucket := client.Bucket("najeira-test")
	object := bucket.Object("cloud_storage_test.go")
	writer := object.NewWriter(ctx)

	writer.ObjectAttrs.ContentType = "text/plain"
	writer.ObjectAttrs.CacheControl = "no-cache"
	writer.ObjectAttrs.ACL = []storage.ACLRule{
		storage.ACLRule{
			Entity: storage.AllUsers,
			Role:   storage.RoleReader,
		},
	}

	if _, err = io.Copy(writer, f); err != nil {
		log.Fatal(err)
	}

	if err := writer.Close(); err != nil {
		log.Fatal(err)
	}

	attr := writer.Attrs()
	log.Printf("https://storage.googleapis.com/%s/%s", attr.Bucket, attr.Name)
}
```