

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 创建 Lambda 函数来评估 Lambda 挂钩的资源
<a name="lambda-hooks-create-lambda-function"></a>

CloudFormation Lambda Hooks 允许您对自己的自定义代码进行评估 CloudFormation 和 AWS 云端控制 API 操作。您的 Hook 可以阻止操作继续进行，或者向调用者发出警告并允许操作继续进行。创建 Lambda 挂钩时，可以将其配置为拦截和评估以下操作： CloudFormation 
+ 资源操作
+ 堆栈操作
+ 更改集合操作

**Topics**
+ [

## 开发 Lambda 挂钩
](#lambda-hooks-create-lambda-function-develop)
+ [

## 使用 Lambda 挂钩评估资源操作
](#lambda-hooks-create-lambda-function-resource)
+ [

## 使用 Lambda 挂钩评估堆栈操作
](#lambda-hooks-create-lambda-function-stack)
+ [

## 使用 Lambda 挂钩评估变更集操作
](#lambda-hooks-create-lambda-function-change-set)

## 开发 Lambda 挂钩
<a name="lambda-hooks-create-lambda-function-develop"></a>

当 Hook 调用你的 Lambda 时，它将等待长达 30 秒钟让 Lambda 评估输入。Lambda 将返回一个 JSON 响应，指示挂钩是成功还是失败。

**Topics**
+ [

### 请求输入
](#lambda-hooks-create-lambda-function-request-input)
+ [

### 响应输入
](#lambda-hooks-create-lambda-function-request-response)
+ [

### 示例
](#lambda-hooks-create-lambda-function-request-example)

### 请求输入
<a name="lambda-hooks-create-lambda-function-request-input"></a>

传递给 Lambda 函数的输入取决于 Hook 目标操作（例如：堆栈、资源或更改集）。

### 响应输入
<a name="lambda-hooks-create-lambda-function-request-response"></a>

为了在您的请求成功或失败时与 Hook 进行通信，您的 Lambda 函数需要返回 JSON 响应。

以下是 Hooks 期望的响应的示例形状：

```
{ 
  "挂钩状态": "SUCCESS" or "FAILED" or "IN_PROGRESS", 
  "errorCode": "NonCompliant" or "InternalFailure"
  "message": String, 
  "clientRequestToken": String,
  "回调上下文": None, 
  "callbackDelaySeconds": Integer,
  "annotations": [
    {
      "annotationName": String,
      "status": "PASSED" or "FAILED" or "SKIPPED",
      "statusMessage": String,
      "remediationMessage": String,
      "remediationLink": String,
      "severityLevel": "INFORMATIONAL" or "LOW" or "MEDIUM" or "HIGH" or "CRITICAL"
    }
  ]
}
```

挂钩状态  <a name="lambda-hook-response-hookstatus"></a>
挂钩的状态。此字段为必填字段。  
*有效值*：(`SUCCESS`\$1 `FAILED` \$1`IN_PROGRESS`)  
Hook 可以返回 `IN_PROGRESS` 3 次。如果未返回任何结果，Hook 将失败。对于 Lambda 挂钩，这意味着您的 Lambda 函数最多可以被调用 3 次。

errorCode  <a name="lambda-hook-response-errorcode"></a>
显示操作是否经过评估并被确定为无效，或者挂钩内是否发生了错误，从而阻止了评估。如果挂钩失败，则此字段为必填字段。  
*有效值*：(`NonCompliant`\$1`InternalFailure`)

message  <a name="lambda-hook-response-message"></a>
给调用者的消息，说明挂钩成功或失败的原因。  
计算 CloudFormation 操作时，此字段被截断为 4096 个字符。  
在评估 Cloud Control API 操作时，此字段被截断为 1024 个字符。

clientRequestToken  <a name="lambda-hook-response-clientrequesttoken"></a>
作为 Hook 请求的输入而提供的请求令牌。此字段为必填字段。

回调上下文  <a name="lambda-hook-response-callbackcontext"></a>
如果您指明`hookStatus`是，则`IN_PROGRESS`会传递一个额外的上下文，该上下文在重新调用 Lambda 函数时作为输入提供。

callbackDelaySeconds  <a name="lambda-hook-response-callbackdelayseconds"></a>
Hook 应该等多久才能再次调用这个 Hook。

annotations  <a name="lambda-hook-response-annotations"></a>
一系列注释对象，可提供更多详细信息和补救指导。    
注释名称  
注释的标识符。  
status  
Hook 调用状态。当注解表示逻辑时，这会很有用，其通过/失败评估与防护规则类似。  
*有效值*：(`PASSED`\$1 `FAILED` \$1`SKIPPED`)  
statusMessage  
具体状态的解释。  
补救消息  
关于修复`FAILED`状态的建议。例如，如果资源缺少加密，则可以说明如何为资源配置添加加密。  
修正链接  
用于获取更多补救指南的 HTTP 网址。  
severityLevel  
定义与任何此类违规行为相关的相对风险。在为 Hook 调用结果分配严重性级别时，您可以参考 AWS Security Hub CSPM [严重性框架](https://docs.aws.amazon.com/securityhub/latest/userguide/asff-required-attributes.html#Severity)，以此作为如何构建有意义的严重性类别的示例。  
*有效值*：(`INFORMATIONAL`\$1 `LOW` \$1 `MEDIUM` \$1 `HIGH` \$1`CRITICAL`)

### 示例
<a name="lambda-hooks-create-lambda-function-request-example"></a>

以下是成功响应的示例：

```
{ 
  "hookStatus": "SUCCESS",
  "message": "compliant",
  "clientRequestToken": "123avjdjk31"  
}
```

以下是响应失败的示例：

```
{ 
  "hookStatus": "FAILED",
  "errorCode": "NonCompliant",
  "message": "S3 Bucket Versioning must be enabled.",
  "clientRequestToken": "123avjdjk31"
 }
```

## 使用 Lambda 挂钩评估资源操作
<a name="lambda-hooks-create-lambda-function-resource"></a>

每当您创建、更新或删除资源时，都被视为资源操作。例如，如果您运行更新 CloudFormation 堆栈以创建新资源，则表示您已完成资源操作。当您使用 Cloud Control API 创建、更新或删除资源时，这也被视为资源操作。您可以将 CloudFormation Lambda 挂钩配置为目标`RESOURCE`和挂钩`TargetOperations`配置中的`CLOUD_CONTROL`操作。

**注意**  
只有在使用 Cloud Control API `delete-resource` 中的操作触发器删除资源时，才会调用 `delete` Hook 处理程序 CloudFormation `delete-stack`。

**Topics**
+ [

### Lambda Hook 资源输入语法
](#lambda-hooks-create-lambda-function-resource-input)
+ [

### Lambda Hook 资源变更输入示例
](#lambda-hooks-create-lambda-function-resource-example)
+ [

### 用于资源操作的 Lambda 函数示例
](#lambda-hooks-create-lambda-function-resource-example-function)

### Lambda Hook 资源输入语法
<a name="lambda-hooks-create-lambda-function-resource-input"></a>

当您的 Lambda 被调用进行资源操作时，您将收到一个 JSON 输入，其中包含资源属性、建议的属性以及与 Hook 调用相关的上下文。

以下是 JSON 输入的示例形状：

```
{
    "awsAccountId": String,
    "stackId": String,
    "changeSetId": String,
    "hookTypeName": String,
    "hookTypeVersion": String,
    "hookModel": {
        "LambdaFunction": String
    },
    "actionInvocationPoint": "CREATE_PRE_PROVISION" or "UPDATE_PRE_PROVISION" or "DELETE_PRE_PROVISION"
    "requestData": {
        "targetName": String,
        "targetType": String,
        "targetLogicalId": String,
        "targetModel": {
            "resourceProperties": {...},
            "previousResourceProperties": {...}
        }
    },
    "requestContext": {
        "调用": 1,
        "回调上下文": null
    }
}
```

`awsAccountId`  <a name="lambda-hook-resource-awsaccountid"></a>
 AWS 账户 包含正在评估的资源的 ID。

`stackId`  <a name="lambda-hook-resource-stackid"></a>
此操作所属 CloudFormation 堆栈的堆栈 ID。如果调用方是云控制 API，则此字段为空。

`changeSetId`  <a name="lambda-hook-resource-changesetid"></a>
启动 Hook 调用的更改集的 ID。如果资源变更是由云控制 API 或、或`delete-stack`操作启动的，则此值为`create-stack`空。`update-stack`

`hookTypeName`  <a name="lambda-hook-resource-hooktypename"></a>
正在运行的 Hook 的名称。

`hookTypeVersion`  <a name="lambda-hook-resource-hooktypeversion"></a>
正在运行的 Hook 的版本。

`hookModel`  <a name="lambda-hook-resource-hookmodel"></a>  
`LambdaFunction`  <a name="lambda-hook-resource-hookmodel-lambdafunction"></a>
挂钩调用的当前 Lambda ARN。

`actionInvocationPoint`  <a name="lambda-hook-resource-actioninvocationpoint"></a>
配置逻辑中挂钩运行的确切位置。  
*有效值*：(`CREATE_PRE_PROVISION`\$1 `UPDATE_PRE_PROVISION` \$1`DELETE_PRE_PROVISION`)

`requestData`  <a name="lambda-hook-resource-requestdata"></a>  
`targetName`  <a name="lambda-hook-resource-requestdata-targetname"></a>
正在评估的目标类型，例如`AWS::S3::Bucket`。  
`targetType`  <a name="lambda-hook-resource-requestdata-targettype"></a>
例如，正在评估的目标类型`AWS::S3::Bucket`。对于使用云控制 API 置备的资源，此值将`RESOURCE`为。  
`targetLogicalId`  <a name="lambda-hook-resource-requestdata-targetlogicalid"></a>
正在评估的资源的逻辑 ID。如果 Hook 调用的来源是 CloudFormation，则这将是 CloudFormation 模板中定义的逻辑资源 ID。如果此 Hook 调用的来源是 Cloud Control API，则这将是一个构造值。  
`targetModel`  <a name="lambda-hook-resource-requestdata-targetmodel"></a>  
`resourceProperties`  <a name="lambda-hook-resource-requestdata-targetmodel-resourceproperties"></a>
正在修改的资源的建议属性。如果要删除资源，则此值将为空。  
`previousResourceProperties`  <a name="lambda-hook-resource-requestdata-targetmodel-previousresourceproperties"></a>
当前与正在修改的资源关联的属性。如果正在创建资源，则此值将为空。

`requestContext`  <a name="lambda-hook-resource-requestcontext"></a>  
调用  <a name="lambda-hook-resource-requestcontext-invocation"></a>
当前执行挂钩的尝试。  
回调上下文  <a name="lambda-hook-resource-requestcontext-callbackcontext"></a>
如果 Hookwas 设置为`IN_PROGRESS`，并且`callbackContext`已返回，则它将在重新调用后出现在这里。

### Lambda Hook 资源变更输入示例
<a name="lambda-hooks-create-lambda-function-resource-example"></a>

以下示例输入显示了一个 Lambda 挂钩，它将接收要更新的`AWS::DynamoDB::Table`资源定义，其中 of `ReadCapacityUnits` 从 3 更改`ProvisionedThroughput`为 10。这是 Lambda 可用于评估的数据。

```
{
    "awsAccountId": "123456789012",
    "stackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/MyStack/1a2345b6-0000-00a0-a123-00abc0abc000",
    "hookTypeName": "my::lambda::resourcehookfunction",
    "hookTypeVersion": "00000008",
    "hookModel": {
        "LambdaFunction": "arn:aws:lambda:us-west-2:123456789012:function:MyFunction"
    },
    "actionInvocationPoint": "UPDATE_PRE_PROVISION",
    "requestData": {
        "targetName": "AWS::DynamoDB::Table",
        "targetType": "AWS::DynamoDB::Table",
        "targetLogicalId": "DDBTable",
        "targetModel": {
            "resourceProperties": {
                "AttributeDefinitions": [
                    {
                        "AttributeType": "S",
                        "AttributeName": "Album"
                    },
                    {
                        "AttributeType": "S",
                        "AttributeName": "Artist"
                    }
                ],
                "ProvisionedThroughput": {
                    "WriteCapacityUnits": 5,
                    "ReadCapacityUnits": 10
                },
                "KeySchema": [
                    {
                        "KeyType": "HASH",
                        "AttributeName": "Album"
                    },
                    {
                        "KeyType": "RANGE",
                        "AttributeName": "Artist"
                    }
                ]
            },
            "previousResourceProperties": {
                "AttributeDefinitions": [
                    {
                        "AttributeType": "S",
                        "AttributeName": "Album"
                    },
                    {
                        "AttributeType": "S",
                        "AttributeName": "Artist"
                    }
                ],
                "ProvisionedThroughput": {
                    "WriteCapacityUnits": 5,
                    "ReadCapacityUnits": 5
                },
                "KeySchema": [
                    {
                        "KeyType": "HASH",
                        "AttributeName": "Album"
                    },
                    {
                        "KeyType": "RANGE",
                        "AttributeName": "Artist"
                    }
                ]
            }
        }
    },
    "requestContext": {
        "invocation": 1,
        "callbackContext": null
    }    
}
```

要查看该资源类型的所有可用属性，请参见[https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-dynamodb-table.html](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-dynamodb-table.html)。

### 用于资源操作的 Lambda 函数示例
<a name="lambda-hooks-create-lambda-function-resource-example-function"></a>

以下是一个简单的函数，它会让 DynamoDB 的任何资源更新失败，DynamoDB 会尝试将 of 设置为`ReadCapacity`大于 `ProvisionedThroughput` 10 的值。如果挂接成功，则将向呼叫者显示消息 “ReadCapacity 配置正确”。如果请求验证失败，则挂钩将失败，状态为 “ReadCapacity 不能超过 10”。

------
#### [ Node.js ]

```
export const handler = async (event, context) => {
    var targetModel = event?.requestData?.targetModel;
    var targetName = event?.requestData?.targetName;
    var response = {
        "hookStatus": "SUCCESS",
        "message": "ReadCapacity is correctly configured.",
        "clientRequestToken": event.clientRequestToken
    };

    if (targetName == "AWS::DynamoDB::Table") {
        var readCapacity = targetModel?.resourceProperties?.ProvisionedThroughput?.ReadCapacityUnits;
        if (readCapacity > 10) {
            response.hookStatus = "FAILED";
            response.errorCode = "NonCompliant";
            response.message = "ReadCapacity must be cannot be more than 10.";
        }
    }
    return response;
};
```

------
#### [ Python ]

```
import json
                            
def lambda_handler(event, context):
    # Using dict.get() for safe access to nested dictionary values
    request_data = event.get('requestData', {})
    target_model = request_data.get('targetModel', {})
    target_name = request_data.get('targetName', '')
    
    response = {
        "hookStatus": "SUCCESS",
        "message": "ReadCapacity is correctly configured.",
        "clientRequestToken": event.get('clientRequestToken')
    }
    
    if target_name == "AWS::DynamoDB::Table":
        # Safely navigate nested dictionary
        resource_properties = target_model.get('resourceProperties', {})
        provisioned_throughput = resource_properties.get('ProvisionedThroughput', {})
        read_capacity = provisioned_throughput.get('ReadCapacityUnits')
        
        if read_capacity and read_capacity > 10:
            response['hookStatus'] = "FAILED"
            response['errorCode'] = "NonCompliant"
            response['message'] = "ReadCapacity must be cannot be more than 10."
    
    return response
```

------

## 使用 Lambda 挂钩评估堆栈操作
<a name="lambda-hooks-create-lambda-function-stack"></a>

每当您使用新模板创建、更新或删除堆栈时，都可以将您的 CloudFormation Lambda Hook 配置为从评估新模板开始，并可能阻止堆栈操作继续进行。您可以将 CloudFormation Lambda 挂钩配置为挂钩配置中的`STACK`操作目标。`TargetOperations`

**Topics**
+ [

### Lambda Hook 堆栈输入语法
](#lambda-hooks-create-lambda-function-stack-input)
+ [

### Lambda Hook 堆栈更改输入示例
](#lambda-hooks-create-lambda-function-stack-example)
+ [

### 用于堆栈操作的 Lambda 函数示例
](#lambda-hooks-create-lambda-function-stack-example-function)

### Lambda Hook 堆栈输入语法
<a name="lambda-hooks-create-lambda-function-stack-input"></a>

当为堆栈操作调用 Lambda 时，您将收到一个包含 Hook 调用上下文和请求上下文的 JSON 请求。`actionInvocationPoint`由于 CloudFormation 模板的大小以及 Lambda 函数接受的输入大小有限，因此实际模板存储在 Amazon S3 对象中。的输入`requestData`包括指向另一个对象的 Amazon S3 签名 URL，其中包含当前和以前的模板版本。

以下是 JSON 输入的示例形状：

```
{
    "clientRequesttoken": String,
    "awsAccountId": String,
    "stackID": String,
    "changeSetId": String,
    "hookTypeName": String,
    "hookTypeVersion": String,
    "hookModel": {
        "LambdaFunction":String
    },
    "actionInvocationPoint": "CREATE_PRE_PROVISION" or "UPDATE_PRE_PROVISION" or "DELETE_PRE_PROVISION"
    "requestData": {
        "targetName": "STACK",
        "targetType": "STACK",
        "targetLogicalId": String,
        "payload": String (S3 Presigned URL)
    },
    "requestContext": {
        "invocation": Integer,
        "callbackContext": String
    }
}
```

`clientRequesttoken`  <a name="lambda-hook-stack-clientrequesttoken"></a>
作为 Hook 请求的输入而提供的请求令牌。此字段为必填字段。

`awsAccountId`  <a name="lambda-hook-stack-awsaccountid"></a>
 AWS 账户 包含正在评估的堆栈的 ID。

`stackID`  <a name="lambda-hook-stack-stackid"></a>
堆栈的堆 CloudFormation 栈 ID。

`changeSetId`  <a name="lambda-hook-stack-changesetid"></a>
启动 Hook 调用的更改集的 ID。如果堆栈变更是由云控制 API 或、或`delete-stack`操作启动的，则此值为`create-stack`空。`update-stack`

`hookTypeName`  <a name="lambda-hook-stack-hooktypename"></a>
正在运行的 Hook 的名称。

`hookTypeVersion`  <a name="lambda-hook-stack-hooktypeversion"></a>
正在运行的 Hook 的版本。

`hookModel`  <a name="lambda-hook-stack-hookmodel"></a>  
`LambdaFunction`  <a name="lambda-hook-stack-hookmodel-lambdafunction"></a>
挂钩调用的当前 Lambda ARN。

`actionInvocationPoint`  <a name="lambda-hook-stack-actioninvocationpoint"></a>
配置逻辑中挂钩运行的确切位置。  
*有效值*：(`CREATE_PRE_PROVISION`\$1 `UPDATE_PRE_PROVISION` \$1`DELETE_PRE_PROVISION`)

`requestData`  <a name="lambda-hook-stack-requestdata"></a>  
`targetName`  <a name="lambda-hook-stack-requestdata-targetname"></a>
这个值将是 `STACK`。  
`targetType`  <a name="lambda-hook-stack-requestdata-targettype"></a>
这个值将是 `STACK`。  
`targetLogicalId`  <a name="lambda-hook-stack-requestdata-targetlogicalid"></a>
堆栈名称。  
`payload`  <a name="lambda-hook-stack-requestdata-payload"></a>
Amazon S3 预签名 URL，其中包含一个包含当前和之前模板定义的 JSON 对象。

`requestContext`  <a name="lambda-hook-stack-requestcontext"></a>
如果正在重新调用 Hook，则将设置此对象。    
`invocation`  <a name="lambda-hook-stack-requestcontext-invocation"></a>
当前执行挂钩的尝试。  
`callbackContext`  <a name="lambda-hook-stack-requestcontext-callbackcontext"></a>
如果 Hook 设置为`IN_PROGRESS`并`callbackContext`已返回，则它将在重新调用时出现在这里。

请求数据中的`payload`属性是您的代码需要获取的 URL。收到网址后，您将获得一个包含以下架构的对象：

```
{
    "template": String,
    "previousTemplate": String
}
```

`template`  <a name="lambda-hook-stack-payload-template"></a>
提供给`create-stack`或的完整 CloudFormation 模板`update-stack`。它可以是 JSON 或 YAML 字符串，具体取决于提供的内容。 CloudFormation  
在`delete-stack`操作中，此值将为空。

`previousTemplate`  <a name="lambda-hook-stack-payload-previoustemplate"></a>
之前的 CloudFormation 模板。它可以是 JSON 或 YAML 字符串，具体取决于提供的内容。 CloudFormation  
在`delete-stack`操作中，此值将为空。

### Lambda Hook 堆栈更改输入示例
<a name="lambda-hooks-create-lambda-function-stack-example"></a>

以下是堆栈变更输入的示例。Hook 正在评估一项更改，该更改`ObjectLockEnabled`将更新为 true，并添加了一个 Amazon SQS 队列：

```
{
    "clientRequestToken": "f8da6d11-b23f-48f4-814c-0fb6a667f50e",
    "awsAccountId": "123456789012",
    "stackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/MyStack/1a2345b6-0000-00a0-a123-00abc0abc000",
    "changeSetId": null,
    "hookTypeName": "my::lambda::stackhook",
    "hookTypeVersion": "00000008",
    "hookModel": {
        "LambdaFunction": "arn:aws:lambda:us-west-2:123456789012:function:MyFunction"
    },
    "actionInvocationPoint": "UPDATE_PRE_PROVISION",
    "requestData": {
        "targetName": "STACK",
        "targetType": "STACK",
        "targetLogicalId": "my-cloudformation-stack",
        "payload": "https://s3......"
    },
    "requestContext": {
        "invocation": 1,
        "callbackContext": null
    }
}
```

以下是以下`payload`示例`requestData`：

```
{
    "template": "{\"Resources\":{\"S3Bucket\":{\"Type\":\"AWS::S3::Bucket\",\"Properties\":{\"ObjectLockEnabled\":true}},\"SQSQueue\":{\"Type\":\"AWS::SQS::Queue\",\"Properties\":{\"QueueName\":\"NewQueue\"}}}}",
    "previousTemplate": "{\"Resources\":{\"S3Bucket\":{\"Type\":\"AWS::S3::Bucket\",\"Properties\":{\"ObjectLockEnabled\":false}}}}"
}
```

### 用于堆栈操作的 Lambda 函数示例
<a name="lambda-hooks-create-lambda-function-stack-example-function"></a>

以下示例是一个简单的函数，它下载堆栈操作有效负载，解析模板 JSON 并返回`SUCCESS`。

------
#### [ Node.js ]

```
export const handler = async (event, context) => {
    var targetType = event?.requestData?.targetType;
    var payloadUrl = event?.requestData?.payload;
    
    var response = {
        "hookStatus": "SUCCESS",
        "message": "Stack update is compliant",
        "clientRequestToken": event.clientRequestToken
    };
    try {
        const templateHookPayloadRequest = await fetch(payloadUrl);
        const templateHookPayload = await templateHookPayloadRequest.json()
        if (templateHookPayload.template)  {
            // Do something with the template templateHookPayload.template
            // JSON or YAML
        }
        if (templateHookPayload.previousTemplate) {
            // Do something with the template templateHookPayload.previousTemplate
            // JSON or YAML        
        }        
    } catch (error) {
        console.log(error);
        response.hookStatus = "FAILED";
        response.message = "Failed to evaluate stack operation.";
        response.errorCode = "InternalFailure";
    }
    return response;
};
```

------
#### [ Python ]

要使用 Python，你需要导入该`requests`库。为此，您需要在创建 Lambda 函数时将该库包含在部署包中。有关更多信息，请参阅《*AWS Lambda 开发人员指南》*中的[创建带有依赖项的.zip 部署包](https://docs.aws.amazon.com/lambda/latest/dg/python-package.html#python-package-create-dependencies)。

```
import json
import requests

def lamnbda_handler(event, context):
    # Safely access nested dictionary values
    request_data = event.get('requestData', {})
    target_type = request_data.get('targetType')
    payload_url = request_data.get('payload')
    
    response = {
        "hookStatus": "SUCCESS",
        "message": "Stack update is compliant",
        "clientRequestToken": event.get('clientRequestToken')
    }
    
    try:
        # Fetch the payload
        template_hook_payload_request = requests.get(payload_url)
        template_hook_payload_request.raise_for_status()  # Raise an exception for bad responses
        template_hook_payload = template_hook_payload_request.json()
        
        if 'template' in template_hook_payload:
            # Do something with the template template_hook_payload['template']
            # JSON or YAML
            pass
        
        if 'previousTemplate' in template_hook_payload:
            # Do something with the template template_hook_payload['previousTemplate']
            # JSON or YAML
            pass

    except Exception as error:
        print(error)
        response['hookStatus'] = "FAILED"
        response['message'] = "Failed to evaluate stack operation."
        response['errorCode'] = "InternalFailure"
    
    return response
```

------

## 使用 Lambda 挂钩评估变更集操作
<a name="lambda-hooks-create-lambda-function-change-set"></a>

每当您创建更改集时，都可以将您的 CloudFormation Lambda Hook 配置为首先评估新的更改集并可能阻止其执行。您可以将 CloudFormation Lambda 挂钩配置为挂钩配置中的`CHANGE_SET`操作目标。`TargetOperations`

**Topics**
+ [

### Lambda 挂钩更改集输入语法
](#lambda-hooks-create-lambda-function-change-set-input)
+ [

### 示例 Lambda 挂钩更改集更改输入
](#lambda-hooks-create-lambda-function-change-set-example)
+ [

### 用于变更集操作的 Lambda 函数示例
](#lambda-hooks-create-lambda-function-change-set-example-function)

### Lambda 挂钩更改集输入语法
<a name="lambda-hooks-create-lambda-function-change-set-input"></a>

变更集操作的输入与堆栈操作类似，但是的有效载荷`requestData`还包括变更集引入的资源变更列表。

以下是 JSON 输入的示例形状：

```
{
    "clientRequesttoken": String,
    "awsAccountId": String,
    "stackID": String,
    "changeSetId": String,
    "hookTypeName": String,
    "hookTypeVersion": String,
    "hookModel": {
        "LambdaFunction":String
    },
    "requestData": {
        "targetName": "CHANGE_SET",
        "targetType": "CHANGE_SET",
        "targetLogicalId": String,
        "payload": String (S3 Presigned URL)
    },
    "requestContext": {
        "invocation": Integer,
        "callbackContext": String
    }
}
```

`clientRequesttoken`  <a name="lambda-hook-change-set-clientrequesttoken"></a>
作为 Hook 请求的输入而提供的请求令牌。此字段为必填字段。

`awsAccountId`  <a name="lambda-hook-change-set-awsaccountid"></a>
 AWS 账户 包含正在评估的堆栈的 ID。

`stackID`  <a name="lambda-hook-change-set-stackid"></a>
堆栈的堆 CloudFormation 栈 ID。

`changeSetId`  <a name="lambda-hook-change-set-changesetid"></a>
启动 Hook 调用的更改集的 ID。

`hookTypeName`  <a name="lambda-hook-change-set-hooktypename"></a>
正在运行的 Hook 的名称。

`hookTypeVersion`  <a name="lambda-hook-change-set-hooktypeversion"></a>
正在运行的 Hook 的版本。

`hookModel`  <a name="lambda-hook-change-set-hookmodel"></a>  
`LambdaFunction`  <a name="lambda-hook-change-set-hookmodel-lambdafunction"></a>
挂钩调用的当前 Lambda ARN。

`requestData`  <a name="lambda-hook-change-set-requestdata"></a>  
`targetName`  <a name="lambda-hook-change-set-requestdata-targetname"></a>
这个值将是 `CHANGE_SET`。  
`targetType`  <a name="lambda-hook-change-set-requestdata-targettype"></a>
这个值将是 `CHANGE_SET`。  
`targetLogicalId`  <a name="lambda-hook-change-set-requestdata-targetlogicalid"></a>
更改集合 ARN...  
`payload`  <a name="lambda-hook-change-set-requestdata-payload"></a>
Amazon S3 预签名 URL，其中包含带有当前模板的 JSON 对象，以及此更改集引入的更改列表。

`requestContext`  <a name="lambda-hook-change-set-requestcontext"></a>
如果正在重新调用 Hook，则将设置此对象。    
`invocation`  <a name="lambda-hook-change-set-requestcontext-invocation"></a>
当前执行挂钩的尝试。  
`callbackContext`  <a name="lambda-hook-change-set-requestcontext-callbackcontext"></a>
如果 Hook 设置为`IN_PROGRESS`并`callbackContext`已返回，则它将在重新调用时出现在这里。

请求数据中的`payload`属性是您的代码需要获取的 URL。收到网址后，您将获得一个包含以下架构的对象：

```
{
    "template": String,
    "changedResources": [
        {
            "action": String,
            "beforeContext": JSON String,
            "afterContext": JSON String,
            "lineNumber": Integer,
            "logicalResourceId": String,
            "resourceType": String
        }
    ]
}
```

`template`  <a name="lambda-hook-change-set-payload-template"></a>
提供给`create-stack`或的完整 CloudFormation 模板`update-stack`。它可以是 JSON 或 YAML 字符串，具体取决于提供的内容。 CloudFormation

`changedResources`  <a name="lambda-hook-change-set-payload-changed-resources"></a>
已更改的资源列表。    
`action`  <a name="lambda-hook-change-set-payload-changed-resources-action"></a>
应用于资源的更改类型。  
*有效值*：(`CREATE`\$1 `UPDATE` \$1`DELETE`)  
`beforeContext`  <a name="lambda-hook-change-set-payload-changed-resources-beforecontext"></a>
更改前资源属性的 JSON 字符串。创建资源时，此值为空。此 JSON 字符串中的所有布尔值和数字值均为字符串。  
`afterContext`  <a name="lambda-hook-change-set-payload-changed-resources-aftercontext"></a>
如果执行此更改集，则为资源属性的 JSON 字符串。删除资源时，此值为空。此 JSON 字符串中的所有布尔值和数字值均为字符串。  
`lineNumber`  <a name="lambda-hook-change-set-payload-changed-resources-linenumber"></a>
模板中导致此更改的行号。如果操作是，则`DELETE`此值将为空。  
`logicalResourceId`  <a name="lambda-hook-change-set-payload-changed-resources-logicalresourceid"></a>
正在更改的资源的逻辑资源 ID。  
`resourceType`  <a name="lambda-hook-change-set-payload-changed-resources-resourcetype"></a>
正在更改的资源类型。

### 示例 Lambda 挂钩更改集更改输入
<a name="lambda-hooks-create-lambda-function-change-set-example"></a>

以下是更改集更改输入的示例。在以下示例中，您可以看到变更集引入的更改。第一个更改是删除名为的队列`CoolQueue`。第二个更改是添加一个名为的新队列`NewCoolQueue`。最后一项更改是对的更新`DynamoDBTable`。

```
{
    "clientRequestToken": "f8da6d11-b23f-48f4-814c-0fb6a667f50e",
    "awsAccountId": "123456789012",
    "stackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/MyStack/1a2345b6-0000-00a0-a123-00abc0abc000",
    "changeSetId": "arn:aws:cloudformation:us-west-2:123456789012:changeSet/SampleChangeSet/1a2345b6-0000-00a0-a123-00abc0abc000",
    "hookTypeName": "my::lambda::changesethook",
    "hookTypeVersion": "00000008",
    "hookModel": {
        "LambdaFunction": "arn:aws:lambda:us-west-2:123456789012:function:MyFunction"
    },
    "actionInvocationPoint": "CREATE_PRE_PROVISION",
    "requestData": {
        "targetName": "CHANGE_SET",
        "targetType": "CHANGE_SET",
        "targetLogicalId": "arn:aws:cloudformation:us-west-2:123456789012:changeSet/SampleChangeSet/1a2345b6-0000-00a0-a123-00abc0abc000",
        "payload": "https://s3......"
    },
    "requestContext": {
        "invocation": 1,
        "callbackContext": null
    }
}
```

以下是以下`payload`示例`requestData.payload`：

```
{
  template: 'Resources:\n' +
    '  DynamoDBTable:\n' +
    '    Type: AWS::DynamoDB::Table\n' +
    '    Properties:\n' +
    '      AttributeDefinitions:\n' +
    '        - AttributeName: "PK"\n' +
    '          AttributeType: "S"\n' +
    '      BillingMode: "PAY_PER_REQUEST"\n' +
    '      KeySchema:\n' +
    '        - AttributeName: "PK"\n' +
    '          KeyType: "HASH"\n' +
    '      PointInTimeRecoverySpecification:\n' +
    '        PointInTimeRecoveryEnabled: false\n' +
    '  NewSQSQueue:\n' +
    '    Type: AWS::SQS::Queue\n' +
    '    Properties:\n' +
    '      QueueName: "NewCoolQueue"',
  changedResources: [
    {
      logicalResourceId: 'SQSQueue',
      resourceType: 'AWS::SQS::Queue',
      action: 'DELETE',
      lineNumber: null,
      beforeContext: '{"Properties":{"QueueName":"CoolQueue"}}',
      afterContext: null
    },
    {
      logicalResourceId: 'NewSQSQueue',
      resourceType: 'AWS::SQS::Queue',
      action: 'CREATE',
      lineNumber: 14,
      beforeContext: null,
      afterContext: '{"Properties":{"QueueName":"NewCoolQueue"}}'
    },
    {
      logicalResourceId: 'DynamoDBTable',
      resourceType: 'AWS::DynamoDB::Table',
      action: 'UPDATE',
      lineNumber: 2,
      beforeContext: '{"Properties":{"BillingMode":"PAY_PER_REQUEST","AttributeDefinitions":[{"AttributeType":"S","AttributeName":"PK"}],"KeySchema":[{"KeyType":"HASH","AttributeName":"PK"}]}}',
      afterContext: '{"Properties":{"BillingMode":"PAY_PER_REQUEST","PointInTimeRecoverySpecification":{"PointInTimeRecoveryEnabled":"false"},"AttributeDefinitions":[{"AttributeType":"S","AttributeName":"PK"}],"KeySchema":[{"KeyType":"HASH","AttributeName":"PK"}]}}'
    }
  ]
}
```

### 用于变更集操作的 Lambda 函数示例
<a name="lambda-hooks-create-lambda-function-change-set-example-function"></a>

以下示例是一个简单的函数，它下载更改集操作有效负载，循环浏览每个更改，然后在返回 a 之前打印出之前和之后的属性`SUCCESS`。

------
#### [ Node.js ]

```
export const handler = async (event, context) => {
    var payloadUrl = event?.requestData?.payload;    
    var response = {
        "hookStatus": "SUCCESS",
        "message": "Change set changes are compliant",
        "clientRequestToken": event.clientRequestToken
    };
    try {
        const changeSetHookPayloadRequest = await fetch(payloadUrl);
        const changeSetHookPayload = await changeSetHookPayloadRequest.json();
        const changes = changeSetHookPayload.changedResources || [];
        for(const change of changes) {
            var beforeContext = {};
            var afterContext = {};
            if(change.beforeContext) {
                beforeContext = JSON.parse(change.beforeContext);
            }
            if(change.afterContext) {
                afterContext = JSON.parse(change.afterContext);
            }
            console.log(beforeContext)
            console.log(afterContext)
            // Evaluate Change here
        }
    } catch (error) {
        console.log(error);
        response.hookStatus = "FAILED";
        response.message = "Failed to evaluate change set operation.";
        response.errorCode = "InternalFailure";
    }
    return response;
};
```

------
#### [ Python ]

要使用 Python，你需要导入该`requests`库。为此，您需要在创建 Lambda 函数时将该库包含在部署包中。有关更多信息，请参阅《*AWS Lambda 开发人员指南》*中的[创建带有依赖项的.zip 部署包](https://docs.aws.amazon.com/lambda/latest/dg/python-package.html#python-package-create-dependencies)。

```
import json
import requests

def lambda_handler(event, context):
    payload_url = event.get('requestData', {}).get('payload')
    response = {
        "hookStatus": "SUCCESS",
        "message": "Change set changes are compliant",
        "clientRequestToken": event.get('clientRequestToken')
    }

    try:
        change_set_hook_payload_request = requests.get(payload_url)
        change_set_hook_payload_request.raise_for_status()  # Raises an HTTPError for bad responses
        change_set_hook_payload = change_set_hook_payload_request.json()
        
        changes = change_set_hook_payload.get('changedResources', [])
        
        for change in changes:
            before_context = {}
            after_context = {}
            
            if change.get('beforeContext'):
                before_context = json.loads(change['beforeContext'])
            
            if change.get('afterContext'):
                after_context = json.loads(change['afterContext'])
            
            print(before_context)
            print(after_context)
            # Evaluate Change here

    except requests.RequestException as error:
        print(error)
        response['hookStatus'] = "FAILED"
        response['message'] = "Failed to evaluate change set operation."
        response['errorCode'] = "InternalFailure"
    except json.JSONDecodeError as error:
        print(error)
        response['hookStatus'] = "FAILED"
        response['message'] = "Failed to parse JSON payload."
        response['errorCode'] = "InternalFailure"

    return response
```

------