4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Goを触ってみよう【aws-sdk-go】

Last updated at Posted at 2019-05-26

まえがき

 もう結構使っている人も多いと思われるGo言語ですが、最近になってようやっと触りだしたこのごろです。きっかけはaws-sdkPythonboto3ライブラリを使ってインスタンス情報を抜き出そうとした時にdict型(辞書型)に翻弄された結果Goに逃げただけです。

やりたいこと

 上述の通り、Goでaws-sdkを利用して、インスタンス情報からInstance-typevCPUMemoryを抜き出してファイルに出力する!です。
背景としてはプロビジョニングツールとしてTerraformを利用しているのですが、DBのCloudwatch metricsをいちいち手入力するのはつらみなので、テンプレートで出せるようにしたかったためです。


突然のGopherくん!!!
Gopher
©Renée French
ref.) The Go Gopher

Goのインストール

 いわずもがな、まずは環境を用意しなければならないです。
個人的にハマったポイントをあらかじめ挙げておきます。

  • $GOPATHの設定
  • dep or Modules

インストール

まずは公式サイトよろしく、インストールしましょう。

Windowsの場合はインストーラーをダウンロードしてポチポチ https://golang.org/dl/
MacやLinuxその他の場合はコマンドでもOK
Install.sh
tar -C /usr/local -xzf go$VERSION.$OS-$ARCH.tar.gz
#Macの場合はbrewでもOK
brew install go
Goのインストール完了後は`go get golang.org/dl/go$VERSION`で別バージョンをインストールすることも可能です。 ### $GOPATHの設定 結論からいくとどこでもいいらしい…が、おかげで嵌まってしまった。 参考:[GOPATH は適当に決めて問題ない](https://qiita.com/yuku_t/items/c7ab1b1519825cc2c06f)

今のところ個人の範囲でしか使っていないのであまり使わないような気がしていますが、結局 goenv にお任せすることにしました。

  • Macの場合brew install goenv でもインストールできます。
Goインストール
$ 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

Release

とりあえずHello World!

なにはともあれ、Hello World!をまずはやってみましょう。

以下のようなファイルを作って、

hello.go
package main
import "fmt"

func main(){
    fmt.Printf("Hello World!\n")
}

実行

go run
$ go run hello.go
Hello World!

buildしてバイナリにしちゃう。

go build
$ go build hello.go
$ ls -l hello
-rwxr-xr-x  1 User  staff  2020040  5 25 16:09 hello
$ ./hello
Hello World!

install してモジュール化もできます。

go 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に関してはインストールすらまだな状態ですし、Modulesv1.12から本格参戦っぽいので…今からやるのであればModulesの方が良いと思います。
そのうちこのあたりについては、また記事書こうと思います。。

Goでaws-sdkを使ってみる

 まずはライブラリが必要なのでaws-sdk-goを入手しましょう。
aws-sdk-go-v2もありますがまだpreviewなのでやめておきました。
では公式ドキュメントに従ってインストールしてみます。

go get
$ 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の使い方について

go mod
$ 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

その後作成したスクリプトを実行すると…

go run rds_instance_parser.go
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ファイルが出来上がっていました。
中身はこんな感じ。

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にも以下が追記がされていたので、 こちらに必要なモジュールを追記しておけば依存解決もできるのかな?

go.mod
$ cat go.mod
module github.com/you/hello
# これが追記されていた
require github.com/aws/aws-sdk-go v1.16.3 // indirect

おまけ

ちなみに作ったスクリプトはこんな感じです。
rds_instance_parser.go
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)
}

一部出力結果抜粋

value.tf
 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でもできています。
…が、ごちゃごちゃしているので比較記事でもそのうち書きます。

おしまい

標準ライブラリでできることがかなり多いと思いました。(今回はほぼ使ってないけど。)
バックエンド系とかこのへんで実装してみたいなぁ…そのうち書きます。

宿題

次回予告的なのです。

  • depModules
  • Modulesについて(go.modとgo.sum)
  • PythonとGoの比較
  • Goのバックエンドへの実装
4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?