#mynavi(AWSメモ)
#setlinebreak(on);

* 概要 [#baf9a431]
#html(<div class="pl10">)
[[AWS SDK for Go>https://aws.amazon.com/jp/sdk-for-go/]] を触ってみる。
以下、基本的に Lambda での利用を想定。
#html(</div>)

* 目次 [#n8f6e596]
#contents
- 関連
-- [[Go言語]]
-- [[AWSメモ]]
- 参考
-- https://aws.amazon.com/jp/sdk-for-go/
-- https://docs.aws.amazon.com/lambda/latest/dg/go-programming-model.html
-- https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/go-programming-model-handler-types.html
-- https://github.com/aws/aws-lambda-go
-- https://github.com/sbstjn/go-lambda-example

* Go Lambda のハンドラ [#r9c95b8b]
#html(<div class="pl10">)

** 基本形 [#mabb1221]
#html(<div class="pl10">)

有効な関数ハンドラは以下のページに記載されている通り。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/go-programming-model-handler-types.html#go-programming-model-handler-types-signatures

例)
#mycode2(){{
package main

import (
    "github.com/aws/aws-lambda-go/lambda"
)

func hello() (string, error) {
    return "Hello World!", nil 
}

func main() {
    lambda.Start(hello)
}
}}
#html(</div>)

** Lambdaプロキシ統合を使用する場合 [#t0777c47]
#html(<div class="pl10">)

API Gateway用のLambdaで Lambdaプロキシ統合を使用する場合、
リクエストデータの受け取りには events.APIGatewayProxyRequest を、レスポンスデータの設定には events.APIGatewayProxyResponse を使用する。

https://github.com/aws/aws-lambda-go/blob/master/events/apigw.go

上記URLを見る限り、各パラメータはそれぞれ以下の通り取得できると思われる。

| パラメータ種別 | 取得方法 | 補足 |h
| GETパラメータ | APIGatewayProxyRequest.QueryStringParameters ||
| POSTデータ | APIGatewayProxyRequest.Body | 恐らく文字列として取得される為、JSONエンコードが必要? |
| PATHパラメータ | APIGatewayProxyRequest.PathParameters ||
| リソース名 | APIGatewayProxyRequest.Resource ||

sample.go
#mycode2(){{
package main

import (
    "fmt"
    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
)

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    return events.APIGatewayProxyResponse{
        Body:       fmt.Sprintf("Hello %s!", request.QueryStringParameters["name"]),
        StatusCode: 200,
    }, nil 
}

func main() {
    lambda.Start(handler)
}
}}
#html(</div>)

#html(</div>)


* SAM CLI によるローカルでの動作確認 [#he04f37f]
#html(<div class="pl10">)

** ハンドラをビルドしておく [#fcc6d4af]
#html(<div class="pl10">)
#myterm2(){{
GOOS=linux GOARCH=amd64 go build -o sample sample.go
}}
#html(</div>)


** template.yml の作成 [#n66669fb]
#html(<div class="pl10">)
#mycode2(){{
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Resources:
  SampleFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: sample
      Runtime: go1.x
      Tracing: Active
      Events:
        GetEvent:
          Type: Api 
          Properties:
            Path: /
            Method: get
}}
#html(</div>)

** SAM CLI で Lambda をinvoke [#p86fb77d]
#html(<div class="pl10">)

sample_request.json
#mycode2(){{
{
  "path": "/",
  "httpMethod": "GET",
  "queryStringParameters": {
    "name": "Taro"
  &#125;
&#125;
}}

イベントJSONの内容を入力として invoke
#myterm2(){{
sam local invoke "SampleFunction" -e sample_request.json
}}

標準入力からリクエストデータを渡す場合は -e に "-" を指定する。(JSONを作りたくない場合)
#myterm2(){{
echo '{"path": "/", "httpMethod": "GET", "queryStringParameters": {"name": "Taro"&#125;&#125;' | sam local invoke -e "-" "SampleFunction"
}}

#html(</div>)

** SAM CLI で API を起動 [#y602bcbb]
#html(<div class="pl10">)

ローカルでAPI Gatewayを起動
#myterm2(){{
sam local start-api --template template.yml
}}

動作確認
#myterm2(){{
curl http://127.0.0.1:3000/?name=Taro
Hello Taro!
}}

#html(</div>)

#html(</div>)


* ビルド [#w404a51e]
#html(<div class="pl10">)

GOPATHを通しておく
#myterm2(){{
export GOPATH=/usr/local/Cellar/go/1.13.5
}}

必要なライブラリをgo getしておく
#myterm2(){{
go get github.com/aws/aws-lambda-go/lambda
}}

ビルド
#myterm2(){{
GOOS=linux GOARCH=amd64 go build -o main main.go
}}

#html(</div>)


* パッケージングとデプロイ [#j446ef26]
#html(<div class="pl10">)
#TODO(aws cloudformation package と deploy を使用する場合はzip圧縮等も自動でやってくれる?(未確認))
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-go-how-to-create-deployment-package.html
#html(</div>)



* 実装メモ [#d8c9b220]
#html(<div class="pl10">)

** JSONデータを返却する [#o5b0e485]
#html(<div class="pl10">)

#mycode2(){{
package main

import (
    "fmt"
    "encoding/json"
    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
)

type response struct {
    Name    string `json:"name"`
    Message string `json:message`
}

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    res := &response{
        Name: request.QueryStringParameters["name"],
        Message: fmt.Sprintf("Hello %s!", request.QueryStringParameters["name"]),
    }   
    body, err := json.Marshal(res)
    fmt.Printf("%+v", string(body))
    if err != nil {
        return events.APIGatewayProxyResponse{}, err 
    }   
    return events.APIGatewayProxyResponse{
        Headers: map[string]string{
            "Content-Type": "application/json",
        },  
        Body: string(body),
        StatusCode: 200,
    }, nil 
}

func main() {
    lambda.Start(handler)
}
}}
#html(</div>)


** S3へのアクセス [#t40d3423]
#html(<div class="pl10">)

https://docs.aws.amazon.com/sdk-for-go/api/service/s3/

#mycode2(){{
package main

import (
    "fmt"
    "encoding/json"
    "strings"
    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/s3"
    "github.com/aws/aws-sdk-go/aws"
)

type response struct {
    Objects []string `json:objects`
}

var svc *s3.S3
var mybucket = "bucket.example"

/**
 * 初期処理
 */
func init() {
    svc = s3.New(session.New())
}

/**
 * Lambdaハンドラ
 */
func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {

    // バケットにオブジェクトを追加
    putInput := &s3.PutObjectInput{
        Body: aws.ReadSeekCloser(strings.NewReader("test1 text!")),
        Bucket: aws.String(mybucket),
        Key:  aws.String("test1.txt"),
    }   
    putResult, putErr := svc.PutObject(putInput)
    if putErr != nil {
        fmt.Printf("Put Error!\n")
        fmt.Printf("%+v\n", putErr)
    } else {
        fmt.Printf("Put Success!\n")
        fmt.Printf("%+v\n", putResult)
    }   

    // バケット内のオブジェクトを取得
    getInput := &s3.GetObjectInput{
        Bucket: aws.String(mybucket),
        Key:    aws.String("test1.txt"),
    }   
    getResult, getErr := svc.GetObject(getInput)
    if getErr != nil {
        fmt.Printf("Get Error!\n")
        fmt.Printf("%+v\n", getErr)
    } else {
        fmt.Printf("Get Success!\n")
        fmt.Printf("%+v\n", getResult)
    }

    // バケット内のオブジェクト一覧を取得
    listInput := &s3.ListObjectsV2Input{Bucket: aws.String(mybucket)}
    result, _ := svc.ListObjectsV2(listInput)
    myObjects := result.Contents
    objects := []string{}
    for i := 0; i < len(myObjects); i++ {
        objects = append(objects, *myObjects[i].Key)
    }   

    // レスポンスデータの設定
    body, err := json.Marshal(&response{Objects: objects})
    if err != nil {
        return events.APIGatewayProxyResponse{}, err
    } else {
        return events.APIGatewayProxyResponse{
            Headers: map[string]string{
                "Content-Type": "application/json",
            },
            Body: string(body),
            StatusCode: 200,
        }, nil
    }
}

func main() {
    lambda.Start(handler)
}
}}


#html(</div>)

** DynamoDBへのアクセス [#p18da634]
#html(<div class="pl10">)

テーブル名:SampleTable、パーティションキー: pkey、ソートキー: skey とした場合の、登録(PutItem)、検索(GetItem、Query)の例。
※以下 scan や BatchXXX 系の例は記載しないが、Python 等と同じくメソッドは用意されている模様。

参考: https://docs.aws.amazon.com/sdk-for-go/api/service/dynamodb/


#mycode2(){{
package main

import (
    "fmt"
    "encoding/json"
    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/dynamodb"
    "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
    "github.com/aws/aws-sdk-go/aws"
)

type response struct {
    Items []Record `json:items`
}

type Record struct {
    Pkey   string `json:pkey`
    Skey   string `json:skey`
    Value  string `json:value`
}

var svc *dynamodb.DynamoDB
var mytable = "SampleTable"

/**
 * 初期処理
 */
func init() {
    svc = dynamodb.New(session.New())
}

/**
 * Lambdaハンドラ
 */
func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {

    //-------------------------------
    // PutItem
    //-------------------------------
    putInput := &dynamodb.PutItemInput{
        Item: map[string]*dynamodb.AttributeValue{
            "pkey": {S: aws.String("test1")},
            "skey": {S: aws.String("101")},
            "value": {S: aws.String("sample data!")},
        },
        ReturnConsumedCapacity: aws.String("TOTAL"),
        TableName:              aws.String(mytable),
    }
    putResult, putErr := svc.PutItem(putInput)
    if putErr != nil {
        fmt.Printf("Put Error!\n")
        fmt.Printf("%+v\n", putErr)
    } else {
        fmt.Printf("Put Success!\n")
        fmt.Printf("%+v\n", putResult)
    }  

    //-------------------------------
    // GetItem
    //-------------------------------
    getInput := &dynamodb.GetItemInput{
        Key: map[string]*dynamodb.AttributeValue{
            "pkey": {S: aws.String("test1")},
            "skey": {S: aws.String("101")},
        },  
        TableName: aws.String(mytable),
    }   
    getResult, getErr := svc.GetItem(getInput)
    if getErr != nil {
        fmt.Printf("Get Error!\n")
        fmt.Printf("%+v\n", getErr)
    } else {
        fmt.Printf("Get Success!\n")
        fmt.Printf("%+v\n", getResult)
    }

    //-------------------------------
    // Query
    //-------------------------------
    items := []Record{}
    queryInput := &dynamodb.QueryInput{
        ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
            ":key": {S: aws.String("test1")},
        },
        KeyConditionExpression: aws.String("pkey = :key"),
        TableName:              aws.String(mytable),
    }
    queryResult, queryErr := svc.Query(queryInput)
    if queryErr != nil {
        fmt.Printf("Query Error!\n")
        fmt.Printf("%+v\n", queryErr)
    } else {
        fmt.Printf("Query Success!\n")
        fmt.Printf("%+v\n", queryResult)
        // 取得結果の構造体への変換は dynamodbattribute を使用して行う事ができる
        // https://docs.aws.amazon.com/sdk-for-go/api/service/dynamodb/dynamodbattribute/
        err := dynamodbattribute.UnmarshalListOfMaps(queryResult.Items, &items)
        if err != nil {
            panic(fmt.Sprintf("failed to unmarshal Dynamodb Scan Items, %v", err))
        }
    }

    //-------------------------------
    // レスポンスデータの設定
    //-------------------------------
    body, err := json.Marshal(&response{Items: items})
    if err != nil {
        return events.APIGatewayProxyResponse{}, err
    } else {
        return events.APIGatewayProxyResponse{
            Headers: map[string]string{
                "Content-Type": "application/json",
            },
            Body: string(body),
            StatusCode: 200,
        }, nil
    }
}

func main() {
    lambda.Start(handler)
}
}}

#html(</div>)


** Credentialの取得 [#pb6ba495]
#html(<div class="pl10">)

任意の Credential を指定する場合は credentials.NewChainCredentials などが利用できる。

ec2metrics.go
#mycode2(){{
package main

import (
    "fmt"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/awsutil"
    "github.com/aws/aws-sdk-go/aws/credentials"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/cloudwatch"
)

func main() {

    region          := "ap-northeast-1"
    accessKeyId     := "XXXXXXXXXXXXXXXXXXXXXX"
    secretAccessKey := "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

    value, err := getAllMetrics(region, accessKeyId, secretAccessKey)
    fmt.Printf("%+v", value)
    if err != nil{
        fmt.Printf("Error!")
    }   
}

/**
 * メトリクスの一覧を取得する.
 */
func getAllMetrics(region string, accessKeyId string, secretAccessKey string) (cloudwatch.ListMetricsOutput, error) {

    namespace := "AWS/EC2"
    //metric    := "CPUUtilization"

    creds, err := GetCredentials(accessKeyId, secretAccessKey)
    if err != nil {
        return cloudwatch.ListMetricsOutput{}, err 
    }   

    cfg := &aws.Config{
        Region:      aws.String(region),
        Credentials: creds,
    }   
    sess, err := session.NewSession(cfg)
    if err != nil {
        return cloudwatch.ListMetricsOutput{}, err 
    }   
    svc := cloudwatch.New(sess, cfg)

    params := &cloudwatch.ListMetricsInput{
        Namespace: aws.String(namespace),
        //MetricName: aws.String(metric),
    }   

    var resp cloudwatch.ListMetricsOutput
    err = svc.ListMetricsPages(params,
        func(page *cloudwatch.ListMetricsOutput, lastPage bool) bool {
            metrics, _ := awsutil.ValuesAtPath(page, "Metrics")
            for _, metric := range metrics {
                resp.Metrics = append(resp.Metrics, metric.(*cloudwatch.Metric))
            }   
            return !lastPage
        })  
    if err != nil {
        return resp, err 
    }   

    return resp, err 
}

/**
 * クレデンシャル取得.
 */
func GetCredentials(accessKeyId string, secretAccessKey string) (*credentials.Credentials, error) {

    creds := credentials.NewChainCredentials(
        []credentials.Provider{
            &credentials.StaticProvider{Value: credentials.Value{
                AccessKeyID:     accessKeyId,
                SecretAccessKey: secretAccessKey,
            &#125;&#125;, 
            //&credentials.EnvProvider{},    // 環境変数から取得?
        })  

    return creds, nil 
}
}}

#html(</div>)
#html(</div>)

トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS