1

我开始使用 AWS SAM,现在我只有一些单元测试,但我想尝试在预流量挂钩函数中运行集成测试。

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

这个示例中,我拼凑出我必须使用代码部署 SDK 并调用PutLifecycleEventHookExecutionStatus,但具体情况仍不清楚。go的aws 代码示例存储库也没有代码部署示例。

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

我想从测试一个简单地查询 DynamoDB 的 lambda 函数开始。

4

2 回答 2

2

像这样的工作:

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()
}
于 2019-07-18T22:10:50.893 回答
0

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

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

  • 没有办法将新版本的金丝雀暴露给用户群的专用部分,这意味着有时他们会使用新版本或旧版本
  • 调用发布到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:
})

我的 pre hook 函数的 go 代码。PutLifecycleEventHookExecutionStatus告诉代码部署 pre hook 是否成功。不幸的是,如果部署消息失败,您在 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)
}
于 2021-04-23T14:39:35.270 回答