お題
表題の通り。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でアクセスだったけど、趣旨としては同じなので。
バケット内のオブジェクトに対する(誰でも参照可能にする)公開アクセス制御については以下参照。
https://qiita.com/sky0621/items/60c654fd1817bbf085ae#08なので一般公開してみる
上記からダウンロードした動画ファイル(sample.mp4
)のアップロード先も作っておく。
※アップロード前なので当然バケット内は空。
プログラム
プロジェクト全体は下記。
https://github.com/sky0621/tips-go/tree/568b40bd824d9e0f7bd5eb8bdce07565555768bb/try/mp4_download_and_upload
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())
}
プチ解説
res, err := http.Get(*mpath)
欲しいものがテキストだろうとバイナリだろうと、これだけで事足りる。
ctx := context.Background()
client, err := storage.NewClient(ctx, option.WithCredentialsFile(*credentialPath))
あらかじめローカルから自分のGCPプロジェクトのCloud Storageにアクセス可能なクレデンシャルを作ってあるので、そのファイルをオプションとして渡す。
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
」に動画がアップロードされている。