まえがき
もう結構使っている人も多いと思われるGo言語ですが、最近になってようやっと触りだしたこのごろです。きっかけはaws-sdk
でPython
のboto3ライブラリを使ってインスタンス情報を抜き出そうとした時にdict型(辞書型)に翻弄された結果Go
に逃げただけです。
やりたいこと
上述の通り、Goでaws-sdkを利用して、インスタンス情報からInstance-type
、vCPU
、Memory
を抜き出してファイルに出力する!です。
背景としてはプロビジョニングツールとしてTerraformを利用しているのですが、DBのCloudwatch metricsをいちいち手入力するのはつらみなので、テンプレートで出せるようにしたかったためです。
突然のGopherくん!!!
©Renée French
ref.) The Go Gopher
Goのインストール
いわずもがな、まずは環境を用意しなければならないです。
個人的にハマったポイントをあらかじめ挙げておきます。
-
$GOPATH
の設定 -
dep
orModules
インストール
まずは公式サイトよろしく、インストールしましょう。
Windowsの場合はインストーラーをダウンロードしてポチポチ
https://golang.org/dl/MacやLinuxその他の場合はコマンドでもOK
tar -C /usr/local -xzf go$VERSION.$OS-$ARCH.tar.gz
#Macの場合はbrewでもOK
brew install go
今のところ個人の範囲でしか使っていないのであまり使わないような気がしていますが、結局 goenv にお任せすることにしました。
- Macの場合
brew install goenv
でもインストールできます。
$ goenv install --list
Available versions:
1.2.2
1.3.0
=======多いので割愛=======
1.11.3
1.11.4
1.12beta1
#1.12はまだbetaになってました
$ goenv install 1.11.4
Downloading go1.11.4.darwin-amd64.tar.gz...
-> https://dl.google.com/go/go1.11.4.darwin-amd64.tar.gz
Installing Go Darwin 64bit 1.11.4...
Installed Go Darwin 64bit 1.11.4 to /Users/Tetsu/.goenv/versions/1.11.4
$ goenv global 1.11.4
$ go version
go version go1.11.4 darwin/amd64
とりあえずHello World!
なにはともあれ、Hello World!
をまずはやってみましょう。
以下のようなファイルを作って、
package main
import "fmt"
func main(){
fmt.Printf("Hello World!\n")
}
実行
$ go run hello.go
Hello World!
build
してバイナリにしちゃう。
$ go build hello.go
$ ls -l hello
-rwxr-xr-x 1 User staff 2020040 5 25 16:09 hello
$ ./hello
Hello World!
install
してモジュール化もできます。
$ ls -l ~/go/src/test
-rw-r--r-- 1 User staff 76 5 25 16:09 hello.go
$ go install test
$ ls -l ~/go/bin/test
-rwxr-xr-x 1 User staff 2020040 5 25 16:18 /Users/Tetsu/go/bin/test
$ ~/go/bin/test
Hello World!
depとModules
正直に言うとよくわかっていません。というよりこの後書くスクリプトが動けば良かったので、まだまだ必要な場面に出くわしていないだけです。。
depに関してはインストールすらまだな状態ですし、Modulesもv1.12から本格参戦っぽいので…今からやるのであればModulesの方が良いと思います。
そのうちこのあたりについては、また記事書こうと思います。。
Goでaws-sdkを使ってみる
まずはライブラリが必要なのでaws-sdk-go
を入手しましょう。
aws-sdk-go-v2
もありますがまだpreviewなのでやめておきました。
では公式ドキュメントに従ってインストールしてみます。
$ go get -u github.com/aws/aws-sdk-go
go: finding github.com/aws/aws-sdk-go v1.16.3
go: finding github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af
go: downloading github.com/aws/aws-sdk-go v1.16.3
実はこのときにModules
を利用してaws-sdk-go
のインストールを実施してます。
参考:go1.11のmodulesの使い方について
$ mkdir tmp
$ cd tmp
tmp $
tmp $ go mod init github.com/own/test
go: creating new go.mod: module github.com/own/test
tmp $ ls -l
-rw-r--r-- 1 User staff 28 5 18 13:53 go.mod
tmp $ cat go.mod
module github.com/own/test
その後作成したスクリプトを実行すると…
tmp $ go run $FILE_PATH/rds_instance_parser.go -args value.tf
go: downloading github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af
tmp $ ls -l
total 24
-rwxr-xr-x 1 User staff 3826 5 18 13:58 value.tf
-rw-r--r-- 1 User staff 83 5 18 13:54 go.mod
-rw-r--r-- 1 User staff 408 5 18 13:54 go.sum
スクリプトの成果物とgo.sum
ファイルが出来上がっていました。
中身はこんな感じ。
tmp $ cat go.sum
github.com/aws/aws-sdk-go v1.16.3 h1:esEQzoR8SVXtwg42nRoR/YLftI4ktsZg6Qwr7jnDXy8=
github.com/aws/aws-sdk-go v1.16.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
どうやら上記スクリプト実行での依存関係にあるモジュールのバージョンをチェックした結果がgo.sum
ファイルに書き込まれているっぽい。
go.mod
にも以下が追記がされていたので、 こちらに必要なモジュールを追記しておけば依存解決もできるのかな?
$ cat go.mod
module github.com/you/hello
# これが追記されていた
require github.com/aws/aws-sdk-go v1.16.3 // indirect
おまけ
ちなみに作ったスクリプトはこんな感じです。
package main
import (
"errors"
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/pricing"
)
func getProduct(nextToken string) (interface{}, interface{}, interface{}, *string, bool, error) {
svc := pricing.New(session.New(&aws.Config{Region: aws.String("us-east-1")}))
input := &pricing.GetProductsInput{
Filters: []*pricing.Filter{
{
Field: aws.String("location"),
Type: aws.String("TERM_MATCH"),
Value: aws.String("US East (N. Virginia)"),
},
},
ServiceCode: aws.String("AmazonRDS"),
FormatVersion: aws.String("aws_v1"),
MaxResults: aws.Int64(1),
NextToken: aws.String(nextToken),
}
result, err := svc.GetProducts(input)
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case pricing.ErrCodeInternalErrorException:
fmt.Println(pricing.ErrCodeInternalErrorException, aerr.Error())
case pricing.ErrCodeInvalidParameterException:
fmt.Println(pricing.ErrCodeInvalidParameterException, aerr.Error())
case pricing.ErrCodeNotFoundException:
fmt.Println(pricing.ErrCodeNotFoundException, aerr.Error())
case pricing.ErrCodeInvalidNextTokenException:
fmt.Println(pricing.ErrCodeInvalidNextTokenException, aerr.Error())
case pricing.ErrCodeExpiredNextTokenException:
fmt.Println(pricing.ErrCodeExpiredNextTokenException, aerr.Error())
default:
fmt.Println(aerr.Error())
}
} else {
// Print the error, cast err to awserr.Error to get the Code and
// Message from an error.
fmt.Println(err.Error())
}
}
switch result.NextToken {
case nil:
return nil, nil, nil, nil, false, nil
default:
instanceType := result.PriceList[0]["product"].(map[string]interface{})["attributes"].(map[string]interface{})["instanceType"]
if instanceType == nil {
nToken := result.NextToken
return nil, nil, nil, nToken, true, errors.New("InstanceType is nil")
}
vcpu := result.PriceList[0]["product"].(map[string]interface{})["attributes"].(map[string]interface{})["vcpu"]
memory := result.PriceList[0]["product"].(map[string]interface{})["attributes"].(map[string]interface{})["memory"]
nToken := result.NextToken
return instanceType, vcpu, memory, nToken, true, nil
}
}
func main() {
type dbInstanceType struct {
Name interface{}
Vcpu interface{}
Memory interface{}
}
type dbInstanceTypeList []dbInstanceType
var dbInstanceTypes dbInstanceTypeList
var nextToken = ""
for i := 0; ; {
i++
instanceType, vcpu, memory, nToken, next, err := getProduct(nextToken)
if err != nil {
nextToken = *nToken
continue
}
if next != true {
break
}
dbInstanceTypeInfo := dbInstanceType{
Name: instanceType,
Vcpu: vcpu,
Memory: memory,
}
dbInstanceTypes = append(dbInstanceTypes, dbInstanceTypeInfo)
nextToken = *nToken
}
results := make([]dbInstanceType, 0, len(dbInstanceTypes))
encountered := map[interface{}]bool{}
for i := 0; i < len(dbInstanceTypes); i++ {
if !encountered[dbInstanceTypes[i]] {
encountered[dbInstanceTypes[i]] = true
results = append(results, dbInstanceTypes[i])
}
}
maxLength := 0
for i := 0; i < len(results); i++ {
if len(results[i].Name.(string)) > maxLength {
maxLength = len(results[i].Name.(string))
}
}
instanceSpecs := ""
for i := 0; i < len(results); i++ {
NAME := results[i].Name.(string)
MEMORY := results[i].Memory.(string)
// In the API, db.r5.4xlarge has a memory of 192 GiB, but correctly it is 128 GiB
if NAME == "db.r5.4xlarge" {
MEMORY = "128 GiB"
}
MEMORY = strings.Replace(MEMORY, " GiB", "", 1)
s, _ := strconv.ParseFloat(MEMORY, 64)
s = s * (1024 * 1024 * 1024)
MEMORY = strconv.FormatFloat(s, 'f', 0, 64)
if len(NAME) < maxLength {
NAME = NAME + strings.Repeat(" ", maxLength-len(NAME))
}
VCPU := results[i].Vcpu.(string)
SPACE := strings.Repeat(" ", 4-len(VCPU))
instanceSpec := " " + NAME + " = {cpu_cores = " + VCPU + "," + SPACE + "memory = " + MEMORY + "}\n"
instanceSpecs = instanceSpecs + instanceSpec
}
content := []byte(
"locals {\n" +
" instance_types = {\n" +
instanceSpecs +
" }\n" +
"}\n",
)
ioutil.WriteFile(os.Args[len(os.Args)-1], content, os.ModePerm)
}
一部出力結果抜粋
instance_types = {
db.m4.4xlarge = {cpu_cores = 16, memory = 68719476736}
db.r4.4xlarge = {cpu_cores = 16, memory = 130996502528}
db.r3.xlarge = {cpu_cores = 4, memory = 32749125632}
}
中身の解説は別の記事で書きます。
簡単に説明だけしておくとPricingのGetProductsというライブラリを利用して、バージニア北部からRDSインスタンスのスペックを抜き出して整形しているだけです。(バージニア北部から抜き出しているのは、対応しているインスタンスタイプが多いからです。)
冒頭Goに逃げたと書きましたが実はPythonでもできています。
…が、ごちゃごちゃしているので比較記事でもそのうち書きます。
おしまい
標準ライブラリでできることがかなり多いと思いました。(今回はほぼ使ってないけど。)
バックエンド系とかこのへんで実装してみたいなぁ…そのうち書きます。
宿題
次回予告的なのです。
- dep と Modules
- Modulesについて(go.modとgo.sum)
- PythonとGoの比較
- Goのバックエンドへの実装