

# Creating and managing CloudFormation Hooks
<a name="creating-and-managing-hooks"></a>

CloudFormation Hooks provide a mechanism to evaluate your CloudFormation resources before allowing stack creation, modification, or deletion. This feature helps you ensure that your CloudFormation resources comply with your organization's security, operational, and cost optimization best practices.

To create a Hook, you have four options.
+ **Proactive controls as Hooks** – Evaluates resources using proactive controls from the AWS Control Tower Control Catalog.
+ **Guard Hook** – Evaluates resources using an AWS CloudFormation Guard rule.
+ **Lambda Hook** – Forwards requests for resource evaluation to an AWS Lambda function.
+ **Custom Hook** – Uses a custom Hook handler that you manually develop. 

------
#### [ Proactive controls as Hooks ]

To create a Hook from proactive controls, follow these steps:

1. Navigate to the CloudFormation console and begin creating a Hook.

1. Choose specific controls from the Control Catalog that you want your Hook to evaluate resources against.

   These controls will automatically apply whenever specified resources are created or updated. Your selection determines which resource types the Hook will evaluate.

1. Set the Hook mode to either warn users about non-compliance or prevent non-compliant operations.

1. Configure optional filters to include or exclude stacks by stack name or stack role.

1. After completing the configuration, activate the Hook to begin enforcement.

------
#### [ Guard Hook ]

To create a Guard Hook, follow these steps:

1. Write your resource evaluation logic as a Guard policy rule using the Guard domain-specific language (DSL).

1. Store the Guard policy rule in an Amazon S3 bucket.

1. Navigate to the CloudFormation console and begin creating a Guard Hook.

1. Provide the Amazon S3 path to your Guard rule.

1. Choose the specific target types that the Hook will evaluate. 
   + CloudFormation resources (`RESOURCE`)
   + Entire stack templates (`STACK`)
   + Change sets (`CHANGE_SET`)
   + Cloud Control API resources (`CLOUD_CONTROL`)

1. Choose the deployment actions (create, update, delete) that will invoke your Hook.

1. Choose how the Hook responds when it fails evaluation.

1. Configure optional filters to specify which resource types the Hook should evaluate

1. Configure optional filters to include or exclude stacks by stack name or stack role.

1. After completing the configuration, activate the Hook to begin enforcement.

------
#### [ Lambda Hook ]

To create a Lambda Hook, follow these steps:

1. Write your resource evaluation logic as a Lambda function.

1. Navigate to the CloudFormation console and begin creating a Lambda Hook.

1. Provide the Amazon Resource Name (ARN) for your Lambda function.

1. Choose the specific target types that the Hook will evaluate. 
   + CloudFormation resources (`RESOURCE`)
   + Entire stack templates (`STACK`)
   + Change sets (`CHANGE_SET`)
   + Cloud Control API resources (`CLOUD_CONTROL`)

1. Choose the deployment actions (create, update, delete) that will invoke your Hook.

1. Choose how the Hook responds when it fails evaluation.

1. Configure optional filters to specify which resource types the Hook should evaluate

1. Configure optional filters to include or exclude stacks by stack name or stack role.

1. After completing the configuration, activate the Hook to begin enforcement.

------
#### [ Custom Hook ]

Custom Hooks are extensions that you register in the CloudFormation registry using the CloudFormation Command Line Interface (CFN-CLI).

To create a custom Hook, follow these main steps:

1. **Initiate the project** – Generate the files needed to develop a custom Hook.

1. **Model the Hook** – Write a schema that defines the Hook and the handlers that specify the operations that can invoke the Hook.

1. **Register and activate the Hook** – After you have created a Hook, you need to register it in the account and Region where you want to use it and this activates it.

------

The following topics provide more information for creating and managing Hooks.

**Topics**
+ [Concepts](hooks-concepts.md)
+ [Proactive controls as Hooks](proactive-controls-hooks.md)
+ [Guard Hooks](guard-hooks.md)
+ [Lambda Hooks](lambda-hooks.md)
+ [Custom Hooks](hooks-develop.md)

# CloudFormation Hooks concepts
<a name="hooks-concepts"></a>

The following terminology and concepts are central to your understanding and use of CloudFormation Hooks.

## Hook
<a name="hook-terms-hook"></a>

A Hook contains code that is invoked immediately before CloudFormation creates, updates, or deletes stacks or specific resources. It can also be invoked during a create change set operation. Hooks can inspect the template, resources, or change set that CloudFormation is about to provision. Additionally, Hooks can be invoked immediately before the [Cloud Control API](https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/what-is-cloudcontrolapi.html) creates, update, or deletes specific resources.

If a Hook identifies any configurations that don't comply with the organizational guidelines defined in your Hook logic, then you may choose to either `WARN` users or `FAIL`, preventing CloudFormation from provisioning the resource.

Hooks have the following characteristics:
+ **Proactive validation** – Reduces risk, operational overhead, and cost by identifying non-compliant resources before they're created, updated, or deleted.
+ **Automatic enforcement** – Provides enforcement in your AWS account to prevent non-compliant resources from being provisioned by CloudFormation.

## Failure mode
<a name="hook-terms-failure-mode"></a>

Your Hook logic can return success or failure. A success response will allow the operation to continue. A failure for non-compliant resources can result in the following:
+ `FAIL` – Stops provisioning operation.
+ `WARN` – Allows provisioning to continue with a warning message.

Creating Hooks in `WARN` mode is an effective way to monitor Hook behavior without affecting stack operations. First, activate Hooks in `WARN` mode to understand which operations will be impacted. After you have assessed the potential effects, you can switch the Hook to `FAIL` mode to start preventing non-compliant operations.

## Hook targets
<a name="hook-terms-hook-target"></a>

Hook targets specify the operations that a Hook will evaluate. These can be operations on:
+ Resources supported by CloudFormation (`RESOURCE`) 
+ Stack templates (`STACK`)
+ Change sets (`CHANGE_SET`)
+ Resources supported by the [Cloud Control API](https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/what-is-cloudcontrolapi.html) (`CLOUD_CONTROL`) 

You define one or more targets that specify the broadest operations that the Hook will evaluate. For example, you can author a Hook targeting `RESOURCE` to target all AWS resources and `STACK` to target all stack templates.

## Target actions
<a name="hook-terms-target-action"></a>

Target actions define the specific actions (`CREATE`, `UPDATE`, or `DELETE`) that will invoke a Hook. For `RESOURCE`, `STACK`, and `CLOUD_CONTROL` targets, all target actions are applicable. For `CHANGE_SET` targets, only the `CREATE` action is applicable.

## Annotations
<a name="hook-annotations"></a>

[GetHookResult](https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_GetHookResult.html) responses can return *annotations* that provide detailed compliance check results and remediation guidance for each evaluated resource. For details on the API's annotation structure, see [Annotation](https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_Annotation.html) in the *AWS CloudFormation API Reference*. For instructions on viewing these validation results, see [View invocation results for CloudFormation Hooks](hooks-view-invocations.md).

You can encrypt annotations as needed for sensitive compliance information by specifying your own KMS key when configuring the Hook. For more information, see [Hook configuration schema syntax reference](hook-configuration-schema.md). For information about setting up the key policy that you need when you specify your KMS key for Hooks, see [AWS KMS key policy and permissions for encrypting CloudFormation Hooks results at rest](hooks-kms-key-policy.md).

**Important**  
Note that the `KmsKeyId` option to specify a customer managed key is currently only available when you use the AWS CLI to configure your Hook.

## Hook handler
<a name="hook-terms-hook-handler"></a>

For custom Hooks, this is the code that handles evaluation. It is associated with a target invocation point and a target action that mark an exact point where a Hook runs. You write handlers that host logic for these specific points. For example, a `PRE` target invocation point with `CREATE` target action makes a `preCreate` Hook handler. Code within the Hook handler runs when a matching target invocation point and service are performing an associated target action.

*Valid values*: (`preCreate` \$1 `preUpdate` \$1 `preDelete`)

**Important**  
Stack operations that result in the status of `UpdateCleanup` do not invoke a Hook. For example, during the following two scenarios, the Hook's `preDelete` handler is not invoked:  
the stack is updated after removing one resource from the template.
a resource with the update type of [replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-replacement) is deleted.

## Timeout and retry limits
<a name="hook-timeout-and-retry-limits"></a>

Hooks have a 30-second timeout limit per invocation and are limited to 3 retry attempts. If an invocation exceeds the timeout, we return an error message stating that the Hook execution timed out. After the third retry, CloudFormation marks the Hook execution as failed.

# AWS Control Tower proactive controls as Hooks
<a name="proactive-controls-hooks"></a>

The AWS Control Tower Control Catalog provides pre-built compliance rules (proactive controls) that you can implement as Hooks. This approach saves setup time and helps you validate resource configurations against AWS best practices across your organization without writing code. 

Proactive controls evaluate AWS resources before deployment, preventing non-compliant resources from being created rather than detecting issues later. They check configurations against established security, operational, and governance standards. 

To get started, simply activate proactive control-based Hooks in your desired account and Region. These Hooks will then evaluate specific target types to ensure compliance with your selected controls. 

For more information about available proactive controls, see the [AWS Control Tower Control Catalog](https://docs.aws.amazon.com/controltower/latest/controlreference/controls-reference.html).

**Topics**
+ [AWS CLI commands for working with Hooks](#commonly-used-commands-proactive-controls-hooks)
+ [Activate a proactive control-based Hook in your account](proactive-controls-hooks-activate-hooks.md)
+ [Delete proactive control-based Hooks in your account](proactive-controls-hooks-delete-hooks.md)

## AWS CLI commands for working with Hooks
<a name="commonly-used-commands-proactive-controls-hooks"></a>

The AWS CLI commands for working with proactive control-based Hooks include: 
+ [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/activate-type.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/activate-type.html) to start the activation process for a proactive control-based Hook.
+ [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/set-type-configuration.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/set-type-configuration.html) to specify the controls to apply to a proactive control-based Hook in your account.
+ [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/list-types.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/list-types.html) to list the Hooks in your account.
+ [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/describe-type.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/describe-type.html) to return detailed information about a specific Hook or specific Hook version, including current configuration data.
+ [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deactivate-type.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deactivate-type.html) to remove a previously activated Hook from your account.

# Activate a proactive control-based Hook in your account
<a name="proactive-controls-hooks-activate-hooks"></a>

The following topic shows you how to activate a proactive control-based Hook in your account, which makes it usable in the account and Region it was activated in.

**Important**  
Before you continue, verify that you have the permissions required to work with Hooks and view proactive controls from the CloudFormation console. For more information, see [Grant IAM permissions for CloudFormation Hooks](grant-iam-permissions-for-hooks.md).

**Topics**
+ [Activate a proactive control-based Hook (console)](#proactive-controls-hooks-activate-hook-console)
+ [Activate a proactive control-based Hook (AWS CLI)](#proactive-controls-hooks-activate-hooks-cli)

## Activate a proactive control-based Hook (console)
<a name="proactive-controls-hooks-activate-hook-console"></a>

**To activate a proactive control-based Hook for use in your account**

1. Sign in to the AWS Management Console and open the CloudFormation console at [https://console.aws.amazon.com/cloudformation](https://console.aws.amazon.com/cloudformation/).

1. On the navigation bar at the top of the screen, choose the AWS Region where you want to create the Hook in.

1. In the navigation pane on the left, choose **Hooks**.

1. On the **Hooks** page, choose **Create a Hook**, and then choose **With the Control Catalog**.

1. On the **Select controls** page, for **Proactive controls**, select one or more proactive controls to use.

   These controls will automatically apply whenever specified resources are created or updated. Your selection determines which resource types the Hook will evaluate.

1. Choose **Next**.

1. For **Hook name**, choose one of the following options:
   + Provide a short, descriptive name that will be added after `Private::Controls::`. For example, if you enter *`MyTestHook`*, the full Hook name becomes `Private::Controls::MyTestHook`.
   + Provide the full Hook name (also called an alias) using this format: `Provider::ServiceName::HookName`.

1. For **Hook mode**, choose how the Hook responds when controls fail their evaluation:
   + **Warn** — Issues warnings to users but allows actions to continue. This is useful for non-critical validations or informational checks.
   + **Fail** — Prevents the action from proceeding. This is helpful for enforcing strict compliance or security policies.

1. Choose **Next**.

1. (Optional) For **Hook filters**, do the following:

   1. For **Filtering criteria**, choose the logic for applying stack name and stack role filters:
      + **All stack names and stack roles** – The Hook will only be invoked when all specified filters match.
      + **Any stack names and stack roles** – The Hook will be invoked if at least one of the specified filters match.

   1. For **Stack names**, include or exclude specific stacks from Hook invocations.
      + For **Include**, specify the stack names to include. Use this when you have a small set of specific stacks you want to target. Only the stacks specified in this list will invoke the Hook.
      + For **Exclude**, specify the stack names to exclude. Use this when you want to invoke the Hook on most stacks but exclude a few specific ones. All stacks except those listed here will invoke the Hook.

   1. For **Stack roles**, include or exclude specific stacks from Hook invocations based on their associated IAM roles.
      + For **Include**, specify one or more IAM role ARNs to target stacks associated with these roles. Only stack operations initiated by these roles will invoke the Hook.
      + For **Exclude**, specify one or more IAM role ARNs for stacks you want to exclude. The Hook will be invoked on all stacks except those initiated by the specified roles.

1. Choose **Next**.

1. On the **Review and activate** page, review your choices. To make changes, choose **Edit** on the related section.

1. When you're ready to proceed, choose **Activate Hook**.

## Activate a proactive control-based Hook (AWS CLI)
<a name="proactive-controls-hooks-activate-hooks-cli"></a>

Before you continue, confirm that you have identified the proactive controls that you'll use with this Hook. For more information, see the [AWS Control Tower Control Catalog](https://docs.aws.amazon.com/controltower/latest/controlreference/controls-reference.html).

**To activate a proactive control-based Hook for use in your account (AWS CLI)**

1. To start activating a Hook, use the following [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/activate-type.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/activate-type.html) command, replacing the placeholders with your specific values.

   ```
   aws cloudformation activate-type --type HOOK \
     --type-name AWS::ControlTower::Hook  \
     --publisher-id aws-hooks \
     --type-name-alias MyOrg::Security::ComplianceHook \
     --region us-west-2
   ```

1. To finish activating the Hook, you must configure it using a JSON configuration file.

   Use the **cat** command to create a JSON file with the following structure. For more information, see [Hook configuration schema syntax reference](hook-configuration-schema.md).

   This following example configures a Hook that invokes on specific IAM, Amazon EC2, and Amazon S3 resources during `CREATE` and `UPDATE` operations. It applies three proactive controls (`CT.IAM.PR.5`, `CT.EC2.PR.17`, `CT.S3.PR.12`) to validate these resources against compliance standards. The hook operates in `WARN` mode, meaning it will flag non-compliant resources with warnings but won't block deployments.

   ```
   $ cat > config.json
   {
     "CloudFormationConfiguration": {
       "HookConfiguration": {
         "HookInvocationStatus": "ENABLED",
         "TargetOperations": ["RESOURCE"],
         "FailureMode": "WARN",
         "Properties": {
           "ControlsToApply": "CT.IAM.PR.5,CT.EC2.PR.17,CT.S3.PR.12"
         },
         "TargetFilters": {
           "Actions": [
             "CREATE",
             "UPDATE"
           ]
         }
       }
     }
   }
   ```
   + `HookInvocationStatus`: Set to `ENABLED` to enable the Hook.
   + `TargetOperations`: Set to `RESOURCE` as this is the only supported value for a proactive control-based Hook.
   + `FailureMode`: Set to either `FAIL` or `WARN`.
   + `ControlsToApply`: Specify the control IDs of the proactive controls to use. For more information, see the [AWS Control Tower Control Catalog](https://docs.aws.amazon.com/controltower/latest/controlreference/controls-reference.html).
   + (Optional) `TargetFilters`: For `Actions`, you can specify `CREATE` or `UPDATE`, or both (default), to control when the Hook is invoked. Specifying `CREATE` alone limits the Hook to `CREATE` operations only. Other `TargetFilters` properties have no effect.

1. Use the following [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/set-type-configuration.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/set-type-configuration.html) command, along with the JSON file you created, to apply the configuration. Replace the placeholders with your specific values.

   ```
   aws cloudformation set-type-configuration \
     --configuration file://config.json \
     --type-arn "arn:aws:cloudformation:us-west-2:123456789012:type/hook/MyOrg-Security-ComplianceHook" \
     --region us-west-2
   ```

# Delete proactive control-based Hooks in your account
<a name="proactive-controls-hooks-delete-hooks"></a>

When you no longer need an activated proactive control-based Hook, use the following procedures to delete it in your account.

To temporarily disable a Hook instead of deleting it, see [Disable and enable CloudFormation Hooks](hooks-disable-enable.md).

**Topics**
+ [Delete a proactive control-based Hook in your account (console)](#proactive-controls-hooks-delete-hook-console)
+ [Delete a proactive control-based Hook in your account (AWS CLI)](#proactive-controls-hooks-delete-hook-cli)

## Delete a proactive control-based Hook in your account (console)
<a name="proactive-controls-hooks-delete-hook-console"></a>

**To delete a proactive control-based Hook in your account**

1. Sign in to the AWS Management Console and open the CloudFormation console at [https://console.aws.amazon.com/cloudformation](https://console.aws.amazon.com/cloudformation/).

1. On the navigation bar at the top of the screen, choose the AWS Region where the Hook is located.

1. From the navigation pane, choose **Hooks**.

1. On the **Hooks** page, find the proactive control-based Hook you want to delete.

1. Select the check box next to your Hook and choose **Delete**. 

1. When prompted for confirmation, type out the Hook name to confirm deleting the specified Hook and then choose **Delete**.

## Delete a proactive control-based Hook in your account (AWS CLI)
<a name="proactive-controls-hooks-delete-hook-cli"></a>

**Note**  
Before you can delete the Hook, you must first disable it. For more information, see [Disable and enable a Hook in your account (AWS CLI)](hooks-disable-enable.md#hooks-disable-enable-cli).

Use the following [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deactivate-type.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deactivate-type.html) command to deactivate a Hook, which removes it from your account. Replace placeholders with your specific values.

```
aws cloudformation deactivate-type \
  --type-arn "arn:aws:cloudformation:us-west-2:123456789012:type/hook/MyOrg-Security-ComplianceHook" \
  --region us-west-2
```

# Guard Hooks
<a name="guard-hooks"></a>

To use an AWS CloudFormation Guard Hook in your account, you must *activate* the Hook for the account and Region where you want to use it. Activating a Hook makes it usable in stack operations in the account and Region where it's activated. 

When you activate a Guard Hook, CloudFormation creates an entry in your account's registry for the activated Hook as a private Hook. This allows you to set any configuration properties the Hook includes. Configuration properties define how the Hook is configured for a given AWS account and Region.

**Topics**
+ [AWS CLI commands for working with Guard Hooks](#commonly-used-commands-guard-hooks)
+ [Write Guard rules to evaluate resources for Guard Hooks](guard-hooks-write-rules.md)
+ [Prepare to create a Guard Hook](guard-hooks-prepare-to-create-hook.md)
+ [Activate a Guard Hook in your account](guard-hooks-activate-hooks.md)
+ [View logs for the Guard Hooks in your account](guard-hooks-view-logs.md)
+ [Delete Guard Hooks in your account](guard-hooks-delete-hooks.md)

## AWS CLI commands for working with Guard Hooks
<a name="commonly-used-commands-guard-hooks"></a>

The AWS CLI commands for working with Guard Hooks include: 
+ [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/activate-type.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/activate-type.html) to start the activation process for a Guard Hook.
+ [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/set-type-configuration.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/set-type-configuration.html) to specify the configuration data for a Hook in your account.
+ [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/list-types.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/list-types.html) to list the Hooks in your account.
+ [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/describe-type.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/describe-type.html) to return detailed information about a specific Hook or specific Hook version, including current configuration data.
+ [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deactivate-type.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deactivate-type.html) to remove a previously activated Hook from your account.

# Write Guard rules to evaluate resources for Guard Hooks
<a name="guard-hooks-write-rules"></a>

AWS CloudFormation Guard is an open-source and general purpose domain specific language (DSL) you can use to author policy-as-code. This topic explains how to use Guard to author example rules which can be run in the Guard Hook to automatically evaluate CloudFormation and AWS Cloud Control API operations. It will also focus on the different types of inputs available to your Guard rules depending on when your Guard Hook runs. A Guard Hook can be configured to run during the following types of operations:
+ Resource operations
+ Stack operations
+ Change set operations

For more information on writing Guard rules, see [Writing AWS CloudFormation Guard rules](https://docs.aws.amazon.com/cfn-guard/latest/ug/writing-rules.html)

**Topics**
+ [Resource operation Guard rules](#guard-hooks-write-rules-resource-operations)
+ [Stack operation Guard rules](#guard-hooks-write-rules-stack-operations)
+ [Change set operation Guard rules](#guard-hooks-write-rules-change-set-operations)

## Resource operation Guard rules
<a name="guard-hooks-write-rules-resource-operations"></a>

Any time you create, update, or delete a resource, that's considered a resource operation. As an example, if you run update a CloudFormation stack that creates a new resource, you have completed a resource operation. When you create, update or delete a resource using Cloud Control API, that is also considered a resource operation. You can configure your Guard Hook to target `RESOURCE` and `CLOUD_CONTROL` operations in the `TargetOperations` configuration for your Hook. When your Guard Hook evaluates a resource operation, the Guard engine evaluates a resource input.

**Topics**
+ [Guard resource input syntax](#guard-hooks-write-rules-resource-operations-input)
+ [Example Guard resource operation input](#guard-hooks-write-rules-resource-operations-example)
+ [Guard rules for resource changes](#guard-hooks-rules-resource-changes)

### Guard resource input syntax
<a name="guard-hooks-write-rules-resource-operations-input"></a>

The Guard resource input is the data that's made available to your Guard rules to evaluate.

The following is an example shape of a resource input:

```
HookContext:
  AWSAccountID: String
  StackId: String
  HookTypeName: String
  HookTypeVersion: String
  InvocationPoint: [CREATE_PRE_PROVISION, UPDATE_PRE_PROVISION, DELETE_PRE_PROVISION]
  TargetName: String
  TargetType: RESOURCE
  TargetLogicalId: String
  ChangeSetId: String
Resources:
  {ResourceLogicalID}:
    ResourceType: {ResourceType}
    ResourceProperties:
        {ResourceProperties}
Previous:
  ResourceLogicalID:
    ResourceType: {ResourceType}
    ResourceProperties:
        {PreviousResourceProperties}
```

`HookContext`  <a name="guard-hook-resource-hookcontext"></a>  
`AWSAccountID`  <a name="guard-hook-resource-awsaccountid"></a>
The ID of the AWS account containing the resource being evaluated.  
`StackId`  <a name="guard-hook-resource-stackid"></a>
The stack ID of the CloudFormation stack that is part of the resource operation. This is empty if the caller is Cloud Control API.  
`HookTypeName`  <a name="guard-hook-resource-hooktypename"></a>
The name of the Hook that's running.  
`HookTypeVersion`  <a name="guard-hook-resource-hooktypeversion"></a>
The version of the Hook that is running.  
`InvocationPoint`  <a name="guard-hook-resource-invocationpoint"></a>
The exact point in the provisioning logic where the Hook runs.  
*Valid values*: (`CREATE_PRE_PROVISION` \$1 `UPDATE_PRE_PROVISION` \$1 `DELETE_PRE_PROVISION`)  
`TargetName`  <a name="guard-hook-resource-targetname"></a>
The target type being evaluated, for example, `AWS::S3::Bucket`.  
`TargetType`  <a name="guard-hook-resource-targettype"></a>
The target type being evaluated, for example `AWS::S3::Bucket`. For resources provisioned with Cloud Control API, this value will be `RESOURCE`.  
`TargetLogicalId`  <a name="guard-hook-resource-targetlogicalid"></a>
The `TargetLogicalId` of the resource being evaluated. If the origin of the Hook is CloudFormation, this will be the logical ID (also known as logical name) of the resource. If the origin of the Hook is Cloud Control API, this will be a constructed value.  
`ChangeSetId`  <a name="guard-hook-resource-changesetid"></a>
The change set ID that was executed to cause the Hook invocation. This value is empty if the resource change was initiated by Cloud Control API, or the `create-stack`, `update-stack`, or `delete-stack` operations.

`Resources`  <a name="guard-hook-resource-resources"></a>  
`ResourceLogicalID`  <a name="guard-hook-resource-current-resourcelogicalid"></a>
When the operation is initiated by CloudFormation, the `ResourceLogicalID` is the logical ID of the resource in the CloudFormation template.  
When the operation's initiated by Cloud Control API, the `ResourceLogicalID` is a combination of the resource type, name, operation ID, and request ID.  
`ResourceType`  <a name="guard-hook-resource-current-resourcetype"></a>
The type name of the resource (example: `AWS::S3::Bucket`).  
`ResourceProperties`  <a name="guard-hook-resource-current-resourceproperties"></a>
The proposed properties of the resource being modified. When the Guard Hook is running against the CloudFormation resource changes, any functions, parameters, and transforms will be fully resolved. If the resource is being deleted, this value will be empty.

`Previous`  <a name="guard-hook-resource-previous"></a>  
`ResourceLogicalID`  <a name="guard-hook-resource-previous-resourcelogicalid"></a>
When the operation is initiated by CloudFormation, the `ResourceLogicalID` is the logical ID of the resource in the CloudFormation template.  
When the operation's initiated by Cloud Control API, the `ResourceLogicalID` is a combination of the resource type, name, operation ID, and request ID.  
`ResourceType`  <a name="guard-hook-resource-previous-resourcetype"></a>
The type name of the resource (example: `AWS::S3::Bucket`).  
`ResourceProperties`  <a name="guard-hook-resource-previous-resourceproperties"></a>
The current properties associated with the resource being modified. If the resource is being deleted, this value will be empty.

### Example Guard resource operation input
<a name="guard-hooks-write-rules-resource-operations-example"></a>

The following example input shows a Guard Hook that will receive the definition of the `AWS::S3::Bucket` resource to update. This is the data available to Guard for evaluation.

```
HookContext:
  AwsAccountId: "123456789012"
  StackId: "arn:aws:cloudformation:us-west-2:123456789012:stack/MyStack/1a2345b6-0000-00a0-a123-00abc0abc000"
  HookTypeName: org::s3policy::hook
  HookTypeVersion: "00001"
  InvocationPoint: UPDATE_PRE_PROVISION
  TargetName: AWS::S3::Bucket
  TargetType: RESOURCE
  TargetLogicalId: MyS3Bucket
  ChangeSetId: ""
Resources:
  MyS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: amzn-s3-demo-bucket
      ObjectLockEnabled: true
Previous:
  MyS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: amzn-s3-demo-bucket
      ObjectLockEnabled: false
```

To see all of the properties available for the resource type, see [https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-s3-bucket.html](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-s3-bucket.html).

### Guard rules for resource changes
<a name="guard-hooks-rules-resource-changes"></a>

When a Guard Hook evaluates resource changes, it starts by downloading all the rules configured with the Hook. These rules are then evaluated against the resource input. The Hook will fail if any rules fail their evaluation. If there are no failures, the Hook will pass.

The following example is a Guard rule that evaluates if the `ObjectLockEnabled` property is `true` for any `AWS::S3::Bucket` resource types.

```
let s3_buckets_default_lock_enabled = Resources.*[ Type == 'AWS::S3::Bucket']

rule S3_BUCKET_DEFAULT_LOCK_ENABLED when %s3_buckets_default_lock_enabled !empty {
  %s3_buckets_default_lock_enabled.Properties.ObjectLockEnabled exists
  %s3_buckets_default_lock_enabled.Properties.ObjectLockEnabled == true
  <<
    Violation: S3 Bucket ObjectLockEnabled must be set to true.
    Fix: Set the S3 property ObjectLockEnabled parameter to true.
  >>
}
```

When this rule runs against the following input, it will fail since the `ObjectLockEnabled` property isn't set to `true`.

```
Resources:
  MyS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: amzn-s3-demo-bucket 
      ObjectLockEnabled: false
```

When this rule runs against the following input, it will pass since the `ObjectLockEnabled` is set to `true`.

```
Resources:
  MyS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: amzn-s3-demo-bucket
      ObjectLockEnabled: true
```

When a Hook fails, the rules which failed will be propagated back to CloudFormation or Cloud Control API. If a logging bucket has been configured for the Guard Hook, additional rule feedback will be provided there. This additional feedback includes the `Violation` and `Fix` information.

## Stack operation Guard rules
<a name="guard-hooks-write-rules-stack-operations"></a>

When a CloudFormation stack is created, updated, or deleted, you can configure your Guard Hook to start by evaluating the new template and potentially block the stack operation from proceeding. You can configure your Guard Hook to target `STACK` operations in the `TargetOperations` configuration for your Hook.

**Topics**
+ [Guard stack input syntax](#guard-hooks-write-rules-stack-operations-input)
+ [Example Guard stack operation input](#guard-hooks-write-rules-stack-operations-example)
+ [Guard rules for stack changes](#guard-hooks-rules-stack-changes)

### Guard stack input syntax
<a name="guard-hooks-write-rules-stack-operations-input"></a>

The input for Guard stack operations provides the whole CloudFormation template for your Guard rules to evaluate.

The following is an example shape of a stack input:

```
HookContext:
  AWSAccountID: String
  StackId: String
  HookTypeName: String
  HookTypeVersion: String
  InvocationPoint: [CREATE_PRE_PROVISION, UPDATE_PRE_PROVISION, DELETE_PRE_PROVISION]
  TargetName: String
  TargetType:STACK
  ChangeSetId: String
{Proposed CloudFormation Template}
Previous:
    {CloudFormation Template}
```

`HookContext`  <a name="guard-hook-stack-hookcontext"></a>  
`AWSAccountID`  <a name="guard-hook-stack-awsaccountid"></a>
The ID of the AWS account containing the resource.  
`StackId`  <a name="guard-hook-stack-stackid"></a>
The stack ID of the CloudFormation stack that is part of the stack operation.  
`HookTypeName`  <a name="guard-hook-stack-hooktypename"></a>
The name of the Hook that's running.  
`HookTypeVersion`  <a name="guard-hook-stack-hooktypeversion"></a>
The version of the Hook that is running.  
`InvocationPoint`  <a name="guard-hook-stack-invocationpoint"></a>
The exact point in the provisioning logic where the Hook runs.  
*Valid values*: (`CREATE_PRE_PROVISION` \$1 `UPDATE_PRE_PROVISION` \$1 `DELETE_PRE_PROVISION`)  
`TargetName`  <a name="guard-hook-stack-targetname"></a>
The name of the stack being evaluated.  
`TargetType`  <a name="guard-hook-stack-targettype"></a>
This value will be `STACK` when running as a stack-level Hook.  
`ChangeSetId`  <a name="guard-hook-stack-changesetid"></a>
The change set ID that was executed to cause the Hook invocation. This value is empty if the stack operation was initiated by a `create-stack`, `update-stack`, or `delete-stack` operation.

`Proposed CloudFormation Template`  <a name="guard-hook-stack-template-current-template"></a>
The full CloudFormation template value that was passed to CloudFormation `create-stack` or `update-stack` operations. This includes things like the `Resources`, `Outputs`, and `Properties`. It can be a JSON or YAML string depending on what was provided to CloudFormation.  
In `delete-stack` operations, this value will be empty.

`Previous`  <a name="guard-hook-stack-template-previous-template"></a>
The last successfully deployed CloudFormation template. This value is empty if the stack is being created or deleted.  
In `delete-stack` operations, this value will be empty.

**Note**  
The templates provided are what is passed into `create` or `update` stack operations. When deleting a stack, no template values are provided.

### Example Guard stack operation input
<a name="guard-hooks-write-rules-stack-operations-example"></a>

The following example input shows a Guard Hook that will receive a full template and the previously deployed template. The template in this example is using the JSON format.

```
HookContext:
  AwsAccountId: 123456789012
  StackId: "arn:aws:cloudformation:us-west-2:123456789012:stack/MyStack/1a2345b6-0000-00a0-a123-00abc0abc000"
  HookTypeName: org::templatechecker::hook
  HookTypeVersion: "00001"
  InvocationPoint: UPDATE_PRE_PROVISION
  TargetName: MyStack
  TargetType: CHANGE_SET
  TargetLogicalId: arn:aws:cloudformation:us-west-2:123456789012:changeSet/SampleChangeSet/1a2345b6-0000-00a0-a123-00abc0abc000
  ChangeSetId: arn:aws:cloudformation:us-west-2:123456789012:changeSet/SampleChangeSet/1a2345b6-0000-00a0-a123-00abc0abc000
Resources: {
   "S3Bucket": {
        "Type": "AWS::S3::Bucket",
        "Properties": {
           "BucketEncryption": {
               "ServerSideEncryptionConfiguration": [ 
                {"ServerSideEncryptionByDefault": 
                    {"SSEAlgorithm": "aws:kms", 
                      "KMSMasterKeyID": "KMS-KEY-ARN" }, 
                      "BucketKeyEnabled": true } 
                ] 
           }
        }
}
Previous: {
    "AWSTemplateFormatVersion": "2010-09-09",
    "Resources": {
        "S3Bucket": {
            "Type": "AWS::S3::Bucket",
            "Properties": {}
        }
    }
}
```

### Guard rules for stack changes
<a name="guard-hooks-rules-stack-changes"></a>

When a Guard Hook evaluates stack changes, it starts by downloading all the rules configured with the Hook. These rules are then evaluated against the resource input. The Hook will fail if any rules fail their evaluation. If there are no failures, the Hook will pass.

The following example is a Guard rule that evaluates if there are any `AWS::S3::Bucket` resource types containing a property called `BucketEncryption`, with the `SSEAlgorithm` set to either `aws:kms` or `AES256`.

```
let s3_buckets_s3_default_encryption = Resources.*[ Type == 'AWS::S3::Bucket']

rule S3_DEFAULT_ENCRYPTION_KMS when %s3_buckets_s3_default_encryption !empty {
  %s3_buckets_s3_default_encryption.Properties.BucketEncryption exists
  %s3_buckets_s3_default_encryption.Properties.BucketEncryption.ServerSideEncryptionConfiguration[*].ServerSideEncryptionByDefault.SSEAlgorithm in ["aws:kms","AES256"]
  <<
    Violation: S3 Bucket default encryption must be set.
    Fix: Set the S3 Bucket property BucketEncryption.ServerSideEncryptionConfiguration.ServerSideEncryptionByDefault.SSEAlgorithm to either "aws:kms" or "AES256"
  >>
}
```

When the rule runs against the following template, it will `fail`.

```
AWSTemplateFormatVersion: 2010-09-09
Description: S3 bucket without default encryption
Resources:
  EncryptedS3Bucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: !Sub 'encryptedbucket-${AWS::Region}-${AWS::AccountId}'
```

When the rule runs against the following template, it will `pass`.

```
AWSTemplateFormatVersion: 2010-09-09
Description: S3 bucket with default encryption using SSE-KMS with an S3 Bucket Key
Resources:
  EncryptedS3Bucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: !Sub 'encryptedbucket-${AWS::Region}-${AWS::AccountId}'
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: 'aws:kms'
              KMSMasterKeyID: KMS-KEY-ARN
            BucketKeyEnabled: true
```

## Change set operation Guard rules
<a name="guard-hooks-write-rules-change-set-operations"></a>

When a CloudFormation change set is created, you can configure your Guard Hook to evaluate the template and changes proposed in the change set to block the change set execution.

**Topics**
+ [Guard change set input syntax](#guard-hooks-write-rules-change-set-operations-input)
+ [Example Guard change set operation input](#guard-hooks-write-rules-change-set-operations-example)
+ [Guard rule for change set operations](#guard-hooks-rules-change-set-operations)

### Guard change set input syntax
<a name="guard-hooks-write-rules-change-set-operations-input"></a>

The Guard change set input is the data that's made available to your Guard rules to evaluate.

The following is an example shape of a change set input:

```
HookContext:
  AWSAccountID: String
  StackId: String
  HookTypeName: String
  HookTypeVersion: String
  InvocationPoint: [CREATE_PRE_PROVISION, UPDATE_PRE_PROVISION, DELETE_PRE_PROVISION]
  TargetName: CHANGE_SET
  TargetType:CHANGE_SET
  TargetLogicalId:ChangeSet ID
  ChangeSetId: String
{Proposed CloudFormation Template}
Previous:
  {CloudFormation Template}
Changes: [{ResourceChange}]
```

The `ResourceChange` model syntax is:

```
logicalResourceId: String 
resourceType: String
action: CREATE, UPDATE, DELETE
lineNumber: Number
beforeContext: JSON String
afterContext: JSON String
```

`HookContext`  <a name="guard-hook-change-set-hookcontext"></a>  
`AWSAccountID`  <a name="guard-hook-change-set-awsaccountid"></a>
The ID of the AWS account containing the resource.  
`StackId`  <a name="guard-hook-change-set-stackid"></a>
The stack ID of the CloudFormation stack that is part of the stack operation.  
`HookTypeName`  <a name="guard-hook-change-set-hooktypename"></a>
The name of the Hook that's running.  
`HookTypeVersion`  <a name="guard-hook-change-set-hooktypeversion"></a>
The version of the Hook that is running.  
`InvocationPoint`  <a name="guard-hook-change-set-invocationpoint"></a>
The exact point in the provisioning logic where the Hook runs.  
*Valid values*: (`CREATE_PRE_PROVISION` \$1 `UPDATE_PRE_PROVISION` \$1 `DELETE_PRE_PROVISION`)  
`TargetName`  <a name="guard-hook-change-set-targetname"></a>
The name of the stack being evaluated.  
`TargetType`  <a name="guard-hook-change-set-targettype"></a>
This value will be `CHANGE_SET` when running as a change set-level Hook.  
`TargetLogicalId`  <a name="guard-hook-change-set-targetlogicalid"></a>
This value will be the ARN of the change set.  
`ChangeSetId`  <a name="guard-hook-change-set-changesetid"></a>
The change set ID that was executed to cause the Hook invocation. This value is empty if the stack operation was initiated by a `create-stack`, `update-stack`, or `delete-stack` operation.

`Proposed CloudFormation Template`  <a name="guard-hook-change-set-current-template"></a>
The full CloudFormation template that was provided to a `create-change-set` operation. It can be a JSON or YAML string depending on what was provided to CloudFormation.

`Previous`  <a name="guard-hook-change-set-previous-template"></a>
The last successfully deployed CloudFormation template. This value is empty if the stack is being created or deleted.

`Changes`  <a name="guard-hook-change-set-changes"></a>
The `Changes` model. This lists the resource changes.

Changes    
logicalResourceId  <a name="guard-hook-change-set-change-logicalresourceid"></a>
The logical resource name of the changed resource.  
resourceType  <a name="guard-hook-change-set-change-resourcetype"></a>
The resource type that will be changed.  
action  <a name="guard-hook-change-set-change-action"></a>
The type of operation being performed on the resource.  
*Valid values*: (`CREATE` \$1 `UPDATE` \$1 `DELETE`)  
lineNumber  <a name="guard-hook-change-set-change-linenumber"></a>
The line number in the template associated with the change.  
beforeContext  <a name="guard-hook-change-set-change-beforecontext"></a>
A JSON string of properties of the resource before the change:  

```
{"properties": {"property1": "value"}}
```  
afterContext  <a name="guard-hook-change-set-change-aftercontext"></a>
A JSON string of properties of the resource after the change:  

```
{"properties": {"property1": "new value"}}
```

### Example Guard change set operation input
<a name="guard-hooks-write-rules-change-set-operations-example"></a>

The following example input shows a Guard Hook that will receive a full template, the previously deployed template, and a list of resource changes. The template in this example is using the JSON format.

```
HookContext:
  AwsAccountId: "00000000"
  StackId: MyStack
  HookTypeName: org::templatechecker::hook
  HookTypeVersion: "00001"
  InvocationPoint: UPDATE_PRE_PROVISION
  TargetName: my-example-stack
  TargetType:STACK
  TargetLogicalId: arn...:changeSet/change-set
  ChangeSetId: ""
Resources: {
    "S3Bucket": {
       "Type": "AWS::S3::Bucket",
       "Properties": {
           "BucketName": "amzn-s3-demo-bucket",
           "VersioningConfiguration":{
              "Status": "Enabled"
            }                
         }
    }
Previous: {
    "AWSTemplateFormatVersion": "2010-09-09",
    "Resources": {
        "S3Bucket": {
            "Type": "AWS::S3::Bucket",
            "Properties": {
                "BucketName": "amzn-s3-demo-bucket",
                "VersioningConfiguration":{
                  "Status": "Suspended"
                }
            }
        }
    }
}
Changes: [
  {
    "logicalResourceId": "S3Bucket",
    "resourceType": "AWS::S3::Bucket",
    "action": "UPDATE",
    "lineNumber": 5,
    "beforeContext": "{\"Properties\":{\"VersioningConfiguration\":{\"Status\":\"Suspended\"}}}",
    "afterContext": "{\"Properties\":{\"VersioningConfiguration\":{\"Status\":\"Enabled\"}}}"
  }
]
```

### Guard rule for change set operations
<a name="guard-hooks-rules-change-set-operations"></a>

The following example is a Guard rule that evaluates changes to Amazon S3 buckets, and ensures that `VersionConfiguration` is not disabled.

```
let s3_buckets_changing = Changes[resourceType == 'AWS::S3::Bucket']

rule S3_VERSIONING_STAY_ENABLED when %s3_buckets_changing !empty {
    let afterContext = json_parse(%s3_buckets_changing.afterContext)
    when %afterContext.Properties.VersioningConfiguration.Status !empty {
        %afterContext.Properties.VersioningConfiguration.Status == 'Enabled'
    }
}
```

# Prepare to create a Guard Hook
<a name="guard-hooks-prepare-to-create-hook"></a>

Before you create a Guard Hook, you must complete the following prerequisites:
+ You must have already created a Guard rule. For more information, see the [Write Guard rules for Hooks](guard-hooks-write-rules.md).
+ The user or role that creates the Hook must have sufficient permissions to activate Hooks. For more information, see [Grant IAM permissions for CloudFormation Hooks](grant-iam-permissions-for-hooks.md).
+ To use the AWS CLI or an SDK to create a Guard Hook, you must manually create an execution role with IAM permissions and a trust policy to allow CloudFormation to invoke a Guard Hook. 

## Create an execution role for a Guard Hook
<a name="guard-hooks-create-execution-role"></a>

A Hook uses an execution role for the permissions that it requires to invoke that Hook in your AWS account.

This role can be created automatically if you create a Guard Hook from the AWS Management Console; otherwise, you must create this role yourself.

The following section shows you how to set up permissions to create your Guard Hook. 

### Required permissions
<a name="guard-hooks-execution-role-permissions"></a>

Follow the guidance at [Create a role using custom trust policies](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-custom.html) in the *IAM User Guide* to create a role with a custom trust policy.

Then, complete the following steps to set up your permissions:

1. Attach the following minimum privilege policy to the IAM role you want to use to create the Guard Hook.

------
#### [ JSON ]

****  

   ```
   {
     "Version":"2012-10-17",		 	 	 
     "Statement": [
       {
         "Effect": "Allow",
         "Action": [
           "s3:ListBucket",
           "s3:GetObject",
           "s3:GetObjectVersion"
         ],
         "Resource": [
           "arn:aws:s3:::my-guard-output-bucket/*",
           "arn:aws:s3:::my-guard-rules-bucket"
         ]
       },
       {
         "Effect": "Allow",
         "Action": [
           "s3:PutObject"
         ],
         "Resource": [
           "arn:aws:s3:::my-guard-output-bucket/*"
         ]
       }
     ]
   }
   ```

------

1. Give your Hook permission to assume the role by adding a trust policy to the role. The following shows an example trust policy you can use.

------
#### [ JSON ]

****  

   ```
   {
     "Version":"2012-10-17",		 	 	 
     "Statement": [
       {
         "Effect": "Allow",
         "Principal": {
           "Service": [
             "hooks.cloudformation.amazonaws.com"
           ]
         },
         "Action": "sts:AssumeRole"
       }
     ]
   }
   ```

------

# Activate a Guard Hook in your account
<a name="guard-hooks-activate-hooks"></a>

The following topic shows you how to activate a Guard Hook in your account, which makes it usable in the account and Region it was activated in.

**Topics**
+ [Activate a Guard Hook (console)](#guard-hooks-activate-hook-console)
+ [Activate a Guard Hook (AWS CLI)](#guard-hooks-activate-hooks-cli)
+ [Related resources](#related-resources-guard-hooks)

## Activate a Guard Hook (console)
<a name="guard-hooks-activate-hook-console"></a>

**To activate a Guard Hook for use in your account**

1. Sign in to the AWS Management Console and open the CloudFormation console at [https://console.aws.amazon.com/cloudformation](https://console.aws.amazon.com/cloudformation/).

1. On the navigation bar at the top of the screen, choose the AWS Region where you want to create the Hook in.

1. In the navigation pane on the left, choose **Hooks**.

1. On the **Hooks** page, choose **Create a Hook**, and then choose **With Guard**.

1. If you *haven't* created any Guard rules yet, create your Guard rule, store it in Amazon S3, and then return to this procedure. Refer to the example rules in [Write Guard rules to evaluate resources for Guard Hooks](guard-hooks-write-rules.md) to get started.

   If you have already created your Guard rule and stored it in S3, proceed to the next step. 
**Note**  
The object stored in S3 must have one of the following file extensions: `.guard`, `.zip`, or `.tar.gz`.

1. For **Guard Hook source**, **Store your Guard rules in S3**, do the following:
   + For **S3 URI**, specify the S3 path to your rules file or use the **Browse S3** button to open a dialog box to browse for and select the S3 object.
   + (Optional) For **Object version**, if your S3 bucket has versioning enabled, you can select a specific version of the S3 object. 

     The Guard Hook downloads your rules from S3 every time the Hook is invoked. To prevent accidental changes or deletions, we recommend using a version when configuring your Guard Hook.

1. (Optional) For **S3 bucket for Guard output report**, specify an S3 bucket to store the Guard output report. This report contains the results of your Guard rule validations.

   To configure the output report destination, choose one of the following options:
   + Select the **Use the same bucket my Guard rules are stored in** check box to use the same bucket where your Guard rules are located.
   + Choose a different S3 bucket name for storing the Guard output report.

1. (Optional) Expand **Guard rule input parameters**, and then provide the following information under **Store your Guard rule input parameters in S3**:
   + For **S3 URI**, specify the S3 path to a parameter file or use the **Browse S3** button to open a dialog box to browse for and select the S3 object.
   + (Optional) For **Object version**, if your S3 bucket has versioning enabled, you can select a specific version of the S3 object. 

1. Choose **Next**.

1. For **Hook name**, choose one of the following options:
   + Provide a short, descriptive name that will be added after `Private::Guard::`. For example, if you enter *`MyTestHook`*, the full Hook name becomes `Private::Guard::MyTestHook`.
   + Provide the full Hook name (also called an alias) using this format: `Provider::ServiceName::HookName` 

1. For **Hook targets**, choose what to evaluate:
   + **Stacks** — Evaluates stack templates when users create, update, or delete stacks.
   + **Resources** — Evaluates individual resource changes when users update stacks.
   + **Change sets** — Evaluates planned updates when users create change sets.
   + **Cloud Control API** — Evaluates create, update or delete operations initiated by the [Cloud Control API](https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/what-is-cloudcontrolapi.html).

1. For **Actions**, choose which actions (create, update, delete) will invoke your Hook.

1. For **Hook mode**, choose how the Hook responds when rules fail their evaluation:
   + **Warn** — Issues warnings to users but allows actions to continue. This is useful for non-critical validations or informational checks.
   + **Fail** — Prevents the action from proceeding. This is helpful for enforcing strict compliance or security policies.

1. For **Execution role**, choose the IAM role that the Hook assumes to retrieve your Guard rules from S3 and optionally write a detailed Guard output report back. You can either allow CloudFormation to automatically create an execution role for you or you can specify a role that you've created. 

1. Choose **Next**.

1. (Optional) For **Hook filters**, do the following:

   1. For **Resource filter**, specify which resource types can invoke the Hook. This ensures that the Hook is only invoked for relevant resources.

   1. For **Filtering criteria**, choose the logic for applying stack name and stack role filters:
      + **All stack names and stack roles** – The Hook will only be invoked when all specified filters match.
      + **Any stack names and stack roles** – The Hook will be invoked if at least one of the specified filters match.
**Note**  
For Cloud Control API operations, all **Stack names** and **Stack roles** filters are ignored.

   1. For **Stack names**, include or exclude specific stacks from Hook invocations.
      + For **Include**, specify the stack names to include. Use this when you have a small set of specific stacks you want to target. Only the stacks specified in this list will invoke the Hook.
      + For **Exclude**, specify the stack names to exclude. Use this when you want to invoke the Hook on most stacks but exclude a few specific ones. All stacks except those listed here will invoke the Hook.

   1. For **Stack roles**, include or exclude specific stacks from Hook invocations based on their associated IAM roles.
      + For **Include**, specify one or more IAM role ARNs to target stacks associated with these roles. Only stack operations initiated by these roles will invoke the Hook.
      + For **Exclude**, specify one or more IAM role ARNs for stacks you want to exclude. The Hook will be invoked on all stacks except those initiated by the specified roles.

1. Choose **Next**.

1. On the **Review and activate** page, review your choices. To make changes, choose **Edit** on the related section.

1. When you're ready to proceed, choose **Activate Hook**.

## Activate a Guard Hook (AWS CLI)
<a name="guard-hooks-activate-hooks-cli"></a>

Before you continue, confirm that you have created the Guard rule and the execution role that you'll use with this Hook. For more information, see [Write Guard rules to evaluate resources for Guard Hooks](guard-hooks-write-rules.md) and [Create an execution role for a Guard Hook](guard-hooks-prepare-to-create-hook.md#guard-hooks-create-execution-role).

**To activate a Guard Hook for use in your account (AWS CLI)**

1. To start activating a Hook, use the following [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/activate-type.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/activate-type.html) command, replacing the placeholders with your specific values. This command authorizes the Hook to use a specified execution role from your AWS account.

   ```
   aws cloudformation activate-type --type HOOK \
     --type-name AWS::Hooks::GuardHook \
     --publisher-id aws-hooks \
     --type-name-alias Private::Guard::MyTestHook \
     --execution-role-arn arn:aws:iam::123456789012:role/my-execution-role \
     --region us-west-2
   ```

1. To finish activating the Hook, you must configure it using a JSON configuration file.

   Use the **cat** command to create a JSON file with the following structure. For more information, see [Hook configuration schema syntax reference](hook-configuration-schema.md).

   ```
   $ cat > config.json
   {
     "CloudFormationConfiguration": {
       "HookConfiguration": {
         "HookInvocationStatus": "ENABLED",
         "TargetOperations": [
           "STACK",
           "RESOURCE",
           "CHANGE_SET"
         ],
         "FailureMode": "WARN",
         "Properties": {
           "ruleLocation": "s3://amzn-s3-demo-bucket/MyGuardRules.guard",
           "logBucket": "amzn-s3-demo-logging-bucket"
         },
         "TargetFilters": {
           "Actions": [
             "CREATE",
             "UPDATE",
             "DELETE"
           ]
         }
       }
     }
   }
   ```
   + `HookInvocationStatus`: Set to `ENABLED` to enable the Hook.
   + `TargetOperations`: Specify the operations that the Hook will evaluate.
   + `FailureMode`: Set to either `FAIL` or `WARN`.
   + `ruleLocation`: Replace with the S3 URI where your rule is stored. The object stored in S3 must have one of the following ﬁle extensions: `.guard`, `.zip`, and `.tar.gz`.
   + `logBucket`: (Optional) Specify the name of an S3 bucket for Guard JSON reports.
   + `TargetFilters`: Specify the types of actions that will invoke the Hook.

1. Use the following [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/set-type-configuration.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/set-type-configuration.html) command, along with the JSON file you created, to apply the configuration. Replace the placeholders with your specific values.

   ```
   aws cloudformation set-type-configuration \
     --configuration file://config.json \
     --type-arn "arn:aws:cloudformation:us-west-2:123456789012:type/hook/MyTestHook" \
     --region us-west-2
   ```

## Related resources
<a name="related-resources-guard-hooks"></a>

We provide template examples that you can use to understand how to declare a Guard Hook in a CloudFormation stack template. For more information, see [https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-guardhook.html](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-guardhook.html) in the *AWS CloudFormation User Guide*.

# View logs for the Guard Hooks in your account
<a name="guard-hooks-view-logs"></a>

When you activate a Guard Hook, you can specify an Amazon S3 bucket as the destination for the Hook output report. Once activated, the Hook automatically stores the results of your Guard rule validations in the specified bucket. You can then view these results in the Amazon S3 console.

## View Guard Hook logs in the Amazon S3 console
<a name="guard-hooks-view-logs-console"></a>

**To view the Guard Hook output log file**

1. Sign-in to the [https://console.aws.amazon.com/s3/](https://console.aws.amazon.com/s3/).

1. On the navigation bar at the top of the screen, choose your AWS Region.

1. Choose **Buckets**.

1. Choose the bucket you selected for your Guard output report.

1. Choose the desired validation output report log file.

1. Choose whether you want to **Download** the file or **Open** it to view.

# Delete Guard Hooks in your account
<a name="guard-hooks-delete-hooks"></a>

When you no longer need an activated Guard Hook, use the following procedures to delete it in your account.

To temporarily disable a Hook instead of deleting it, see [Disable and enable CloudFormation Hooks](hooks-disable-enable.md).

**Topics**
+ [Delete a Guard Hook in your account (console)](#guard-hooks-delete-hook-console)
+ [Delete a Guard Hook in your account (AWS CLI)](#guard-hooks-delete-hook-cli)

## Delete a Guard Hook in your account (console)
<a name="guard-hooks-delete-hook-console"></a>

**To delete a Guard Hook in your account**

1. Sign in to the AWS Management Console and open the CloudFormation console at [https://console.aws.amazon.com/cloudformation](https://console.aws.amazon.com/cloudformation/).

1. On the navigation bar at the top of the screen, choose the AWS Region where the Hook is located.

1. From the navigation pane, choose **Hooks**.

1. On the **Hooks** page, find the Guard Hook you want to delete.

1. Select the check box next to your Hook and choose **Delete**. 

1. When prompted for confirmation, type out the Hook name to confirm deleting the specified Hook and then choose **Delete**.

## Delete a Guard Hook in your account (AWS CLI)
<a name="guard-hooks-delete-hook-cli"></a>

**Note**  
Before you can delete the Hook, you must first disable it. For more information, see [Disable and enable a Hook in your account (AWS CLI)](hooks-disable-enable.md#hooks-disable-enable-cli).

Use the following [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deactivate-type.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deactivate-type.html) command to deactivate a Hook, which removes it from your account. Replace placeholders with your specific values.

```
aws cloudformation deactivate-type \
  --type-arn "arn:aws:cloudformation:us-west-2:123456789012:type/hook/MyTestHook" \
  --region us-west-2
```

# Lambda Hooks
<a name="lambda-hooks"></a>

To use an AWS Lambda Hook in your account, you must first *activate* the Hook for the account and Region where you want to use it. Activating a Hook makes it usable in stack operations in the account and Region where it's activated. 

When you activate a Lambda Hook, CloudFormation creates an entry in your account's registry for the activated Hook as a private Hook. This allows you to set any configuration properties the Hook includes. Configuration properties define how the Hook is configured for a given AWS account and Region.

**Topics**
+ [AWS CLI commands for working with Lambda Hooks](#commonly-used-commands-lambda-hooks)
+ [Create Lambda functions to evaluate resources for Lambda Hooks](lambda-hooks-create-lambda-function.md)
+ [Prepare to create a Lambda Hook](lambda-hooks-prepare-to-create-hook.md)
+ [Activate a Lambda Hook in your account](lambda-hooks-activate-hooks.md)
+ [View logs for the Lambda Hooks in your account](lambda-hooks-view-logs.md)
+ [Delete Lambda Hooks in your account](lambda-hooks-delete-hooks.md)

## AWS CLI commands for working with Lambda Hooks
<a name="commonly-used-commands-lambda-hooks"></a>

The AWS CLI commands for working with Lambda Hooks include: 
+ [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/activate-type.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/activate-type.html) to start the activation process for a Lambda Hook.
+ [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/set-type-configuration.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/set-type-configuration.html) to specify the configuration data for a Hook in your account.
+ [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/list-types.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/list-types.html) to list the Hooks in your account.
+ [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/describe-type.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/describe-type.html) to return detailed information about a specific Hook or specific Hook version, including current configuration data.
+ [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deactivate-type.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deactivate-type.html) to remove a previously activated Hook from your account.

# Create Lambda functions to evaluate resources for Lambda Hooks
<a name="lambda-hooks-create-lambda-function"></a>

CloudFormation Lambda Hooks allows you to evaluate CloudFormation and AWS Cloud Control API operations against your own custom code. Your Hook can block an operation from proceeding, or issue a warning to the caller and allow the operation to proceed. When you create a Lambda Hook, you can configure it to intercept and evaluate the following CloudFormation operations:
+ Resource operations
+ Stack operations
+ Change set operations

**Topics**
+ [Developing a Lambda Hook](#lambda-hooks-create-lambda-function-develop)
+ [Evaluating resource operations with Lambda Hooks](#lambda-hooks-create-lambda-function-resource)
+ [Evaluating stack operations with Lambda Hooks](#lambda-hooks-create-lambda-function-stack)
+ [Evaluating change set operations with Lambda Hooks](#lambda-hooks-create-lambda-function-change-set)

## Developing a Lambda Hook
<a name="lambda-hooks-create-lambda-function-develop"></a>

When Hooks invoke your Lambda it will wait up to 30 seconds for the Lambda to evaluate the input. The Lambda will return a JSON response that indicates whether the Hook succeeded or failed.

**Topics**
+ [Request input](#lambda-hooks-create-lambda-function-request-input)
+ [Response input](#lambda-hooks-create-lambda-function-request-response)
+ [Examples](#lambda-hooks-create-lambda-function-request-example)

### Request input
<a name="lambda-hooks-create-lambda-function-request-input"></a>

The input passed to your Lambda function depends on the Hook target operation (examples: stack, resource, or change set). 

### Response input
<a name="lambda-hooks-create-lambda-function-request-response"></a>

In order to communicate to Hooks if your request succeeded or failed, your Lambda function needs to return a JSON response.

The following is an example shape of the response Hooks expects:

```
{ 
  "hookStatus": "SUCCESS" or "FAILED" or "IN_PROGRESS", 
  "errorCode": "NonCompliant" or "InternalFailure"
  "message": String, 
  "clientRequestToken": String,
  "callbackContext": 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"
    }
  ]
}
```

hookStatus  <a name="lambda-hook-response-hookstatus"></a>
The status of the Hook. This is a required field.  
*Valid values*: (`SUCCESS` \$1 `FAILED` \$1 `IN_PROGRESS`)  
A Hook can return `IN_PROGRESS` 3 times. If no result is returned, the Hook will fail. For a Lambda Hook, this means your Lambda function can be invoked up to 3 times.

errorCode  <a name="lambda-hook-response-errorcode"></a>
Shows whether the operation was evaluated and determined to be invalid, or if errors occurred within the Hook, preventing the evaluation. This field is required if the Hook fails.  
*Valid values*: (`NonCompliant` \$1 `InternalFailure`)

message  <a name="lambda-hook-response-message"></a>
The message to the caller that states why the Hook succeeded or failed.  
When evaluating CloudFormation operations, this field is truncated to 4096 characters.  
When evaluating Cloud Control API operations, this field is truncated to 1024 characters.

clientRequestToken  <a name="lambda-hook-response-clientrequesttoken"></a>
The request token that was provided as an input to the Hook request. This is a required field.

callbackContext  <a name="lambda-hook-response-callbackcontext"></a>
If you indicate that the `hookStatus` is `IN_PROGRESS` you pass an additional context that's provided as input when the Lambda function is reinvoked.

callbackDelaySeconds  <a name="lambda-hook-response-callbackdelayseconds"></a>
How long Hooks should wait to invoke this Hook again.

annotations  <a name="lambda-hook-response-annotations"></a>
An array of annotation objects that provide further details and remediation guidance.     
annotationName  
An identifier for the annotation.  
status  
The Hook invocation status. This is helpful when annotations represent logic with pass/fail evaluation similar to a Guard rule.   
*Valid values*: (`PASSED` \$1 `FAILED` \$1 `SKIPPED`)  
statusMessage  
Explanation for the specific status.  
remediationMessage  
Suggestion for fixing a `FAILED` status. For example, if a resource is missing encryption, you can state how to add encryption to the resource configuration.  
remediationLink  
An HTTP URL for additional remediation guidance.  
severityLevel  
Defines the relative risk associated with any violations of this type. When assigning severity levels to your Hook invocation results, you can reference the AWS Security Hub CSPM [severity framework](https://docs.aws.amazon.com/securityhub/latest/userguide/asff-required-attributes.html#Severity) as an example of how to structure meaningful severity categories.   
*Valid values*: (`INFORMATIONAL` \$1 `LOW` \$1 `MEDIUM` \$1 `HIGH` \$1 `CRITICAL`)

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

The following is an example of a successful response:

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

The following is an example of a failed response:

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

## Evaluating resource operations with Lambda Hooks
<a name="lambda-hooks-create-lambda-function-resource"></a>

Any time you create, update, or delete a resource, that's considered a resource operation. As an example, if you run update a CloudFormation stack that creates a new resource, you have completed a resource operation. When you create, update or delete a resource using Cloud Control API, that is also considered a resource operation. You can configure your CloudFormation Lambda Hook to target `RESOURCE` and `CLOUD_CONTROL` operations in the Hook `TargetOperations` configuration.

**Note**  
The `delete` Hook handler is only invoked when a resource is deleted using an operation trigger from Cloud Control API `delete-resource` or CloudFormation `delete-stack`.

**Topics**
+ [Lambda Hook resource input syntax](#lambda-hooks-create-lambda-function-resource-input)
+ [Example Lambda Hook resource change input](#lambda-hooks-create-lambda-function-resource-example)
+ [Example Lambda function for resource operations](#lambda-hooks-create-lambda-function-resource-example-function)

### Lambda Hook resource input syntax
<a name="lambda-hooks-create-lambda-function-resource-input"></a>

When your Lambda is invoked for a resource operation, you'll receive a JSON input containing the resource properties, proposed properties, and the context around the Hook invocation.

The following is an example shape of the JSON input:

```
{
    "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": {
        "invocation": 1,
        "callbackContext": null
    }
}
```

`awsAccountId`  <a name="lambda-hook-resource-awsaccountid"></a>
The ID of the AWS account containing the resource being evaluated.

`stackId`  <a name="lambda-hook-resource-stackid"></a>
The stack ID of the CloudFormation stack this operation is a part of. This field is empty if the caller is Cloud Control API.

`changeSetId`  <a name="lambda-hook-resource-changesetid"></a>
The ID of the change set that initiated the Hook invocation. This value is empty if the resource change was initiated by Cloud Control API, or the `create-stack`, `update-stack`, or `delete-stack` operations.

`hookTypeName`  <a name="lambda-hook-resource-hooktypename"></a>
The name of the Hook that's running.

`hookTypeVersion`  <a name="lambda-hook-resource-hooktypeversion"></a>
The version of the Hook that's running.

`hookModel`  <a name="lambda-hook-resource-hookmodel"></a>  
`LambdaFunction`  <a name="lambda-hook-resource-hookmodel-lambdafunction"></a>
The current Lambda ARN invoked by the Hook.

`actionInvocationPoint`  <a name="lambda-hook-resource-actioninvocationpoint"></a>
The exact point in the provisioning logic where the Hook runs.  
*Valid values*: (`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>
The target type being evaluated, for example, `AWS::S3::Bucket`.  
`targetType`  <a name="lambda-hook-resource-requestdata-targettype"></a>
The target type being evaluated, for example `AWS::S3::Bucket`. For resources provisioned with Cloud Control API, this value will be `RESOURCE`.  
`targetLogicalId`  <a name="lambda-hook-resource-requestdata-targetlogicalid"></a>
The logical ID of the resource being evaluated. If the origin of the Hook invocation is CloudFormation, this will be the logical resource ID defined in your CloudFormation template. If the origin of this Hook invocation is Cloud Control API, this will be a constructed value.  
`targetModel`  <a name="lambda-hook-resource-requestdata-targetmodel"></a>  
`resourceProperties`  <a name="lambda-hook-resource-requestdata-targetmodel-resourceproperties"></a>
The proposed properties of the resource being modified. If the resource is being deleted, this value will be empty.   
`previousResourceProperties`  <a name="lambda-hook-resource-requestdata-targetmodel-previousresourceproperties"></a>
The properties that are currently associated with the resource being modified. If the resource is being created, this value will be empty.

`requestContext`  <a name="lambda-hook-resource-requestcontext"></a>  
invocation  <a name="lambda-hook-resource-requestcontext-invocation"></a>
The current attempt at executing the Hook.   
callbackContext  <a name="lambda-hook-resource-requestcontext-callbackcontext"></a>
If the Hookwas set to `IN_PROGRESS`, and `callbackContext` was returned, it will be here after reinvocation.

### Example Lambda Hook resource change input
<a name="lambda-hooks-create-lambda-function-resource-example"></a>

The following example input shows a Lambda Hook that will receive the definition of the `AWS::DynamoDB::Table` resource to update, where the `ReadCapacityUnits` of `ProvisionedThroughput` is changed from 3 to 10. This is the data available to Lambda for evaluation.

```
{
    "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
    }    
}
```

To see all of the properties available for the resource type, see [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).

### Example Lambda function for resource operations
<a name="lambda-hooks-create-lambda-function-resource-example-function"></a>

The following is a simple function that fails any resource update to DynamoDB, which tries to set the `ReadCapacity` of `ProvisionedThroughput` to something larger than 10. If the Hook succeeds, the message, "ReadCapacity is correctly configured," will display to the caller. If the request fails validation, the Hook will fail with the status, "ReadCapacity cannot be more than 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
```

------

## Evaluating stack operations with Lambda Hooks
<a name="lambda-hooks-create-lambda-function-stack"></a>

Any time you create, update, or delete a stack with a new template, you can configure your CloudFormation Lambda Hook to start by evaluating the new template and potentially block the stack operation from proceeding. You can configure your CloudFormation Lambda Hook to target `STACK` operations in the Hook `TargetOperations` configuration.

**Topics**
+ [Lambda Hook stack input syntax](#lambda-hooks-create-lambda-function-stack-input)
+ [Example Lambda Hook stack change input](#lambda-hooks-create-lambda-function-stack-example)
+ [Example Lambda function for stack operations](#lambda-hooks-create-lambda-function-stack-example-function)

### Lambda Hook stack input syntax
<a name="lambda-hooks-create-lambda-function-stack-input"></a>

When your Lambda is invoked for a stack operation, you'll receive a JSON request containing the Hook invocation context, `actionInvocationPoint`, and request context. Due to the size of CloudFormation templates, and the limited input size accepted by Lambda functions, the actual templates are stored in an Amazon S3 object. The input of the `requestData` includes an Amazon S3 resigned URL to another object, which contains the current and previous template version.

The following is an example shape of the JSON input:

```
{
    "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>
The request token that was provided as an input to the Hook request. This is a required field.

`awsAccountId`  <a name="lambda-hook-stack-awsaccountid"></a>
The ID of the AWS account containing the stack being evaluated.

`stackID`  <a name="lambda-hook-stack-stackid"></a>
The stack ID of the CloudFormation stack.

`changeSetId`  <a name="lambda-hook-stack-changesetid"></a>
The ID of the change set that initiated the Hook invocation. This value is empty if the stack change was initiated by Cloud Control API, or the `create-stack`, `update-stack`, or `delete-stack` operations.

`hookTypeName`  <a name="lambda-hook-stack-hooktypename"></a>
The name of the Hook that's running.

`hookTypeVersion`  <a name="lambda-hook-stack-hooktypeversion"></a>
The version of the Hook that's running.

`hookModel`  <a name="lambda-hook-stack-hookmodel"></a>  
`LambdaFunction`  <a name="lambda-hook-stack-hookmodel-lambdafunction"></a>
The current Lambda ARN invoked by the Hook.

`actionInvocationPoint`  <a name="lambda-hook-stack-actioninvocationpoint"></a>
The exact point in the provisioning logic where the Hook runs.  
*Valid values*: (`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>
This value will be `STACK`.  
`targetType`  <a name="lambda-hook-stack-requestdata-targettype"></a>
This value will be `STACK`.  
`targetLogicalId`  <a name="lambda-hook-stack-requestdata-targetlogicalid"></a>
The stack name.  
`payload`  <a name="lambda-hook-stack-requestdata-payload"></a>
The Amazon S3 presigned URL containing a JSON object with the current and previous template definitions.

`requestContext`  <a name="lambda-hook-stack-requestcontext"></a>
If the Hook is being reinvoked, this object will be set.    
`invocation`  <a name="lambda-hook-stack-requestcontext-invocation"></a>
The current attempt at executing the Hook.  
`callbackContext`  <a name="lambda-hook-stack-requestcontext-callbackcontext"></a>
If the Hook was set to `IN_PROGRESS` and `callbackContext` was returned, it will be here upon reinvocation.

The `payload` property in the request data is a URL that your code needs to fetch. Once it has received the URL, you get an object with the following schema:

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

`template`  <a name="lambda-hook-stack-payload-template"></a>
The full CloudFormation template that was provided to `create-stack` or `update-stack`. It can be a JSON or YAML string depending on what was provided to CloudFormation.  
In `delete-stack` operations, this value will be empty.

`previousTemplate`  <a name="lambda-hook-stack-payload-previoustemplate"></a>
The previous CloudFormation template. It can be a JSON or YAML string depending on what was provided to CloudFormation.  
In `delete-stack` operations, this value will be empty.

### Example Lambda Hook stack change input
<a name="lambda-hooks-create-lambda-function-stack-example"></a>

The following is an example stack change input. The Hook is evaluating a change which updates the `ObjectLockEnabled` to true, and adds an Amazon SQS queue:

```
{
    "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
    }
}
```

This is an example `payload` of the `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}}}}"
}
```

### Example Lambda function for stack operations
<a name="lambda-hooks-create-lambda-function-stack-example-function"></a>

The following example is a simple function that downloads the stack operation payload, parses the template JSON, and returns `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 ]

To use Python, you'll need to import the `requests` library. To do this, you'll need to include the library in your deployment package when creating your Lambda function. For more information, see [Creating a .zip deployment package with dependencies](https://docs.aws.amazon.com/lambda/latest/dg/python-package.html#python-package-create-dependencies) in the *AWS Lambda Developer Guide*.

```
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
```

------

## Evaluating change set operations with Lambda Hooks
<a name="lambda-hooks-create-lambda-function-change-set"></a>

Any time you create a change set, you can configure your CloudFormation Lambda Hook to first evaluate the new change set and potentially block its execution. You can configure your CloudFormation Lambda Hook to target `CHANGE_SET` operations in the Hook `TargetOperations` configuration.

**Topics**
+ [Lambda Hook change set input syntax](#lambda-hooks-create-lambda-function-change-set-input)
+ [Example Lambda Hook change set change input](#lambda-hooks-create-lambda-function-change-set-example)
+ [Example Lambda function for change set operations](#lambda-hooks-create-lambda-function-change-set-example-function)

### Lambda Hook change set input syntax
<a name="lambda-hooks-create-lambda-function-change-set-input"></a>

The input for change set operations is similar to stack operations, but the payload of the `requestData` also includes a list of resource changes introduced by the change set.

The following is an example shape of the JSON input:

```
{
    "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>
The request token that was provided as an input to the Hook request. This is a required field.

`awsAccountId`  <a name="lambda-hook-change-set-awsaccountid"></a>
The ID of the AWS account containing the stack being evaluated.

`stackID`  <a name="lambda-hook-change-set-stackid"></a>
The stack ID of the CloudFormation stack.

`changeSetId`  <a name="lambda-hook-change-set-changesetid"></a>
The ID of the change set that initiated the Hook invocation.

`hookTypeName`  <a name="lambda-hook-change-set-hooktypename"></a>
The name of the Hook that's running.

`hookTypeVersion`  <a name="lambda-hook-change-set-hooktypeversion"></a>
The version of the Hook that's running.

`hookModel`  <a name="lambda-hook-change-set-hookmodel"></a>  
`LambdaFunction`  <a name="lambda-hook-change-set-hookmodel-lambdafunction"></a>
The current Lambda ARN invoked by the Hook.

`requestData`  <a name="lambda-hook-change-set-requestdata"></a>  
`targetName`  <a name="lambda-hook-change-set-requestdata-targetname"></a>
This value will be `CHANGE_SET`.  
`targetType`  <a name="lambda-hook-change-set-requestdata-targettype"></a>
This value will be `CHANGE_SET`.  
`targetLogicalId`  <a name="lambda-hook-change-set-requestdata-targetlogicalid"></a>
The change set ARN..  
`payload`  <a name="lambda-hook-change-set-requestdata-payload"></a>
The Amazon S3 presigned URL containing a JSON object with the current template, as well as a list of changes introduced by this change set.

`requestContext`  <a name="lambda-hook-change-set-requestcontext"></a>
If the Hook is being reinvoked, this object will be set.    
`invocation`  <a name="lambda-hook-change-set-requestcontext-invocation"></a>
The current attempt at executing the Hook.  
`callbackContext`  <a name="lambda-hook-change-set-requestcontext-callbackcontext"></a>
If the Hook was set to `IN_PROGRESS` and `callbackContext` was returned, it will be here upon reinvocation.

The `payload` property in the request data is a URL that your code needs to fetch. Once it has received the URL, you get an object with the following schema:

```
{
    "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>
The full CloudFormation template that was provided to `create-stack` or `update-stack`. It can be a JSON or YAML string depending on what was provided to CloudFormation.

`changedResources`  <a name="lambda-hook-change-set-payload-changed-resources"></a>
A list of changed resources.    
`action`  <a name="lambda-hook-change-set-payload-changed-resources-action"></a>
The type of change applied to the resource.  
*Valid values*: (`CREATE` \$1 `UPDATE` \$1 `DELETE`)  
`beforeContext`  <a name="lambda-hook-change-set-payload-changed-resources-beforecontext"></a>
A JSON string of the resource properties before the change. This value is null when the resource is being created. All boolean and number values in this JSON string are STRINGS.  
`afterContext`  <a name="lambda-hook-change-set-payload-changed-resources-aftercontext"></a>
A JSON string of the resources properties if this change set is executed. This value is null when the resource is being deleted. All boolean and number values in this JSON string are STRINGS.  
`lineNumber`  <a name="lambda-hook-change-set-payload-changed-resources-linenumber"></a>
The line number in the template that caused this change. If the action is `DELETE` this value will be null.   
`logicalResourceId`  <a name="lambda-hook-change-set-payload-changed-resources-logicalresourceid"></a>
The logical resource ID of the resource being changed.  
`resourceType`  <a name="lambda-hook-change-set-payload-changed-resources-resourcetype"></a>
The resource type that’s being changed.

### Example Lambda Hook change set change input
<a name="lambda-hooks-create-lambda-function-change-set-example"></a>

The following is an example change set change input. In the following example, you can see the changes introduced by the change set. The first change is deleting a queue called `CoolQueue`. The second change is adding a new queue called `NewCoolQueue`. The last change is an update to the `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
    }
}
```

This is an example `payload` of the `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"}]}}'
    }
  ]
}
```

### Example Lambda function for change set operations
<a name="lambda-hooks-create-lambda-function-change-set-example-function"></a>

The following example is a simple function that downloads the change set operation payload, loops through each change, and then prints out the before and after properties before it returns 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 ]

To use Python, you'll need to import the `requests` library. To do this, you'll need to include the library in your deployment package when creating your Lambda function. For more information, see [Creating a .zip deployment package with dependencies](https://docs.aws.amazon.com/lambda/latest/dg/python-package.html#python-package-create-dependencies) in the *AWS Lambda Developer Guide*.

```
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
```

------

# Prepare to create a Lambda Hook
<a name="lambda-hooks-prepare-to-create-hook"></a>

Before you create a Lambda Hook, you must complete the following prerequisites:
+ You must have already created a Lambda function. For more information, see the [Create Lambda functions for Hooks](lambda-hooks-create-lambda-function.md).
+ The user or role that creates the Hook must have sufficient permissions to activate Hooks. For more information, see [Grant IAM permissions for CloudFormation Hooks](grant-iam-permissions-for-hooks.md).
+ To use the AWS CLI or an SDK to create a Lambda Hook, you must manually create an execution role with IAM permissions and a trust policy to allow CloudFormation to invoke a Lambda Hook. 

## Create an execution role for a Lambda Hook
<a name="lambda-hooks-create-execution-role"></a>

A Hook uses an execution role for the permissions that it requires to invoke that Hook in your AWS account.

This role can be created automatically if you create a Lambda Hook from the AWS Management Console; otherwise, you must create this role yourself.

The following section shows you how to set up permissions to create your Lambda Hook. 

### Required permissions
<a name="lambda-hooks-execution-role-permissions"></a>

Follow the guidance at [Create a role using custom trust policies](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-custom.html) in the *IAM User Guide* to create a role with a custom trust policy.

Then, complete the following steps to set up your permissions:

1. Attach the following minimum privilege policy to the IAM role you want to use to create the Lambda Hook.

------
#### [ JSON ]

****  

   ```
   {
     "Version":"2012-10-17",		 	 	 
     "Statement": [
       {
         "Effect": "Allow",
         "Action": "lambda:InvokeFunction",
         "Resource": "arn:aws:lambda:us-west-2:123456789012:function:MyFunction"
       }
     ]
   }
   ```

------

1. Give your Hook permission to assume the role by adding a trust policy to the role. The following shows an example trust policy you can use.

------
#### [ JSON ]

****  

   ```
   {
     "Version":"2012-10-17",		 	 	 
     "Statement": [
       {
         "Effect": "Allow",
         "Principal": {
           "Service": [
             "hooks.cloudformation.amazonaws.com"
           ]
         },
         "Action": "sts:AssumeRole"
       }
     ]
   }
   ```

------

# Activate a Lambda Hook in your account
<a name="lambda-hooks-activate-hooks"></a>

The following topic shows you how to activate a Lambda Hook in your account, which makes it usable in the account and Region it was activated in.

**Topics**
+ [Activate a Lambda Hook (console)](#lambda-hooks-activate-hook-console)
+ [Activate a Lambda Hook (AWS CLI)](#lambda-hooks-activate-hooks-cli)
+ [Related resources](#related-resources-lambda-hooks)

## Activate a Lambda Hook (console)
<a name="lambda-hooks-activate-hook-console"></a>

**To activate a Lambda Hook for use in your account**

1. Sign in to the AWS Management Console and open the CloudFormation console at [https://console.aws.amazon.com/cloudformation](https://console.aws.amazon.com/cloudformation/).

1. On the navigation bar at the top of the screen, choose the AWS Region where you want to create the Hook in.

1. If you *haven't* created a Lambda function for the Hook, do the following:
   + Open the [Functions page](https://console.aws.amazon.com/lambda/home#/functions) on the Lambda console.
   + Create the Lambda function that you'll use with this Hook, and then return to this procedure. For more information, see [Create Lambda functions to evaluate resources for Lambda Hooks](lambda-hooks-create-lambda-function.md). 

   If you have already created your Lambda function, proceed to the next step. 

1. In the navigation pane on the left, choose **Hooks**.

1. On the **Hooks** page, choose **Create a Hook**, and then choose **With Lambda**.

1. For **Hook name**, choose one of the following options:
   + Provide a short, descriptive name that will be added after `Private::Lambda::`. For example, if you enter *`MyTestHook`*, the full Hook name becomes `Private::Lambda::MyTestHook`.
   + Provide the full Hook name (also called an alias) using this format: `Provider::ServiceName::HookName` 

1. For **Lambda function**, provide the Lambda function to be used with this Hook. You can use: 
   + The full Amazon Resource Name (ARN) without a suffix.
   + A qualified ARN with a version or alias suffix.

1. For **Hook targets**, choose what to evaluate:
   + **Stacks** — Evaluates stack templates when users create, update, or delete stacks.
   + **Resources** — Evaluates individual resource changes when users update stacks.
   + **Change sets** — Evaluates planned updates when users create change sets.
   + **Cloud Control API** — Evaluates create, update or delete operations initiated by the [Cloud Control API](https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/what-is-cloudcontrolapi.html).

1. For **Actions**, choose which actions (create, update, delete) will invoke your Hook.

1. For **Hook mode**, choose how the Hook responds when the Lambda function invoked by the Hook returns a `FAILED` response:
   + **Warn** — Issues warnings to users but allows actions to continue. This is useful for non-critical validations or informational checks.
   + **Fail** — Prevents the action from proceeding. This is helpful for enforcing strict compliance or security policies.

1. For **Execution role**, choose the IAM role that the Hook assumes to invoke your Lambda function. You can either allow CloudFormation to automatically create an execution role for you or you can specify a role that you've created. 

1. Choose **Next**.

1. (Optional) For **Hook filters**, do the following:

   1. For **Resource filter**, specify which resource types can invoke the Hook. This ensures that the Hook is only invoked for relevant resources.

   1. For **Filtering criteria**, choose the logic for applying stack name and stack role filters:
      + **All stack names and stack roles** – The Hook will only be invoked when all specified filters match.
      + **Any stack names and stack roles** – The Hook will be invoked if at least one of the specified filters match.
**Note**  
For Cloud Control API operations, all **Stack names** and **Stack roles** filters are ignored.

   1. For **Stack names**, include or exclude specific stacks from Hook invocations.
      + For **Include**, specify the stack names to include. Use this when you have a small set of specific stacks you want to target. Only the stacks specified in this list will invoke the Hook.
      + For **Exclude**, specify the stack names to exclude. Use this when you want to invoke the Hook on most stacks but exclude a few specific ones. All stacks except those listed here will invoke the Hook.

   1. For **Stack roles**, include or exclude specific stacks from Hook invocations based on their associated IAM roles.
      + For **Include**, specify one or more IAM role ARNs to target stacks associated with these roles. Only stack operations initiated by these roles will invoke the Hook.
      + For **Exclude**, specify one or more IAM role ARNs for stacks you want to exclude. The Hook will be invoked on all stacks except those initiated by the specified roles.

1. Choose **Next**.

1. On the **Review and activate** page, review your choices. To make changes, choose **Edit** on the related section.

1. When you're ready to proceed, choose **Activate Hook**.

## Activate a Lambda Hook (AWS CLI)
<a name="lambda-hooks-activate-hooks-cli"></a>

Before you continue, confirm that you have created the Lambda function and the execution role that you'll use with this Hook. For more information, see [Create Lambda functions to evaluate resources for Lambda Hooks](lambda-hooks-create-lambda-function.md) and [Create an execution role for a Lambda Hook](lambda-hooks-prepare-to-create-hook.md#lambda-hooks-create-execution-role).

**To activate a Lambda Hook for use in your account (AWS CLI)**

1. To start activating a Hook, use the following [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/activate-type.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/activate-type.html) command, replacing the placeholders with your specific values. This command authorizes the Hook to use a specified execution role from your AWS account.

   ```
   aws cloudformation activate-type --type HOOK \
     --type-name AWS::Hooks::LambdaHook \
     --publisher-id aws-hooks \
     --execution-role-arn arn:aws:iam::123456789012:role/my-execution-role \
     --type-name-alias Private::Lambda::MyTestHook \
     --region us-west-2
   ```

1. To finish activating the Hook, you must configure it using a JSON configuration file.

   Use the **cat** command to create a JSON file with the following structure. For more information, see [Hook configuration schema syntax reference](hook-configuration-schema.md).

   ```
   $ cat > config.json
   {
     "CloudFormationConfiguration": {
       "HookConfiguration": {
         "HookInvocationStatus": "ENABLED",
         "TargetOperations": [
           "CLOUD_CONTROL"
         ],
         "FailureMode": "WARN",
         "Properties": {
           "LambdaFunction": "arn:aws:lambda:us-west-2:123456789012:function:MyFunction"
         },
         "TargetFilters": {
           "Actions": [
             "CREATE",
             "UPDATE",
             "DELETE"
           ]
         }
       }
     }
   }
   ```
   + `HookInvocationStatus`: Set to `ENABLED` to enable the Hook.
   + `TargetOperations`: Specify the operations that the Hook will evaluate.
   + `FailureMode`: Set to either `FAIL` or `WARN`.
   + `LambdaFunction`: Specify the ARN of the Lambda function.
   + `TargetFilters`: Specify the types of actions that will invoke the Hook.

1. Use the following [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/set-type-configuration.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/set-type-configuration.html) command, along with the JSON file you created, to apply the configuration. Replace the placeholders with your specific values.

   ```
   aws cloudformation set-type-configuration \
     --configuration file://config.json \
     --type-arn "arn:aws:cloudformation:us-west-2:123456789012:type/hook/MyTestHook" \
     --region us-west-2
   ```

## Related resources
<a name="related-resources-lambda-hooks"></a>

We provide template examples that you can use to understand how to declare a Lambda Hook in a CloudFormation stack template. For more information, see [https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-lambdahook.html](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-lambdahook.html) in the *AWS CloudFormation User Guide*.

# View logs for the Lambda Hooks in your account
<a name="lambda-hooks-view-logs"></a>

When using a Lambda Hook, your validation output report log file can be found in the Lambda console.

## View Lambda Hook logs in the Lambda console
<a name="lambda-hooks-view-logs-console"></a>

**To view the Lambda Hook output log file**

1. Sign-in to the Lambda console.

1. On the navigation bar at the top of the screen, choose your AWS Region.

1. Choose **Functions**.

1. Choose desired Lambda function.

1. Choose the **Test** tab.

1. Choose **CloudWatch Logs Live Trail**

1. Choose the drop-down menu and select the log groups you want to view.

1. Choose **Start**. The log will display in the **CloudWatch Logs Live Trail** window. Choose **View in columns** or **View in plain text** depending on your preference.
   + You can add more filters to the results by adding them in the **Add filter pattern** field. This field allows you filter results to only include events that match the specified pattern. 

For more information on viewing logs for Lambda functions, see [Viewing CloudWatch Logs for Lambda functions](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs-view.html).

# Delete Lambda Hooks in your account
<a name="lambda-hooks-delete-hooks"></a>

When you no longer need an activated Lambda Hook, use the following procedures to delete it in your account.

To temporarily disable a Hook instead of deleting it, see [Disable and enable CloudFormation Hooks](hooks-disable-enable.md).

**Topics**
+ [Delete a Lambda Hook in your account (console)](#lambda-hooks-delete-hook-console)
+ [Delete a Lambda Hook in your account (AWS CLI)](#lambda-hooks-delete-hook-cli)

## Delete a Lambda Hook in your account (console)
<a name="lambda-hooks-delete-hook-console"></a>

**To delete a Lambda Hook in your account**

1. Sign in to the AWS Management Console and open the CloudFormation console at [https://console.aws.amazon.com/cloudformation](https://console.aws.amazon.com/cloudformation/).

1. On the navigation bar at the top of the screen, choose the AWS Region where the Hook is located.

1. From the navigation pane, choose **Hooks**.

1. On the **Hooks** page, find the Lambda Hook you want to delete.

1. Select the check box next to your Hook and choose **Delete**. 

1. When prompted for confirmation, type out the Hook name to confirm deleting the specified Hook and then choose **Delete**.

## Delete a Lambda Hook in your account (AWS CLI)
<a name="lambda-hooks-delete-hook-cli"></a>

**Note**  
Before you can delete the Hook, you must first disable it. For more information, see [Disable and enable a Hook in your account (AWS CLI)](hooks-disable-enable.md#hooks-disable-enable-cli).

Use the following [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deactivate-type.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deactivate-type.html) command to deactivate a Hook, which removes it from your account. Replace placeholders with your specific values.

```
aws cloudformation deactivate-type \
  --type-arn "arn:aws:cloudformation:us-west-2:123456789012:type/hook/MyTestHook" \
  --region us-west-2
```

# Developing custom Hooks using the CloudFormation CLI
<a name="hooks-develop"></a>

This section is for customers who want to develop custom Hooks and register them in the CloudFormation registry. It provides an overview of the structure of CloudFormation Hooks, and guides for developing, registering, testing, managing, and publishing your own Hooks with Python or Java.

There are three major steps in developing a custom Hook:

1. **Initiate**

   To develop custom Hooks, you must configure and use the CloudFormation CLI. To initiate a Hook's project and its required files, use the CloudFormation CLI [https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-cli-init.html](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-cli-init.html) command and specify that you want to create a Hook. For more information, see [Initiating a custom CloudFormation Hooks project](hooks-init.md).

1. **Model**

   To model, author, and validate your Hook schema, define the Hook, its properties, and their attributes.

   The CloudFormation CLI creates empty handler functions which correspond to a specific Hook invocation point. Add your own logic to these handlers to control what happens during your Hook invocation at each stage of its target lifecycle. For more information, see [Modeling custom CloudFormation Hooks](hooks-model.md).

1. **Register**

   To register a Hook, submit your Hook to be registered either as a private or a public third-party extension. Register your Hook with the `[submit](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-cli-submit.html)` operation. For more information, see [Registering a custom Hook with CloudFormation](registering-hooks.md).

   The following tasks are associated with registering your Hook:

   1. *Publish* – Hooks are published to the registry.

   1. *Configure* – Hooks are configured when the type configuration invokes against stacks.
**Note**  
Hooks will time out after 30 seconds and retry up to 3 times. For more information, see [Timeout and retry limits](hooks-concepts.md#hook-timeout-and-retry-limits).

**Topics**
+ [Prerequisites](hooks-prerequisites.md)
+ [Initiating a Hooks project](hooks-init.md)
+ [Modeling Hooks](hooks-model.md)
+ [Registering Hooks](registering-hooks.md)
+ [Testing Hooks](testing-hooks.md)
+ [Updating Hooks](updating-registered-hook.md)
+ [Deregistering Hooks](deregistering-hooks.md)
+ [Publishing Hooks](hooks-publishing.md)
+ [Schema syntax](hooks-schema.md)

# Prerequisites for developing custom CloudFormation Hooks
<a name="hooks-prerequisites"></a>

You can develop a custom Hook with Java or Python. The following are the prerequisites for developing custom Hooks:

**Java prerequisites**
+ [Apache Maven](https://maven.apache.org/install.html)
+ [JDK 17](https://www.oracle.com/java/technologies/downloads/#java17)
**Note**  
If you intend to use the [CloudFormation Command Line Interface (CLI)](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/what-is-cloudformation-cli.html) to initiate a Hooks project for Java, you must install Python 3.8 or later as well. The Java plugin for the CloudFormation CLI can be installed through `pip` (Python's package manager), which is distrubted with Python.

To implement Hook handlers for your Java Hooks project, you can download the [Java Hook handler example files](samples/java-handlers.zip).

**Python prerequisites**
+ [Python version 3.8](https://www.python.org/downloads/) or later.

To implement Hook handlers for your Python Hooks project, you can download the [Python Hook handler example files](samples/python-handlers.zip).

## Permissions for developing Hooks
<a name="hooks-development-permissions"></a>

In addition to the CloudFormation `Create`, `Update`, and `Delete` stack permissions, you'll need access to the following AWS CloudFormation operations. Access to these operations is managed through your IAM role's CloudFormation policy.
+ [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/register-type.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/register-type.html)
+ [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/list-types.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/list-types.html)
+ [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deregister-type.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deregister-type.html)
+ [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/set-type-configuration.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/set-type-configuration.html)

For more information, see [Grant IAM permissions for CloudFormation Hooks](grant-iam-permissions-for-hooks.md).

## Set up a development environment for Hooks
<a name="hooks-environment"></a>

To develop Hooks, you should be familiar with [CloudFormation templates](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-guide.html), and either Python or Java. 

 

**To install the CloudFormation CLI, and the associated plugins:**

1. Install the the CloudFormation CLI with `pip`, the Python package manager.

   ```
   pip3 install cloudformation-cli
   ```

1. Install either the Python or Java plugin for the CloudFormation CLI.

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

   ```
   pip3 install cloudformation-cli-python-plugin
   ```

------
#### [ Java ]

   ```
   pip3 install cloudformation-cli-java-plugin
   ```

------

To upgrade the CloudFormation CLI and the plugin, you can use the upgrade option.

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

```
pip3 install --upgrade cloudformation-cli cloudformation-cli-python-plugin
```

------
#### [ Java ]

```
pip3 install --upgrade cloudformation-cli cloudformation-cli-java-plugin
```

------

# Initiating a custom CloudFormation Hooks project
<a name="hooks-init"></a>

The first step in creating your custom Hooks project is to initiate the project. You can use the CloudFormation CLI `init` command to initiate your custom Hooks project.

The `init` command launches a wizard that walks you through setting up the project, including a Hooks schema file. Use this schema file as a starting point for defining the shape and semantics of your Hooks. For more information, see [Schema syntax](hooks-schema.md).

**To inititate a Hook project:**

1. Create a directory for the project.

   ```
   mkdir ~/mycompany-testing-mytesthook
   ```

1. Navigate to the new directory.

   ```
   cd ~/mycompany-testing-mytesthook
   ```

1. Use the CloudFormation CLI `init` command to initiate the project.

   ```
   cfn init
   ```

   The command returns the following output.

   ```
   Initializing new project
   ```

1. The `init` command launches a wizard that walks you through setting up the project. When prompted, enter `h` to specify a Hooks project.

   ```
   Do you want to develop a new resource(r) a module(m) or a hook(h)?
   ```

   ```
   h
   ```

1. Enter a a name for your Hook type.

   ```
   What's the name of your hook type?
   (Organization::Service::Hook)
   ```

   ```
   MyCompany::Testing::MyTestHook
   ```

1. If only one language plugin is installed, it is selected by default. If more than one language plugin is installed, you can choose your desired language. Enter a number selection for the language of your choice.

   ```
   Select a language for code generation:
   [1] java
   [2] python38
   [3] python39
   (enter an integer):
   ```

1. Set up packaging based on chosen development lanaguage.

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

   (*Optional*) Choose Docker for platform-independent packaging. While Docker isn't required, it's highly recommended to make packaging easier.

   ```
   Use docker for platform-independent packaging (Y/n)?
   This is highly recommended unless you are experienced with cross-platform Python packaging.
   ```

------
#### [ Java ]

   Set Java package name and choose a codegen model. You can use the default package name, or create a new one.

   ```
   Enter a package name (empty for default 'com.mycompany.testing.mytesthook'):
   ```

   ```
   Choose codegen model - 1 (default) or 2 (guided-aws):
   ```

------

*Results*: You have successfully initiated the project and have generated the files needed to develop a Hook. The following is an example of the directories and files that make up a Hooks project for Python 3.8.

```
mycompany-testing-mytesthook.json
rpdk.log
README.md
requirements.txt
hook-role.yaml
template.yml
docs
    README.md
src
    __init__.py
    handlers.py
    models.py
    target_models
        aws_s3_bucket.py
```

**Note**  
The files in the `src` directory are created based on your language selection. There are some useful comments and examples in the generated files. Some files, such as `models.py`, are automatically updated in a later step when you run the `generate` command to add runtime code for your handlers.

# Modeling custom CloudFormation Hooks
<a name="hooks-model"></a>

Modeling custom CloudFormation Hooks involves creating a schema that defines the Hook, its properties, and their attributes. When you create your custom Hook project using the `cfn init` command, an example Hook schema is created as a JSON-formatted text file, `hook-name.json`.

Target invocation points and target actions specify the exact point where the Hook is invoked. *Hook handlers* host executable custom logic for these points. For example, a target action of the `CREATE` operation uses a `preCreate` handler. Your code written in the handler will invoke when Hook targets and services perform a matching action. *Hook targets* are the destination where hooks are invoked. You can specify targets such as, CloudFormation public resources, private resources, or custom resources. Hooks support an unlimited number of Hook targets.

The schema contains permissions required for the Hook. Authoring the Hook requires you to specify permissions for each Hook handler. CloudFormation encourages authors to write policies that follow the standard security advice of granting *least privilege*, or granting only the permissions required to perform a task. Determine what users (and roles) need to do, and then craft policies that allow them to perform *only* those tasks for Hook operations. CloudFormation uses these permissions to scope-down Hook users provided permissions. These permissions are passed down to the Hook. Hook handlers use these permissions to access AWS resources.

You can use the following schema file as a starting point to define your Hook. Use the Hook schema to specify which handlers you want to implement. If you choose not to implement a specific handler, remove it from the handlers' section of the Hook schema. For more details on the schema, see [Schema syntax](hooks-schema.md).

```
{
    "typeName":"MyCompany::Testing::MyTestHook",
    "description":"Verifies S3 bucket and SQS queues properties before create and update",
    "sourceUrl":"https://mycorp.com/my-repo.git",
    "documentationUrl":"https://mycorp.com/documentation",
    "typeConfiguration":{
        "properties":{
            "minBuckets":{
                "description":"Minimum number of compliant buckets",
                "type":"string"
            },
            "minQueues":{
                "description":"Minimum number of compliant queues",
                "type":"string"
            },
            "encryptionAlgorithm":{
                "description":"Encryption algorithm for SSE",
                "default":"AES256",
                "type":"string"
            }
        },
        "required":[
            
        ],
        "additionalProperties":false
    },
    "handlers":{
        "preCreate":{
            "targetNames":[
                "AWS::S3::Bucket",
                "AWS::SQS::Queue"
            ],
            "permissions":[
                
            ]
        },
        "preUpdate":{
            "targetNames":[
                "AWS::S3::Bucket",
                "AWS::SQS::Queue"
            ],
            "permissions":[
                
            ]
        },
        "preDelete":{
            "targetNames":[
                "AWS::S3::Bucket",
                "AWS::SQS::Queue"
            ],
            "permissions":[
                "s3:ListBucket",
                "s3:ListAllMyBuckets",
                "s3:GetEncryptionConfiguration",
                "sqs:ListQueues",
                "sqs:GetQueueAttributes",
                "sqs:GetQueueUrl"
            ]
        }
    },
    "additionalProperties":false
}
```

**Topics**
+ [Modeling custom CloudFormation Hooks using Java](hooks-model-java.md)
+ [Modeling custom CloudFormation Hooks using Python](hooks-model-python.md)

# Modeling custom CloudFormation Hooks using Java
<a name="hooks-model-java"></a>

Modeling custom CloudFormation Hooks involves creating a schema that defines the Hook, its properties, and their attributes. This tutorial walks you through modeling custom Hooks using Java.

## Step 1: Add project dependencies
<a name="model-hook-project-dependencies"></a>

Java based Hooks projects rely on Maven's `pom.xml` file as a dependency. Expand the following section and copy the source code into the `pom.xml` file in the root of the project.

### Hook project dependencies (pom.xml)
<a name="hook-java-dependencies"></a>

```
<?xml version="1.0" encoding="UTF-8"?>
<project
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mycompany.testing.mytesthook</groupId>
    <artifactId>mycompany-testing-mytesthook-handler</artifactId>
    <name>mycompany-testing-mytesthook-handler</name>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <aws.java.sdk.version>2.16.1</aws.java.sdk.version>
        <checkstyle.version>8.36.2</checkstyle.version>
        <commons-io.version>2.8.0</commons-io.version>
        <jackson.version>2.11.3</jackson.version>
        <maven-checkstyle-plugin.version>3.1.1</maven-checkstyle-plugin.version>
        <mockito.version>3.6.0</mockito.version>
        <spotbugs.version>4.1.4</spotbugs.version>
        <spotless.version>2.5.0</spotless.version>
        <maven-javadoc-plugin.version>3.2.0</maven-javadoc-plugin.version>
        <maven-source-plugin.version>3.2.1</maven-source-plugin.version>
        <cfn.generate.args/>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>software.amazon.awssdk</groupId>
                <artifactId>bom</artifactId>
                <version>2.16.1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/software.amazon.cloudformation/aws-cloudformation-rpdk-java-plugin -->
        <dependency>
            <groupId>software.amazon.cloudformation</groupId>
            <artifactId>aws-cloudformation-rpdk-java-plugin</artifactId>
            <version>[2.0.0,3.0.0)</version>
        </dependency>

        <!-- AWS Java SDK v2 Dependencies -->
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>sdk-core</artifactId>
        </dependency>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>cloudformation</artifactId>
        </dependency>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>s3</artifactId>
        </dependency>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>utils</artifactId>
        </dependency>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>apache-client</artifactId>
        </dependency>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>sqs</artifactId>
        </dependency>

        <!-- Test dependency for Java Providers -->
        <dependency>
            <groupId>software.amazon.cloudformation</groupId>
            <artifactId>cloudformation-cli-java-plugin-testing-support</artifactId>
            <version>1.0.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-s3 -->
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-s3</artifactId>
            <version>1.12.85</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>${commons-io.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.4</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>29.0-jre</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-cloudformation -->
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-cloudformation</artifactId>
            <version>1.11.555</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.14</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/software.amazon.cloudformation/aws-cloudformation-resource-schema -->
        <dependency>
            <groupId>software.amazon.cloudformation</groupId>
            <artifactId>aws-cloudformation-resource-schema</artifactId>
            <version>[2.0.5, 3.0.0)</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-cbor -->
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-cbor</artifactId>
            <version>${jackson.version}</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>${jackson.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.module/jackson-modules-java8 -->
        <dependency>
            <groupId>com.fasterxml.jackson.module</groupId>
            <artifactId>jackson-modules-java8</artifactId>
            <version>${jackson.version}</version>
            <type>pom</type>
            <scope>runtime</scope>
        </dependency>

         <!-- https://mvnrepository.com/artifact/org.json/json -->
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20180813</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-core -->
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-core</artifactId>
            <version>1.11.1034</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.amazonaws/aws-lambda-java-core -->
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-core</artifactId>
            <version>1.2.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.amazonaws/aws-lambda-java-log4j2 -->
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-log4j2</artifactId>
            <version>1.2.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.8</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
            <scope>provided</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.17.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.17.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.17.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.assertj/assertj-core -->
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.12.2</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.5.0-M1</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>3.6.0</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter -->
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-junit-jupiter</artifactId>
            <version>3.6.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <compilerArgs>
                        <arg>-Xlint:all,-options,-processing</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <createDependencyReducedPom>false</createDependencyReducedPom>
                    <filters>
                        <filter>
                            <artifact>*:*</artifact>
                            <excludes>
                                <exclude>**/Log4j2Plugins.dat</exclude>
                            </excludes>
                        </filter>
                    </filters>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.6.0</version>
                <executions>
                    <execution>
                        <id>generate</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                        <configuration>
                            <executable>cfn</executable>
                            <commandlineArgs>generate ${cfn.generate.args}</commandlineArgs>
                            <workingDirectory>${project.basedir}</workingDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>build-helper-maven-plugin</artifactId>
                <version>3.0.0</version>
                <executions>
                    <execution>
                        <id>add-source</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>add-source</goal>
                        </goals>
                        <configuration>
                            <sources>
                                <source>${project.basedir}/target/generated-sources/rpdk</source>
                            </sources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.4</version>
            </plugin>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M3</version>
            </plugin>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.8.4</version>
                <configuration>
                    <excludes>
                        <exclude>**/BaseHookConfiguration*</exclude>
                        <exclude>**/BaseHookHandler*</exclude>
                        <exclude>**/HookHandlerWrapper*</exclude>
                        <exclude>**/ResourceModel*</exclude>
                        <exclude>**/TypeConfigurationModel*</exclude>
                        <exclude>**/model/**/*</exclude>
                    </excludes>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>report</id>
                        <phase>test</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>jacoco-check</id>
                        <goals>
                            <goal>check</goal>
                        </goals>
                        <configuration>
                            <rules>
                                <rule>
                                    <element>PACKAGE</element>
                                    <limits>
                                        <limit>
                                            <counter>BRANCH</counter>
                                            <value>COVEREDRATIO</value>
                                            <minimum>0.8</minimum>
                                        </limit>
                                        <limit>
                                            <counter>INSTRUCTION</counter>
                                            <value>COVEREDRATIO</value>
                                            <minimum>0.8</minimum>
                                        </limit>
                                    </limits>
                                </rule>
                            </rules>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
        <resources>
            <resource>
                <directory>${project.basedir}</directory>
                <includes>
                    <include>mycompany-testing-mytesthook.json</include>
                </includes>
            </resource>
            <resource>
                <directory>${project.basedir}/target/loaded-target-schemas</directory>
                <includes>
                    <include>**/*.json</include>
                </includes>
            </resource>
        </resources>
    </build>
</project>
```

## Step 2: Generate the Hook project package
<a name="model-hook-project-package-java"></a>

Generate your Hook project package. The CloudFormation CLI creates empty handler functions that correspond to specific Hook actions in the target lifecycle as defined in the Hook specification.

```
cfn generate
```

The command returns the following output.

```
Generated files for MyCompany::Testing::MyTestHook
```

**Note**  
Make sure your Lambda runtimes are up-to-date to avoid using a deprecated version. For more information, see [Updating Lambda runtimes for resource types and Hooks](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/runtime-update.html).

## Step 3: Add Hook handlers
<a name="model-hook-project-add-handler-java"></a>

Add your own Hook handler runtime code to the handlers that you choose to implement. For example, you can add the following code for logging.

```
logger.log("Internal testing Hook triggered for target: " + request.getHookContext().getTargetName());
```

The CloudFormation CLI generates a Plain Old Java Objects (Java POJO). The following are output examples generated from `AWS::S3::Bucket`.

**Example AwsS3BucketTargetModel.java**  

```
package com.mycompany.testing.mytesthook.model.aws.s3.bucket;

import...


@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
public class AwsS3BucketTargetModel extends ResourceHookTargetModel<AwsS3Bucket> {

    @JsonIgnore
    private static final TypeReference<AwsS3Bucket> TARGET_REFERENCE =
        new TypeReference<AwsS3Bucket>() {};

    @JsonIgnore
    private static final TypeReference<AwsS3BucketTargetModel> MODEL_REFERENCE =
        new TypeReference<AwsS3BucketTargetModel>() {};

    @JsonIgnore
    public static final String TARGET_TYPE_NAME = "AWS::S3::Bucket";


    @JsonIgnore
    public TypeReference<AwsS3Bucket> getHookTargetTypeReference() {
        return TARGET_REFERENCE;
    }

    @JsonIgnore
    public TypeReference<AwsS3BucketTargetModel> getTargetModelTypeReference() {
        return MODEL_REFERENCE;
    }
}
```

**Example AwsS3Bucket.java**  

```
package com.mycompany.testing.mytesthook.model.aws.s3.bucket;

import ...


@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
public class AwsS3Bucket extends ResourceHookTarget {
    @JsonIgnore
    public static final String TYPE_NAME = "AWS::S3::Bucket";

    @JsonIgnore
    public static final String IDENTIFIER_KEY_ID = "/properties/Id";

    @JsonProperty("InventoryConfigurations")
    private List<InventoryConfiguration> inventoryConfigurations;

    @JsonProperty("WebsiteConfiguration")
    private WebsiteConfiguration websiteConfiguration;

    @JsonProperty("DualStackDomainName")
    private String dualStackDomainName;

    @JsonProperty("AccessControl")
    private String accessControl;

    @JsonProperty("AnalyticsConfigurations")
    private List<AnalyticsConfiguration> analyticsConfigurations;

    @JsonProperty("AccelerateConfiguration")
    private AccelerateConfiguration accelerateConfiguration;

    @JsonProperty("PublicAccessBlockConfiguration")
    private PublicAccessBlockConfiguration publicAccessBlockConfiguration;

    @JsonProperty("BucketName")
    private String bucketName;

    @JsonProperty("RegionalDomainName")
    private String regionalDomainName;

    @JsonProperty("OwnershipControls")
    private OwnershipControls ownershipControls;

    @JsonProperty("ObjectLockConfiguration")
    private ObjectLockConfiguration objectLockConfiguration;

    @JsonProperty("ObjectLockEnabled")
    private Boolean objectLockEnabled;

    @JsonProperty("LoggingConfiguration")
    private LoggingConfiguration loggingConfiguration;

    @JsonProperty("ReplicationConfiguration")
    private ReplicationConfiguration replicationConfiguration;

    @JsonProperty("Tags")
    private List<Tag> tags;

    @JsonProperty("DomainName")
    private String domainName;

    @JsonProperty("BucketEncryption")
    private BucketEncryption bucketEncryption;

    @JsonProperty("WebsiteURL")
    private String websiteURL;

    @JsonProperty("NotificationConfiguration")
    private NotificationConfiguration notificationConfiguration;

    @JsonProperty("LifecycleConfiguration")
    private LifecycleConfiguration lifecycleConfiguration;

    @JsonProperty("VersioningConfiguration")
    private VersioningConfiguration versioningConfiguration;

    @JsonProperty("MetricsConfigurations")
    private List<MetricsConfiguration> metricsConfigurations;

    @JsonProperty("IntelligentTieringConfigurations")
    private List<IntelligentTieringConfiguration> intelligentTieringConfigurations;

    @JsonProperty("CorsConfiguration")
    private CorsConfiguration corsConfiguration;

    @JsonProperty("Id")
    private String id;

    @JsonProperty("Arn")
    private String arn;

    @JsonIgnore
    public JSONObject getPrimaryIdentifier() {
        final JSONObject identifier = new JSONObject();
        if (this.getId() != null) {
            identifier.put(IDENTIFIER_KEY_ID, this.getId());
        }

        // only return the identifier if it can be used, i.e. if all components are present
        return identifier.length() == 1 ? identifier : null;
    }

    @JsonIgnore
    public List<JSONObject> getAdditionalIdentifiers() {
        final List<JSONObject> identifiers = new ArrayList<JSONObject>();
        // only return the identifiers if any can be used
        return identifiers.isEmpty() ? null : identifiers;
    }
}
```

**Example BucketEncryption.java**  

```
package software.amazon.testing.mytesthook.model.aws.s3.bucket;

import ...


@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
public class BucketEncryption {
    @JsonProperty("ServerSideEncryptionConfiguration")
    private List<ServerSideEncryptionRule> serverSideEncryptionConfiguration;

}
```

**Example ServerSideEncryptionRule.java**  

```
package com.mycompany.testing.mytesthook.model.aws.s3.bucket;

import ...


@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
public class ServerSideEncryptionRule {
    @JsonProperty("BucketKeyEnabled")
    private Boolean bucketKeyEnabled;

    @JsonProperty("ServerSideEncryptionByDefault")
    private ServerSideEncryptionByDefault serverSideEncryptionByDefault;

}
```

**Example ServerSideEncryptionByDefault.java**  

```
package com.mycompany.testing.mytesthook.model.aws.s3.bucket;

import ...


@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
public class ServerSideEncryptionByDefault {
    @JsonProperty("SSEAlgorithm")
    private String sSEAlgorithm;

    @JsonProperty("KMSMasterKeyID")
    private String kMSMasterKeyID;

}
```

With the POJOs generated, you can now write the handlers that actually implement the Hook’s functionality. For this example, implement the `preCreate` and `preUpdate` invocation point for the handlers.

## Step 4: Implement Hook handlers
<a name="model-hook-project-code-handler-java"></a>

**Topics**
+ [Coding the API client builder](#java-code-api-client-builder)
+ [Coding the API request maker](#code-the-api-request-maker)
+ [Implementing the helper code](#implement-helper-code)
+ [Implementing the base handler](#implement-base-hook-handler)
+ [Implementing the `preCreate` handler](#implementing-precreate-handler)
+ [Coding the `preCreate` handler](#coding-the-precreate-handler)
+ [Updating the `preCreate` test](#updating-the-precreate-handler)
+ [Implementing the `preUpdate` handler](#implementing-preupdate-handler)
+ [Coding the `preUpdate` handler](#coding-preupdate-handler)
+ [Updating the `preUpdate` test](#update-the-preupdate-handler)
+ [Implementing the `preDelete` handler](#implement-the-predelete-hander)
+ [Coding the `preDelete` handler](#code-the-predelete-handler)
+ [Updating the `preDelete` handler](#update-the-predelete-handler)

### Coding the API client builder
<a name="java-code-api-client-builder"></a>

1. In your IDE, open the `ClientBuilder.java` file, located in the `src/main/java/com/mycompany/testing/mytesthook` folder.

1. Replace the entire contents of the `ClientBuilder.java` file with the following code.  
**Example ClientBuilder.java**  

   ```
   package com.awscommunity.kms.encryptionsettings;
   
   import software.amazon.awssdk.services.ec2.Ec2Client;
   import software.amazon.cloudformation.HookLambdaWrapper;
   
   /**
    * Describes static HTTP clients (to consume less memory) for API calls that
    * this hook makes to a number of AWS services.
    */
   public final class ClientBuilder {
   
       private ClientBuilder() {
       }
   
       /**
        * Create an HTTP client for Amazon EC2.
        *
        * @return Ec2Client An {@link Ec2Client} object.
        */
       public static Ec2Client getEc2Client() {
           return Ec2Client.builder().httpClient(HookLambdaWrapper.HTTP_CLIENT).build();
       }
   }
   ```

### Coding the API request maker
<a name="code-the-api-request-maker"></a>

1. In your IDE, open the `Translator.java` file, located in the `src/main/java/com/mycompany/testing/mytesthook` folder.

1. Replace the entire contents of the `Translator.java` file with the following code.  
**Example Translator.java**  

   ```
   package com.mycompany.testing.mytesthook;
   
   
   import software.amazon.awssdk.services.s3.model.GetBucketEncryptionRequest;
   import software.amazon.awssdk.services.s3.model.ListBucketsRequest;
   import software.amazon.awssdk.services.sqs.model.ListQueuesRequest;
   import software.amazon.cloudformation.proxy.hook.targetmodel.HookTargetModel;
   
   /**
    * This class is a centralized placeholder for
    *  - api request construction
    *  - object translation to/from aws sdk
    */
   
   public class Translator {
   
       static ListBucketsRequest translateToListBucketsRequest(final HookTargetModel targetModel) {
           return ListBucketsRequest.builder().build();
       }
   
       static ListQueuesRequest translateToListQueuesRequest(final String nextToken) {
           return ListQueuesRequest.builder().nextToken(nextToken).build();
       }
   
       static ListBucketsRequest createListBucketsRequest() {
           return ListBucketsRequest.builder().build();
       }
   
       static ListQueuesRequest createListQueuesRequest() {
           return createListQueuesRequest(null);
       }
   
       static ListQueuesRequest createListQueuesRequest(final String nextToken) {
           return ListQueuesRequest.builder().nextToken(nextToken).build();
       }
   
       static GetBucketEncryptionRequest createGetBucketEncryptionRequest(final String bucket) {
           return GetBucketEncryptionRequest.builder().bucket(bucket).build();
       }
   }
   ```

### Implementing the helper code
<a name="implement-helper-code"></a>

1. In your IDE, open the `AbstractTestBase.java` file, located in the `src/main/java/com/mycompany/testing/mytesthook` folder.

1. Replace the entire contents of the `AbstractTestBase.java` file with the following code.  
**Example Translator.java**  

   ```
   package com.mycompany.testing.mytesthook;
   
   import com.google.common.collect.ImmutableMap;
   import org.mockito.Mockito;
   import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
   import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
   import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
   import software.amazon.awssdk.awscore.AwsRequest;
   import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration;
   import software.amazon.awssdk.awscore.AwsResponse;
   import software.amazon.awssdk.core.SdkClient;
   import software.amazon.awssdk.core.pagination.sync.SdkIterable;
   import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
   import software.amazon.cloudformation.proxy.Credentials;
   import software.amazon.cloudformation.proxy.LoggerProxy;
   import software.amazon.cloudformation.proxy.OperationStatus;
   import software.amazon.cloudformation.proxy.ProgressEvent;
   import software.amazon.cloudformation.proxy.ProxyClient;
   import software.amazon.cloudformation.proxy.hook.targetmodel.HookTargetModel;
   
   import javax.annotation.Nonnull;
   import java.time.Duration;
   import java.util.concurrent.CompletableFuture;
   import java.util.function.Function;
   import java.util.function.Supplier;
   
   import static org.assertj.core.api.Assertions.assertThat;
   
   @lombok.Getter
   public class AbstractTestBase {
       protected final AwsSessionCredentials awsSessionCredential;
       protected final AwsCredentialsProvider v2CredentialsProvider;
       protected final AwsRequestOverrideConfiguration configuration;
       protected final LoggerProxy loggerProxy;
       protected final Supplier<Long> awsLambdaRuntime = () -> Duration.ofMinutes(15).toMillis();
       protected final AmazonWebServicesClientProxy proxy;
       protected final Credentials mockCredentials =
           new Credentials("mockAccessId", "mockSecretKey", "mockSessionToken");
   
       @lombok.Setter
       private SdkClient serviceClient;
   
       protected AbstractTestBase() {
           loggerProxy = Mockito.mock(LoggerProxy.class);
           awsSessionCredential = AwsSessionCredentials.create(mockCredentials.getAccessKeyId(),
               mockCredentials.getSecretAccessKey(), mockCredentials.getSessionToken());
           v2CredentialsProvider = StaticCredentialsProvider.create(awsSessionCredential);
           configuration = AwsRequestOverrideConfiguration.builder()
               .credentialsProvider(v2CredentialsProvider)
               .build();
           proxy = new AmazonWebServicesClientProxy(
               loggerProxy,
               mockCredentials,
               awsLambdaRuntime
           ) {
               @Override
               public <ClientT> ProxyClient<ClientT> newProxy(@Nonnull Supplier<ClientT> client) {
                   return new ProxyClient<ClientT>() {
                       @Override
                       public <RequestT extends AwsRequest, ResponseT extends AwsResponse>
                           ResponseT injectCredentialsAndInvokeV2(RequestT request,
                                                                  Function<RequestT, ResponseT> requestFunction) {
                           return proxy.injectCredentialsAndInvokeV2(request, requestFunction);
                       }
   
                       @Override
                       public <RequestT extends AwsRequest, ResponseT extends AwsResponse> CompletableFuture<ResponseT>
                           injectCredentialsAndInvokeV2Async(RequestT request, Function<RequestT, CompletableFuture<ResponseT>> requestFunction) {
                           return proxy.injectCredentialsAndInvokeV2Async(request, requestFunction);
                       }
   
                       @Override
                       public <RequestT extends AwsRequest, ResponseT extends AwsResponse, IterableT extends SdkIterable<ResponseT>>
                           IterableT
                           injectCredentialsAndInvokeIterableV2(RequestT request, Function<RequestT, IterableT> requestFunction) {
                           return proxy.injectCredentialsAndInvokeIterableV2(request, requestFunction);
                       }
   
                       @SuppressWarnings("unchecked")
                       @Override
                       public ClientT client() {
                           return (ClientT) serviceClient;
                       }
                   };
               }
           };
       }
   
       protected void assertResponse(final ProgressEvent<HookTargetModel, CallbackContext> response, final OperationStatus expectedStatus, final String expectedMsg) {
           assertThat(response).isNotNull();
           assertThat(response.getStatus()).isEqualTo(expectedStatus);
           assertThat(response.getCallbackContext()).isNull();
           assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
           assertThat(response.getMessage()).isNotNull();
           assertThat(response.getMessage()).isEqualTo(expectedMsg);
       }
   
       protected HookTargetModel createHookTargetModel(final Object resourceProperties) {
           return HookTargetModel.of(ImmutableMap.of("ResourceProperties", resourceProperties));
       }
       
       protected HookTargetModel createHookTargetModel(final Object resourceProperties, final Object previousResourceProperties) {
           return HookTargetModel.of(
                   ImmutableMap.of(
                       "ResourceProperties", resourceProperties,
                       "PreviousResourceProperties", previousResourceProperties
                   )
           );
       }
   }
   ```

### Implementing the base handler
<a name="implement-base-hook-handler"></a>

1. In your IDE, open the `BaseHookHandlerStd.java` file, located in the `src/main/java/com/mycompany/testing/mytesthook` folder.

1. Replace the entire contents of the `BaseHookHandlerStd.java` file with the following code.  
**Example Translator.java**  

   ```
   package com.mycompany.testing.mytesthook;
   
   import com.mycompany.testing.mytesthook.model.aws.s3.bucket.AwsS3Bucket;
   import com.mycompany.testing.mytesthook.model.aws.sqs.queue.AwsSqsQueue;
   import software.amazon.awssdk.services.s3.S3Client;
   import software.amazon.awssdk.services.sqs.SqsClient;
   import software.amazon.cloudformation.exceptions.UnsupportedTargetException;
   import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
   import software.amazon.cloudformation.proxy.Logger;
   import software.amazon.cloudformation.proxy.ProgressEvent;
   import software.amazon.cloudformation.proxy.ProxyClient;
   import software.amazon.cloudformation.proxy.hook.HookHandlerRequest;
   import software.amazon.cloudformation.proxy.hook.targetmodel.HookTargetModel;
   
   
   public abstract class BaseHookHandlerStd extends BaseHookHandler<CallbackContext, TypeConfigurationModel> {
       public static final String HOOK_TYPE_NAME = "MyCompany::Testing::MyTestHook";
   
       protected Logger logger;
   
       @Override
       public ProgressEvent<HookTargetModel, CallbackContext> handleRequest(
               final AmazonWebServicesClientProxy proxy,
               final HookHandlerRequest request,
               final CallbackContext callbackContext,
               final Logger logger,
               final TypeConfigurationModel typeConfiguration
       ) {
           this.logger = logger;
   
           final String targetName = request.getHookContext().getTargetName();
   
           final ProgressEvent<HookTargetModel, CallbackContext> result;
           if (AwsS3Bucket.TYPE_NAME.equals(targetName)) {
               result = handleS3BucketRequest(
                       proxy,
                       request,
                       callbackContext != null ? callbackContext : new CallbackContext(),
                       proxy.newProxy(ClientBuilder::createS3Client),
                       typeConfiguration
               );
           } else if (AwsSqsQueue.TYPE_NAME.equals(targetName)) {
               result = handleSqsQueueRequest(
                       proxy,
                       request,
                       callbackContext != null ? callbackContext : new CallbackContext(),
                       proxy.newProxy(ClientBuilder::createSqsClient),
                       typeConfiguration
               );
           } else {
               throw new UnsupportedTargetException(targetName);
           }
   
           log(
               String.format(
                   "Result for [%s] invocation for target [%s] returned status [%s] with message [%s]",
                   request.getHookContext().getInvocationPoint(),
                   targetName,
                   result.getStatus(),
                   result.getMessage()
               )
           );
   
           return result;
       }
   
       protected abstract ProgressEvent<HookTargetModel, CallbackContext> handleS3BucketRequest(
               final AmazonWebServicesClientProxy proxy,
               final HookHandlerRequest request,
               final CallbackContext callbackContext,
               final ProxyClient<S3Client> proxyClient,
               final TypeConfigurationModel typeConfiguration
       );
   
       protected abstract ProgressEvent<HookTargetModel, CallbackContext> handleSqsQueueRequest(
               final AmazonWebServicesClientProxy proxy,
               final HookHandlerRequest request,
               final CallbackContext callbackContext,
               final ProxyClient<SqsClient> proxyClient,
               final TypeConfigurationModel typeConfiguration
       );
   
       protected void log(final String message) {
           if (logger != null) {
               logger.log(message);
           } else {
               System.out.println(message);
           }
       }
   }
   ```

### Implementing the `preCreate` handler
<a name="implementing-precreate-handler"></a>

The `preCreate` handler verifies the server-side encryption settings for either an `AWS::S3::Bucket` or `AWS::SQS::Queue` resource.
+ For an `AWS::S3::Bucket` resource, the Hook will only pass if the following is true:
  + The Amazon S3 bucket encryption is set.
  + The Amazon S3 bucket key is enabled for the bucket.
  + The encryption algorithm set for the Amazon S3 bucket is the correct algorithm required.
  + The AWS Key Management Service key ID is set.
+ For an `AWS::SQS::Queue` resource, the Hook will only pass if the following is true:
  + The AWS Key Management Service key ID is set.

### Coding the `preCreate` handler
<a name="coding-the-precreate-handler"></a>

1. In your IDE, open the `PreCreateHookHandler.java` file, located in the `src/main/java/software/mycompany/testing/mytesthook` folder.

1. Replace the entire contents of the `PreCreateHookHandler.java` file with the following code.

   ```
   package com.mycompany.testing.mytesthook;
   
   import com.mycompany.testing.mytesthook.model.aws.s3.bucket.AwsS3Bucket;
   import com.mycompany.testing.mytesthook.model.aws.s3.bucket.AwsS3BucketTargetModel;
   import com.mycompany.testing.mytesthook.model.aws.s3.bucket.BucketEncryption;
   import com.mycompany.testing.mytesthook.model.aws.s3.bucket.ServerSideEncryptionByDefault;
   import com.mycompany.testing.mytesthook.model.aws.s3.bucket.ServerSideEncryptionRule;
   import com.mycompany.testing.mytesthook.model.aws.sqs.queue.AwsSqsQueue;
   import com.mycompany.testing.mytesthook.model.aws.sqs.queue.AwsSqsQueueTargetModel;
   import org.apache.commons.collections.CollectionUtils;
   import org.apache.commons.lang3.StringUtils;
   import software.amazon.cloudformation.exceptions.UnsupportedTargetException;
   import software.amazon.cloudformation.proxy.HandlerErrorCode;
   import software.amazon.cloudformation.proxy.Logger;
   import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
   import software.amazon.cloudformation.proxy.hook.HookStatus;
   import software.amazon.cloudformation.proxy.hook.HookProgressEvent;
   import software.amazon.cloudformation.proxy.hook.HookHandlerRequest;
   import software.amazon.cloudformation.proxy.hook.targetmodel.ResourceHookTargetModel;
   
   import java.util.List;
   
   public class PreCreateHookHandler extends BaseHookHandler<TypeConfigurationModel, CallbackContext> {
   
       @Override
       public HookProgressEvent<CallbackContext> handleRequest(
           final AmazonWebServicesClientProxy proxy,
           final HookHandlerRequest request,
           final CallbackContext callbackContext,
           final Logger logger,
           final TypeConfigurationModel typeConfiguration) {
   
           final String targetName = request.getHookContext().getTargetName();
           if ("AWS::S3::Bucket".equals(targetName)) {
               final ResourceHookTargetModel<AwsS3Bucket> targetModel = request.getHookContext().getTargetModel(AwsS3BucketTargetModel.class);
   
               final AwsS3Bucket bucket = targetModel.getResourceProperties();
               final String encryptionAlgorithm = typeConfiguration.getEncryptionAlgorithm();
   
               return validateS3BucketEncryption(bucket, encryptionAlgorithm);
   
           } else if ("AWS::SQS::Queue".equals(targetName)) {
               final ResourceHookTargetModel<AwsSqsQueue> targetModel = request.getHookContext().getTargetModel(AwsSqsQueueTargetModel.class);
   
               final AwsSqsQueue queue = targetModel.getResourceProperties();
               return validateSQSQueueEncryption(queue);
           } else {
               throw new UnsupportedTargetException(targetName);
           }
       }
   
       private HookProgressEvent<CallbackContext> validateS3BucketEncryption(final AwsS3Bucket bucket, final String requiredEncryptionAlgorithm) {
           HookStatus resultStatus = null;
           String resultMessage = null;
   
           if (bucket != null) {
               final BucketEncryption bucketEncryption = bucket.getBucketEncryption();
               if (bucketEncryption != null) {
                   final List<ServerSideEncryptionRule> serverSideEncryptionRules = bucketEncryption.getServerSideEncryptionConfiguration();
                   if (CollectionUtils.isNotEmpty(serverSideEncryptionRules)) {
                       for (final ServerSideEncryptionRule rule : serverSideEncryptionRules) {
                           final Boolean bucketKeyEnabled = rule.getBucketKeyEnabled();
                           if (bucketKeyEnabled) {
                               final ServerSideEncryptionByDefault serverSideEncryptionByDefault = rule.getServerSideEncryptionByDefault();
   
                               final String encryptionAlgorithm = serverSideEncryptionByDefault.getSSEAlgorithm();
                               final String kmsKeyId = serverSideEncryptionByDefault.getKMSMasterKeyID(); // "KMSMasterKeyID" is name of the property for an AWS::S3::Bucket;
   
                               if (!StringUtils.equals(encryptionAlgorithm, requiredEncryptionAlgorithm) && StringUtils.isBlank(kmsKeyId)) {
                                   resultStatus = HookStatus.FAILED;
                                   resultMessage = "KMS Key ID not set and SSE Encryption Algorithm is incorrect for bucket with name: " + bucket.getBucketName();
                               } else if (!StringUtils.equals(encryptionAlgorithm, requiredEncryptionAlgorithm)) {
                                   resultStatus = HookStatus.FAILED;
                                   resultMessage = "SSE Encryption Algorithm is incorrect for bucket with name: " + bucket.getBucketName();
                               } else if (StringUtils.isBlank(kmsKeyId)) {
                                   resultStatus = HookStatus.FAILED;
                                   resultMessage = "KMS Key ID not set for bucket with name: " + bucket.getBucketName();
                               } else {
                                   resultStatus = HookStatus.SUCCESS;
                                   resultMessage = "Successfully invoked PreCreateHookHandler for target: AWS::S3::Bucket";
                               }
                           } else {
                               resultStatus = HookStatus.FAILED;
                               resultMessage = "Bucket key not enabled for bucket with name: " + bucket.getBucketName();
                           }
   
                           if (resultStatus == HookStatus.FAILED) {
                               break;
                           }
                       }
                   } else {
                       resultStatus = HookStatus.FAILED;
                       resultMessage = "No SSE Encryption configurations for bucket with name: " + bucket.getBucketName();
                   }
               } else {
                   resultStatus = HookStatus.FAILED;
                   resultMessage = "Bucket Encryption not enabled for bucket with name: " + bucket.getBucketName();
               }
           } else {
               resultStatus = HookStatus.FAILED;
               resultMessage = "Resource properties for S3 Bucket target model are empty";
           }
   
           return HookProgressEvent.<CallbackContext>builder()
                   .status(resultStatus)
                   .message(resultMessage)
                   .errorCode(resultStatus == HookStatus.FAILED ? HandlerErrorCode.ResourceConflict : null)
                   .build();
       }
   
       private HookProgressEvent<CallbackContext> validateSQSQueueEncryption(final AwsSqsQueue queue) {
           if (queue == null) {
               return HookProgressEvent.<CallbackContext>builder()
                       .status(HookStatus.FAILED)
                       .message("Resource properties for SQS Queue target model are empty")
                       .errorCode(HandlerErrorCode.ResourceConflict)
                       .build();
           }
   
           final String kmsKeyId = queue.getKmsMasterKeyId(); // "KmsMasterKeyId" is name of the property for an AWS::SQS::Queue
           if (StringUtils.isBlank(kmsKeyId)) {
               return HookProgressEvent.<CallbackContext>builder()
                       .status(HookStatus.FAILED)
                       .message("Server side encryption turned off for queue with name: " + queue.getQueueName())
                       .errorCode(HandlerErrorCode.ResourceConflict)
                       .build();
           }
   
           return HookProgressEvent.<CallbackContext>builder()
                       .status(HookStatus.SUCCESS)
                       .message("Successfully invoked PreCreateHookHandler for target: AWS::SQS::Queue")
                       .build();
       }
   }
   ```

### Updating the `preCreate` test
<a name="updating-the-precreate-handler"></a>

1. In your IDE, open the `PreCreateHandlerTest.java` file, located in the `src/test/java/software/mycompany/testing/mytesthook` folder.

1. Replace the entire contents of `PreCreateHandlerTest.java` file with the following code.

   ```
   package com.mycompany.testing.mytesthook;
   
   import com.google.common.collect.ImmutableMap;
   import com.mycompany.testing.mytesthook.model.aws.s3.bucket.AwsS3Bucket;
   import com.mycompany.testing.mytesthook.model.aws.s3.bucket.BucketEncryption;
   import com.mycompany.testing.mytesthook.model.aws.s3.bucket.ServerSideEncryptionByDefault;
   import com.mycompany.testing.mytesthook.model.aws.s3.bucket.ServerSideEncryptionRule;
   import com.mycompany.testing.mytesthook.model.aws.sqs.queue.AwsSqsQueue;
   import org.junit.jupiter.api.BeforeEach;
   import org.junit.jupiter.api.Test;
   import org.junit.jupiter.api.extension.ExtendWith;
   import org.mockito.Mock;
   import org.mockito.junit.jupiter.MockitoExtension;
   import software.amazon.cloudformation.exceptions.UnsupportedTargetException;
   import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
   import software.amazon.cloudformation.proxy.HandlerErrorCode;
   import software.amazon.cloudformation.proxy.Logger;
   import software.amazon.cloudformation.proxy.hook.HookContext;
   import software.amazon.cloudformation.proxy.hook.HookHandlerRequest;
   import software.amazon.cloudformation.proxy.hook.HookProgressEvent;
   import software.amazon.cloudformation.proxy.hook.HookStatus;
   import software.amazon.cloudformation.proxy.hook.targetmodel.HookTargetModel;
   
   import java.util.Collections;
   import java.util.Map;
   
   import static org.assertj.core.api.Assertions.assertThat;
   import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
   import static org.mockito.Mockito.mock;
   
   @ExtendWith(MockitoExtension.class)
   public class PreCreateHookHandlerTest {
   
       @Mock
       private AmazonWebServicesClientProxy proxy;
   
       @Mock
       private Logger logger;
   
       @BeforeEach
       public void setup() {
           proxy = mock(AmazonWebServicesClientProxy.class);
           logger = mock(Logger.class);
       }
       
       @Test
       public void handleRequest_awsSqsQueueSuccess() {
           final PreCreateHookHandler handler = new PreCreateHookHandler();
   
           final AwsSqsQueue queue = buildSqsQueue("MyQueue", "KmsKey");
           final HookTargetModel targetModel = createHookTargetModel(queue);
           final TypeConfigurationModel typeConfiguration = TypeConfigurationModel.builder().encryptionAlgorithm("AES256").build();
   
           final HookHandlerRequest request = HookHandlerRequest.builder()
               .hookContext(HookContext.builder().targetName("AWS::SQS::Queue").targetModel(targetModel).build())
               .build();
   
           final HookProgressEvent<CallbackContext> response = handler.handleRequest(proxy, request, null, logger, typeConfiguration);
           assertResponse(response, HookStatus.SUCCESS, "Successfully invoked PreCreateHookHandler for target: AWS::SQS::Queue");
       }
   
       @Test
       public void handleRequest_awsS3BucketSuccess() {
           final PreCreateHookHandler handler = new PreCreateHookHandler();
   
           final AwsS3Bucket bucket = buildAwsS3Bucket("amzn-s3-demo-bucket", true, "AES256", "KmsKey");
           final HookTargetModel targetModel = createHookTargetModel(bucket);
           final TypeConfigurationModel typeConfiguration = TypeConfigurationModel.builder().encryptionAlgorithm("AES256").build();
   
           final HookHandlerRequest request = HookHandlerRequest.builder()
               .hookContext(HookContext.builder().targetName("AWS::S3::Bucket").targetModel(targetModel).build())
               .build();
   
           final HookProgressEvent<CallbackContext> response = handler.handleRequest(proxy, request, null, logger, typeConfiguration);
           assertResponse(response, HookStatus.SUCCESS, "Successfully invoked PreCreateHookHandler for target: AWS::S3::Bucket");
       }
   
       @Test
       public void handleRequest_awsS3BucketFail_bucketKeyNotEnabled() {
           final PreCreateHookHandler handler = new PreCreateHookHandler();
   
           final AwsS3Bucket bucket = buildAwsS3Bucket("amzn-s3-demo-bucket", false, "AES256", "KmsKey");
           final HookTargetModel targetModel = createHookTargetModel(bucket);
           final TypeConfigurationModel typeConfiguration = TypeConfigurationModel.builder().encryptionAlgorithm("AES256").build();
   
           final HookHandlerRequest request = HookHandlerRequest.builder()
               .hookContext(HookContext.builder().targetName("AWS::S3::Bucket").targetModel(targetModel).build())
               .build();
   
           final HookProgressEvent<CallbackContext> response = handler.handleRequest(proxy, request, null, logger, typeConfiguration);
           assertResponse(response, HookStatus.FAILED, "Bucket key not enabled for bucket with name: amzn-s3-demo-bucket");
       }
   
       @Test
       public void handleRequest_awsS3BucketFail_incorrectSSEEncryptionAlgorithm() {
           final PreCreateHookHandler handler = new PreCreateHookHandler();
   
           final AwsS3Bucket bucket = buildAwsS3Bucket("amzn-s3-demo-bucket", true, "SHA512", "KmsKey");
           final HookTargetModel targetModel = createHookTargetModel(bucket);
          final TypeConfigurationModel typeConfiguration = TypeConfigurationModel.builder().encryptionAlgorithm("AES256").build();
   
           final HookHandlerRequest request = HookHandlerRequest.builder()
               .hookContext(HookContext.builder().targetName("AWS::S3::Bucket").targetModel(targetModel).build())
               .build();
   
           final HookProgressEvent<CallbackContext> response = handler.handleRequest(proxy, request, null, logger, typeConfiguration);
           assertResponse(response, HookStatus.FAILED, "SSE Encryption Algorithm is incorrect for bucket with name: amzn-s3-demo-bucket");
       }
   
       @Test
       public void handleRequest_awsS3BucketFail_kmsKeyIdNotSet() {
           final PreCreateHookHandler handler = new PreCreateHookHandler();
   
           final AwsS3Bucket bucket = buildAwsS3Bucket("amzn-s3-demo-bucket", true, "AES256", null);
           final HookTargetModel targetModel = createHookTargetModel(bucket);
           final TypeConfigurationModel typeConfiguration = TypeConfigurationModel.builder().encryptionAlgorithm("AES256").build();
   
           final HookHandlerRequest request = HookHandlerRequest.builder()
               .hookContext(HookContext.builder().targetName("AWS::S3::Bucket").targetModel(targetModel).build())
               .build();
   
           final HookProgressEvent<CallbackContext> response = handler.handleRequest(proxy, request, null, logger, typeConfiguration);
           assertResponse(response, HookStatus.FAILED, "KMS Key ID not set for bucket with name: amzn-s3-demo-bucket");
       }
   
       @Test
       public void handleRequest_awsSqsQueueFail_serverSideEncryptionOff() {
           final PreCreateHookHandler handler = new PreCreateHookHandler();
   
           final AwsSqsQueue queue = buildSqsQueue("MyQueue", null);
           final HookTargetModel targetModel = createHookTargetModel(queue);
           final TypeConfigurationModel typeConfiguration = TypeConfigurationModel.builder().encryptionAlgorithm("AES256").build();
   
           final HookHandlerRequest request = HookHandlerRequest.builder()
               .hookContext(HookContext.builder().targetName("AWS::SQS::Queue").targetModel(targetModel).build())
               .build();
   
           final HookProgressEvent<CallbackContext> response = handler.handleRequest(proxy, request, null, logger, typeConfiguration);
           assertResponse(response, HookStatus.FAILED, "Server side encryption turned off for queue with name: MyQueue");
       }
   
       @Test
       public void handleRequest_unsupportedTarget() {
           final PreCreateHookHandler handler = new PreCreateHookHandler();
   
           final Map<String, Object> unsupportedTarget = ImmutableMap.of("ResourceName", "MyUnsupportedTarget");
           final HookTargetModel targetModel = createHookTargetModel(unsupportedTarget);
           final TypeConfigurationModel typeConfiguration = TypeConfigurationModel.builder().encryptionAlgorithm("AES256").build();
   
           final HookHandlerRequest request = HookHandlerRequest.builder()
               .hookContext(HookContext.builder().targetName("AWS::Unsupported::Target").targetModel(targetModel).build())
               .build();
   
           assertThatExceptionOfType(UnsupportedTargetException.class)
                   .isThrownBy(() -> handler.handleRequest(proxy, request, null, logger, typeConfiguration))
                   .withMessageContaining("Unsupported target")
                   .withMessageContaining("AWS::Unsupported::Target")
                   .satisfies(e -> assertThat(e.getErrorCode()).isEqualTo(HandlerErrorCode.InvalidRequest));
       }
   
       private void assertResponse(final HookProgressEvent<CallbackContext> response, final HookStatus expectedStatus, final String expectedErrorMsg) {
           assertThat(response).isNotNull();
           assertThat(response.getStatus()).isEqualTo(expectedStatus);
           assertThat(response.getCallbackContext()).isNull();
           assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
           assertThat(response.getMessage()).isNotNull();
           assertThat(response.getMessage()).isEqualTo(expectedErrorMsg);
       }
   
       private HookTargetModel createHookTargetModel(final Object resourceProperties) {
           return HookTargetModel.of(ImmutableMap.of("ResourceProperties", resourceProperties));
       }
   
       @SuppressWarnings("SameParameterValue")
       private AwsSqsQueue buildSqsQueue(final String queueName, final String kmsKeyId) {
           return AwsSqsQueue.builder()
                   .queueName(queueName)
                   .kmsMasterKeyId(kmsKeyId) // "KmsMasterKeyId" is name of the property for an AWS::SQS::Queue
                   .build();
       }
   
       @SuppressWarnings("SameParameterValue")
       private AwsS3Bucket buildAwsS3Bucket(
               final String bucketName,
               final Boolean bucketKeyEnabled,
               final String sseAlgorithm,
               final String kmsKeyId
       ) {
           return AwsS3Bucket.builder()
                   .bucketName(bucketName)
                   .bucketEncryption(
                       BucketEncryption.builder()
                           .serverSideEncryptionConfiguration(
                               Collections.singletonList(
                                   ServerSideEncryptionRule.builder()
                                       .bucketKeyEnabled(bucketKeyEnabled)
                                       .serverSideEncryptionByDefault(
                                           ServerSideEncryptionByDefault.builder()
                                               .sSEAlgorithm(sseAlgorithm)
                                               .kMSMasterKeyID(kmsKeyId) // "KMSMasterKeyID" is name of the property for an AWS::S3::Bucket
                                               .build()
                                       ).build()
                               )
                           ).build()
                   ).build();
       }
   }
   ```

### Implementing the `preUpdate` handler
<a name="implementing-preupdate-handler"></a>

Implement a `preUpdate` handler, which initiates before the update operations for all specified targets in the handler. The `preUpdate` handler accomplishes the following:
+ For an `AWS::S3::Bucket` resource, the Hook will only pass if the following is true:
  + The bucket encryption algorithm for an Amazon S3 bucket hasn't been modified.

### Coding the `preUpdate` handler
<a name="coding-preupdate-handler"></a>

1. In your IDE, open the `PreUpdateHookHandler.java` file, located in the `src/main/java/software/mycompany/testing/mytesthook` folder.

1. Replace the entire contents of the `PreUpdateHookHandler.java` file with the following code.

   ```
   package com.mycompany.testing.mytesthook;
   
   import com.mycompany.testing.mytesthook.model.aws.s3.bucket.AwsS3Bucket;
   import com.mycompany.testing.mytesthook.model.aws.s3.bucket.AwsS3BucketTargetModel;
   import com.mycompany.testing.mytesthook.model.aws.s3.bucket.ServerSideEncryptionRule;
   import org.apache.commons.lang3.StringUtils;
   import software.amazon.cloudformation.exceptions.UnsupportedTargetException;
   import software.amazon.cloudformation.proxy.HandlerErrorCode;
   import software.amazon.cloudformation.proxy.Logger;
   import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
   import software.amazon.cloudformation.proxy.hook.HookStatus;
   import software.amazon.cloudformation.proxy.hook.HookProgressEvent;
   import software.amazon.cloudformation.proxy.hook.HookHandlerRequest;
   import software.amazon.cloudformation.proxy.hook.targetmodel.ResourceHookTargetModel;
   
   import java.util.List;
   
   public class PreUpdateHookHandler extends BaseHookHandler<TypeConfigurationModel, CallbackContext> {
   
       @Override
       public HookProgressEvent<CallbackContext> handleRequest(
           final AmazonWebServicesClientProxy proxy,
           final HookHandlerRequest request,
           final CallbackContext callbackContext,
           final Logger logger,
           final TypeConfigurationModel typeConfiguration) {
   
           final String targetName = request.getHookContext().getTargetName();
           if ("AWS::S3::Bucket".equals(targetName)) {
               final ResourceHookTargetModel<AwsS3Bucket> targetModel = request.getHookContext().getTargetModel(AwsS3BucketTargetModel.class);
   
               final AwsS3Bucket bucketProperties = targetModel.getResourceProperties();
               final AwsS3Bucket previousBucketProperties = targetModel.getPreviousResourceProperties();
   
               return validateBucketEncryptionRulesNotUpdated(bucketProperties, previousBucketProperties);
           } else {
               throw new UnsupportedTargetException(targetName);
           }
       }
   
       private HookProgressEvent<CallbackContext> validateBucketEncryptionRulesNotUpdated(final AwsS3Bucket resourceProperties, final AwsS3Bucket previousResourceProperties) {
           final List<ServerSideEncryptionRule> bucketEncryptionConfigs = resourceProperties.getBucketEncryption().getServerSideEncryptionConfiguration();
           final List<ServerSideEncryptionRule> previousBucketEncryptionConfigs = previousResourceProperties.getBucketEncryption().getServerSideEncryptionConfiguration();
   
           if (bucketEncryptionConfigs.size() != previousBucketEncryptionConfigs.size()) {
               return HookProgressEvent.<CallbackContext>builder()
                       .status(HookStatus.FAILED)
                       .errorCode(HandlerErrorCode.NotUpdatable)
                       .message(
                           String.format(
                               "Current number of bucket encryption configs does not match previous. Current has %d configs while previously there were %d configs",
                               bucketEncryptionConfigs.size(),
                               previousBucketEncryptionConfigs.size()
                           )
                       ).build();
           }
   
           for (int i = 0; i < bucketEncryptionConfigs.size(); ++i) {
               final String currentEncryptionAlgorithm = bucketEncryptionConfigs.get(i).getServerSideEncryptionByDefault().getSSEAlgorithm();
               final String previousEncryptionAlgorithm = previousBucketEncryptionConfigs.get(i).getServerSideEncryptionByDefault().getSSEAlgorithm();
   
               if (!StringUtils.equals(currentEncryptionAlgorithm, previousEncryptionAlgorithm)) {
                   return HookProgressEvent.<CallbackContext>builder()
                       .status(HookStatus.FAILED)
                       .errorCode(HandlerErrorCode.NotUpdatable)
                       .message(
                           String.format(
                               "Bucket Encryption algorithm can not be changed once set. The encryption algorithm was changed to '%s' from '%s'.",
                               currentEncryptionAlgorithm,
                               previousEncryptionAlgorithm
                           )
                       )
                       .build();
               }
           }
   
           return HookProgressEvent.<CallbackContext>builder()
                       .status(HookStatus.SUCCESS)
                       .message("Successfully invoked PreUpdateHookHandler for target: AWS::SQS::Queue")
                       .build();
       }
   }
   ```

### Updating the `preUpdate` test
<a name="update-the-preupdate-handler"></a>

1. In your IDE, open the `PreUpdateHandlerTest.java` file in the `src/main/java/com/mycompany/testing/mytesthook` folder.

1. Replace the entire contents of the `PreUpdateHandlerTest.java` file with the following code.

   ```
   package com.mycompany.testing.mytesthook;
   
   import com.google.common.collect.ImmutableMap;
   import com.mycompany.testing.mytesthook.model.aws.s3.bucket.AwsS3Bucket;
   import com.mycompany.testing.mytesthook.model.aws.s3.bucket.BucketEncryption;
   import com.mycompany.testing.mytesthook.model.aws.s3.bucket.ServerSideEncryptionByDefault;
   import com.mycompany.testing.mytesthook.model.aws.s3.bucket.ServerSideEncryptionRule;
   import org.junit.jupiter.api.BeforeEach;
   import org.junit.jupiter.api.Test;
   import org.junit.jupiter.api.extension.ExtendWith;
   import org.mockito.Mock;
   import org.mockito.junit.jupiter.MockitoExtension;
   import software.amazon.cloudformation.exceptions.UnsupportedTargetException;
   import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
   import software.amazon.cloudformation.proxy.HandlerErrorCode;
   import software.amazon.cloudformation.proxy.Logger;
   import software.amazon.cloudformation.proxy.hook.HookContext;
   import software.amazon.cloudformation.proxy.hook.HookHandlerRequest;
   import software.amazon.cloudformation.proxy.hook.HookProgressEvent;
   import software.amazon.cloudformation.proxy.hook.HookStatus;
   import software.amazon.cloudformation.proxy.hook.targetmodel.HookTargetModel;
   
   import java.util.Arrays;
   import java.util.stream.Stream;
   
   import static org.assertj.core.api.Assertions.assertThat;
   import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
   import static org.mockito.Mockito.mock;
   
   @ExtendWith(MockitoExtension.class)
   public class PreUpdateHookHandlerTest {
   
       @Mock
       private AmazonWebServicesClientProxy proxy;
   
       @Mock
       private Logger logger;
   
       @BeforeEach
       public void setup() {
           proxy = mock(AmazonWebServicesClientProxy.class);
           logger = mock(Logger.class);
       }
   
       @Test
       public void handleRequest_awsS3BucketSuccess() {
           final PreUpdateHookHandler handler = new PreUpdateHookHandler();
   
           final ServerSideEncryptionRule serverSideEncryptionRule = buildServerSideEncryptionRule("AES256");
           final AwsS3Bucket resourceProperties = buildAwsS3Bucket("amzn-s3-demo-bucket", serverSideEncryptionRule);
           final AwsS3Bucket previousResourceProperties = buildAwsS3Bucket("amzn-s3-demo-bucket", serverSideEncryptionRule);
           final HookTargetModel targetModel = createHookTargetModel(resourceProperties, previousResourceProperties);
           final TypeConfigurationModel typeConfiguration = TypeConfigurationModel.builder().encryptionAlgorithm("AES256").build();
   
           final HookHandlerRequest request = HookHandlerRequest.builder()
               .hookContext(HookContext.builder().targetName("AWS::S3::Bucket").targetModel(targetModel).build())
               .build();
   
           final HookProgressEvent<CallbackContext> response = handler.handleRequest(proxy, request, null, logger, typeConfiguration);
           assertResponse(response, HookStatus.SUCCESS, "Successfully invoked PreUpdateHookHandler for target: AWS::SQS::Queue");
       }
   
       @Test
       public void handleRequest_awsS3BucketFail_bucketEncryptionConfigsDontMatch() {
           final PreUpdateHookHandler handler = new PreUpdateHookHandler();
   
           final ServerSideEncryptionRule[] serverSideEncryptionRules = Stream.of("AES256", "SHA512", "AES32")
                   .map(this::buildServerSideEncryptionRule)
                   .toArray(ServerSideEncryptionRule[]::new);
   
           final AwsS3Bucket resourceProperties = buildAwsS3Bucket("amzn-s3-demo-bucket", serverSideEncryptionRules[0]);
           final AwsS3Bucket previousResourceProperties = buildAwsS3Bucket("amzn-s3-demo-bucket", serverSideEncryptionRules);
           final HookTargetModel targetModel = createHookTargetModel(resourceProperties, previousResourceProperties);
           final TypeConfigurationModel typeConfiguration = TypeConfigurationModel.builder().encryptionAlgorithm("AES256").build();
   
           final HookHandlerRequest request = HookHandlerRequest.builder()
               .hookContext(HookContext.builder().targetName("AWS::S3::Bucket").targetModel(targetModel).build())
               .build();
   
           final HookProgressEvent<CallbackContext> response = handler.handleRequest(proxy, request, null, logger, typeConfiguration);
           assertResponse(response, HookStatus.FAILED, "Current number of bucket encryption configs does not match previous. Current has 1 configs while previously there were 3 configs");
       }
   
       @Test
       public void handleRequest_awsS3BucketFail_bucketEncryptionAlgorithmDoesNotMatch() {
           final PreUpdateHookHandler handler = new PreUpdateHookHandler();
   
           final AwsS3Bucket resourceProperties = buildAwsS3Bucket("amzn-s3-demo-bucket", buildServerSideEncryptionRule("SHA512"));
           final AwsS3Bucket previousResourceProperties = buildAwsS3Bucket("amzn-s3-demo-bucket", buildServerSideEncryptionRule("AES256"));
           final HookTargetModel targetModel = createHookTargetModel(resourceProperties, previousResourceProperties);
           final TypeConfigurationModel typeConfiguration = TypeConfigurationModel.builder().encryptionAlgorithm("AES256").build();
   
           final HookHandlerRequest request = HookHandlerRequest.builder()
               .hookContext(HookContext.builder().targetName("AWS::S3::Bucket").targetModel(targetModel).build())
               .build();
   
           final HookProgressEvent<CallbackContext> response = handler.handleRequest(proxy, request, null, logger, typeConfiguration);
           assertResponse(response, HookStatus.FAILED, String.format("Bucket Encryption algorithm can not be changed once set. The encryption algorithm was changed to '%s' from '%s'.", "SHA512", "AES256"));
       }
   
       @Test
       public void handleRequest_unsupportedTarget() {
           final PreUpdateHookHandler handler = new PreUpdateHookHandler();
   
           final Object resourceProperties = ImmutableMap.of("FileSizeLimit", 256);
           final Object previousResourceProperties = ImmutableMap.of("FileSizeLimit", 512);
           final HookTargetModel targetModel = createHookTargetModel(resourceProperties, previousResourceProperties);
           final TypeConfigurationModel typeConfiguration = TypeConfigurationModel.builder().encryptionAlgorithm("AES256").build();
   
           final HookHandlerRequest request = HookHandlerRequest.builder()
               .hookContext(HookContext.builder().targetName("AWS::Unsupported::Target").targetModel(targetModel).build())
               .build();
   
           assertThatExceptionOfType(UnsupportedTargetException.class)
                   .isThrownBy(() -> handler.handleRequest(proxy, request, null, logger, typeConfiguration))
                   .withMessageContaining("Unsupported target")
                   .withMessageContaining("AWS::Unsupported::Target")
                   .satisfies(e -> assertThat(e.getErrorCode()).isEqualTo(HandlerErrorCode.InvalidRequest));
       }
   
       private void assertResponse(final HookProgressEvent<CallbackContext> response, final HookStatus expectedStatus, final String expectedErrorMsg) {
           assertThat(response).isNotNull();
           assertThat(response.getStatus()).isEqualTo(expectedStatus);
           assertThat(response.getCallbackContext()).isNull();
           assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
           assertThat(response.getMessage()).isNotNull();
           assertThat(response.getMessage()).isEqualTo(expectedErrorMsg);
       }
   
       private HookTargetModel createHookTargetModel(final Object resourceProperties, final Object previousResourceProperties) {
           return HookTargetModel.of(
                   ImmutableMap.of(
                       "ResourceProperties", resourceProperties,
                       "PreviousResourceProperties", previousResourceProperties
                   )
           );
       }
   
       @SuppressWarnings("SameParameterValue")
       private AwsS3Bucket buildAwsS3Bucket(
               final String bucketName,
               final ServerSideEncryptionRule ...serverSideEncryptionRules
               ) {
           return AwsS3Bucket.builder()
                   .bucketName(bucketName)
                   .bucketEncryption(
                       BucketEncryption.builder()
                           .serverSideEncryptionConfiguration(
                                   Arrays.asList(serverSideEncryptionRules)
                           ).build()
                   ).build();
       }
   
       private ServerSideEncryptionRule buildServerSideEncryptionRule(final String encryptionAlgorithm) {
           return ServerSideEncryptionRule.builder()
                   .bucketKeyEnabled(true)
                   .serverSideEncryptionByDefault(
                           ServerSideEncryptionByDefault.builder()
                                   .sSEAlgorithm(encryptionAlgorithm)
                                   .build()
                   ).build();
       }
   }
   ```

### Implementing the `preDelete` handler
<a name="implement-the-predelete-hander"></a>

Implement a `preDelete` handler, which initiates before the delete operations for all specified targets in the handler. The `preDelete` handler accomplishes the following:
+ For an `AWS::S3::Bucket` resource, the Hook will only pass if the following is true:
  + Verifies that the minimum required complaint resources will exist in the account after delete the resource.
  + The minimum required complaint resources amount is set in the Hook’s type configuration.

### Coding the `preDelete` handler
<a name="code-the-predelete-handler"></a>

1. In your IDE, open the `PreDeleteHookHandler.java` file in the `src/main/java/com/mycompany/testing/mytesthook` folder.

1. Replace the entire contents of the `PreDeleteHookHandler.java` file with the following code.

   ```
   package com.mycompany.testing.mytesthook;
   
   import com.google.common.annotations.VisibleForTesting;
   import com.mycompany.testing.mytesthook.model.aws.s3.bucket.AwsS3Bucket;
   import com.mycompany.testing.mytesthook.model.aws.s3.bucket.AwsS3BucketTargetModel;
   import com.mycompany.testing.mytesthook.model.aws.sqs.queue.AwsSqsQueue;
   import com.mycompany.testing.mytesthook.model.aws.sqs.queue.AwsSqsQueueTargetModel;
   import org.apache.commons.lang3.StringUtils;
   import org.apache.commons.lang3.math.NumberUtils;
   import software.amazon.awssdk.services.cloudformation.CloudFormationClient;
   import software.amazon.awssdk.services.cloudformation.model.CloudFormationException;
   import software.amazon.awssdk.services.cloudformation.model.DescribeStackResourceRequest;
   import software.amazon.awssdk.services.s3.S3Client;
   import software.amazon.awssdk.services.s3.model.Bucket;
   import software.amazon.awssdk.services.s3.model.S3Exception;
   import software.amazon.awssdk.services.sqs.SqsClient;
   import software.amazon.awssdk.services.sqs.model.GetQueueAttributesRequest;
   import software.amazon.awssdk.services.sqs.model.GetQueueUrlRequest;
   import software.amazon.awssdk.services.sqs.model.ListQueuesRequest;
   import software.amazon.awssdk.services.sqs.model.ListQueuesResponse;
   import software.amazon.awssdk.services.sqs.model.QueueAttributeName;
   import software.amazon.awssdk.services.sqs.model.SqsException;
   import software.amazon.cloudformation.exceptions.CfnGeneralServiceException;
   import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
   import software.amazon.cloudformation.proxy.HandlerErrorCode;
   import software.amazon.cloudformation.proxy.OperationStatus;
   import software.amazon.cloudformation.proxy.ProgressEvent;
   import software.amazon.cloudformation.proxy.ProxyClient;
   import software.amazon.cloudformation.proxy.hook.HookContext;
   import software.amazon.cloudformation.proxy.hook.HookHandlerRequest;
   import software.amazon.cloudformation.proxy.hook.targetmodel.HookTargetModel;
   import software.amazon.cloudformation.proxy.hook.targetmodel.ResourceHookTargetModel;
   
   import java.util.ArrayList;
   import java.util.Collection;
   import java.util.HashSet;
   import java.util.List;
   import java.util.Objects;
   import java.util.stream.Collectors;
   
   public class PreDeleteHookHandler extends BaseHookHandlerStd {
   
       private ProxyClient<S3Client> s3Client;
       private ProxyClient<SqsClient> sqsClient;
   
       @Override
       protected ProgressEvent<HookTargetModel, CallbackContext> handleS3BucketRequest(
               final AmazonWebServicesClientProxy proxy,
               final HookHandlerRequest request,
               final CallbackContext callbackContext,
               final ProxyClient<S3Client> proxyClient,
               final TypeConfigurationModel typeConfiguration
       ) {
           final HookContext hookContext = request.getHookContext();
           final String targetName = hookContext.getTargetName();
           if (!AwsS3Bucket.TYPE_NAME.equals(targetName)) {
               throw new RuntimeException(String.format("Request target type [%s] is not 'AWS::S3::Bucket'", targetName));
           }
           this.s3Client = proxyClient;
   
           final String encryptionAlgorithm = typeConfiguration.getEncryptionAlgorithm();
           final int minBuckets = NumberUtils.toInt(typeConfiguration.getMinBuckets());
   
           final ResourceHookTargetModel<AwsS3Bucket> targetModel = hookContext.getTargetModel(AwsS3BucketTargetModel.class);
           final List<String> buckets = listBuckets().stream()
                   .filter(b -> !StringUtils.equals(b, targetModel.getResourceProperties().getBucketName()))
                   .collect(Collectors.toList());
   
           final List<String> compliantBuckets = new ArrayList<>();
           for (final String bucket : buckets) {
               if (getBucketSSEAlgorithm(bucket).contains(encryptionAlgorithm)) {
                   compliantBuckets.add(bucket);
               }
   
               if (compliantBuckets.size() >= minBuckets) {
                   return ProgressEvent.<HookTargetModel, CallbackContext>builder()
                           .status(OperationStatus.SUCCESS)
                           .message("Successfully invoked PreDeleteHookHandler for target: AWS::S3::Bucket")
                           .build();
               }
           }
   
           return ProgressEvent.<HookTargetModel, CallbackContext>builder()
                   .status(OperationStatus.FAILED)
                   .errorCode(HandlerErrorCode.NonCompliant)
                   .message(String.format("Failed to meet minimum of [%d] encrypted buckets.", minBuckets))
                   .build();
       }
   
       @Override
       protected ProgressEvent<HookTargetModel, CallbackContext> handleSqsQueueRequest(
               final AmazonWebServicesClientProxy proxy,
               final HookHandlerRequest request,
               final CallbackContext callbackContext,
               final ProxyClient<SqsClient> proxyClient,
               final TypeConfigurationModel typeConfiguration
       ) {
           final HookContext hookContext = request.getHookContext();
           final String targetName = hookContext.getTargetName();
           if (!AwsSqsQueue.TYPE_NAME.equals(targetName)) {
               throw new RuntimeException(String.format("Request target type [%s] is not 'AWS::SQS::Queue'", targetName));
           }
           this.sqsClient = proxyClient;
           final int minQueues = NumberUtils.toInt(typeConfiguration.getMinQueues());
   
           final ResourceHookTargetModel<AwsSqsQueue> targetModel = hookContext.getTargetModel(AwsSqsQueueTargetModel.class);
   
           final String queueName = Objects.toString(targetModel.getResourceProperties().get("QueueName"), null);
   
           String targetQueueUrl = null;
           if (queueName != null) {
               try {
                   targetQueueUrl = sqsClient.injectCredentialsAndInvokeV2(
                           GetQueueUrlRequest.builder().queueName(
                                   queueName
                           ).build(),
                           sqsClient.client()::getQueueUrl
                   ).queueUrl();
               } catch (SqsException e) {
                   log(String.format("Error while calling GetQueueUrl API for queue name [%s]: %s", queueName, e.getMessage()));
               }
           } else {
               log("Queue name is empty, attempting to get queue's physical ID");
               try {
                   final ProxyClient<CloudFormationClient> cfnClient = proxy.newProxy(ClientBuilder::createCloudFormationClient);
                   targetQueueUrl = cfnClient.injectCredentialsAndInvokeV2(
                           DescribeStackResourceRequest.builder()
                                   .stackName(hookContext.getTargetLogicalId())
                                   .logicalResourceId(hookContext.getTargetLogicalId())
                                   .build(),
                           cfnClient.client()::describeStackResource
                   ).stackResourceDetail().physicalResourceId();
               } catch (CloudFormationException e) {
                   log(String.format("Error while calling DescribeStackResource API for queue name: %s", e.getMessage()));
               }
           }
   
           // Creating final variable for the filter lambda
           final String finalTargetQueueUrl = targetQueueUrl;
   
           final List<String> compliantQueues = new ArrayList<>();
   
           String nextToken = null;
           do {
               final ListQueuesRequest req = Translator.createListQueuesRequest(nextToken);
               final ListQueuesResponse res = sqsClient.injectCredentialsAndInvokeV2(req, sqsClient.client()::listQueues);
               final List<String> queueUrls = res.queueUrls().stream()
                       .filter(q -> !StringUtils.equals(q, finalTargetQueueUrl))
                       .collect(Collectors.toList());
   
               for (final String queueUrl : queueUrls) {
                   if (isQueueEncrypted(queueUrl)) {
                       compliantQueues.add(queueUrl);
                   }
   
                   if (compliantQueues.size() >= minQueues) {
                       return ProgressEvent.<HookTargetModel, CallbackContext>builder()
                           .status(OperationStatus.SUCCESS)
                           .message("Successfully invoked PreDeleteHookHandler for target: AWS::SQS::Queue")
                           .build();
                   }
                   nextToken = res.nextToken();
               }
           } while (nextToken != null);
   
           return ProgressEvent.<HookTargetModel, CallbackContext>builder()
                   .status(OperationStatus.FAILED)
                   .errorCode(HandlerErrorCode.NonCompliant)
                   .message(String.format("Failed to meet minimum of [%d] encrypted queues.", minQueues))
                   .build();
       }
   
       private List<String> listBuckets() {
           try {
               return s3Client.injectCredentialsAndInvokeV2(Translator.createListBucketsRequest(), s3Client.client()::listBuckets)
                       .buckets()
                       .stream()
                       .map(Bucket::name)
                       .collect(Collectors.toList());
           } catch (S3Exception e) {
               throw new CfnGeneralServiceException("Error while calling S3 ListBuckets API", e);
           }
       }
   
       @VisibleForTesting
       Collection<String> getBucketSSEAlgorithm(final String bucket) {
           try {
               return s3Client.injectCredentialsAndInvokeV2(Translator.createGetBucketEncryptionRequest(bucket), s3Client.client()::getBucketEncryption)
                       .serverSideEncryptionConfiguration()
                       .rules()
                       .stream()
                       .filter(r -> Objects.nonNull(r.applyServerSideEncryptionByDefault()))
                       .map(r -> r.applyServerSideEncryptionByDefault().sseAlgorithmAsString())
                       .collect(Collectors.toSet());
           } catch (S3Exception e) {
               return new HashSet<>();
           }
       }
   
       @VisibleForTesting
       boolean isQueueEncrypted(final String queueUrl) {
           try {
               final GetQueueAttributesRequest request = GetQueueAttributesRequest.builder()
                       .queueUrl(queueUrl)
                       .attributeNames(QueueAttributeName.KMS_MASTER_KEY_ID)
                       .build();
               final String kmsKeyId = sqsClient.injectCredentialsAndInvokeV2(request, sqsClient.client()::getQueueAttributes)
                       .attributes()
                       .get(QueueAttributeName.KMS_MASTER_KEY_ID);
   
               return StringUtils.isNotBlank(kmsKeyId);
           } catch (SqsException e) {
               throw new CfnGeneralServiceException("Error while calling SQS GetQueueAttributes API", e);
           }
       }
   }
   ```

### Updating the `preDelete` handler
<a name="update-the-predelete-handler"></a>

1. In your IDE, open the `PreDeleteHookHandler.java` file in the `src/main/java/com/mycompany/testing/mytesthook` folder.

1. Replace the entire contents of the `PreDeleteHookHandler.java` file with the following code.

   ```
   package com.mycompany.testing.mytesthook;
   
   import com.google.common.collect.ImmutableList;
   import com.google.common.collect.ImmutableMap;
   import com.mycompany.testing.mytesthook.model.aws.s3.bucket.AwsS3Bucket;
   import org.junit.jupiter.api.BeforeEach;
   import org.junit.jupiter.api.Test;
   import org.junit.jupiter.api.extension.ExtendWith;
   import org.mockito.Mock;
   import org.mockito.Mockito;
   import org.mockito.junit.jupiter.MockitoExtension;
   import software.amazon.awssdk.services.s3.S3Client;
   import software.amazon.awssdk.services.s3.model.Bucket;
   import software.amazon.awssdk.services.s3.model.GetBucketEncryptionRequest;
   import software.amazon.awssdk.services.s3.model.GetBucketEncryptionResponse;
   import software.amazon.awssdk.services.s3.model.ListBucketsRequest;
   import software.amazon.awssdk.services.s3.model.ListBucketsResponse;
   import software.amazon.awssdk.services.s3.model.S3Exception;
   import software.amazon.awssdk.services.s3.model.ServerSideEncryptionByDefault;
   import software.amazon.awssdk.services.s3.model.ServerSideEncryptionConfiguration;
   import software.amazon.awssdk.services.s3.model.ServerSideEncryptionRule;
   import software.amazon.awssdk.services.sqs.SqsClient;
   import software.amazon.awssdk.services.sqs.model.GetQueueAttributesRequest;
   import software.amazon.awssdk.services.sqs.model.GetQueueAttributesResponse;
   import software.amazon.awssdk.services.sqs.model.GetQueueUrlRequest;
   import software.amazon.awssdk.services.sqs.model.GetQueueUrlResponse;
   import software.amazon.awssdk.services.sqs.model.ListQueuesRequest;
   import software.amazon.awssdk.services.sqs.model.ListQueuesResponse;
   import software.amazon.awssdk.services.sqs.model.QueueAttributeName;
   import software.amazon.cloudformation.proxy.Logger;
   import software.amazon.cloudformation.proxy.OperationStatus;
   import software.amazon.cloudformation.proxy.ProgressEvent;
   import software.amazon.cloudformation.proxy.hook.HookContext;
   import software.amazon.cloudformation.proxy.hook.HookHandlerRequest;
   import software.amazon.cloudformation.proxy.hook.targetmodel.HookTargetModel;
   
   import java.util.Arrays;
   import java.util.Collection;
   import java.util.HashMap;
   import java.util.List;
   import java.util.stream.Collectors;
   
   import static org.mockito.ArgumentMatchers.any;
   import static org.mockito.Mockito.mock;
   import static org.mockito.Mockito.never;
   import static org.mockito.Mockito.times;
   import static org.mockito.Mockito.verify;
   import static org.mockito.Mockito.when;
   
   @ExtendWith(MockitoExtension.class)
   public class PreDeleteHookHandlerTest extends AbstractTestBase {
   
       @Mock private S3Client s3Client;
       @Mock private SqsClient sqsClient;
       @Mock private Logger logger;
   
       @BeforeEach
       public void setup() {
           s3Client = mock(S3Client.class);
           sqsClient = mock(SqsClient.class);
           logger = mock(Logger.class);
       }
   
       @Test
       public void handleRequest_awsS3BucketSuccess() {
           final PreDeleteHookHandler handler = Mockito.spy(new PreDeleteHookHandler());
   
           final List<Bucket> bucketList = ImmutableList.of(
                   Bucket.builder().name("bucket1").build(),
                   Bucket.builder().name("bucket2").build(),
                   Bucket.builder().name("toBeDeletedBucket").build(),
                   Bucket.builder().name("bucket3").build(),
                   Bucket.builder().name("bucket4").build(),
                   Bucket.builder().name("bucket5").build()
           );
           final ListBucketsResponse mockResponse = ListBucketsResponse.builder().buckets(bucketList).build();
           when(s3Client.listBuckets(any(ListBucketsRequest.class))).thenReturn(mockResponse);
           when(s3Client.getBucketEncryption(any(GetBucketEncryptionRequest.class)))
                   .thenReturn(buildGetBucketEncryptionResponse("AES256"))
                   .thenReturn(buildGetBucketEncryptionResponse("AES256", "aws:kms"))
                   .thenThrow(S3Exception.builder().message("No Encrypt").build())
                   .thenReturn(buildGetBucketEncryptionResponse("aws:kms"))
                   .thenReturn(buildGetBucketEncryptionResponse("AES256"));
           setServiceClient(s3Client);
   
           final TypeConfigurationModel typeConfiguration = TypeConfigurationModel.builder()
                   .encryptionAlgorithm("AES256")
                   .minBuckets("3")
                   .build();
   
           final HookHandlerRequest request = HookHandlerRequest.builder()
                   .hookContext(
                       HookContext.builder()
                           .targetName("AWS::S3::Bucket")
                           .targetModel(
                               createHookTargetModel(
                                   AwsS3Bucket.builder()
                                       .bucketName("toBeDeletedBucket")
                                       .build()
                               )
                           )
                           .build())
                   .build();
   
           final ProgressEvent<HookTargetModel, CallbackContext> response = handler.handleRequest(proxy, request, null, logger, typeConfiguration);
   
           verify(s3Client, times(5)).getBucketEncryption(any(GetBucketEncryptionRequest.class));
           verify(handler, never()).getBucketSSEAlgorithm("toBeDeletedBucket");
   
           assertResponse(response, OperationStatus.SUCCESS, "Successfully invoked PreDeleteHookHandler for target: AWS::S3::Bucket");
       }
   
   
       @Test
       public void handleRequest_awsSqsQueueSuccess() {
           final PreDeleteHookHandler handler = Mockito.spy(new PreDeleteHookHandler());
   
           final List<String> queueUrls = ImmutableList.of(
                   "https://queue1.queue",
                   "https://queue2.queue",
                   "https://toBeDeletedQueue.queue",
                   "https://queue3.queue",
                   "https://queue4.queue",
                   "https://queue5.queue"
           );
   
           when(sqsClient.getQueueUrl(any(GetQueueUrlRequest.class)))
                   .thenReturn(GetQueueUrlResponse.builder().queueUrl("https://toBeDeletedQueue.queue").build());
           when(sqsClient.listQueues(any(ListQueuesRequest.class)))
                   .thenReturn(ListQueuesResponse.builder().queueUrls(queueUrls).build());
           when(sqsClient.getQueueAttributes(any(GetQueueAttributesRequest.class)))
                   .thenReturn(GetQueueAttributesResponse.builder().attributes(ImmutableMap.of(QueueAttributeName.KMS_MASTER_KEY_ID, "kmsKeyId")).build())
                   .thenReturn(GetQueueAttributesResponse.builder().attributes(new HashMap<>()).build())
                   .thenReturn(GetQueueAttributesResponse.builder().attributes(ImmutableMap.of(QueueAttributeName.KMS_MASTER_KEY_ID, "kmsKeyId")).build())
                   .thenReturn(GetQueueAttributesResponse.builder().attributes(new HashMap<>()).build())
                   .thenReturn(GetQueueAttributesResponse.builder().attributes(ImmutableMap.of(QueueAttributeName.KMS_MASTER_KEY_ID, "kmsKeyId")).build());
           setServiceClient(sqsClient);
   
           final TypeConfigurationModel typeConfiguration = TypeConfigurationModel.builder()
                   .minQueues("3")
                   .build();
   
           final HookHandlerRequest request = HookHandlerRequest.builder()
                   .hookContext(
                       HookContext.builder()
                           .targetName("AWS::SQS::Queue")
                           .targetModel(
                               createHookTargetModel(
                                   ImmutableMap.of("QueueName", "toBeDeletedQueue")
                               )
                           )
                           .build())
                   .build();
   
           final ProgressEvent<HookTargetModel, CallbackContext> response = handler.handleRequest(proxy, request, null, logger, typeConfiguration);
   
           verify(sqsClient, times(5)).getQueueAttributes(any(GetQueueAttributesRequest.class));
           verify(handler, never()).isQueueEncrypted("toBeDeletedQueue");
   
           assertResponse(response, OperationStatus.SUCCESS, "Successfully invoked PreDeleteHookHandler for target: AWS::SQS::Queue");
       }
   
       @Test
       public void handleRequest_awsS3BucketFailed() {
           final PreDeleteHookHandler handler = Mockito.spy(new PreDeleteHookHandler());
   
           final List<Bucket> bucketList = ImmutableList.of(
                   Bucket.builder().name("bucket1").build(),
                   Bucket.builder().name("bucket2").build(),
                   Bucket.builder().name("toBeDeletedBucket").build(),
                   Bucket.builder().name("bucket3").build(),
                   Bucket.builder().name("bucket4").build(),
                   Bucket.builder().name("bucket5").build()
           );
           final ListBucketsResponse mockResponse = ListBucketsResponse.builder().buckets(bucketList).build();
           when(s3Client.listBuckets(any(ListBucketsRequest.class))).thenReturn(mockResponse);
           when(s3Client.getBucketEncryption(any(GetBucketEncryptionRequest.class)))
                   .thenReturn(buildGetBucketEncryptionResponse("AES256"))
                   .thenReturn(buildGetBucketEncryptionResponse("AES256", "aws:kms"))
                   .thenThrow(S3Exception.builder().message("No Encrypt").build())
                   .thenReturn(buildGetBucketEncryptionResponse("aws:kms"))
                   .thenReturn(buildGetBucketEncryptionResponse("AES256"));
           setServiceClient(s3Client);
   
           final TypeConfigurationModel typeConfiguration = TypeConfigurationModel.builder()
                   .encryptionAlgorithm("AES256")
                   .minBuckets("10")
                   .build();
   
           final HookHandlerRequest request = HookHandlerRequest.builder()
                   .hookContext(
                       HookContext.builder()
                           .targetName("AWS::S3::Bucket")
                           .targetModel(
                               createHookTargetModel(
                                   AwsS3Bucket.builder()
                                       .bucketName("toBeDeletedBucket")
                                       .build()
                               )
                           )
                           .build())
                   .build();
   
           final ProgressEvent<HookTargetModel, CallbackContext> response = handler.handleRequest(proxy, request, null, logger, typeConfiguration);
   
           verify(s3Client, times(5)).getBucketEncryption(any(GetBucketEncryptionRequest.class));
           verify(handler, never()).getBucketSSEAlgorithm("toBeDeletedBucket");
   
           assertResponse(response, OperationStatus.FAILED, "Failed to meet minimum of [10] encrypted buckets.");
       }
   
   
       @Test
       public void handleRequest_awsSqsQueueFailed() {
           final PreDeleteHookHandler handler = Mockito.spy(new PreDeleteHookHandler());
   
           final List<String> queueUrls = ImmutableList.of(
                   "https://queue1.queue",
                   "https://queue2.queue",
                   "https://toBeDeletedQueue.queue",
                   "https://queue3.queue",
                   "https://queue4.queue",
                   "https://queue5.queue"
           );
   
           when(sqsClient.getQueueUrl(any(GetQueueUrlRequest.class)))
                   .thenReturn(GetQueueUrlResponse.builder().queueUrl("https://toBeDeletedQueue.queue").build());
           when(sqsClient.listQueues(any(ListQueuesRequest.class)))
                   .thenReturn(ListQueuesResponse.builder().queueUrls(queueUrls).build());
           when(sqsClient.getQueueAttributes(any(GetQueueAttributesRequest.class)))
                   .thenReturn(GetQueueAttributesResponse.builder().attributes(ImmutableMap.of(QueueAttributeName.KMS_MASTER_KEY_ID, "kmsKeyId")).build())
                   .thenReturn(GetQueueAttributesResponse.builder().attributes(new HashMap<>()).build())
                   .thenReturn(GetQueueAttributesResponse.builder().attributes(ImmutableMap.of(QueueAttributeName.KMS_MASTER_KEY_ID, "kmsKeyId")).build())
                   .thenReturn(GetQueueAttributesResponse.builder().attributes(new HashMap<>()).build())
                   .thenReturn(GetQueueAttributesResponse.builder().attributes(ImmutableMap.of(QueueAttributeName.KMS_MASTER_KEY_ID, "kmsKeyId")).build());
           setServiceClient(sqsClient);
   
           final TypeConfigurationModel typeConfiguration = TypeConfigurationModel.builder()
                   .minQueues("10")
                   .build();
   
           final HookHandlerRequest request = HookHandlerRequest.builder()
                   .hookContext(
                       HookContext.builder()
                           .targetName("AWS::SQS::Queue")
                           .targetModel(
                               createHookTargetModel(
                                   ImmutableMap.of("QueueName", "toBeDeletedQueue")
                               )
                           )
                           .build())
                   .build();
   
           final ProgressEvent<HookTargetModel, CallbackContext> response = handler.handleRequest(proxy, request, null, logger, typeConfiguration);
   
           verify(sqsClient, times(5)).getQueueAttributes(any(GetQueueAttributesRequest.class));
           verify(handler, never()).isQueueEncrypted("toBeDeletedQueue");
   
           assertResponse(response, OperationStatus.FAILED, "Failed to meet minimum of [10] encrypted queues.");
       }
   
       private GetBucketEncryptionResponse buildGetBucketEncryptionResponse(final String ...sseAlgorithm) {
           return buildGetBucketEncryptionResponse(
                   Arrays.stream(sseAlgorithm)
                       .map(a -> ServerSideEncryptionRule.builder().applyServerSideEncryptionByDefault(
                           ServerSideEncryptionByDefault.builder()
                               .sseAlgorithm(a)
                               .build()
                           ).build()
                       )
                       .collect(Collectors.toList())
           );
       }
   
       private GetBucketEncryptionResponse buildGetBucketEncryptionResponse(final Collection<ServerSideEncryptionRule> rules) {
           return GetBucketEncryptionResponse.builder()
                   .serverSideEncryptionConfiguration(
                       ServerSideEncryptionConfiguration.builder().rules(
                           rules
                       ).build()
                   ).build();
       }
   }
   ```

# Modeling custom CloudFormation Hooks using Python
<a name="hooks-model-python"></a>

Modeling custom CloudFormation Hooks involves creating a schema that defines the Hook, its properties, and their attributes. This tutorial walks you through modeling custom Hooks using Python.

## Step 1: Generate the Hook project package
<a name="model-hook-project-package-python"></a>

Generate your Hook project package. The CloudFormation CLI creates empty handler functions that correspond to specific Hook actions in the target lifecycle as defined in the Hook specification.

```
cfn generate
```

The command returns the following output.

```
Generated files for MyCompany::Testing::MyTestHook
```

**Note**  
Make sure your Lambda runtimes are up-to-date to avoid using a deprecated version. For more information, see [Updating Lambda runtimes for resource types and Hooks](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/runtime-update.html).

## Step 2: Add Hook handlers
<a name="model-hook-project-add-handler"></a>

Add your own Hook handler runtime code to the handlers that you choose to implement. For example, you can add the following code for logging.

```
LOG.setLevel(logging.INFO)
LOG.info("Internal testing Hook triggered for target: " + request.hookContext.targetName);
```

The CloudFormation CLI generates the `src/models.py` file from the [Hook configuration schema syntax reference](hook-configuration-schema.md).

**Example models.py**  

```
import sys
from dataclasses import dataclass
from inspect import getmembers, isclass
from typing import (
    AbstractSet,
    Any,
    Generic,
    Mapping,
    MutableMapping,
    Optional,
    Sequence,
    Type,
    TypeVar,
)

from cloudformation_cli_python_lib.interface import (
    BaseModel,
    BaseHookHandlerRequest,
)
from cloudformation_cli_python_lib.recast import recast_object
from cloudformation_cli_python_lib.utils import deserialize_list

T = TypeVar("T")


def set_or_none(value: Optional[Sequence[T]]) -> Optional[AbstractSet[T]]:
    if value:
        return set(value)
    return None


@dataclass
class HookHandlerRequest(BaseHookHandlerRequest):
    pass


@dataclass
class TypeConfigurationModel(BaseModel):
    limitSize: Optional[str]
    cidr: Optional[str]
    encryptionAlgorithm: Optional[str]

    @classmethod
    def _deserialize(
        cls: Type["_TypeConfigurationModel"],
        json_data: Optional[Mapping[str, Any]],
    ) -> Optional["_TypeConfigurationModel"]:
        if not json_data:
            return None
        return cls(
            limitSize=json_data.get("limitSize"),
            cidr=json_data.get("cidr"),
            encryptionAlgorithm=json_data.get("encryptionAlgorithm"),
        )


_TypeConfigurationModel = TypeConfigurationModel
```

## Step 3: Implement Hook handlers
<a name="model-hook-project-code-handler-python"></a>

With the Python data classes generated, you can write the handlers that actually implement the Hook’s functionality. In this example, you’ll implement the `preCreate`, `preUpdate`, and `preDelete` invocation points for the handlers.

**Topics**
+ [Implement the preCreate handler](#model-hook-project-code-handler-python-precreate)
+ [Implement the preUpdate handler](#model-hook-project-code-handler-python-preupdate)
+ [Implement the preDelete handler](#model-hook-project-code-handler-python-predelete)
+ [Implement a Hook handler](#model-hook-project-code-handler-python-hook-handler)

### Implement the preCreate handler
<a name="model-hook-project-code-handler-python-precreate"></a>

The `preCreate` handler verifies the server-side encryption settings for either an `AWS::S3::Bucket ` or `AWS::SQS::Queue` resource.
+ For an `AWS::S3::Bucket` resource, the Hook will only pass if the following is true.
  + The Amazon S3 bucket encryption is set.
  + The Amazon S3 bucket key is enabled for the bucket.
  + The encryption algorithm set for the Amazon S3 bucket is the correct algorithm required.
  + The AWS Key Management Service key ID is set.
+ For an `AWS::SQS::Queue` resource, the Hook will only pass if the following is true.
  + The AWS Key Management Service key ID is set.

### Implement the preUpdate handler
<a name="model-hook-project-code-handler-python-preupdate"></a>

Implement a `preUpdate` handler, which initiates before the update operations for all specified targets in the handler. The `preUpdate` handler accomplishes the following:
+ For an `AWS::S3::Bucket` resource, the Hook will only pass if the following is true:
  + The bucket encryption algorithm for an Amazon S3 bucket hasn't been modified.

### Implement the preDelete handler
<a name="model-hook-project-code-handler-python-predelete"></a>

Implement a `preDelete` handler, which initiates before the delete operations for all specified targets in the handler. The `preDelete` handler accomplishes the following:
+ For an `AWS::S3::Bucket` resource, the Hook will only pass if the following is true:
  + Verifies that the minimum required compliant resources will exist in the account after delete the resource.
  + The minimum required compliant resources amount is set in the Hook’s configuration.

### Implement a Hook handler
<a name="model-hook-project-code-handler-python-hook-handler"></a>

1. In your IDE, open the `handlers.py` file, located in the `src` folder.

1. Replace the entire contents of the `handlers.py` file with the following code.  
**Example handlers.py**  

   ```
   import logging
   from typing import Any, MutableMapping, Optional
   import botocore
   
   from cloudformation_cli_python_lib import (
       BaseHookHandlerRequest,
       HandlerErrorCode,
       Hook,
       HookInvocationPoint,
       OperationStatus,
       ProgressEvent,
       SessionProxy,
       exceptions,
   )
   
   from .models import HookHandlerRequest, TypeConfigurationModel
   
   # Use this logger to forward log messages to CloudWatch Logs.
   LOG = logging.getLogger(__name__)
   TYPE_NAME = "MyCompany::Testing::MyTestHook"
   
   LOG.setLevel(logging.INFO)
   
   hook = Hook(TYPE_NAME, TypeConfigurationModel)
   test_entrypoint = hook.test_entrypoint
   
   
   def _validate_s3_bucket_encryption(
       bucket: MutableMapping[str, Any], required_encryption_algorithm: str
   ) -> ProgressEvent:
       status = None
       message = ""
       error_code = None
   
       if bucket:
           bucket_name = bucket.get("BucketName")
   
           bucket_encryption = bucket.get("BucketEncryption")
           if bucket_encryption:
               server_side_encryption_rules = bucket_encryption.get(
                   "ServerSideEncryptionConfiguration"
               )
               if server_side_encryption_rules:
                   for rule in server_side_encryption_rules:
                       bucket_key_enabled = rule.get("BucketKeyEnabled")
                       if bucket_key_enabled:
                           server_side_encryption_by_default = rule.get(
                               "ServerSideEncryptionByDefault"
                           )
   
                           encryption_algorithm = server_side_encryption_by_default.get(
                               "SSEAlgorithm"
                           )
                           kms_key_id = server_side_encryption_by_default.get(
                               "KMSMasterKeyID"
                           )  # "KMSMasterKeyID" is name of the property for an AWS::S3::Bucket
   
                           if encryption_algorithm == required_encryption_algorithm:
                               if encryption_algorithm == "aws:kms" and not kms_key_id:
                                   status = OperationStatus.FAILED
                                   message = f"KMS Key ID not set for bucket with name: f{bucket_name}"
                               else:
                                   status = OperationStatus.SUCCESS
                                   message = f"Successfully invoked PreCreateHookHandler for AWS::S3::Bucket with name: {bucket_name}"
                           else:
                               status = OperationStatus.FAILED
                               message = f"SSE Encryption Algorithm is incorrect for bucket with name: {bucket_name}"
                       else:
                           status = OperationStatus.FAILED
                           message = f"Bucket key not enabled for bucket with name: {bucket_name}"
   
                       if status == OperationStatus.FAILED:
                           break
               else:
                   status = OperationStatus.FAILED
                   message = f"No SSE Encryption configurations for bucket with name: {bucket_name}"
           else:
               status = OperationStatus.FAILED
               message = (
                   f"Bucket Encryption not enabled for bucket with name: {bucket_name}"
               )
       else:
           status = OperationStatus.FAILED
           message = "Resource properties for S3 Bucket target model are empty"
   
       if status == OperationStatus.FAILED:
           error_code = HandlerErrorCode.NonCompliant
   
       return ProgressEvent(status=status, message=message, errorCode=error_code)
   
   
   def _validate_sqs_queue_encryption(queue: MutableMapping[str, Any]) -> ProgressEvent:
       if not queue:
           return ProgressEvent(
               status=OperationStatus.FAILED,
               message="Resource properties for SQS Queue target model are empty",
               errorCode=HandlerErrorCode.NonCompliant,
           )
       queue_name = queue.get("QueueName")
   
       kms_key_id = queue.get(
           "KmsMasterKeyId"
       )  # "KmsMasterKeyId" is name of the property for an AWS::SQS::Queue
       if not kms_key_id:
           return ProgressEvent(
               status=OperationStatus.FAILED,
               message=f"Server side encryption turned off for queue with name: {queue_name}",
               errorCode=HandlerErrorCode.NonCompliant,
           )
   
       return ProgressEvent(
           status=OperationStatus.SUCCESS,
           message=f"Successfully invoked PreCreateHookHandler for targetAWS::SQS::Queue with name: {queue_name}",
       )
   
   
   @hook.handler(HookInvocationPoint.CREATE_PRE_PROVISION)
   def pre_create_handler(
       session: Optional[SessionProxy],
       request: HookHandlerRequest,
       callback_context: MutableMapping[str, Any],
       type_configuration: TypeConfigurationModel,
   ) -> ProgressEvent:
       target_name = request.hookContext.targetName
       if "AWS::S3::Bucket" == target_name:
           return _validate_s3_bucket_encryption(
               request.hookContext.targetModel.get("resourceProperties"),
               type_configuration.encryptionAlgorithm,
           )
       elif "AWS::SQS::Queue" == target_name:
           return _validate_sqs_queue_encryption(
               request.hookContext.targetModel.get("resourceProperties")
           )
       else:
           raise exceptions.InvalidRequest(f"Unknown target type: {target_name}")
   
   
   def _validate_bucket_encryption_rules_not_updated(
       resource_properties, previous_resource_properties
   ) -> ProgressEvent:
       bucket_encryption_configs = resource_properties.get("BucketEncryption", {}).get(
           "ServerSideEncryptionConfiguration", []
       )
       previous_bucket_encryption_configs = previous_resource_properties.get(
           "BucketEncryption", {}
       ).get("ServerSideEncryptionConfiguration", [])
   
       if len(bucket_encryption_configs) != len(previous_bucket_encryption_configs):
           return ProgressEvent(
               status=OperationStatus.FAILED,
               message=f"Current number of bucket encryption configs does not match previous. Current has {str(len(bucket_encryption_configs))} configs while previously there were {str(len(previous_bucket_encryption_configs))} configs",
               errorCode=HandlerErrorCode.NonCompliant,
           )
   
       for i in range(len(bucket_encryption_configs)):
           current_encryption_algorithm = (
               bucket_encryption_configs[i]
               .get("ServerSideEncryptionByDefault", {})
               .get("SSEAlgorithm")
           )
           previous_encryption_algorithm = (
               previous_bucket_encryption_configs[i]
               .get("ServerSideEncryptionByDefault", {})
               .get("SSEAlgorithm")
           )
   
           if current_encryption_algorithm != previous_encryption_algorithm:
               return ProgressEvent(
                   status=OperationStatus.FAILED,
                   message=f"Bucket Encryption algorithm can not be changed once set. The encryption algorithm was changed to {current_encryption_algorithm} from {previous_encryption_algorithm}.",
                   errorCode=HandlerErrorCode.NonCompliant,
               )
   
       return ProgressEvent(
           status=OperationStatus.SUCCESS,
           message="Successfully invoked PreUpdateHookHandler for target: AWS::SQS::Queue",
       )
   
   
   def _validate_queue_encryption_not_disabled(
       resource_properties, previous_resource_properties
   ) -> ProgressEvent:
       if previous_resource_properties.get(
           "KmsMasterKeyId"
       ) and not resource_properties.get("KmsMasterKeyId"):
           return ProgressEvent(
               status=OperationStatus.FAILED,
               errorCode=HandlerErrorCode.NonCompliant,
               message="Queue encryption can not be disable",
           )
       else:
           return ProgressEvent(status=OperationStatus.SUCCESS)
   
   
   @hook.handler(HookInvocationPoint.UPDATE_PRE_PROVISION)
   def pre_update_handler(
       session: Optional[SessionProxy],
       request: BaseHookHandlerRequest,
       callback_context: MutableMapping[str, Any],
       type_configuration: MutableMapping[str, Any],
   ) -> ProgressEvent:
       target_name = request.hookContext.targetName
       if "AWS::S3::Bucket" == target_name:
           resource_properties = request.hookContext.targetModel.get("resourceProperties")
           previous_resource_properties = request.hookContext.targetModel.get(
               "previousResourceProperties"
           )
   
           return _validate_bucket_encryption_rules_not_updated(
               resource_properties, previous_resource_properties
           )
       elif "AWS::SQS::Queue" == target_name:
           resource_properties = request.hookContext.targetModel.get("resourceProperties")
           previous_resource_properties = request.hookContext.targetModel.get(
               "previousResourceProperties"
           )
   
           return _validate_queue_encryption_not_disabled(
               resource_properties, previous_resource_properties
           )
       else:
           raise exceptions.InvalidRequest(f"Unknown target type: {target_name}")
   ```

Continue to the next topic [Registering a custom Hook with CloudFormation](registering-hooks.md).

# Registering a custom Hook with CloudFormation
<a name="registering-hooks"></a>

Once you have created a custom Hook, you need to register it with CloudFormation so you can use it. In this section, you'll learn to package and register your Hook for use in your AWS account.

## Package a Hook (Java)
<a name="registering-hooks-package"></a>

If you've developed your Hook with Java, use Maven to package it.

In the directory of your Hook project, run the following command to build your Hook, run unit tests, and package your project as a `JAR` file that you can use to submit your Hook to the CloudFormation registry.

```
mvn clean package
```

## Register a custom Hook
<a name="registering-hooks-register"></a>

**To register a Hook**

1. (Optional) Configure your default AWS Region name to `us-west-2`, by submitting the [https://docs.aws.amazon.com/cli/latest/reference/configure/](https://docs.aws.amazon.com/cli/latest/reference/configure/) operation.

   ```
   $ aws configure
   AWS Access Key ID [None]: <Your Access Key ID>
   AWS Secret Access Key [None]: <Your Secret Key>
   Default region name [None]: us-west-2
   Default output format [None]: json
   ```

1. (Optional) The following command builds and packages your Hook project without registering it.

   ```
   $ cfn submit --dry-run
   ```

1. Register your Hook by using the CloudFormation CLI [https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-cli-submit.html](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-cli-submit.html) operation.

   ```
   $ cfn submit --set-default
   ```

   The command returns the following command.

   ```
   {‘ProgressStatus’: ‘COMPLETE’}
   ```

   *Results*: You've successfully registered your Hook.

## Verifying Hooks are accessible in your account
<a name="verifying-hooks"></a>

Verify that your Hook is available in your AWS account and in the Regions to which you have submitted it.

1. To verify your Hook, use the [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/list-types.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/list-types.html) command to list your newly registered Hook and return a summary description of it.

   ```
   $ aws cloudformation list-types
   ```

   The command returns the following output and will also show you publicly available Hooks you can activate in your AWS account and Regions.

   ```
   {
       "TypeSummaries": [
           {
               "Type": "HOOK",
               "TypeName": "MyCompany::Testing::MyTestHook",
               "DefaultVersionId": "00000001",
               "TypeArn": "arn:aws:cloudformation:us-west-2:ACCOUNT_ID/type/hook/MyCompany-Testing-MyTestHook",
               "LastUpdated": "2021-08-04T23:00:03.058000+00:00",
               "Description": "Verifies S3 bucket and SQS queues properties before creating or updating"
           }
       ]
   }
   ```

1. Retrieve the `TypeArn` from the `list-type` output for your Hook and save it.

   ```
   export HOOK_TYPE_ARN=arn:aws:cloudformation:us-west-2:ACCOUNT_ID/type/hook/MyCompany-Testing-MyTestHook
   ```

To learn how to publish Hooks for public use, see [Publishing Hooks for public use](hooks-publishing.md).

### Configure Hooks
<a name="configure-hooks"></a>

After you've developed and registered your Hook, you can configure your Hook in your AWS account by publishing it to the registry.
+ To configure a Hook in your account, use the [https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_SetTypeConfiguration.html](https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_SetTypeConfiguration.html) operation. This operation enables the Hook’s properties that are defined in the Hook’s schema `properties` section. In the following example, the `minBuckets` property is set to `1` in the configuration.
**Note**  
By enabling Hooks in your account, you are authorizing a Hook to use defined permissions from your AWS account. CloudFormation removes non-required permissions before passing your permissions to the Hook. CloudFormation recommends customers or Hook users to review the Hook permissions and be aware of what permissions the Hooks are allowed to before enabling Hooks in your account.

  Specify the configuration data for your registered Hook extension in the same account and AWS Region.

  ```
  $ aws cloudformation set-type-configuration --region us-west-2 
    --configuration '{"CloudFormationConfiguration":{"HookConfiguration":{"HookInvocationStatus":"ENABLED","FailureMode":"FAIL","Properties":{"minBuckets": "1","minQueues": "1", "encryptionAlgorithm": "aws:kms"}}}}'
    --type-arn $HOOK_TYPE_ARN
  ```
**Important**  
To enable your Hook to proactively inspect the configuration of your stack, you must set the `HookInvocationStatus` to `ENABLED` in the `HookConfiguration` section, after the Hook has been registered and activated in your account.

## Accessing AWS APIs in handlers
<a name="accessing-apis-in-handlers"></a>

If your Hooks uses an AWS API in any of its handlers, the CFN-CLI automatically creates an IAM execution role template, `hook-role.yaml`. The `hook-role.yaml` template is based on the permissions specified for each handler in the handler's section of the Hook schema. If the `--role-arn` flag is not used during the [https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-cli-generate.html](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-cli-generate.html) operation, the role in this stack will be provisioned and used as the execution role of the Hook.

For more information, see [Accessing AWS APIs from a resource type.](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-develop.html#resource-type-develop-executionrole)

### hook-role.yaml template
<a name="resource-role.yaml"></a>

**Note**  
If you choose to create your own execution role, we highly recommend practicing the principle of least privilege by allow listing only `hooks.cloudformation.amazonaws.com` and `resources.cloudformation.amazonaws.com`.

The following template uses the IAM, Amazon S3, and Amazon SQS permissions.

```
AWSTemplateFormatVersion: 2010-09-09
Description: >
  This CloudFormation template creates a role assumed by CloudFormation during
  Hook operations on behalf of the customer.
Resources:
  ExecutionRole:
    Type: 'AWS::IAM::Role'
    Properties:
      MaxSessionDuration: 8400
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - resources.cloudformation.amazonaws.com
                - hooks.cloudformation.amazonaws.com
            Action: 'sts:AssumeRole'
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
              StringLike:
                aws:SourceArn: !Sub arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:type/hook/MyCompany-Testing-MyTestHook/*
      Path: /
      Policies:
        - PolicyName: HookTypePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 's3:GetEncryptionConfiguration'
                  - 's3:ListBucket'
                  - 's3:ListAllMyBuckets'
                  - 'sqs:GetQueueAttributes'
                  - 'sqs:GetQueueUrl'
                  - 'sqs:ListQueues'
                Resource: '*'
Outputs:
  ExecutionRoleArn:
    Value: !GetAtt 
      - ExecutionRole
      - Arn
```

# Testing a custom Hook in your AWS account
<a name="testing-hooks"></a>

Now that you've coded your handler functions that correspond to an invocation point, it's time to test your custom Hook on a CloudFormation stack.

The Hook failure mode is set to `FAIL` if the CloudFormation template didn't provision an S3 bucket with the following:
+ The Amazon S3 bucket encryption is set.
+ The Amazon S3 bucket key is enabled for the bucket.
+ The encryption algorithm set for the Amazon S3 bucket is the correct algorithm required.
+ The AWS Key Management Service key ID is set.

In the following example, create a template called `my-failed-bucket-stack.yml` with a stack name of `my-hook-stack` that fails the stack configuration and stops before the resource provisions.

## Testing Hooks by provisioning a stack
<a name="testing-hooks-provision-stack"></a>

### Example 1: To provision a stack
<a name="provision-a-stack-example-1"></a>

**Provision a non-compliant stack**

1. Author a template that specifies an S3 bucket. For example, `my-failed-bucket-stack.yml`.

   ```
   AWSTemplateFormatVersion: 2010-09-09
   Resources:
     S3Bucket:
       Type: AWS::S3::Bucket
       Properties: {}
   ```

1. Create a stack, and specify your template in the AWS Command Line Interface (AWS CLI). In the following example, specify the stack name as `my-hook-stack` and the template name as `my-failed-bucket-stack.yml`.

   ```
   $ aws cloudformation create-stack \
     --stack-name my-hook-stack \
     --template-body file://my-failed-bucket-stack.yml
   ```

1. (Optional) View your stack progress by specifying your stack name. In the following example, specify the stack name `my-hook-stack`.

   ```
   $ aws cloudformation describe-stack-events \
     --stack-name my-hook-stack
   ```

   Use the `describe-stack-events` operation to see the Hook failure while creating the bucket. The following is an example output of the command.

   ```
   {
       "StackEvents": [
       ...
           {
               "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/my-hook-stack/2c693970-f57e-11eb-a0fb-061a2a83f0b9",
               "EventId": "S3Bucket-CREATE_FAILED-2021-08-04T23:47:03.305Z",
               "StackName": "my-hook-stack",
               "LogicalResourceId": "S3Bucket",
               "PhysicalResourceId": "",
               "ResourceType": "AWS::S3::Bucket",
               "Timestamp": "2021-08-04T23:47:03.305000+00:00",
               "ResourceStatus": "CREATE_FAILED",
               "ResourceStatusReason": "The following hook(s) failed: [MyCompany::Testing::MyTestHook]",
               "ResourceProperties": "{}",
               "ClientRequestToken": "Console-CreateStack-abe71ac2-ade4-a762-0499-8d34d91d6a92"
           },
       ...
       ]
   }
   ```

   *Results*: The Hook invocation failed the stack configuration and stopped the resource from provisioning.

**Use a CloudFormation template to pass Hook validation**

1. To create a stack and pass the Hook validation, update the template so that your resource uses an encrypted S3 bucket. This example uses the template `my-encrypted-bucket-stack.yml`.

   ```
   AWSTemplateFormatVersion: 2010-09-09
   Description: |
     This CloudFormation template provisions an encrypted S3 Bucket
   Resources:
     EncryptedS3Bucket:
       Type: AWS::S3::Bucket
       Properties:
         BucketName: !Sub encryptedbucket-${AWS::Region}-${AWS::AccountId}
         BucketEncryption:
           ServerSideEncryptionConfiguration:
             - ServerSideEncryptionByDefault:
                 SSEAlgorithm: 'aws:kms'
                 KMSMasterKeyID: !Ref EncryptionKey
               BucketKeyEnabled: true
     EncryptionKey:
       Type: AWS::KMS::Key
       DeletionPolicy: Retain
       Properties:
         Description: KMS key used to encrypt the resource type artifacts
         EnableKeyRotation: true
         KeyPolicy:
           Version: 2012-10-17
           Statement:
             - Sid: Enable full access for owning account
               Effect: Allow
               Principal:
                 AWS: !Ref AWS::AccountId
               Action: 'kms:*'
               Resource: '*'
   Outputs:
     EncryptedBucketName:
       Value: !Ref EncryptedS3Bucket
   ```
**Note**  
Hooks won't be invoked for skipped resources.

1. Create a stack and specify your template. In this example, the stack name is `my-encrypted-bucket-stack`.

   ```
   $ aws cloudformation create-stack \
     --stack-name my-encrypted-bucket-stack \
     --template-body file://my-encrypted-bucket-stack.yml \
   ```

1. (Optional) View your stack progress by specifying the stack name.

   ```
   $ aws cloudformation describe-stack-events \
     --stack-name my-encrypted-bucket-stack
   ```

   Use the `describe-stack-events` command to view the response. The following is an example of the `describe-stack-events` command.

   ```
   {
       "StackEvents": [
       ...
           {
               "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/my-encrypted-bucket-stack/82a97150-f57a-11eb-8eb2-06a6bdcc7779",
               "EventId": "EncryptedS3Bucket-CREATE_COMPLETE-2021-08-04T23:23:20.973Z",
               "StackName": "my-encrypted-bucket-stack",
               "LogicalResourceId": "EncryptedS3Bucket",
               "PhysicalResourceId": "encryptedbucket-us-west-2-123456789012",
               "ResourceType": "AWS::S3::Bucket",
               "Timestamp": "2021-08-04T23:23:20.973000+00:00",
               "ResourceStatus": "CREATE_COMPLETE",
               "ResourceProperties": "{\"BucketName\":\"encryptedbucket-us-west-2-123456789012\",\"BucketEncryption\":{\"ServerSideEncryptionConfiguration\":[{\"BucketKeyEnabled\":\"true\",\"ServerSideEncryptionByDefault\":{\"SSEAlgorithm\":\"aws:kms\",\"KMSMasterKeyID\":\"ENCRYPTION_KEY_ARN\"}}]}}",
               "ClientRequestToken": "Console-CreateStack-39df35ac-ca00-b7f6-5661-4e917478d075"
           },
           {
               "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/my-encrypted-bucket-stack/82a97150-f57a-11eb-8eb2-06a6bdcc7779",
               "EventId": "EncryptedS3Bucket-CREATE_IN_PROGRESS-2021-08-04T23:22:59.410Z",
               "StackName": "my-encrypted-bucket-stack",
               "LogicalResourceId": "EncryptedS3Bucket",
               "PhysicalResourceId": "encryptedbucket-us-west-2-123456789012",
               "ResourceType": "AWS::S3::Bucket",
               "Timestamp": "2021-08-04T23:22:59.410000+00:00",
               "ResourceStatus": "CREATE_IN_PROGRESS",
               "ResourceStatusReason": "Resource creation Initiated",
               "ResourceProperties": "{\"BucketName\":\"encryptedbucket-us-west-2-123456789012\",\"BucketEncryption\":{\"ServerSideEncryptionConfiguration\":[{\"BucketKeyEnabled\":\"true\",\"ServerSideEncryptionByDefault\":{\"SSEAlgorithm\":\"aws:kms\",\"KMSMasterKeyID\":\"ENCRYPTION_KEY_ARN\"}}]}}",
               "ClientRequestToken": "Console-CreateStack-39df35ac-ca00-b7f6-5661-4e917478d075"
           },
           {
               "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/my-encrypted-bucket-stack/82a97150-f57a-11eb-8eb2-06a6bdcc7779",
               "EventId": "EncryptedS3Bucket-6516081f-c1f2-4bfe-a0f0-cefa28679994",
               "StackName": "my-encrypted-bucket-stack",
               "LogicalResourceId": "EncryptedS3Bucket",
               "PhysicalResourceId": "",
               "ResourceType": "AWS::S3::Bucket",
               "Timestamp": "2021-08-04T23:22:58.349000+00:00",
               "ResourceStatus": "CREATE_IN_PROGRESS",
               "ResourceStatusReason": "Hook invocations complete.  Resource creation initiated",
               "ClientRequestToken": "Console-CreateStack-39df35ac-ca00-b7f6-5661-4e917478d075"
           },
       ...
       ]
   }
   ```

   *Results*: CloudFormation successfully created the stack. The Hook's logic verified that the `AWS::S3::Bucket` resource contained server-side encryption before provisioning the resource.

### Example 2: To provision a stack
<a name="provision-a-stack-example-2"></a>

**Provision a non-compliant stack**

1. Author a template that specifies an S3 bucket. For example `aes256-bucket.yml`.

   ```
   AWSTemplateFormatVersion: 2010-09-09
   Description: |
     This CloudFormation template provisions an encrypted S3 Bucket
   Resources:
     EncryptedS3Bucket:
       Type: AWS::S3::Bucket
       Properties:
         BucketName: !Sub encryptedbucket-${AWS::Region}-${AWS::AccountId}
         BucketEncryption:
           ServerSideEncryptionConfiguration:
             - ServerSideEncryptionByDefault:
                 SSEAlgorithm: AES256
               BucketKeyEnabled: true
   Outputs:
     EncryptedBucketName:
       Value: !Ref EncryptedS3Bucket
   ```

1. Create a stack, and specify your template in the AWS CLI. In the following example, specify the stack name as `my-hook-stack` and the template name as `aes256-bucket.yml`.

   ```
   $ aws cloudformation create-stack \
     --stack-name my-hook-stack \
     --template-body file://aes256-bucket.yml
   ```

1. (Optional) View your stack progress by specifying your stack name. In the following example, specify the stack name `my-hook-stack`.

   ```
   $ aws cloudformation describe-stack-events \
     --stack-name my-hook-stack
   ```

   Use the `describe-stack-events` operation to see the Hook failure while creating the bucket. The following is an example output of the command.

   ```
   {
       "StackEvents": [
       ...
           {
               "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/my-hook-stack/2c693970-f57e-11eb-a0fb-061a2a83f0b9",
               "EventId": "S3Bucket-CREATE_FAILED-2021-08-04T23:47:03.305Z",
               "StackName": "my-hook-stack",
               "LogicalResourceId": "S3Bucket",
               "PhysicalResourceId": "",
               "ResourceType": "AWS::S3::Bucket",
               "Timestamp": "2021-08-04T23:47:03.305000+00:00",
               "ResourceStatus": "CREATE_FAILED",
               "ResourceStatusReason": "The following hook(s) failed: [MyCompany::Testing::MyTestHook]",
               "ResourceProperties": "{}",
               "ClientRequestToken": "Console-CreateStack-abe71ac2-ade4-a762-0499-8d34d91d6a92"
           },
       ...
       ]
   }
   ```

   *Results*: The Hook invocation failed the stack configuration and stopped the resource from provisioning. The stack failed due to the S3 bucket encryption configured incorrectly. The Hook type configuration requires `aws:kms` while this bucket uses `AES256`.

**Use a CloudFormation template to pass Hook validation**

1. To create a stack and pass the Hook validation, update the template so that your resource uses an encrypted S3 bucket. This example uses the template `kms-bucket-and-queue.yml`.

   ```
   AWSTemplateFormatVersion: 2010-09-09
   Description: |
     This CloudFormation template provisions an encrypted S3 Bucket
   Resources:
     EncryptedS3Bucket:
       Type: AWS::S3::Bucket
       Properties:
         BucketName: !Sub encryptedbucket-${AWS::Region}-${AWS::AccountId}
         BucketEncryption:
           ServerSideEncryptionConfiguration:
             - ServerSideEncryptionByDefault:
                 SSEAlgorithm: 'aws:kms'
                 KMSMasterKeyID: !Ref EncryptionKey
               BucketKeyEnabled: true
     EncryptedQueue:
       Type: AWS::SQS::Queue
       Properties:
         QueueName: !Sub encryptedqueue-${AWS::Region}-${AWS::AccountId}
         KmsMasterKeyId: !Ref EncryptionKey
     EncryptionKey:
       Type: AWS::KMS::Key
       DeletionPolicy: Retain
       Properties:
         Description: KMS key used to encrypt the resource type artifacts
         EnableKeyRotation: true
         KeyPolicy:
           Version: 2012-10-17
           Statement:
             - Sid: Enable full access for owning account
               Effect: Allow
               Principal:
                 AWS: !Ref AWS::AccountId
               Action: 'kms:*'
               Resource: '*'
   Outputs:
     EncryptedBucketName:
       Value: !Ref EncryptedS3Bucket
     EncryptedQueueName:
       Value: !Ref EncryptedQueue
   ```
**Note**  
Hooks won't be invoked for skipped resources.

1. Create a stack and specify your template. In this example, the stack name is `my-encrypted-bucket-stack`.

   ```
   $ aws cloudformation create-stack \
     --stack-name my-encrypted-bucket-stack \
     --template-body file://kms-bucket-and-queue.yml
   ```

1. (Optional) View your stack progress by specifying the stack name.

   ```
   $ aws cloudformation describe-stack-events \
     --stack-name my-encrypted-bucket-stack
   ```

   Use the `describe-stack-events` command to view the response. The following is an example of the `describe-stack-events` command.

   ```
   {
       "StackEvents": [
       ...
           {
               "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/my-encrypted-bucket-stack/82a97150-f57a-11eb-8eb2-06a6bdcc7779",
               "EventId": "EncryptedS3Bucket-CREATE_COMPLETE-2021-08-04T23:23:20.973Z",
               "StackName": "my-encrypted-bucket-stack",
               "LogicalResourceId": "EncryptedS3Bucket",
               "PhysicalResourceId": "encryptedbucket-us-west-2-123456789012",
               "ResourceType": "AWS::S3::Bucket",
               "Timestamp": "2021-08-04T23:23:20.973000+00:00",
               "ResourceStatus": "CREATE_COMPLETE",
               "ResourceProperties": "{\"BucketName\":\"encryptedbucket-us-west-2-123456789012\",\"BucketEncryption\":{\"ServerSideEncryptionConfiguration\":[{\"BucketKeyEnabled\":\"true\",\"ServerSideEncryptionByDefault\":{\"SSEAlgorithm\":\"aws:kms\",\"KMSMasterKeyID\":\"ENCRYPTION_KEY_ARN\"}}]}}",
               "ClientRequestToken": "Console-CreateStack-39df35ac-ca00-b7f6-5661-4e917478d075"
           },
           {
               "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/my-encrypted-bucket-stack/82a97150-f57a-11eb-8eb2-06a6bdcc7779",
               "EventId": "EncryptedS3Bucket-CREATE_IN_PROGRESS-2021-08-04T23:22:59.410Z",
               "StackName": "my-encrypted-bucket-stack",
               "LogicalResourceId": "EncryptedS3Bucket",
               "PhysicalResourceId": "encryptedbucket-us-west-2-123456789012",
               "ResourceType": "AWS::S3::Bucket",
               "Timestamp": "2021-08-04T23:22:59.410000+00:00",
               "ResourceStatus": "CREATE_IN_PROGRESS",
               "ResourceStatusReason": "Resource creation Initiated",
               "ResourceProperties": "{\"BucketName\":\"encryptedbucket-us-west-2-123456789012\",\"BucketEncryption\":{\"ServerSideEncryptionConfiguration\":[{\"BucketKeyEnabled\":\"true\",\"ServerSideEncryptionByDefault\":{\"SSEAlgorithm\":\"aws:kms\",\"KMSMasterKeyID\":\"ENCRYPTION_KEY_ARN\"}}]}}",
               "ClientRequestToken": "Console-CreateStack-39df35ac-ca00-b7f6-5661-4e917478d075"
           },
           {
               "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/my-encrypted-bucket-stack/82a97150-f57a-11eb-8eb2-06a6bdcc7779",
               "EventId": "EncryptedS3Bucket-6516081f-c1f2-4bfe-a0f0-cefa28679994",
               "StackName": "my-encrypted-bucket-stack",
               "LogicalResourceId": "EncryptedS3Bucket",
               "PhysicalResourceId": "",
               "ResourceType": "AWS::S3::Bucket",
               "Timestamp": "2021-08-04T23:22:58.349000+00:00",
               "ResourceStatus": "CREATE_IN_PROGRESS",
               "ResourceStatusReason": "Hook invocations complete.  Resource creation initiated",
               "ClientRequestToken": "Console-CreateStack-39df35ac-ca00-b7f6-5661-4e917478d075"
           },
       ...
       ]
   }
   ```

   *Results*: CloudFormation successfully created the stack. The Hook's logic verified that the `AWS::S3::Bucket` resource contained server-side encryption before provisioning the resource.

# Updating a custom Hook
<a name="updating-registered-hook"></a>

Updating a custom Hook allows revisions in the Hook to be made available in the CloudFormation registry.

To update a custom Hook, submit your revisions to the CloudFormation registry through the CloudFormation CLI [https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-cli-submit.html](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-cli-submit.html) operation.

```
$ cfn submit
```

To specify the default version of your Hook in your account, use the [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/set-type-default-version.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/set-type-default-version.html) command and specify the type, type name, and version ID.

```
$ aws cloudformation set-type-default-version \
    --type HOOK \
    --type-name MyCompany::Testing::MyTestHook \
    --version-id 00000003
```

To retrieve information about the versions of a Hook, use [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/list-type-versions.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/list-type-versions.html).

```
$ aws cloudformation list-type-versions \
  --type HOOK \
  --type-name "MyCompany::Testing::MyTestHook"
```

# Deregistering a custom Hook from the CloudFormation registry
<a name="deregistering-hooks"></a>

Deregistering a custom Hook marks the extension or extension version as `DEPRECATED` in the CloudFormation registry, which removes it from active use. Once deprecated, the custom Hook can't be used in a CloudFormation operation.

**Note**  
Before deregistering the Hook, you must individually deregister all previous active versions of that extension. For more information, see [https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_DeregisterType.html](https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_DeregisterType.html).

To deregister a Hook, use the [https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deregister-type.html](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deregister-type.html) operation and specify your Hook ARN.

```
$ aws cloudformation deregister-type \
    --arn HOOK_TYPE_ARN
```

This command doesn't produce an output.

# Publishing Hooks for public use
<a name="hooks-publishing"></a>

To develop a public third-party Hook, develop your Hook as a private extension. Then, in each AWS Region in which you want to make the extension publicly available: 

1. Register your Hook as a private extension in the CloudFormation registry.

1. Test your Hook to make sure it meets all necessary requirements for being published in the CloudFormation registry.

1. Publish your Hook to the CloudFormation registry.
**Note**  
Before you publish any extension in a given Region, you must first register as an extension publisher in that Region. To do this in multiple Regions simultaneously, see [Publishing extensions in multiple Regions using StackSets](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/publish-extension-stacksets.html) in the * CloudFormation CLI User Guide*.

After you've developed and registered your Hook, you can make it publicly available to general CloudFormation users by *publishing* it to the CloudFormation registry, as a third-party public extension.

Public third-party Hooks enable you to offer CloudFormation users to proactively inspect the configuration of AWS resources before provisioning. As with private Hooks, public Hooks are treated the same as any Hook published by AWS within CloudFormation.

Hooks published to the registry are visible by all CloudFormation users in the AWS Regions in which they're published. Users can then *activate* your extension in their account, which makes it available for use in their templates. For more information, see [Use third-party public extensions from the CloudFormation registry](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/registry-public.html) in the *CloudFormation User Guide*.

# Testing a custom Hook for public use
<a name="hooks-testing-registered-hooks"></a>

In order to publish your registered custom Hook, it must pass all test requirements defined for it. The following is a list of requirements needed before publishing your custom Hook as a third-party extension.

Each handler and target is tested twice. Once for `SUCCESS` and once for `FAILED`.
+ For `SUCCESS` response case:
  + Status must be `SUCCESS`.
  + Must not return an error code.
  + Callback delay should be set to `0` seconds, if specified.
+ For `FAILED` response case:
  + Status must be `FAILED`.
  + Must return an error code.
  + Must have a message in response.
  + Callback delay should be set to `0` seconds, if specified.
+ For `IN_PROGRESS` response case:
  + Must not return an error code.
  + `Result` field must not be set in response.

# Specifying input data for use in contract tests
<a name="hooks-input-data-contract-test"></a>

By default, the CloudFormation performs contract tests using input properties generated from the patterns you define in your Hook schema. However, most Hooks are complex enough that the input properties for precreating or preupdating provisioning stacks requires an understanding of the resource being provisioned. To address this, you can specify the input the CloudFormation uses when performing its contract tests.

CloudFormation offers two ways for you to specify the input data for it to use when performing contract tests:
+ Overrides file

  Using an `overrides` file provides a light-weight way of specifying input data for certain specific properties for the CloudFormation to use during `preCreate`, `preUpdate` and `preDelete` operations testing.
+ Input files

  You can also use multiple `input` files to specify contract test input data if:
  + You want or need to specify different input data for create, update, and delete operations, or invalid data with which to test.
  + You want to specify multiple different input data sets.

## Specifying input data using an override file
<a name="hook-override-inputs"></a>

The following is an example of Amazon S3 Hook's input data using the `overrides` file.

```
{
    "CREATE_PRE_PROVISION": {
        "AWS::S3::Bucket": {
            "resourceProperties": {
                "/BucketName": "encryptedbucket-us-west-2-contractor",
                "/BucketEncryption/ServerSideEncryptionConfiguration": [
                    {
                        "BucketKeyEnabled": true,
                        "ServerSideEncryptionByDefault": {
                            "KMSMasterKeyID": "KMS-KEY-ARN",
                            "SSEAlgorithm": "aws:kms"
                        }
                    }
                ]
            }
        },
        "AWS::SQS::Queue": {
            "resourceProperties": {
                "/QueueName": "MyQueueContract",
                "/KmsMasterKeyId": "hellocontract"
            }
        }
    },
    "UPDATE_PRE_PROVISION": {
        "AWS::S3::Bucket": {
            "resourceProperties": {
                "/BucketName": "encryptedbucket-us-west-2-contractor",
                "/BucketEncryption/ServerSideEncryptionConfiguration": [
                    {
                        "BucketKeyEnabled": true,
                        "ServerSideEncryptionByDefault": {
                            "KMSMasterKeyID": "KMS-KEY-ARN",
                            "SSEAlgorithm": "aws:kms"
                        }
                    }
                ]
            },
            "previousResourceProperties": {
                "/BucketName": "encryptedbucket-us-west-2-contractor",
                "/BucketEncryption/ServerSideEncryptionConfiguration": [
                    {
                        "BucketKeyEnabled": true,
                        "ServerSideEncryptionByDefault": {
                            "KMSMasterKeyID": "KMS-KEY-ARN",
                            "SSEAlgorithm": "aws:kms"
                        }
                    }
                ]
            }
        }
    },
    "INVALID_UPDATE_PRE_PROVISION": {
        "AWS::S3::Bucket": {
            "resourceProperties": {
                "/BucketName": "encryptedbucket-us-west-2-contractor",
                "/BucketEncryption/ServerSideEncryptionConfiguration": [
                    {
                        "BucketKeyEnabled": true,
                        "ServerSideEncryptionByDefault": {
                            "KMSMasterKeyID": "KMS-KEY-ARN",
                            "SSEAlgorithm": "AES256"
                        }
                    }
                ]
            },
            "previousResourceProperties": {
                "/BucketName": "encryptedbucket-us-west-2-contractor",
                "/BucketEncryption/ServerSideEncryptionConfiguration": [
                    {
                        "BucketKeyEnabled": true,
                        "ServerSideEncryptionByDefault": {
                            "KMSMasterKeyID": "KMS-KEY-ARN",
                            "SSEAlgorithm": "aws:kms"
                        }
                    }
                ]
            }
        }
    },
    "INVALID": {
        "AWS::SQS::Queue": {
            "resourceProperties": {
                "/QueueName": "MyQueueContract",
                "/KmsMasterKeyId": "KMS-KEY-ARN"
            }
        }
    }
}
```

## Specifying input data using input files
<a name="hook-test-inputs"></a>

Use `input` files to specify different kinds of input data for the CloudFormation to use: `preCreate` input, `preUpdate` input, and invalid input. Each kind of data is specified in a separate file. You can also specify multiple sets of input data for contract tests.

To specify `input` files for the CloudFormation to use in contract testing, add an `inputs` folder to the root directory of your Hooks project. Then add your input files.

Specify which kind of input data a file contains by using the following naming conventions, where **n** is an integer:
+ `inputs_n_pre_create.json`: Use files with `preCreate` handlers for specifying inputs for creating the resource.
+ `inputs_n_pre_update.json`: Use files with `preUpdate` handlers for specifying inputs for updating the resource.
+ `inputs_n_pre_delete.json`: Use files with `preDelete` handlers for specifying inputs for deleting the resource.
+ `inputs_n_invalid.json`: For specifying invalid inputs to test.

To specify multiple sets of input data for contract tests, increment the integer in the file names to order your input data sets. For example, your first set of input files should be named `inputs_1_pre_create.json`, `inputs_1_pre_update.json`, and `inputs_1_pre_invalid.json`. Your next set would be named `inputs_2_pre_create.json`, `inputs_2_pre_update.json`, and `inputs_2_pre_invalid.json`, and so on.

Each input file is a JSON file containing only the resource properties to be used in testing.

The following is an example directory for `inputs` for Amazon S3 specifying input data using input files.

`inputs_1_pre_create.json`  <a name="inputs_1_pre_create.json"></a>
The following is an example of the `inputs_1_pre_create.json` contract test.  

```
{
    "AWS::S3::Bucket": {
        "resourceProperties": {
            "AccessControl": "BucketOwnerFullControl",
            "AnalyticsConfigurations": [],
            "BucketEncryption": {
                "ServerSideEncryptionConfiguration": [
                    {
                        "BucketKeyEnabled": true,
                        "ServerSideEncryptionByDefault": {
                            "KMSMasterKeyID": "KMS-KEY-ARN",
                            "SSEAlgorithm": "aws:kms"
                        }
                    }
                ]
            },
            "BucketName": "encryptedbucket-us-west-2"
        }
    },
    "AWS::SQS::Queue": {
        "resourceProperties": {
            "QueueName": "MyQueue",
            "KmsMasterKeyId": "KMS-KEY-ARN"
        }
    }
}
```

`inputs_1_pre_update.json`  <a name="inputs_1_pre_update.json"></a>
The following is an example of the `inputs_1_pre_update.json` contract test.  

```
{
    "AWS::S3::Bucket": {
        "resourceProperties": {
            "BucketEncryption": {
                "ServerSideEncryptionConfiguration": [
                    {
                        "BucketKeyEnabled": true,
                        "ServerSideEncryptionByDefault": {
                            "KMSMasterKeyID": "KMS-KEY-ARN",
                            "SSEAlgorithm": "aws:kms"
                        }
                    }
                ]
            },
            "BucketName": "encryptedbucket-us-west-2"
        },
        "previousResourceProperties": {
            "BucketEncryption": {
                "ServerSideEncryptionConfiguration": [
                    {
                        "BucketKeyEnabled": true,
                        "ServerSideEncryptionByDefault": {
                            "KMSMasterKeyID": "KMS-KEY-ARN",
                            "SSEAlgorithm": "aws:kms"
                        }
                    }
                ]
            },
            "BucketName": "encryptedbucket-us-west-2"
        }
    }
}
```

`inputs_1_invalid.json`  <a name="inputs_1_invalid.json"></a>
The following is an example of the `inputs_1_invalid.json` contract test.  

```
{
    "AWS::S3::Bucket": {
        "resourceProperties": {
            "AccessControl": "BucketOwnerFullControl",
            "AnalyticsConfigurations": [],
            "BucketEncryption": {
                "ServerSideEncryptionConfiguration": [
                    {
                        "ServerSideEncryptionByDefault": {
                            "SSEAlgorithm": "AES256"
                        }
                    }
                ]
            },
            "BucketName": "encryptedbucket-us-west-2"
        }
    },
    "AWS::SQS::Queue": {
        "resourceProperties": {
            "NotValid": "The property of this resource is not valid."
        }
    }
}
```

`inputs_1_invalid_pre_update.json`  <a name="inputs_1_invalid_pre_update.json"></a>
The following is an example of the `inputs_1_invalid_pre_update.json` contract test.  

```
{
    "AWS::S3::Bucket": {
        "resourceProperties": {
            "BucketEncryption": {
                "ServerSideEncryptionConfiguration": [
                    {
                        "BucketKeyEnabled": true,
                        "ServerSideEncryptionByDefault": {
                            "KMSMasterKeyID": "KMS-KEY-ARN",
                            "SSEAlgorithm": "AES256"
                        }
                    }
                ]
            },
            "BucketName": "encryptedbucket-us-west-2"
        },
        "previousResourceProperties": {
            "BucketEncryption": {
                "ServerSideEncryptionConfiguration": [
                    {
                        "BucketKeyEnabled": true,
                        "ServerSideEncryptionByDefault": {
                            "KMSMasterKeyID": "KMS-KEY-ARN",
                            "SSEAlgorithm": "aws:kms"
                        }
                    }
                ]
            },
            "BucketName": "encryptedbucket-us-west-2"
        }
    }
}
```

For more information, see [Publishing extensions to make them available for public use](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/publish-extension.html) in the * CloudFormation CLI User Guide*.

# Schema syntax reference for CloudFormation Hooks
<a name="hooks-schema"></a>

This section describes the syntax of the schema that you use to develop CloudFormation Hooks.

A Hook includes a Hook specification represented by a JSON schema and Hook handlers. The first step in creating a custom Hook is modeling a schema that defines the Hook, its properties, and their attributes. When you initialize a custom Hook project using the CloudFormation CLI [https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-cli-init.html](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-cli-init.html) command, a Hook schema file is created for you. Use this schema file as a starting point for defining the shape and semantics of your custom Hook.

## Schema syntax
<a name="schema-syntax"></a>

The following schema is the structure for a Hook.

```
{
"typeName": "string",
    "description": "string",
    "sourceUrl": "string",
    "documentationUrl": "string",
    "definitions": {
        "definitionName": {
          . . .
        }
    },
    "typeConfiguration": {
        "properties": {
             "propertyName": {
                "description": "string",
                "type": "string",
                 . . .
            },
        },
    "required": [
        "propertyName"
         . . .
            ],
    "additionalProperties": false
    },
    "handlers": {
        "preCreate": {
            "targetNames": [
            ],
            "permissions": [
            ]
        },
        "preUpdate": {
            "targetNames": [
            ],
            "permissions": [
            ]
        },
        "preDelete": {
            "targetNames": [
            ],
            "permissions": [
            ]
        }
   },
   "additionalProperties": false
}
```

`typeName`  <a name="hooks-properties-typeName"></a>
The unique name for your Hook. Specifies a three-part namespace for your Hook, with a recommended pattern of `Organization::Service::Hook`.  
The following organization namespaces are reserved and can't be used in your Hook type names:  
+ `Alexa`
+ `AMZN`
+ `Amazon`
+ `ASK`
+ `AWS`
+ `Custom`
+ `Dev`
*Required*: Yes  
 *Pattern*: `^[a-zA-Z0-9]{2,64}::[a-zA-Z0-9]{2,64}::[a-zA-Z0-9]{2,64}$`  
*Minimum*: `10`  
*Maximum*: `196`

`description`  <a name="hooks-properties-description"></a>
A short description of the Hook that's displayed in the CloudFormation console.  
*Required*: Yes

`sourceUrl`  <a name="hooks-properties-sourceUrl"></a>
The URL of the source code for the Hook, if public.  
*Required*: No  
*Maximum*: `4096`

`documentationUrl`  <a name="hooks-properties-documentationurl"></a>
The URL of a page providing detailed documentation for the Hook.  
*Required*: Yes  
*Pattern*: `^https\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])(\:[0-9]*)*([\?/#].*)?$`  
*Maximum*: `4096`  
Although the Hook schema should include complete and accurate property descriptions, you can use the `documentationURL` property to provide users with more details, including examples, use cases, and other detailed information.

`definitions`  <a name="hooks-properties-definitions"></a>
Use the `definitions` block to provide shared Hook property schemas.  
It's considered a best practice to use the `definitions` section to define schema elements that can be used at multiple points in your Hook type schema. You can then use a JSON pointer to reference that element at the appropriate places in your Hook type schema.  
*Required*: No

`typeConfiguration`  <a name="hooks-properties-typeconfiguration"></a>
The definition of a Hook’s configuration data.  
*Required*: Yes

`properties`  <a name="hooks-properties-properties"></a>
The properties of the Hook. All properties of a Hook must be expressed in the schema. Align the Hook schema properties with the Hook type configuration properties.  
Nested properties aren't allowed. Instead, define any nested properties in the `definitions` element, and use a `$ref` pointer to reference them in the desired property.
The following properties are currently supported:  
+ `default` – The default value of the property.
+ `description` – A description of the property.
+ `pattern` – A regex pattern used for validating the input.
+ `type` – The accepted type of the property.

`additionalProperties`  <a name="hooks-properties-additionalproperties"></a>
`additionalProperties` must be set to `false`. All properties of a Hook must be expressed in the schema: arbitrary inputs aren't allowed.  
*Required*: Yes  
*Valid values*: `false`

`handlers`  <a name="hooks-properties-handlers"></a>
Handlers specify the operations which can initiate the Hook defined in the schema, such as Hook invocation points. For example, a `preUpdate` handler is invoked before the update operations for all specified targets in the handler.  
*Valid values*: `preCreate` \$1 `preUpdate` \$1 `preDelete`  
At least one value must be specified for the handler.
Stack operations that result in the status of `UpdateCleanup` do not invoke a Hook. For example, during the following two scenarios, the Hook's `preDelete` handler is not invoked:  
+ the stack is updated after removing one resource from the template.
+ a resource with the update type of [replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-replacement) is deleted.

`targetNames`  <a name="hooks-properties-targetNames"></a>
A string array of type names that Hook targets. For example, if a `preCreate` handler has an `AWS::S3::Bucket` target, the Hook runs for Amazon S3 buckets during the preprovisioning phase.  
+ `TargetName`

  Specify at least one target name for each implemented handler.

  *Pattern*: `^[a-zA-Z0-9]{2,64}::[a-zA-Z0-9]{2,64}::[a-zA-Z0-9]{2,64}$`

  *Minimum*: `1`

  *Required*: Yes
**Warning**  
SSM SecureString and Secrets Manager dynamic references are not resolved before they are passed to Hooks.

`permissions`  <a name="hooks-properties-permissions"></a>
A string array that specifies the AWS permissions needed to invoke the handler.  
*Required*: Yes

`additionalProperties`  <a name="hooks-additional-properties"></a>
`additionalProperties` must be set to `false`. All properties of a Hook must be expressed in the schema: arbitrary inputs aren't allowed.  
*Required*: Yes  
*Valid values*: `false`

## Example Hooks schemas
<a name="example-hooks"></a>

 **Example 1** 

The Java and the Python walkthroughs use the following code example. The following is an example structure for a Hook called `mycompany-testing-mytesthook.json`.

```
{
    "typeName":"MyCompany::Testing::MyTestHook",
    "description":"Verifies S3 bucket and SQS queues properties before create and update",
    "sourceUrl":"https://mycorp.com/my-repo.git",
    "documentationUrl":"https://mycorp.com/documentation",
    "typeConfiguration":{
        "properties":{
            "minBuckets":{
                "description":"Minimum number of compliant buckets",
                "type":"string"
            },
            "minQueues":{
                "description":"Minimum number of compliant queues",
                "type":"string"
            },
            "encryptionAlgorithm":{
                "description":"Encryption algorithm for SSE",
                "default":"AES256",
                "type":"string",
                "pattern": "[a-zA-Z]*[1-9]"
            }
        },
        "required":[
            
        ],
        "additionalProperties":false
    },
    "handlers":{
        "preCreate":{
            "targetNames":[
                "AWS::S3::Bucket",
                "AWS::SQS::Queue"
            ],
            "permissions":[
                
            ]
        },
        "preUpdate":{
            "targetNames":[
                "AWS::S3::Bucket",
                "AWS::SQS::Queue"
            ],
            "permissions":[
                
            ]
        },
        "preDelete":{
            "targetNames":[
                "AWS::S3::Bucket",
                "AWS::SQS::Queue"
            ],
            "permissions":[
                "s3:ListBucket",
                "s3:ListAllMyBuckets",
                "s3:GetEncryptionConfiguration",
                "sqs:ListQueues",
                "sqs:GetQueueAttributes",
                "sqs:GetQueueUrl"
            ]
        }
    },
    "additionalProperties":false
}
```

 **Example 2** 

The following example is a schema that uses the `STACK` and `CHANGE_SET` for `targetNames` to target a stack template and a change set operation.

```
{
    "typeName":"MyCompany::Testing::MyTestHook",
    "description":"Verifies Stack and Change Set properties before create and update",
    "sourceUrl":"https://mycorp.com/my-repo.git",
    "documentationUrl":"https://mycorp.com/documentation",
    "typeConfiguration":{
        "properties":{
            "minBuckets":{
                "description":"Minimum number of compliant buckets",
                "type":"string"
            },
            "minQueues":{
                "description":"Minimum number of compliant queues",
                "type":"string"
            },
            "encryptionAlgorithm":{
                "description":"Encryption algorithm for SSE",
                "default":"AES256",
                "type":"string",
                "pattern": "[a-zA-Z]*[1-9]"
            }
        },
        "required":[
        ],
        "additionalProperties":false
    },
    "handlers":{
        "preCreate":{
            "targetNames":[
                "STACK",
                "CHANGE_SET"
            ],
            "permissions":[  
            ]
        },
        "preUpdate":{
            "targetNames":[
                "STACK"
            ],
            "permissions":[
            ]
        },
        "preDelete":{
            "targetNames":[
                "STACK"
            ],
            "permissions":[
                
            ]
        }
    },
    "additionalProperties":false
}
```