AWS day2: Amazon S3 × SDK for Go
こんにちは。
今回はAWS奮闘記第二弾!ということで、Amazon S3 の理解を深めようと思います。
本日の内容は以下です。
目標
参考
- バケットポリシーに関するドキュメント
バケットポリシーの例 特定の IP アドレスへのアクセスの制限 https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/example-bucket-policies.html
1. 10分間ハンズオンをやってみる
毎度ですが、まずはサービスの雰囲気を掴むに最適なハンズオンを進めていきます。
ハンズオン:ファイルの保存と取得
画像を貼り付けるのが面倒だったので、GUIも見たい!という方は上記ハンズオンで進めることをお勧めします(所々画像が古くなっているっぽいのでご注意)。
1) Amazon s3 コンソールに入る
検索バーから「s3」で検索する
2) S3バケットを作成する
バケット
ファイルを保存するためのコンテナのこと。
リージョンを選択
[ブロックパブリックアクセスのバケット設定]
デフォルト: [パブリックアクセスを全てブロック]
ACL
AzureでいうNSG(ネットワークに関する許可・拒否制限をかけることでセキュリティ強化を行う機能)のようなものと思われる詳細設定 ここではデフォルト[オブジェクトロック] [無効]
[バケットを作成] をクリックする
バケットはあっという間に作成できました。
3) ファイルのアップロード・ダウンロード
説明を書くまでもなさそう・・・画面に表示されている通りに操作すればバケットにファイルをアップロードできます。
アップロード
- バケット名を選択し、バケットに移動
- [アップロード]を選択
- [ファイルを追加]をクリックし[次へ]を選択
- アクセス許可に関する設定を行い(ここではデフォルトのまま)[次へ]
- プロパティを設定する
ストレージクラス
冗長性やアクセスの頻度でクラス化され、それに基づいた料金体系となる- 暗号化
- メタデータ
- タグ
- [アップロード]を選択
GUIで簡単にファイルをアップロードできました。
ダウンロード
- ダウンロードしたいファイルの横チェックボックスをONにし、[ダウンロード]を選択
4) オブジェクトとバケットを削除
どちらもs3コンソールから削除できる。
簡単に削除できました。
2. AWS SDK for Goでやってみる
例のごとくAWS SDK for Go でS3を操作してみようと思います。
ただ作るだけだと面白くない(ハードルを上げている)ので、以下の操作をS3バケットでやってみます。
Reference は こちら。
ここから先やりたいことは、ほとんど 公式Docs に記載されていました。さすがS3の情報は豊富だな(← Lightsail で苦戦した人)
前提条件
上記の手順については day1 で触れていますのでご参考までに。
1) S3バケットをリスト表示する
まずはS3バケットのリスト表示をGoでやってみます。処理の流れは以下。
list_s3bucket.go
package main import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" "fmt" "os" ) func main() { // Initialize the session that the SDK uses to load credencials from the shared credencial file, and create a new Amazon S3 service client sess, err := session.NewSession(&aws.Config{ Region: aws.String("ap-northeast-1")}, ) // Create S3 service client svc := s3.New(sess) // Call ListBuckets result, err := svc.ListBuckets(nil) if err != nil { exitErrorf("Unable to list buckets, %V", err) } fmt.Println("Buckets:") // loop through the buckets, printing the name and creation date of each bucket for _, b := range result.Buckets { fmt.Printf("* %s created on %s\n", aws.StringValue(b.Name), aws.TimeValue(b.CreationDate)) } } // we use this function to display errors and exit func exitErrorf(msg string, args ...interface{}) { // prefix F: specify the place where the function write fmt.Fprintf(os.Stderr, msg+"\n", args...) // exit with exit code 1 os.Exit(1) }
上記を実行してみます。
❯ go run list_s3bucket.go Buckets: * domb-ri-test-bk created on 2020-05-14 13:13:03 +0000 UTC
2) S3 バケットの作成
次に、S3バケットを作成してみます。処理の流れは以下。
create_s3bucket.go
package main import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" "fmt" "os" ) func main() { // The program requires one argument, the name of the bucket to create if len(os.Args) != 2 { exitErrorf("Bucket name missing!\nUsage: %s bucket_name", os.Args[0]) } bucket := os.Args[1] sess, err := session.NewSession(&aws.Config{ Region: aws.String("ap-northeast-1")}, ) svc := s3.New(sess) // Call CreateBucket, passing in the bucket name defined previously _, err = svc.CreateBucket(&s3.CreateBucketInput{ Bucket: aws.String(bucket), }) if err != nil { exitErrorf("Unable to create bucket %q, %v", bucket, err) } // Wait until bucket is created before finishing fmt.Printf("Waiting for bucket %q to be created...\n", bucket) err = svc.WaitUntilBucketExists(&s3.HeadBucketInput{ Bucket: aws.String(bucket), }) if err != nil { exitErrorf("Error occurred while waiting for bucket to be created, %v", bucket) } fmt.Printf("Bucket %q successfully created\n", bucket) } func exitErrorf(msg string, args ...interface{}) { fmt.Fprintf(os.Stderr, msg+"\n", args...) os.Exit(1) }
os.Args
でコマンドラインのパラメータを受け取れる- [0] 実行したコマンド名
- [1]~ コマンドに渡された引数
_(アンダースコア)
変数は、宣言はするけど使わない変数- Goでは宣言した変数を一度も使わないとエラーになる
書式指定子(verb)
は%...
で表す 参考: Qiita%V
値をデフォルトのフォーマットで出力%q
ここではGoの文法上のエスケープをした文字列を出力
上記を実行してみます。実行時に、バケット名を記載します。
> go run create_s3bucket.go new-bucket-domb-ri Waiting for bucket "new-bucket-domb-ri" to be created... Bucket "new-bucket-domb-ri" successfully created ❯ go run list_s3bucket.go Buckets: * domb-ri-test-bk created on 2020-05-14 13:13:03 +0000 UTC * new-bucket-domb-ri created on 2020-05-20 01:51:13 +0000 UTC
新しいバケットが作成できていることを確認しました。
3) バケットにファイルをアップロード
バケットにファイルをアップロードする動作をSDKでやってみます。ファイル名は実行時に指定します。
処理の流れは以下。
upload_s3obj.go
package main import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3/s3manager" "fmt" "os" ) func main() { // Get the bucket and file name from the command line arguments if len(os.Args) != 3 { exitErrorf("Bucket name missing!\nUsage: %s bucket_name", os.Args[0]) } bucket := os.Args[1] filename := os.Args[2] file, err := os.Open(filename) if err != nil { exitErrorf("Unable to open file %q, %v", err) } // Defer the execution of Close() defer file.Close() sess, err := session.NewSession(&aws.Config{ Region: aws.String("ap-northeast-1")}, ) // Setup the S3 Upload Manager // http://docs.aws.amazon.com/sdk-for-go/api/service/s3/s3manager/#NewUploader uploader := s3manager.NewUploader(sess) // Call CreateBucket, passing in the bucket name defined previously _, err = uploader.Upload(&s3manager.UploadInput{ Bucket: aws.String(bucket), Key: aws.String(filename), Body: file, }) if err != nil { exitErrorf("Unable to upload %q to %q, %v", filename, bucket, err) } fmt.Printf("Successfully uploaded %q to %q\n", filename, bucket) } func exitErrorf(msg string, args ...interface{}) { fmt.Fprintf(os.Stderr, msg+"\n", args...) os.Exit(1) }
os.Open()
で、引数で指定したファイルを読み込み専用モードでオープンdefer
にクローズする処理を登録することで、ファイルのクローズ処理を確実に行わせる
→リソース破棄の処理が漏れたり、分散する問題を防ぐ
上記を実行します。実行時には、バケット名・ファイル名を指定します。
# 先に作ったバケットと、適当に用意したファイルを指定 ❯ go run upload_s3obj.go new-bucket-domb-ri up_test.txt Successfully uploaded "up_test.txt" to "new-bucket-domb-ri"
AWSコンソールから up_test.txt がアップロードされていることを確認しました(恐らくSDKで表示する方法もあるがここでは割愛します) 。
4) バケットからファイルをダウンロードする
バケットに配置してあるファイルをダウンロードします。
処理の流れは以下。
- バケット名、ファイル名が指定されているか確認して設定
- 新規ファイルを作成
- SDKが認証情報をロードし、NewDownloader オブジェクト作成のためSessionをイニシャライズ
- バケットからファイルをダウンロード
download_s3obj.go
package main import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3/s3manager" "fmt" "os" ) func main() { // Get the bucket and file name from the command line arguments if len(os.Args) != 3 { exitErrorf("Bucket and item names required\nUsage: %s bucket_name, item_name", os.Args[0]) } bucket := os.Args[1] item := os.Args[2] // Create a new file file, err := os.Create(item) if err != nil{ exitErrorf("Unable to open file %q, %v", item, err) } // Defer the execution of Close() defer file.Close() sess, _ := session.NewSession(&aws.Config{ Region: aws.String("ap-northeast-1")}, ) downloader := s3manager.NewDownloader(sess) // Download the item from the bucket numBytes, err := downloader.Download(file, &s3.GetObjectInput{ Bucket: aws.String(bucket), Key: aws.String(item), }) if err != nil { exitErrorf("Unable to download item %q, %v", item, err) } fmt.Printf("Downloaded", filename(), numBytes, "bytes") } func exitErrorf(msg string, args ...interface{}) { fmt.Fprintf(os.Stderr, msg+"\n", args...) os.Exit(1) }
os.Create()
で新規ファイルを作成する- 同名のファイルが存在する場合は上書きされるので注意
上記を実行します。バケット名・ファイル名の順に指定します。
※最終的に成功したファイルの中身を書いてますが、 file が undifined だと怒られてしばらく苦闘しました。単純にfileを宣言してなかっただけでした。
# ファイルをダウンロードする ❯ go run download_s3obj.go new-bucket-domb-ri up_test.txt Downloaded%!(EXTRA string=up_test.txt, int64=6, string=bytes) ~/.aws ❯ ls download_s3obj.go up_test.txt
ファイルがダウンロードできました。
5) バケット内ファイルを削除する
最後は一掃して、不要なお金がかからないようにしておきましょう。まずはファイルの削除から。
処理の流れは以下。
delete_file_s3.go
package main import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" "fmt" "os" ) func main() { // Get the name of the bucket and object to delete if len(os.Args) != 3{ exitErrorf("Bucket and obj name required¥nUsage: %s bucket_name object_name", os.Args[0]) } bucket := os.Args[1] obj := os.Args[2] sess, err := session.NewSession(&aws.Config{ Region: aws.String("ap-northeast-1")}, ) svc := s3.New(sess) // Call DeleteObject _, err = svc.DeleteObject(&s3.DeleteObjectInput{Bucket: aws.String(bucket), Key: aws.String(obj)}) if err != nil { exitErrorf("Unable to delete obj %q from bucket %q, %v", obj, bucket, err) } err = svc.WaitUntilObjectNotExists(&s3.HeadObjectInput{ Bucket: aws.String(bucket), Key: aws.String(obj), }) fmt.Printf("Obj %q successfully deleted¥n", obj) } func exitErrorf(msg string, args ...interface{}) { fmt.Fprintf(os.Stderr, msg+"\n", args...) os.Exit(1) }
DeleteObject()
により指定したファイルを削除する
上記を実行します。バケット名・ファイル名の順で指定します。
❯ go run delete_file_s3.go new-bucket-domb-ri up_test.txt Obj "up_test.txt" successfully deleted¥n
ファイルが削除できました。
6) バケットの削除
綺麗さっぱり無くしたいので、バケットも消しちゃいます。
処理の流れは以下。
delete_s3obj.go
package main import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" "fmt" "os" ) func main() { // Get the name of the bucket to delete if len(os.Args) != 2{ exitErrorf("bucket name required¥nUsage: %s bucket_name", os.Args[0]) } bucket := os.Args[1] sess, err := session.NewSession(&aws.Config{ Region: aws.String("ap-northeast-1")}, ) svc := s3.New(sess) _, err = svc.DeleteBucket(&s3.DeleteBucketInput{ Bucket: aws.String(bucket), }) if err != nil { exitErrorf("Unable to delete bucket %q, %v", bucket, err) } fmt.Printf("Waiting for bucket %q to be deleted...¥n", bucket) err = svc.WaitUntilBucketNotExists(&s3.HeadBucketInput{ Bucket: aws.String(bucket), }) if err != nil { exitErrorf("Error occurred while waiting for bucket to be deleted, %v", bucket) } fmt.Printf("Bucket %q successfully deleted¥n", bucket) } func exitErrorf(msg string, args ...interface{}) { fmt.Fprintf(os.Stderr, msg+"\n", args...) os.Exit(1) }
svc.DeleteBucket()
で、バケットを削除
上記を実行してみます。
❯ go run delete_s3obj.go new-bucket-domb-ri Waiting for bucket "new-bucket-domb-ri" to be deleted...¥n Error occurred while waiting for bucket to be deleted, new-bucket-domb-ri exit status 1
あらら、エラーが発生?
ひとまずもう一度やってみます。
❯ go run delete_s3obj.go new-bucket-domb-ri Unable to delete bucket "new-bucket-domb-ri", NoSuchBucket: The specified bucket does not exist status code: 404, request id: E46A45A26717EBE6, host id: fxDaSXZgSND1NttlTnU... exit status 1
消えてる・・・謎だ・・・
❯ go run delete_s3obj.go domb-ri-test-bk Wating for bucket "domb-ri-test-bk" to be deleted...¥nBucket "domb-ri-test-bk" successfully deleted¥n ~/.aws
別のバケットで試したら成功しました。なんだったんだろう・・・ 今回の目的は果たせたので、深追いはしないことにします。
終わりに
まずはS3バケットの使い勝手をGUIで確かめてから、S3バケットや配置するファイルに対して AWS SDK for Go を用いて操作してみました。
必ず必要になってくる操作手順は以下でした。
前提条件を満たしているかチェックし、AWSを操作する権限のあるものからのアクセスであることを証明する認証情報をセットし、Sessionを開始して処理を行う・・・
と、当たり前っちゃあ当たり前なのですが「お作法」を学ぶのはプログラミング学習の基本だと思います。非常に勉強になりました。
当初の目標と実績を比較すると、
まあ概ね達成できたかと思います。エラーの出力方法がわかったのは大きな前進でした。
そらで書けるようになるには道のりが長いので、しばらくは写経したり読み込んだりして目と手を慣らしていこうと思います。
- S3のサービス内容や構成、使い所を知る
工夫次第で如何様にも使える便利なストレージサービス、といったところでしょうか。静的Webサイトに使えたり、大小様々なファイルを管理できるみたいなのでアプリの規模感にも柔軟に対応できそうです(お値段の幅もかなり広そう・・・)
というわけで、AWS奮闘記第二弾、これでおしまい! @kuromitsu_ka さん、めっちゃ遅れてごめんなさい!!!