如何在go中编写前置/后置流量挂钩函数?

时间:2018-12-28 13:37:58

标签: go aws-sam

我开始使用AWS SAM,目前只有一些单元测试,但是我想尝试在预流量挂钩功能中运行集成测试。

不幸的是,似乎没有Golang的代码示例,我所能找到的只是Javascript。

this的示例中,我拼凑出必须使用代码部署SDK并调用PutLifecycleEventHookExecutionStatus,但具体细节仍不清楚。 aws code example repo for go也没有用于代码部署的示例。

有关我正在寻找的主题的更多信息,请访问https://github.com/awslabs/serverless-application-model/blob/master/docs/safe_lambda_deployments.rst#pretraffic-posttraffic-hooks

我想通过测试仅查询DynamoDB的lambda函数开始。

2 个答案:

答案 0 :(得分:0)

类似的作品:

package main

import (
    "context"
    "encoding/json"

    "github.com/aws/aws-lambda-go/lambda"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/codedeploy"
)

type CodeDeployEvent struct {
    DeploymentId                  string `json:"deploymentId"`
    LifecycleEventHookExecutionId string `json:"lifecycleEventHookExecutionId"`
}

func HandleRequest(ctx context.Context, event CodeDeployEvent) (string, error) {
    // add some tests here and change status flag as needed . . .
    client := codedeploy.New(session.New())
    params := &codedeploy.PutLifecycleEventHookExecutionStatusInput{
        DeploymentId:                  &event.DeploymentId,
        LifecycleEventHookExecutionId: &event.LifecycleEventHookExecutionId,
        Status:                        "Succeeded",
    }

    req, _ := client.PutLifecycleEventHookExecutionStatusRequest(params)

    _ = req.Send()
}

答案 1 :(得分:0)

我开始实施这个并想分享我的完整解决方案。

在弄清楚如何使用它之后,我决定不使用它,因为它有几个缺点。

  • 无法将新版本的 Canary 公开给用户群的专用部分,这意味着有时他们会使用新版本或旧版本
  • 调用发布到sns的函数会触发所有下游动作,可能会得到下游服务的新旧版本,如果API被破坏会导致很多问题
  • IAM 更改会立即影响两个版本,可能会破坏旧版本。

相反,我将所有内容部署到一个 pre prod 帐户,运行我的集成和 e2e 测试,如果它们成功,我将部署到 prod

用于创建金丝雀部署的 cdk 代码:

const versionAlias = new lambda.Alias(this, 'Alias', {
    aliasName: "alias",
    version: this.lambda.currentVersion,
})

const preHook = new lambda.Function(this, 'LambdaPreHook', {
    description: "pre hook",
    code: lambda.Code.fromAsset('dist/upload/convert-pre-hook'),
    handler: 'main',
    runtime: lambda.Runtime.GO_1_X,
    memorySize: 128,
    timeout: cdk.Duration.minutes(1),
    environment: {
        FUNCTION_NAME: this.lambda.currentVersion.functionName,
    },
    reservedConcurrentExecutions: 5,
    logRetention: RetentionDays.ONE_WEEK,
})
// this.lambda.grantInvoke(preHook) // this doesn't work, I need to grant invoke to all functions :s
preHook.addToRolePolicy(new iam.PolicyStatement({
    actions: [
        "lambda:InvokeFunction",
    ],
    resources: ["*"],
    effect: iam.Effect.ALLOW,
}))

const application = new codedeploy.LambdaApplication(this, 'CodeDeployApplication')
new codedeploy.LambdaDeploymentGroup(this, 'CanaryDeployment', {
    application: application,
    alias: versionAlias,
    deploymentConfig: codedeploy.LambdaDeploymentConfig.ALL_AT_ONCE,
    preHook: preHook,
    autoRollback: {
        failedDeployment: true,
        stoppedDeployment: true,
        deploymentInAlarm: false,
    },
    ignorePollAlarmsFailure: false,
    // alarms:
    // autoRollback: codedeploy.A
    // postHook:
})

我的预钩子函数的代码。 PutLifecycleEventHookExecutionStatus 告诉代码部署 pre 钩子是否成功。不幸的是,如果部署消息失败,那么您在 cdk deploy 输出中得到的消息完全没有用,因此您需要检查前/后挂钩日志。

为了实际运行集成测试,我只需调用 lambda 并检查是否发生错误。

package main

import (
    "encoding/base64"
    "fmt"
    "log"
    "os"

    "github.com/aws/aws-lambda-go/lambda"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/codedeploy"
    lambdaService "github.com/aws/aws-sdk-go/service/lambda"
)

var svc *codedeploy.CodeDeploy
var lambdaSvc *lambdaService.Lambda

type codeDeployEvent struct {
    DeploymentId                  string `json:"deploymentId"`
    LifecycleEventHookExecutionId string `json:"lifecycleEventHookExecutionId"`
}

func handler(e codeDeployEvent) error {
    params := &codedeploy.PutLifecycleEventHookExecutionStatusInput{
        DeploymentId:                  &e.DeploymentId,
        LifecycleEventHookExecutionId: &e.LifecycleEventHookExecutionId,
    }
    err := handle()
    if err != nil {
        log.Println(err)
        params.Status = aws.String(codedeploy.LifecycleEventStatusFailed)
    } else {
        params.Status = aws.String(codedeploy.LifecycleEventStatusSucceeded)
    }

    _, err = svc.PutLifecycleEventHookExecutionStatus(params)
    if err != nil {
        return fmt.Errorf("failed putting the lifecycle event hook execution status. the status was %s", *params.Status)
    }

    return nil
}

func handle() error {
    functionName := os.Getenv("FUNCTION_NAME")
    if functionName == "" {
        return fmt.Errorf("FUNCTION_NAME not set")
    }
    log.Printf("function name: %s", functionName)

    // invoke lambda via sdk
    input := &lambdaService.InvokeInput{
        FunctionName:   &functionName,
        Payload:        nil,
        LogType:        aws.String(lambdaService.LogTypeTail),                   // returns the log in the response
        InvocationType: aws.String(lambdaService.InvocationTypeRequestResponse), // synchronous - default
    }
    err := input.Validate()
    if err != nil {
        return fmt.Errorf("validating the input failed: %v", err)
    }

    resp, err := lambdaSvc.Invoke(input)
    if err != nil {
        return fmt.Errorf("failed to invoke lambda: %v", err)
    }

    decodeString, err := base64.StdEncoding.DecodeString(*resp.LogResult)
    if err != nil {
        return fmt.Errorf("failed to decode the log: %v", err)
    }
    log.Printf("log result: %s", decodeString)

    if resp.FunctionError != nil {
        return fmt.Errorf("lambda was invoked but returned error: %s", *resp.FunctionError)
    }
    return nil
}

func main() {
    sess, err := session.NewSession()
    if err != nil {
        return
    }
    svc = codedeploy.New(sess)
    lambdaSvc = lambdaService.New(sess)
    lambda.Start(handler)
}