「UNIXという考え方」を読んだ
平成最後の日、UNIXという考え方 を読みました。
shop.ohmsha.co.jp
著者自身が書いているように分かりやすい口語体で書かれているので、内容が気になる方は本書を読むことをお勧めします。
以下、本書を読んで感銘を受けたところについて備忘録として纏めておきます。なお、鍵括弧で囲っているのは本書内の文を引用しているものです。
Serverless Frameworkを使ってLambdaでPDFを出力するサンプルを作ってみた
久しぶりにAWS と Serverless Frameworkを使い、LambdaにてDynamoDBからデータを取得し、PDFをS3に出力するサンプルを作ってみました。
CloudWatchなどで定期的にLambdaを起動することを考えると、割と実案件ではありがちな要件かと思われます。
今回作ったサンプルは以下になります。
GitHub - SrcHndWng/go-serverless-dynamo-report
割とサクッと作れるかと思ったのですが、意外と色々調べながら作ることになったので、今回調べた点をメモとして載せておきます。
サンプルを作るのに調べたこと
- DynamoDB Localの使い方
- go-assets-builderを使ってテキストファイルをバイナリに組み込む方法
- gopdfを使ってのPDF作成
以下、それぞれについて書いていきます。
DynamoDB Localの使い方
serverless-dynamodb-localをインストールして「$ sls dynamodb start」でDynamoDB Localを起動すると
Unable to start DynamoDB Local process!
というエラーとなりました。
以下のURLよりDynamoDB Localをダウンロードし、解凍したフォルダごと ./node_modules/dynamodb-localhost/dynamodb/bin に入れることで解決しました。
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/DynamoDBLocal.DownloadingAndRunning.html
go-assets-builderを使ってテキストファイルをバイナリに組み込む方法
GitHub - jessevdk/go-assets-builder: Simple assets builder program for go-assets
こちらをgo get で取得します。
今回はPDFを作成するためのフォントのフォーマットファイルである「times.ttf」ファイルをgo-assets-builderを使ってバイナリに組み込みます。
バイナリに組み込む理由は、Lambdaにシングルバイナリとしてデプロイしたいためです。
READMEにも書いたのですが、「times.ttf」ファイルを以下のサイトより取得するなどして用意し
./report/assets/times.ttf に配置します。
https://github.com/oneplus1000/gopdfsample/tree/master/ttf
その上で以下のコマンドを実行すると、「assets.go」というファイル名でtimes.ttfがビルド可能な.go ファイルに変換されます。
go-assets-builder -p main -o assets.go assets/
今回はややこしいですが、バイナリに組み込んだtimes.ttfをアプリ内で読み込み、ファイルとして復元してLambdaの実行環境内の/tmp/times.ttfに出力しています。
これはtimes.ttfをフォントのフォーマットファイルとして、PDF出力のためのライブラリに渡したいためです。
実装についてはmain.goを参照してください。
assetFile, err := Assets.Open("/assets/times.ttf")
でバイナリに組み込まれたtimes.ttfを開いています。
gopdfを使ってのPDF作成
GitHub - signintech/gopdf: A simple library for generating PDF written in Go lang
gopdfを使い、PDFファイルを出力しています。すごく簡単にですが、縦線・横線とDynamoDBから取得したデータを出力してみました。
gopdfを使った理由は、すべてGolangで書かれているため、ビルドしたバイナリ単体でPDFを出力できるためです。
こちらについては今回のソースよりも、以下のサンプルソースの方が実装方法は分かりやすいかと思いますw
GitHub - oneplus1000/gopdfsample: example of gopdf (https://github.com/signintech/gopdf)
Go Moduleでローカルパッケージを作成する
Using Go Modules - The Go Blog
Go Moduleについての上記の公式サイトではhello.go、それのテストであるhello_test.goを
GOPATHの外に実装する方法について書いてあります。
ですが実際にプログラムを作るときには、ローカルに幾つかのパッケージを作ることが多いかと思います。
例えば以下のような構成です。
. ├── hello/ │ ├── hello.go │ └── hello_test.go └── main.go
上記の構成で
- hello.go、hello_test.goは公式サイトのソースのまま
- main.goからはhello.goのメソッドを呼び出す
- 「$ go mod init example.com/hello_main3」ようなgo mod init はmain.goと同階層で行う
をしたところ、ビルド時に以下のようなエラーとなりました。
build example.com/hello_main3: cannot find module for path ~
対応方法としては以下のサイトを参考にさせて頂きました。
https://pod.hatenablog.com/entry/2018/12/26/074944
結論を書けば、main.go内でhelloパッケージをimportする際に、相対パスではなく絶対パスで指定すればいいようです。
こんな感じになります。
import "example.com/hello_main3/hello"
このように書くことで、ビルドしての実行、helloパッケージ内でのテストの実行をすることができました。
AWS SAMでLambdaのみのFunctionを作成してみた
前回に引き続き、AWS SAMネタです。
sam init コマンドで作成されてるのはAPI Gateway + Lambdaという構成のテンプレートなのですが
Lambda単体で動かしたい or Lambdaと別のイベント(S3とかDynamoDBとか)を切っ掛けに動かしたいこともあるかと思います。
なのでsam initコマンドで作成したテンプレートを改修し、Lambda単体で動くFunctionを作成してみました。
以下、やってみた手順とソースです。
手順
既にsam initでプロジェクトが作成済の前提です。
1. プロジェクト内にフォルダを新たに作り、main.goファイルを作成する。
2. main.goを以下のように作成する。
package main import ( "context" "fmt" "time" "github.com/aws/aws-lambda-go/lambda" ) const timeFormat = "2006-01-02 15:04:05" const messageFormat = "Hello, now is %s!" // MyEvent ... type MyEvent struct { Name string `json:"name"` } // HandleRequest ... func HandleRequest(ctx context.Context) (string, error) { t := time.Now() message := createMessage(t) return message, nil } func createMessage(t time.Time) string { return fmt.Sprintf(messageFormat, t.Format(timeFormat)) } func main() { lambda.Start(HandleRequest) }
今回作成したのは現在日時を返すFunctionです。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/go-programming-model-handler-types.html
こちらを元として作成しました。
3. (デプロイまで試すなら)template.ymlを修正して、追加したFunctionをデプロイ対象に加える。
4. Makefileを修正する。
sam init で作成したhello-world、今回作成したmy-eventの両方をビルドするようにしました。
.PHONY: deps clean build all: deps clean build deps: go get -u github.com/aws/aws-lambda-go/events go get -u github.com/aws/aws-lambda-go/lambda clean: rm -rf ./hello-world/hello-world rm -rf ./my-event/my-event build: GOOS=linux GOARCH=amd64 go build -o hello-world/hello-world ./hello-world GOOS=linux GOARCH=amd64 go build -o my-event/my-event ./my-event
5. テストを作成する
main_test.goを作成し、以下のようなテストメソッドを作成しました。
mainの中身とそのままですがw、メッセージを作成する内部処理をテストしています。
package main import ( "fmt" "testing" "time" ) func TestCreateMessage(t *testing.T) { now := time.Now() message := createMessage(now) expect := fmt.Sprintf(messageFormat, now.Format(timeFormat)) if message != expect { t.Fatal("createMessage Failed.") } }
6. ローカルでの実行
ビルドして、ローカルでLambdaを実行してみます。
$ make $ sam local generate-event cloudwatch scheduled-event > my-event.json $ sam local invoke MyEventFunction --event my-event.json
7. テストの実行
go test -v ./my-event/
参考サイト
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-init.html
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/go-programming-model-handler-types.html
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-generate-event.html
AWS SAMによるテンプレートプロジェクト作成~ローカルでの実行
AWS SAMを使い、テンプレートのプロジェクトを作成してローカルで実行するまでの
流れについて纏めてみました。
既に色々なサイトで書かれてはいますが、今後の自分のために書いておきます。
前提として
となります。
プロジェクト作成
$ sam init --runtime go1.x --name your-project-name
プロジェクトが作成されるので、以下のようにMakefileを編集しました。
.PHONY: deps clean build all: deps clean build deps: go get -u github.com/aws/aws-lambda-go/events go get -u github.com/aws/aws-lambda-go/lambda clean: rm -rf ./hello-world/hello-world build: GOOS=linux GOARCH=amd64 go build -o hello-world/hello-world ./hello-world
ローカルでの実行
AWS SAMのインストールでも必要ですが、実行にはDockerが必要となります。
なのでDockerの起動もコマンドに含んでいます。
$ make $ sam local generate-event apigateway aws-proxy > event.json # API Gatewayのイベントを記述したjsonを作成 $ sudo service docker start # docker起動 $ sudo service docker status # docker起動確認 $ sam local invoke HelloWorldFunction --event event.json
テスト
テストのテンプレートとして作成されるので、その実行コマンドになります。
$ go test -v ./hello-world/
AWS CLI、GolangでS3のPre Signed URLを取得してみる
AWS CLIとGolangで、それぞれS3にあるオブジェクトのPre Signed URLを取得してみました。
やってみた背景としては、リクエストがあった時にS3に配置したオブジェクトを安全にリクエスト元へ渡したい、みたいな要件を耳に挟み
- Pre Singed URLを作ればいいんじゃないのか
- 取得元IPは固定だろうから、バケットにIP制限を別途つける
みたいなことを思いついたためです。
バケットにIP制限を付けるあたりは、以下が参考になりそうです。
S3でIP制限 - Qiita
AWS CLIでのPre Signed URL取得
事前にバケットを用意し、ファイルを入れておきます。
またAWS CLIのインストールも必要です。
以下のコマンドで取得できました。
# バケットの中身を確認 $ aws s3 ls s3://your-bucket/ # pre signed urlを発行。--expires-inで有効期間の秒を設定 $ aws s3 presign s3://your-bucket/sample.txt --expires-in 120 https://your-bucket.s3.amazonaws.com/sample.txt?AWSAccessKeyID=xxxxx
取得できたPre Signed URLをブラウザに張り付け、ファイルの中身が表示されることを確認します。
有効期間が過ぎたら再度アクセスして、今度はAccess Deniedになることも確認します。
AWS SDKでの取得
次にaws-sdk-goで取得してみます。
公式サイトのサンプルソースを改変し、以下のようなソースで実行してみました。
package main import ( "log" "os" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" ) func main() { region := os.Args[1] bucket := os.Args[2] key := os.Args[3] log.Printf("your region = %s, bucket = %s, key = %s\n", region, bucket, key) sess, err := session.NewSession(&aws.Config{ Region: aws.String(region)}, ) svc := s3.New(sess) req, _ := svc.GetObjectRequest(&s3.GetObjectInput{ Bucket: aws.String(bucket), Key: aws.String(key), }) urlStr, err := req.Presign(2 * time.Minute) if err != nil { log.Println("Failed to sign request", err) } log.Println("Pre signed url : ", urlStr) }
ビルドし、引数にリージョン・バケット・キーを渡して実行してみます。
$ ./s3-pre-signed-url-get-sample region your-bucket your-key 2019/03/xx xx:xx:xx Pre signed url : https://your-bucket.s3.amazonaws.com/sample.txt?AWSAccessKeyID=xxxxx
「Pre signed url : 」以降に出力されるPre Signed URLをブラウザに張り付け、ファイルの中身が表示されることを確認します。
こちらも無事、表示されるかと思います。
まとめ
AWS CLI、aws-sdk-goを使ってPre Signed URLを取得できることが分かりました。
後はこれらをアプリに組み込み、リクエストがあり次第、取得したPre Signed URLをリクエスト元に返せばいいことになります。
今回作成したGolangのソースは以下にも載せてあります。
GitHub - SrcHndWng/s3-pre-signed-url-get-sample
参考サイト
以下のサイトを参考にさせていただきました。
ありがとうございました。
【小ネタ】AWS CLIでS3のPre-Signed URLを生成できるようになっていました! | Developers.IO
https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/s3-example-presigned-urls.html
Windows Subsystem for Linux上にAWS SAMの実行環境を構築する + Golangの開発環境を用意する
タイトルにあるように、Windows Subsystem for Linux上でAWS SAMを実行する環境を構築してみました。
単純にLinux上にAWS SAMの実行環境を構築するだけと思いきや、細々と調べながら進めることになったので
その結果をメモ代わりに書いておきます。
AWS SAM
https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html
こちらの公式のガイドを見ればわかるのですが、AWS SAMのCLIの実行には
が必要となります。
Windows Subsystem for LinuxでDockerを動かすのが結構癖がありました。
結論から書いてしまうと
- Dockerは17.09.0を使った
- Windows Subsystem for Linuxを起動するコマンドプロンプト(やパワーシェルのターミナルなど)は管理者権限で起動しておく必要がある
- 管理者権限で起動したターミナル上でdockerを起動しておいたうえで、AWS SAMのコマンドを実行する必要がある
ということになります。
詳細については既に詳しく書かれている方がいたので、そちらを参考にしてください。
どうしても Docker on Ubuntu 18.04 on WSL したかった - Qiita
Windows10 Home の WSL で Docker を使う - Qiita
Golangの開発環境
Windows Subsystem for LinuxでAWS SAMを実行するにあたり、
- Windows上のエディタ(Visual Studio Code) で入力補完を利かせる
- Windows Subsystem for Linux上でビルドを行う
を実現したいと思いました。
結果
- WindowsとWindows Subsystem for Linuxの両方に同じバージョンのGolangをインストールする
- WindowsとWindows Subsystem for LinuxでGOPATHを共有する
ことになりました。
詳細については既に詳しく書かれている方がいたので、そちらを参考にしてください。
WSLENVでWSLとWindowsの環境変数を共有する(Go開発環境編) | re-imagine
まとめ
こんな感じで、一つ一つは簡単なのですが、結構色々と調べつつ環境の構築ができました。
最後に、今回参考にさせていただいたサイトのURLを載せておきたいと思います。