動画ファイルをWeb経由で取得して、そのままCloud Storageにアップロード

# お題
表題の通り。AWSのS3に署名付きURLでアクセスして取得した動画ファイルをそのままCloud Storageにアップロードする要件があったので、事前にちょっとお試しで実装してみる。
今手元に持ってるのがGCPのアカウントだけなので、「S3から署名付きURLで」という部分は端折って、GCPのCloud Storageに別バケット用意してそこに取得対象の動画ファイルを置いて（パーミッション付けて）おくことにする。

# 前提
- GCPの何たるかについては説明しない。
- `gcloud`コマンドの使い方やローカルへのクレデンシャル取得方法などは説明しない。

# 開発環境
## # OS - Linux(Ubuntu)
```
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.2 LTS (Bionic Beaver)"
```

## # 言語 - Go
```
$ go version
go version go1.13.3 linux/amd64
```

## # パッケージマネージャ - Go Modules

## # IDE - Goland
```
GoLand 2019.2.5
Build #GO-192.7142.48, built on November 8, 2019
```

# 実践

## 適当なバケット内にサンプル動画ファイルを準備
※本来の要件的にはAWSのS3に署名付きURLでアクセスだったけど、趣旨としては同じなので。
![screenshot-console.cloud.google.com-2019.12.15-22_05_30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/f2b8dc03-917f-6b8a-6130-c013de10e196.png)
バケット内のオブジェクトに対する（誰でも参照可能にする）公開アクセス制御については以下参照。
https://qiita.com/sky0621/items/60c654fd1817bbf085ae#08なので一般公開してみる

上記からダウンロードした動画ファイル（`sample.mp4`）のアップロード先も作っておく。
![screenshot-console.cloud.google.com-2019.12.15-22_09_08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/6c99cd4b-2fd0-318d-0121-0432f9c6a121.png)
※アップロード前なので当然バケット内は空。

## プログラム

プロジェクト全体は下記。
https://github.com/sky0621/tips-go/tree/568b40bd824d9e0f7bd5eb8bdce07565555768bb/try/mp4_download_and_upload

```go:[cmd/main.go]
package main

import (
	"context"
	"flag"
	"io"
	"log"
	"net/http"

	"github.com/pkg/errors"

	"google.golang.org/api/option"

	"cloud.google.com/go/storage"
)

func main() {
	if err := exec(); err != nil {
		log.Fatal(err)
	}
}

func exec() (e error) {
	credentialPath := flag.String("c", "credential file path", "~/keyfile/gcp.json")
	mpath := flag.String("m", "download url path", "http://localhost:9000/sample.mp4")
	writeBucket := flag.String("b", "write bucket", "http://localhost:7000/bucket")
	objectName := flag.String("o", "object name", "sample.mp4")
	flag.Parse()

	res, err := http.Get(*mpath)
	if err != nil {
		return err
	}
	defer func() {
		e = close(res.Body, e)
	}()

	ctx := context.Background()
	client, err := storage.NewClient(ctx, option.WithCredentialsFile(*credentialPath))
	if err != nil {
		return err
	}
	wc := client.Bucket(*writeBucket).Object(*objectName).NewWriter(ctx)
	if _, err = io.Copy(wc, res.Body); err != nil {
		return err
	}
	defer func() {
		e = closeWriter(wc, e)
	}()

	return nil
}

func close(c io.Closer, e error) error {
	if c == nil {
		return e
	}
	err := c.Close()
	if err == nil {
		return e
	}
	if e == nil {
		return err
	}
	return errors.Wrap(err, e.Error())
}

func closeWriter(w *storage.Writer, e error) error {
	if w == nil {
		return e
	}
	err := w.Close()
	if err == nil {
		return e
	}
	if e == nil {
		return err
	}
	return errors.Wrap(err, e.Error())
}
```

### プチ解説

```go
	res, err := http.Get(*mpath)
```
欲しいものがテキストだろうとバイナリだろうと、これだけで事足りる。

```go
	ctx := context.Background()
	client, err := storage.NewClient(ctx, option.WithCredentialsFile(*credentialPath))
```
あらかじめローカルから自分のGCPプロジェクトのCloud Storageにアクセス可能なクレデンシャルを作ってあるので、そのファイルをオプションとして渡す。

```go
	wc := client.Bucket(*writeBucket).Object(*objectName).NewWriter(ctx)
	if _, err = io.Copy(wc, res.Body); err != nil {
		return err
	}
```
どのバケットにどういうオブジェクト名で書き込むかを指定したら、あとは`http.Get`結果のレスポンスボディを渡すだけ。
※`res.Body`が`io.Reader`インタフェースを実装しているゆえ。

## 試行結果

### アプリ起動

```
$ go run main.go -c=/home/sky0621/gcp/key_file/credential.json -m=https://storage.googleapis.com/download-from-03fa-481a-a325/sample.mp4 -b=upload-to-9618-489b-b030 -o=sample.mp4
```

### 結果
アップロード先バケット「`upload-to-9618-489b-b030`」に動画がアップロードされている。
![screenshot-console.cloud.google.com-2019.12.15-22_13_56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/55d4de8e-46cf-2b8f-b7d5-3ec21ec268cb.png)

# まとめ