Repository: aws-cloudformation/cloudformation-cli

Issue:
Allow specifying --[no-]-use-docker via CLI during packaging / submit
When building hooks or resources we often want to use a different build configuration on a developers desktop versus in CI. Today I can only specify whether or not builds should happen in a docker container during `cfn init` or by modifying the `.rpdk-config` file, which makes it difficult to configure developers to build using docker on their local machine but have our CI workflow build without docker, as the CI workflow may already be running in a container. I can achieve this today by writing a wrapper that injects different config depending on the build environment, but it would be much easier if I could override the `use_docker` flag at build / packaging time.

Example CLI usage:
```
# Create a new project, setting use_docker to true in the initial config
cfn init

# Validate and package using the project config (build in docker container)
cfn submit --dry-run

# Validate and package without using docker, overriding the project settings, e.g. in a CI container
cfn submit --dry-run --no-use-docker
```

Discussion:

Patch:
diff --git a/doc_source/resource-type-walkthrough.md b/doc_source/resource-type-walkthrough.md
index d7e302e..774297a 100755
--- a/doc_source/resource-type-walkthrough.md
+++ b/doc_source/resource-type-walkthrough.md
@@ -4,9 +4,12 @@ In this walkthrough, we'll use the CloudFormation CLI to create a sample resourc
 
 ## Prerequisites<a name="resource-type-walkthrough-prereqs"></a>
 
-For purposes of this walkthrough, it's assumed you have already set up the CloudFormation CLI and associated tooling for your Java development environment:
-
-[Setting up your environment for developing extensions](what-is-cloudformation-cli.md#resource-type-setup)
+- For this walkthrough, you'll need Java 8\. You can also choose to use Java 11 instead, provided that you make the following changes to files you'll use later on as part of this example:
+  - you'll need to change `"runtime": "java8",` to `"runtime": "java11",` in the `.rpdk.config` file;
+  - you'll need to change both occurrences of `Runtime: java8` to `Runtime: java11` in the `template.yml` file;
+  - you'll need to comment out both the `<maven.compiler.source>1.8</maven.compiler.source>` and `<maven.compiler.target>1.8</maven.compiler.target>` lines in the `pom.xml` file, and  add a `<release>11</release>` line inside the `<configuration>` block for the [maven-compiler-plugin](https://maven.apache.org/plugins/maven-compiler-plugin/examples/set-compiler-release.html) in the same `pom.xml` file\.
+- For purposes of this walkthrough, it's assumed you have already set up the CloudFormation CLI and associated tooling for your Java development environment: [Setting up your environment for developing extensions](what-is-cloudformation-cli.md#resource-type-setup)
+- This walkthrough uses an AMI that requires [a subscription](https://aws.amazon.com/marketplace/server/procurement?productId=7d426cb7-9522-4dd7-a56b-55dd8cc1c8d0) before you can use it with your account\.
 
 ## Create the resource type development project<a name="resource-type-walkthrough-model"></a>
 
@@ -24,10 +27,19 @@ This walkthrough uses the Community Edition of the [IntelliJ IDEA](https://www.j
    Initializing new project
    ```
 
-1. The `init` command launches a wizard that walks you through setting up the project, including specifying the resource name\. For this walkthrough, specify `Example::Testing::WordPress`\.
+1. The `init` command launches a wizard that walks you through setting up the project, including specifying the resource name\.  Start by specifying that you want to create a resource\.
+
+   ```
+   Do you want to develop a new resource(r) or a module(m) or a hook(h)?.
+   >> r
+   ```
+
+1. For this walkthrough, specify `Example::Testing::WordPress`\.
 
    ```
-   Enter resource type identifier (Organization::Service::Resource): Example::Testing::WordPress
+   What's the name of your resource type?
+   (Organization::Service::Resource)
+   >> Example::Testing::WordPress
    ```
 
    The wizard then enables you to select the appropriate language plugin\. Currently, the only language plugin available is for Java:
@@ -36,10 +48,21 @@ This walkthrough uses the Community Edition of the [IntelliJ IDEA](https://www.j
    One language plugin found, defaulting to java
    ```
 
-1. Specify the package name\. For this walkthrough, use `com.example.testing.wordpress`
+1. Specify the package name\. For this walkthrough, use `com.example.testing.wordpress`\.
 
    ```
-   Enter a package name (empty for default 'com.example.testing.wordpress'): com.example.testing.wordpress
+   Enter a package name (empty for default 'com.example.testing.wordpress'):
+   >> com.example.testing.wordpress
+   Initialized a new project in /workplace/tobflem/example-testing-wordpress
+   ```
+
+1. Select the default codegen model\.
+
+   ```
+   Choose codegen model - 1 (default) or 2 (guided-aws):
+   >> 1
+   Could not find specified format 'date-time' for type 'string'. Defaulting to 'String'
+   Could not find specified format 'date-time' for type 'string'. Defaulting to 'String'
    Initialized a new project in /workplace/tobflem/example-testing-wordpress
    ```
 
@@ -48,9 +71,12 @@ Initiating the project includes generating the files needed to develop the resou
 ```
 $ ls -1
 README.md
+docs
 example-testing-wordpress.json
+example_inputs
 lombok.config
 pom.xml
+resource-role.yaml
 rpdk.log
 src
 target
@@ -86,7 +112,7 @@ When you initiate the resource type project, an example resource type schema fil
    ```
    {
      "typeName": "Example::Testing::WordPress",
-     "description": "An example resource that creates a website based on WordPress 5.2.2.",
+     "description": "An example resource that creates a website based on WordPress 6.1.1.",
      "sourceUrl": "https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-walkthrough.html",
      "properties": {
        "Name": {
@@ -141,7 +167,6 @@ When you initiate the resource type project, an example resource type schema fil
      },
      "additionalProperties": false,
      "primaryIdentifier": [
-       "/properties/PublicIp",
        "/properties/InstanceId"
      ],
      "readOnlyProperties": [
@@ -193,6 +218,14 @@ For more information on how to add dependencies, see the [Maven documentation](h
 Depending on your IDE, you may have to take additional steps for your IDE to include the new dependency\.
 In IntelliJ IDEA, a dialog should appear to enable you to import these changes\. we recommend allowing automatic importing\.
 
+### Increase the memory size<a name="resource-type-walkthrough-increase-memory-size"></a>
+
+The request handler is limited to 256 MB of memory by default\. This needs to be increased\.
+
+1. Open the project's `template.yml` file in the project's root directory\.
+
+1. Find the `MemorySize` setting and change the value to `512`\.
+
 ### Implement the Create Handler<a name="resource-type-walkthrough-implement-create-handler"></a>
 
 With the necessary dependency specified, we can now start writing the handlers that actually implement the resource's functionality\. For our example resource, we'll implement just the `create` and `delete` operation handlers\.
@@ -237,45 +270,60 @@ The CallbackContext is modeled as a POJO so you can define what information you
    }
    ```
 
-#### Code the Create Handler<a name="resource-type-walkthrough-implement-create-handler-code"></a>
+#### Code the Base Handler<a name="resource-type-walkthrough-implement-base-handler-code"></a>
 
-1. In your IDE, open the `CreateHandler.java` file, located in the `src/main/java/com/example/testing/wordpress/CreateHandler.java` folder\.
+We'll create a base class for the handlers. This simply provides a place to put code that would otherwise be duplicated by the various handlers\.
 
-1. Replace the entire contents of the `CreateHandler.java` file with the following code\.
+1. In your IDE, create a file named `CustomBaseHandler.java` in the `src/main/java/com/example/testing/wordpress` folder\.  Paste the following code into the file, replacing any default code created by your IDE\.
 
    ```
    package com.example.testing.wordpress;
 
-   import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
-   import software.amazon.cloudformation.proxy.Logger;
-   import software.amazon.cloudformation.proxy.OperationStatus;
-   import software.amazon.cloudformation.proxy.ProgressEvent;
-   import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
    import com.amazonaws.services.ec2.AmazonEC2;
-   import com.amazonaws.services.ec2.AmazonEC2ClientBuilder;
-   import com.amazonaws.services.ec2.model.AuthorizeSecurityGroupIngressRequest;
-   import com.amazonaws.services.ec2.model.CreateSecurityGroupRequest;
-   import com.amazonaws.services.ec2.model.DeleteSecurityGroupRequest;
    import com.amazonaws.services.ec2.model.DescribeInstancesRequest;
    import com.amazonaws.services.ec2.model.DescribeInstancesResult;
-   import com.amazonaws.services.ec2.model.DescribeSubnetsRequest;
-   import com.amazonaws.services.ec2.model.DescribeSubnetsResult;
    import com.amazonaws.services.ec2.model.Instance;
-   import com.amazonaws.services.ec2.model.InstanceNetworkInterfaceSpecification;
-   import com.amazonaws.services.ec2.model.IpPermission;
-   import com.amazonaws.services.ec2.model.IpRange;
    import com.amazonaws.services.ec2.model.Reservation;
-   import com.amazonaws.services.ec2.model.RunInstancesRequest;
-   import com.amazonaws.services.ec2.model.Subnet;
-   import com.amazonaws.services.ec2.model.Tag;
-   import com.amazonaws.services.ec2.model.TagSpecification;
+   import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
 
    import java.util.List;
+
+   public abstract class CustomBaseHandler extends BaseHandler<CallbackContext> {
+
+       protected static final String SUPPORTED_REGION = "us-west-2";
+
+       protected static final Instance retrieveCurrentInstanceState(AmazonWebServicesClientProxy clientProxy, AmazonEC2 ec2Client, String instanceId) {
+           DescribeInstancesRequest describeInstancesRequest = new DescribeInstancesRequest().withInstanceIds(instanceId);
+           DescribeInstancesResult describeInstancesResult =
+                   clientProxy.injectCredentialsAndInvoke(describeInstancesRequest, (DescribeInstancesRequest r) -> ec2Client.describeInstances(r));
+           return describeInstancesResult.getReservations()
+                   .stream()
+                   .map(Reservation::getInstances)
+                   .flatMap(List::stream)
+                   .findFirst()
+                   .orElse(new Instance());
+       }
+   }
+   ```
+
+#### Code the Create Handler<a name="resource-type-walkthrough-implement-create-handler-code"></a>
+
+1. In your IDE, open the `CreateHandler.java` file, located in the `src/main/java/com/example/testing/wordpress` folder\.
+
+1. Replace the entire contents of the `CreateHandler.java` file with the following code\.
+
+   ```
+   package com.example.testing.wordpress;
+
+   import com.amazonaws.services.ec2.AmazonEC2;
+   import com.amazonaws.services.ec2.AmazonEC2ClientBuilder;
+   import com.amazonaws.services.ec2.model.*;
+   import software.amazon.cloudformation.proxy.*;
+
    import java.util.UUID;
 
-   public class CreateHandler extends BaseHandler<CallbackContext> {
-       private static final String SUPPORTED_REGION = "us-west-2";
-       private static final String WORDPRESS_AMI_ID = "ami-04fb0368671b6f138";
+   public class CreateHandler extends CustomBaseHandler {
+       private static final String WORDPRESS_AMI_ID = "ami-0039c114b5e564742";
        private static final String INSTANCE_TYPE = "m4.large";
        private static final String SITE_NAME_TAG_KEY = "Name";
        private static final String AVAILABLE_INSTANCE_STATE = "running";
@@ -314,11 +362,13 @@ The CallbackContext is modeled as a POJO so you can define what information you
            }
 
            if (instanceStateSoFar == null) {
+               Instance instance = createEC2Instance(model);
+               model.setInstanceId(instance.getInstanceId());
                return ProgressEvent.<ResourceModel, CallbackContext>builder()
                    .resourceModel(model)
                    .status(OperationStatus.IN_PROGRESS)
                    .callbackContext(CallbackContext.builder()
-                                                   .instance(createEC2Instance(model))
+                                                   .instance(instance)
                                                    .stabilizationRetriesRemaining(NUMBER_OF_STATE_POLL_RETRIES)
                                                    .build())
                    .build();
@@ -336,11 +386,12 @@ The CallbackContext is modeled as a POJO so you can define what information you
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
+               model.setInstanceId(instanceStateSoFar.getInstanceId());
                return ProgressEvent.<ResourceModel, CallbackContext>builder()
                    .resourceModel(model)
                    .status(OperationStatus.IN_PROGRESS)
                    .callbackContext(CallbackContext.builder()
-                                                   .instance(updatedInstanceProgress(instanceStateSoFar.getInstanceId()))
+                                                   .instance(retrieveCurrentInstanceState(clientProxy, ec2Client, instanceStateSoFar.getInstanceId()))
                                                    .stabilizationRetriesRemaining(callbackContext.getStabilizationRetriesRemaining() - 1)
                                                    .build())
                    .build();
@@ -407,7 +458,7 @@ The CallbackContext is modeled as a POJO so you can define what information you
                .withSubnetIds(subnetId);
 
            final DescribeSubnetsResult describeSubnetsResult =
-               clientProxy.injectCredentialsAndInvoke(describeSubnetsRequest, ec2Client::describeSubnets);
+               clientProxy.injectCredentialsAndInvoke(describeSubnetsRequest, (DescribeSubnetsRequest r) -> ec2Client.describeSubnets(r));
 
            return describeSubnetsResult.getSubnets()
                                        .stream()
@@ -438,20 +489,6 @@ The CallbackContext is modeled as a POJO so you can define what information you
                .withTags(new Tag().withKey(SITE_NAME_TAG_KEY).withValue(siteName));
        }
 
-       private Instance updatedInstanceProgress(String instanceId) {
-           DescribeInstancesRequest describeInstancesRequest;
-           DescribeInstancesResult describeInstancesResult;
-
-           describeInstancesRequest = new DescribeInstancesRequest().withInstanceIds(instanceId);
-           describeInstancesResult = clientProxy.injectCredentialsAndInvoke(describeInstancesRequest, ec2Client::describeInstances);
-           return describeInstancesResult.getReservations()
-                                         .stream()
-                                         .map(Reservation::getInstances)
-                                         .flatMap(List::stream)
-                                         .findFirst()
-                                         .orElse(new Instance());
-       }
-
        private void attemptToCleanUpSecurityGroup(String securityGroupId) {
            final DeleteSecurityGroupRequest deleteSecurityGroupRequest = new DeleteSecurityGroupRequest().withGroupId(securityGroupId);
            clientProxy.injectCredentialsAndInvoke(deleteSecurityGroupRequest, ec2Client::deleteSecurityGroup);
@@ -470,33 +507,16 @@ Because our resource type is a high\-level abstraction, a lot of implementation
    ```
    package com.example.testing.wordpress;
 
-   import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
-   import software.amazon.cloudformation.proxy.Logger;
-   import software.amazon.cloudformation.proxy.OperationStatus;
-   import software.amazon.cloudformation.proxy.ProgressEvent;
-   import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
-   import com.amazonaws.services.ec2.model.AuthorizeSecurityGroupIngressRequest;
-   import com.amazonaws.services.ec2.model.AuthorizeSecurityGroupIngressResult;
-   import com.amazonaws.services.ec2.model.CreateSecurityGroupRequest;
-   import com.amazonaws.services.ec2.model.CreateSecurityGroupResult;
-   import com.amazonaws.services.ec2.model.DescribeInstancesRequest;
-   import com.amazonaws.services.ec2.model.DescribeInstancesResult;
-   import com.amazonaws.services.ec2.model.DescribeSubnetsRequest;
-   import com.amazonaws.services.ec2.model.DescribeSubnetsResult;
-   import com.amazonaws.services.ec2.model.GroupIdentifier;
-   import com.amazonaws.services.ec2.model.Instance;
-   import com.amazonaws.services.ec2.model.InstanceState;
-   import com.amazonaws.services.ec2.model.Reservation;
-   import com.amazonaws.services.ec2.model.RunInstancesRequest;
-   import com.amazonaws.services.ec2.model.RunInstancesResult;
-   import com.amazonaws.services.ec2.model.Subnet;
-
+   import com.amazonaws.services.ec2.model.*;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.extension.ExtendWith;
+   import org.mockito.ArgumentMatchers;
    import org.mockito.Mock;
    import org.mockito.junit.jupiter.MockitoExtension;
+   import software.amazon.cloudformation.proxy.*;
 
+   import java.util.function.Function;
 
    import static org.assertj.core.api.Assertions.assertThat;
    import static org.mockito.ArgumentMatchers.any;
@@ -566,10 +586,10 @@ Because our resource type is a high\-level abstraction, a lot of implementation
            final InstanceState inProgressState = new InstanceState().withName("in-progress");
            final GroupIdentifier group = new GroupIdentifier().withGroupId("sg-1234");
            final Instance instance = new Instance().withState(inProgressState).withPublicIpAddress("54.0.0.0").withSecurityGroups(group);
-           doReturn(new DescribeSubnetsResult().withSubnets(new Subnet().withVpcId("vpc-1234"))).when(proxy).injectCredentialsAndInvoke(any(DescribeSubnetsRequest.class), any());
-           doReturn(new RunInstancesResult().withReservation(new Reservation().withInstances(instance))).when(proxy).injectCredentialsAndInvoke(any(RunInstancesRequest.class), any());
-           doReturn(new CreateSecurityGroupResult().withGroupId("sg-1234")).when(proxy).injectCredentialsAndInvoke(any(CreateSecurityGroupRequest.class), any());
-           doReturn(new AuthorizeSecurityGroupIngressResult()).when(proxy).injectCredentialsAndInvoke(any(AuthorizeSecurityGroupIngressRequest.class), any());
+           doReturn(new DescribeSubnetsResult().withSubnets(new Subnet().withVpcId("vpc-1234"))).when(proxy).injectCredentialsAndInvoke(any(DescribeSubnetsRequest.class), ArgumentMatchers.<Function<DescribeSubnetsRequest, DescribeSubnetsResult>>any());
+           doReturn(new RunInstancesResult().withReservation(new Reservation().withInstances(instance))).when(proxy).injectCredentialsAndInvoke(any(RunInstancesRequest.class), ArgumentMatchers.<Function<RunInstancesRequest, RunInstancesResult>>any());
+           doReturn(new CreateSecurityGroupResult().withGroupId("sg-1234")).when(proxy).injectCredentialsAndInvoke(any(CreateSecurityGroupRequest.class), ArgumentMatchers.<Function<CreateSecurityGroupRequest, CreateSecurityGroupResult>>any());
+           doReturn(new AuthorizeSecurityGroupIngressResult()).when(proxy).injectCredentialsAndInvoke(any(AuthorizeSecurityGroupIngressRequest.class), ArgumentMatchers.<Function<AuthorizeSecurityGroupIngressRequest, AuthorizeSecurityGroupIngressResult>>any());
 
            final CreateHandler handler = new CreateHandler();
 
@@ -604,7 +624,7 @@ Because our resource type is a high\-level abstraction, a lot of implementation
            final DescribeInstancesResult describeInstancesResult =
                new DescribeInstancesResult().withReservations(new Reservation().withInstances(instance));
 
-           doReturn(describeInstancesResult).when(proxy).injectCredentialsAndInvoke(any(DescribeInstancesRequest.class), any());
+           doReturn(describeInstancesResult).when(proxy).injectCredentialsAndInvoke(any(DescribeInstancesRequest.class), ArgumentMatchers.<Function<DescribeInstancesRequest, DescribeInstancesResult>>any());
 
            final CreateHandler handler = new CreateHandler();
 
@@ -682,27 +702,18 @@ Again, we'll implement the delete handler as a state machine\.
    ```
    package com.example.testing.wordpress;
 
-   import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
-   import software.amazon.cloudformation.proxy.HandlerErrorCode;
-   import software.amazon.cloudformation.proxy.Logger;
-   import software.amazon.cloudformation.proxy.OperationStatus;
-   import software.amazon.cloudformation.proxy.ProgressEvent;
-   import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
    import com.amazonaws.services.ec2.AmazonEC2;
    import com.amazonaws.services.ec2.AmazonEC2ClientBuilder;
    import com.amazonaws.services.ec2.model.DeleteSecurityGroupRequest;
-   import com.amazonaws.services.ec2.model.DescribeInstancesRequest;
-   import com.amazonaws.services.ec2.model.DescribeInstancesResult;
    import com.amazonaws.services.ec2.model.GroupIdentifier;
    import com.amazonaws.services.ec2.model.Instance;
-   import com.amazonaws.services.ec2.model.Reservation;
    import com.amazonaws.services.ec2.model.TerminateInstancesRequest;
+   import software.amazon.cloudformation.proxy.*;
 
    import java.util.List;
    import java.util.stream.Collectors;
 
-   public class DeleteHandler extends BaseHandler<CallbackContext> {
-       private static final String SUPPORTED_REGION = "us-west-2";
+   public class DeleteHandler extends CustomBaseHandler {
        private static final String DELETED_INSTANCE_STATE = "terminated";
        private static final int NUMBER_OF_STATE_POLL_RETRIES = 60;
        private static final int POLL_RETRY_DELAY_IN_MS = 5000;
@@ -737,7 +748,7 @@ Again, we'll implement the delete handler as a state machine\.
            }
 
            if (callbackContext.getInstanceSecurityGroups() == null) {
-               final Instance currentInstanceState = currentInstanceState(model.getInstanceId());
+               final Instance currentInstanceState = retrieveCurrentInstanceState(clientProxy, ec2Client, model.getInstanceId());
 
                if (DELETED_INSTANCE_STATE.equals(currentInstanceState.getState().getName())) {
                    return ProgressEvent.<ResourceModel, CallbackContext>builder()
@@ -775,7 +786,6 @@ Again, we'll implement the delete handler as a state machine\.
            } else if (callbackContext.getInstance().getState().getName().equals(DELETED_INSTANCE_STATE)) {
                callbackContext.getInstanceSecurityGroups().forEach(this::deleteSecurityGroup);
                return ProgressEvent.<ResourceModel, CallbackContext>builder()
-                   .resourceModel(model)
                    .status(OperationStatus.SUCCESS)
                    .build();
            } else {
@@ -788,7 +798,7 @@ Again, we'll implement the delete handler as a state machine\.
                    .resourceModel(model)
                    .status(OperationStatus.IN_PROGRESS)
                    .callbackContext(CallbackContext.builder()
-                                                   .instance(currentInstanceState(model.getInstanceId()))
+                                                   .instance(retrieveCurrentInstanceState(clientProxy, ec2Client, model.getInstanceId()))
                                                    .instanceSecurityGroups(callbackContext.getInstanceSecurityGroups())
                                                    .stabilizationRetriesRemaining(callbackContext.getStabilizationRetriesRemaining() - 1)
                                                    .build())
@@ -807,20 +817,6 @@ Again, we'll implement the delete handler as a state machine\.
                              .orElse(new Instance());
        }
 
-       private Instance currentInstanceState(String instanceId) {
-           DescribeInstancesRequest describeInstancesRequest;
-           DescribeInstancesResult describeInstancesResult;
-
-           describeInstancesRequest = new DescribeInstancesRequest().withInstanceIds(instanceId);
-           describeInstancesResult = clientProxy.injectCredentialsAndInvoke(describeInstancesRequest, ec2Client::describeInstances);
-           return describeInstancesResult.getReservations()
-                                         .stream()
-                                         .map(Reservation::getInstances)
-                                         .flatMap(List::stream)
-                                         .findFirst()
-                                         .orElse(new Instance());
-       }
-
        private void deleteSecurityGroup(String securityGroupId) {
            final DeleteSecurityGroupRequest deleteSecurityGroupRequest = new DeleteSecurityGroupRequest().withGroupId(securityGroupId);
            clientProxy.injectCredentialsAndInvoke(deleteSecurityGroupRequest, ec2Client::deleteSecurityGroup);
@@ -839,32 +835,17 @@ We'll also need to update the unit test for the delete handler\.
    ```
    package com.example.testing.wordpress;
 
-   import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
-   import software.amazon.cloudformation.proxy.HandlerErrorCode;
-   import software.amazon.cloudformation.proxy.Logger;
-   import software.amazon.cloudformation.proxy.OperationStatus;
-   import software.amazon.cloudformation.proxy.ProgressEvent;
-   import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
-   import com.amazonaws.services.ec2.model.DeleteSecurityGroupRequest;
-   import com.amazonaws.services.ec2.model.DeleteSecurityGroupResult;
-   import com.amazonaws.services.ec2.model.DescribeInstancesRequest;
-   import com.amazonaws.services.ec2.model.DescribeInstancesResult;
-   import com.amazonaws.services.ec2.model.GroupIdentifier;
-   import com.amazonaws.services.ec2.model.Instance;
-   import com.amazonaws.services.ec2.model.InstanceState;
-   import com.amazonaws.services.ec2.model.InstanceStateChange;
-   import com.amazonaws.services.ec2.model.Reservation;
-   import com.amazonaws.services.ec2.model.TerminateInstancesRequest;
-   import com.amazonaws.services.ec2.model.TerminateInstancesResult;
-
+   import com.amazonaws.services.ec2.model.*;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.extension.ExtendWith;
+   import org.mockito.ArgumentMatchers;
    import org.mockito.Mock;
    import org.mockito.junit.jupiter.MockitoExtension;
+   import software.amazon.cloudformation.proxy.*;
 
    import java.util.Arrays;
-
+   import java.util.function.Function;
 
    import static org.assertj.core.api.Assertions.assertThat;
    import static org.mockito.ArgumentMatchers.any;
@@ -890,7 +871,7 @@ We'll also need to update the unit test for the delete handler\.
        @Test
        public void testSuccessState() {
            final DeleteSecurityGroupResult deleteSecurityGroupResult = new DeleteSecurityGroupResult();
-           doReturn(deleteSecurityGroupResult).when(proxy).injectCredentialsAndInvoke(any(DeleteSecurityGroupRequest.class), any());
+           doReturn(deleteSecurityGroupResult).when(proxy).injectCredentialsAndInvoke(any(DeleteSecurityGroupRequest.class), ArgumentMatchers.<Function<DeleteSecurityGroupRequest, DeleteSecurityGroupResult>>any());
 
            final DeleteHandler handler = new DeleteHandler();
 
@@ -913,7 +894,7 @@ We'll also need to update the unit test for the delete handler\.
            assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
            assertThat(response.getCallbackContext()).isNull();
            assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
-           assertThat(response.getResourceModel()).isEqualTo(request.getDesiredResourceState());
+           assertThat(response.getResourceModel()).isNull();
            assertThat(response.getResourceModels()).isNull();
            assertThat(response.getMessage()).isNull();
            assertThat(response.getErrorCode()).isNull();
@@ -924,7 +905,7 @@ We'll also need to update the unit test for the delete handler\.
            final DescribeInstancesResult describeInstancesResult =
                new DescribeInstancesResult().withReservations(new Reservation().withInstances(new Instance().withState(new InstanceState().withName("terminated"))
                                                                                                             .withSecurityGroups(new GroupIdentifier().withGroupId("sg-1234"))));
-           doReturn(describeInstancesResult).when(proxy).injectCredentialsAndInvoke(any(DescribeInstancesRequest.class), any());
+           doReturn(describeInstancesResult).when(proxy).injectCredentialsAndInvoke(any(DescribeInstancesRequest.class), ArgumentMatchers.<Function<DescribeInstancesRequest, DescribeInstancesResult>>any());
 
            final DeleteHandler handler = new DeleteHandler();
 
@@ -952,7 +933,7 @@ We'll also need to update the unit test for the delete handler\.
            final DescribeInstancesResult describeInstancesResult =
                new DescribeInstancesResult().withReservations(new Reservation().withInstances(new Instance().withState(new InstanceState().withName("running"))
                                                                                                             .withSecurityGroups(new GroupIdentifier().withGroupId("sg-1234"))));
-           doReturn(describeInstancesResult).when(proxy).injectCredentialsAndInvoke(any(DescribeInstancesRequest.class), any());
+           doReturn(describeInstancesResult).when(proxy).injectCredentialsAndInvoke(any(DescribeInstancesRequest.class), ArgumentMatchers.<Function<DescribeInstancesRequest, DescribeInstancesResult>>any());
 
            final DeleteHandler handler = new DeleteHandler();
 
@@ -984,7 +965,7 @@ We'll also need to update the unit test for the delete handler\.
            final InstanceState inProgressState = new InstanceState().withName("in-progress");
            final TerminateInstancesResult terminateInstancesResult =
                new TerminateInstancesResult().withTerminatingInstances(new InstanceStateChange().withCurrentState(inProgressState));
-           doReturn(terminateInstancesResult).when(proxy).injectCredentialsAndInvoke(any(TerminateInstancesRequest.class), any());
+           doReturn(terminateInstancesResult).when(proxy).injectCredentialsAndInvoke(any(TerminateInstancesRequest.class), ArgumentMatchers.<Function<TerminateInstancesRequest, TerminateInstancesResult>>any());
 
            final DeleteHandler handler = new DeleteHandler();
 
@@ -1025,7 +1006,7 @@ We'll also need to update the unit test for the delete handler\.
            final Instance instance = new Instance().withState(inProgressState).withSecurityGroups(group);
            final DescribeInstancesResult describeInstancesResult =
                new DescribeInstancesResult().withReservations(new Reservation().withInstances(instance));
-           doReturn(describeInstancesResult).when(proxy).injectCredentialsAndInvoke(any(DescribeInstancesRequest.class), any());
+           doReturn(describeInstancesResult).when(proxy).injectCredentialsAndInvoke(any(DescribeInstancesRequest.class), ArgumentMatchers.<Function<DescribeInstancesRequest, DescribeInstancesResult>>any());
 
            final DeleteHandler handler = new DeleteHandler();
 
@@ -1085,6 +1066,190 @@ We'll also need to update the unit test for the delete handler\.
    }
    ```
 
+#### Code the Read Handler<a name="resource-type-walkthrough-implement-read-handler-code"></a>
+
+1. In your IDE, open the `ReadHandler.java` file, located in the `src/main/java/com/example/testing/wordpress` folder\.
+
+1. Replace the entire contents of the `ReadHandler.java` file with the following code\.
+
+   ```
+   package com.example.testing.wordpress;
+
+   import com.amazonaws.services.ec2.AmazonEC2;
+   import com.amazonaws.services.ec2.AmazonEC2ClientBuilder;
+   import com.amazonaws.services.ec2.model.Instance;
+   import software.amazon.cloudformation.proxy.*;
+
+   public class ReadHandler extends CustomBaseHandler {
+
+       private static final String DELETED_INSTANCE_STATE = "terminated";
+
+       @Override
+       public ProgressEvent<ResourceModel, CallbackContext> handleRequest(
+           final AmazonWebServicesClientProxy proxy,
+           final ResourceHandlerRequest<ResourceModel> request,
+           final CallbackContext callbackContext,
+           final Logger logger) {
+
+           final ResourceModel model = request.getDesiredResourceState();
+
+           AmazonEC2 ec2Client = AmazonEC2ClientBuilder.standard().withRegion(SUPPORTED_REGION).build();
+
+           Instance currentInstanceState = retrieveCurrentInstanceState(proxy, ec2Client, model.getInstanceId());
+
+           if (currentInstanceState.getInstanceId() == null || DELETED_INSTANCE_STATE.equals(currentInstanceState.getState().getName())) {
+               return ProgressEvent.<ResourceModel, CallbackContext>builder()
+                       .status(OperationStatus.FAILED)
+                       .errorCode(HandlerErrorCode.NotFound)
+                       .build();
+           }
+
+           model.setInstanceId(currentInstanceState.getInstanceId());
+           model.setPublicIp(currentInstanceState.getPublicIpAddress());
+           return ProgressEvent.<ResourceModel, CallbackContext>builder()
+               .resourceModel(model)
+               .status(OperationStatus.SUCCESS)
+               .build();
+       }
+   }
+   ```
+
+#### Update the Read Handler unit test<a name="resource-type-walkthrough-implement-read-handler-unit"></a>
+
+We'll also need to update the unit test for the read handler\.
+
+1. In your IDE, open the `ReadHandlerTest.java` file, located in the `src/test/java/com/example/testing/wordpress` folder\.
+
+1. Replace the entire contents of the `ReadHandlerTest.java` file with the following code\.
+
+   ```
+   package com.example.testing.wordpress;
+
+   import com.amazonaws.services.ec2.model.*;
+   import org.junit.jupiter.api.BeforeEach;
+   import org.junit.jupiter.api.Test;
+   import org.junit.jupiter.api.extension.ExtendWith;
+   import org.mockito.ArgumentMatchers;
+   import org.mockito.Mock;
+   import org.mockito.junit.jupiter.MockitoExtension;
+   import software.amazon.cloudformation.proxy.*;
+
+   import java.util.function.Function;
+
+   import static org.assertj.core.api.Assertions.assertThat;
+   import static org.mockito.ArgumentMatchers.any;
+   import static org.mockito.Mockito.doReturn;
+   import static org.mockito.Mockito.mock;
+
+   @ExtendWith(MockitoExtension.class)
+   public class ReadHandlerTest {
+
+       @Mock
+       private AmazonWebServicesClientProxy proxy;
+
+       @Mock
+       private Logger logger;
+
+       @BeforeEach
+       public void setup() {
+           proxy = mock(AmazonWebServicesClientProxy.class);
+           logger = mock(Logger.class);
+       }
+
+       @Test
+       public void testRunningState() {
+           final String instanceId = "i-1234";
+           final String ipAddress = "1.2.3.4";
+           final InstanceState runningState = new InstanceState().withName("running");
+           final Instance instance = new Instance().withState(runningState).withInstanceId("i-1234").withPublicIpAddress(ipAddress);
+           final DescribeInstancesResult describeInstancesResult =
+                   new DescribeInstancesResult().withReservations(new Reservation().withInstances(instance));
+           doReturn(describeInstancesResult).when(proxy).injectCredentialsAndInvoke(any(DescribeInstancesRequest.class), ArgumentMatchers.<Function<DescribeInstancesRequest, DescribeInstancesResult>>any());
+
+           final ReadHandler handler = new ReadHandler();
+
+           final ResourceModel model = ResourceModel.builder().instanceId(instanceId).build();
+
+           final ResourceHandlerRequest<ResourceModel> request = ResourceHandlerRequest.<ResourceModel>builder()
+               .desiredResourceState(model)
+               .build();
+
+           final ProgressEvent<ResourceModel, CallbackContext> response
+               = handler.handleRequest(proxy, request, null, logger);
+
+           assertThat(response).isNotNull();
+           assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
+           assertThat(response.getCallbackContext()).isNull();
+           assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
+           assertThat(response.getResourceModel()).isEqualTo(request.getDesiredResourceState());
+           assertThat(response.getResourceModels()).isNull();
+           assertThat(response.getMessage()).isNull();
+           assertThat(response.getErrorCode()).isNull();
+       }
+
+       @Test
+       public void testTerminatedState() {
+           final String instanceId = "i-1234";
+           final String ipAddress = "1.2.3.4";
+           final InstanceState runningState = new InstanceState().withName("terminated");
+           final Instance instance = new Instance().withState(runningState).withInstanceId("i-1234").withPublicIpAddress(ipAddress);
+           final DescribeInstancesResult describeInstancesResult =
+                   new DescribeInstancesResult().withReservations(new Reservation().withInstances(instance));
+           doReturn(describeInstancesResult).when(proxy).injectCredentialsAndInvoke(any(DescribeInstancesRequest.class), ArgumentMatchers.<Function<DescribeInstancesRequest, DescribeInstancesResult>>any());
+
+           final ReadHandler handler = new ReadHandler();
+
+           final ResourceModel model = ResourceModel.builder().instanceId(instanceId).build();
+
+           final ResourceHandlerRequest<ResourceModel> request = ResourceHandlerRequest.<ResourceModel>builder()
+                   .desiredResourceState(model)
+                   .build();
+
+           final ProgressEvent<ResourceModel, CallbackContext> response
+                   = handler.handleRequest(proxy, request, null, logger);
+
+           assertThat(response).isNotNull();
+           assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED);
+           assertThat(response.getCallbackContext()).isNull();
+           assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
+           assertThat(response.getResourceModel()).isNull();
+           assertThat(response.getResourceModels()).isNull();
+           assertThat(response.getMessage()).isNull();
+           assertThat(response.getErrorCode()).isSameAs(HandlerErrorCode.NotFound);
+       }
+
+       @Test
+       public void testNotFound() {
+           final String instanceId = "i-1234";
+           doReturn(new DescribeInstancesResult()).when(proxy).injectCredentialsAndInvoke(any(DescribeInstancesRequest.class), ArgumentMatchers.<Function<DescribeInstancesRequest, DescribeInstancesResult>>any());
+
+           final ReadHandler handler = new ReadHandler();
+
+           final ResourceModel model = ResourceModel.builder().instanceId(instanceId).build();
+
+           final ResourceHandlerRequest<ResourceModel> request = ResourceHandlerRequest.<ResourceModel>builder()
+                   .desiredResourceState(model)
+                   .build();
+
+           final ProgressEvent<ResourceModel, CallbackContext> response
+                   = handler.handleRequest(proxy, request, null, logger);
+
+           assertThat(response).isNotNull();
+           assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED);
+           assertThat(response.getCallbackContext()).isNull();
+           assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
+           assertThat(response.getResourceModel()).isNull();
+           assertThat(response.getResourceModels()).isNull();
+           assertThat(response.getMessage()).isNull();
+           assertThat(response.getErrorCode()).isSameAs(HandlerErrorCode.NotFound);
+       }
+   }
+   ```
+
+## Build the project<a name="resource-type-walkthrough-build"></a>
+
+Build the project by running `mvn install` in the project's root directory, or using the build command in your IDE.
+
 ## Test the resource type<a name="resource-type-walkthrough-test"></a>
 
 Next, we'll use the AWS SAM CLI to test locally that our resource will work as expected once we submit it to the CloudFormation registry\. To do this, we'll need to define tests for the SAM to run against our create and delete handlers\.
@@ -1200,7 +1365,7 @@ Occasionally these tests will fail with a retry\-able error\. In such a case, ru
                },
                "architecture": "x86_64",
                "ebsOptimized": false,
-               "imageId": "ami-04fb0368671b6f138",
+               "imageId": "ami-0039c114b5e564742",
                "blockDeviceMappings": [],
                "stateTransitionReason": "",
                "clientToken": "207dc686-e95c-4df9-8fcb-ee22bbdde963",
@@ -1366,10 +1531,10 @@ To run resource contract tests, you'll need two shell sessions\.
    $ sam local start-lambda
    ```
 
-1. From the resource package root directory, in a session that is aware of the CloudFormation CLI, run the `test` command:
+1. From the resource package root directory, in a session that is aware of the CloudFormation CLI, run the `test` command\.  This runs some [contract tests](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/contract-tests.html) to verify that the resource handlers satisfy the [contract requirements](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html)\.
 
    ```
-   $ cfn test
+   $ cfn test --region us-west-2
    ```
 
    The session that's running `sam local start-lambda` will display information about the status of your tests\.
@@ -1380,7 +1545,7 @@ Once you have completed implementing and testing your resource provided, the fin
 + In a terminal, run the `submit` command to register the resource type in the us\-west\-2 region\.
 
   ```
-  overrides.jsoncfn submit -v --region us-west-2
+  cfn submit -v --region us-west-2
   ```
 
   The CloudFormation CLI validates the included resource type schema, packages your resource provide project and uploads it to the CloudFormation registry, and then returns a registration token\.
@@ -1434,6 +1599,24 @@ Use [DescribeTypeRegistration](https://docs.aws.amazon.com/AWSCloudFormation/lat
                    "Name": "MyWebsite"
                }
            }
+       },
+       "Outputs": {
+           "InstanceId": {
+               "Value": {
+                   "Fn::GetAtt": [
+                       "MyWordPressSite",
+                       "InstanceId"
+                   ]
+               }
+           },
+           "PublicIp": {
+               "Value": {
+                   "Fn::GetAtt": [
+                       "MyWordPressSite",
+                       "PublicIp"
+                   ]
+               }
+           }
        }
    }
    ```
@@ -1458,3 +1641,56 @@ As a final test of the resource type delete handler, you can delete the `wordpre
 aws cloudformation delete-stack --region us-west-2 \
   --stack-name wordpress
 ```
+
+## Clean up ##
+1. Delete any stacks you created using the resource\.
+1. If you submitted multiple versions of the resource to CloudFormation, deregister the non-default versions\.  Start by getting a list of the versions\.
+
+   ```
+   aws cloudformation list-type-versions --region us-west-2 \
+     --type RESOURCE \
+     --type-name Example::Testing::WordPress
+   ```
+
+   Identify the ARN for each version that has `"IsDefaultVersion": false`\.
+   ```
+   {
+       "TypeVersionSummaries": [
+           {
+               "Type": "RESOURCE",
+               "TypeName": "Example::Testing::WordPress",
+               "VersionId": "00000001",
+               "IsDefaultVersion": false,
+               "Arn": "arn:aws:cloudformation:us-west-2:111122223333:type/resource/Example-Testing-WordPress/00000001",
+               "TimeCreated": "2023-03-15T18:45:36.347000+00:00",
+               "Description": "An example resource that creates a website based on WordPress 6.1.1."
+           },
+           {
+               "Type": "RESOURCE",
+               "TypeName": "Example::Testing::WordPress",
+               "VersionId": "00000002",
+               "IsDefaultVersion": true,
+               "Arn": "arn:aws:cloudformation:us-west-2:111122223333:type/resource/Example-Testing-WordPress/00000002",
+               "TimeCreated": "2023-03-15T18:48:10.317000+00:00",
+               "Description": "An example resource that creates a website based on WordPress 6.1.1."
+           }
+       ]
+   }
+   ```
+
+   Deregister each non-default version\.
+
+   ```
+   aws cloudformation deregister-type --region us-west-2 \
+     --arn arn:aws:cloudformation:us-west-2:111122223333:type/resource/Example-Testing-WordPress/00000001
+   ```
+
+1. Deregister the default version of the resource\.
+
+   ```
+   aws cloudformation deregister-type --region us-west-2 \
+     --type RESOURCE \
+     --type-name Example::Testing::WordPress
+   ```
+
+1. Use the [AWS console](https://us-east-1.console.aws.amazon.com/marketplace/home#/subscriptions) to unsubscribe from the AMI\.
diff --git a/setup.py b/setup.py
index 1680a5f..cb2d6f1 100644
--- a/setup.py
+++ b/setup.py
@@ -39,20 +39,20 @@ setup(
     python_requires=">=3.6",
     install_requires=[
         "boto3>=1.10.20",
-        "Jinja2>=2.11.3",
-        "markupsafe>=2.0.1",
+        "Jinja2>=3.1.2",
+        "markupsafe>=2.1.0",
         "jsonschema>=3.0.1,<4.0",
         "pytest>=4.5.0",
         "pytest-random-order>=1.0.4",
         "pytest-localserver>=0.5.0",
-        "Werkzeug>=0.15",
+        "Werkzeug>=2.2.0",
         "PyYAML>=5.1,<6.0",
         "requests>=2.22",
         "hypothesis>=4.32",
         "colorama>=0.4.1",
         "docker>=4.3.1",
         "ordered-set>=4.0.2",
-        "cfn-lint>=0.43.0",
+        "cfn-lint<=0.72.10",
         "cfn_flip>=1.2.3",
         "nested-lookup",
     ],
diff --git a/src/rpdk/core/__init__.py b/src/rpdk/core/__init__.py
index 213a0eb..45a74cb 100644
--- a/src/rpdk/core/__init__.py
+++ b/src/rpdk/core/__init__.py
@@ -1,5 +1,5 @@
 import logging
 
-__version__ = "0.2.28"
+__version__ = "0.2.30"
 
 logging.getLogger(__name__).addHandler(logging.NullHandler())
diff --git a/src/rpdk/core/contract/suite/resource/handler_update_invalid.py b/src/rpdk/core/contract/suite/resource/handler_update_invalid.py
index 6a0ca10..85eff21 100644
--- a/src/rpdk/core/contract/suite/resource/handler_update_invalid.py
+++ b/src/rpdk/core/contract/suite/resource/handler_update_invalid.py
@@ -10,12 +10,16 @@ from rpdk.core.contract.suite.contract_asserts_commons import failed_event
 
 
 @pytest.mark.update
+def contract_update_without_create(resource_client):
+    test_update_without_create(resource_client)
+
+
 @failed_event(
     error_code=HandlerErrorCode.NotFound,
     msg="An update handler MUST return FAILED with a NotFound error code\
          if the resource did not exist prior to the update request",
 )
-def contract_update_without_create(resource_client):
+def test_update_without_create(resource_client):
     create_request = resource_client.generate_create_example()
     update_request = resource_client.generate_update_example(create_request)
     _status, response, _error = resource_client.call_and_assert(
@@ -25,4 +29,5 @@ def contract_update_without_create(resource_client):
         "message"
     ], "The progress event MUST return an error message\
          when the status is failed"
+
     return _error
diff --git a/src/rpdk/core/data/examples/hook/targets/aws-s3-bucket.json b/src/rpdk/core/data/examples/hook/targets/aws-s3-bucket.json
new file mode 100644
index 0000000..5819be7
--- /dev/null
+++ b/src/rpdk/core/data/examples/hook/targets/aws-s3-bucket.json
@@ -0,0 +1,1611 @@
+{
+    "typeName": "AWS::S3::Bucket",
+    "description": "Resource Type definition for AWS::S3::Bucket",
+    "additionalProperties": false,
+    "properties": {
+        "AccelerateConfiguration": {
+            "$ref": "#/definitions/AccelerateConfiguration",
+            "description": "Configuration for the transfer acceleration state."
+        },
+        "AccessControl": {
+            "description": "A canned access control list (ACL) that grants predefined permissions to the bucket.",
+            "enum": [
+                "AuthenticatedRead",
+                "AwsExecRead",
+                "BucketOwnerFullControl",
+                "BucketOwnerRead",
+                "LogDeliveryWrite",
+                "Private",
+                "PublicRead",
+                "PublicReadWrite"
+            ],
+            "type": "string"
+        },
+        "AnalyticsConfigurations": {
+            "description": "The configuration and any analyses for the analytics filter of an Amazon S3 bucket.",
+            "items": {
+                "$ref": "#/definitions/AnalyticsConfiguration"
+            },
+            "type": "array",
+            "uniqueItems": true,
+            "insertionOrder": true
+        },
+        "BucketEncryption": {
+            "$ref": "#/definitions/BucketEncryption"
+        },
+        "BucketName": {
+            "description": "A name for the bucket. If you don't specify a name, AWS CloudFormation generates a unique physical ID and uses that ID for the bucket name.",
+            "maxLength": 63,
+            "minLength": 3,
+            "pattern": "^[a-z0-9][a-z0-9//.//-]*[a-z0-9]$",
+            "type": "string"
+        },
+        "CorsConfiguration": {
+            "$ref": "#/definitions/CorsConfiguration",
+            "description": "Rules that define cross-origin resource sharing of objects in this bucket."
+        },
+        "IntelligentTieringConfigurations": {
+            "description": "Specifies the S3 Intelligent-Tiering configuration for an Amazon S3 bucket.",
+            "items": {
+                "$ref": "#/definitions/IntelligentTieringConfiguration"
+            },
+            "type": "array",
+            "uniqueItems": true,
+            "insertionOrder": true
+        },
+        "InventoryConfigurations": {
+            "description": "The inventory configuration for an Amazon S3 bucket.",
+            "items": {
+                "$ref": "#/definitions/InventoryConfiguration"
+            },
+            "type": "array",
+            "uniqueItems": true,
+            "insertionOrder": true
+        },
+        "LifecycleConfiguration": {
+            "$ref": "#/definitions/LifecycleConfiguration",
+            "description": "Rules that define how Amazon S3 manages objects during their lifetime."
+        },
+        "LoggingConfiguration": {
+            "$ref": "#/definitions/LoggingConfiguration",
+            "description": "Settings that define where logs are stored."
+        },
+        "MetricsConfigurations": {
+            "description": "Settings that define a metrics configuration for the CloudWatch request metrics from the bucket.",
+            "items": {
+                "$ref": "#/definitions/MetricsConfiguration"
+            },
+            "type": "array",
+            "uniqueItems": true,
+            "insertionOrder": true
+        },
+        "NotificationConfiguration": {
+            "$ref": "#/definitions/NotificationConfiguration",
+            "description": "Configuration that defines how Amazon S3 handles bucket notifications."
+        },
+        "ObjectLockConfiguration": {
+            "$ref": "#/definitions/ObjectLockConfiguration",
+            "description": "Places an Object Lock configuration on the specified bucket."
+        },
+        "ObjectLockEnabled": {
+            "description": "Indicates whether this bucket has an Object Lock configuration enabled.",
+            "type": "boolean"
+        },
+        "OwnershipControls": {
+            "description": "Specifies the container element for object ownership rules.",
+            "$ref": "#/definitions/OwnershipControls"
+        },
+        "PublicAccessBlockConfiguration": {
+            "$ref": "#/definitions/PublicAccessBlockConfiguration"
+        },
+        "ReplicationConfiguration": {
+            "$ref": "#/definitions/ReplicationConfiguration",
+            "description": "Configuration for replicating objects in an S3 bucket."
+        },
+        "Tags": {
+            "description": "An arbitrary set of tags (key-value pairs) for this S3 bucket.",
+            "insertionOrder": false,
+            "items": {
+                "$ref": "#/definitions/Tag"
+            },
+            "type": "array"
+        },
+        "VersioningConfiguration": {
+            "$ref": "#/definitions/VersioningConfiguration"
+        },
+        "WebsiteConfiguration": {
+            "$ref": "#/definitions/WebsiteConfiguration"
+        },
+        "Arn": {
+            "$ref": "#/definitions/Arn",
+            "description": "The Amazon Resource Name (ARN) of the specified bucket.",
+            "examples": [
+                "arn:aws:s3:::mybucket"
+            ]
+        },
+        "DomainName": {
+            "description": "The IPv4 DNS name of the specified bucket.",
+            "examples": [
+                "mystack-mybucket-kdwwxmddtr2g.s3.amazonaws.com"
+            ],
+            "type": "string"
+        },
+        "DualStackDomainName": {
+            "description": "The IPv6 DNS name of the specified bucket. For more information about dual-stack endpoints, see [Using Amazon S3 Dual-Stack Endpoints](https://docs.aws.amazon.com/AmazonS3/latest/dev/dual-stack-endpoints.html).",
+            "examples": [
+                "mystack-mybucket-kdwwxmddtr2g.s3.dualstack.us-east-2.amazonaws.com"
+            ],
+            "type": "string"
+        },
+        "RegionalDomainName": {
+            "description": "Returns the regional domain name of the specified bucket.",
+            "examples": [
+                "mystack-mybucket-kdwwxmddtr2g.s3.us-east-2.amazonaws.com"
+            ],
+            "type": "string"
+        },
+        "WebsiteURL": {
+            "description": "The Amazon S3 website endpoint for the specified bucket.",
+            "examples": [
+                "Example (IPv4): http://mystack-mybucket-kdwwxmddtr2g.s3-website-us-east-2.amazonaws.com/",
+                "Example (IPv6): http://mystack-mybucket-kdwwxmddtr2g.s3.dualstack.us-east-2.amazonaws.com/"
+            ],
+            "format": "uri",
+            "type": "string"
+        }
+    },
+    "definitions": {
+        "TagFilter": {
+            "description": "Tags to use to identify a subset of objects for an Amazon S3 bucket.",
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "Value": {
+                    "type": "string"
+                },
+                "Key": {
+                    "type": "string"
+                }
+            },
+            "required": [
+                "Value",
+                "Key"
+            ]
+        },
+        "Destination": {
+            "description": "Specifies information about where to publish analysis or configuration results for an Amazon S3 bucket and S3 Replication Time Control (S3 RTC).",
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "BucketArn": {
+                    "description": "The Amazon Resource Name (ARN) of the bucket to which data is exported.",
+                    "type": "string"
+                },
+                "BucketAccountId": {
+                    "description": "The account ID that owns the destination S3 bucket. ",
+                    "type": "string"
+                },
+                "Format": {
+                    "description": "Specifies the file format used when exporting data to Amazon S3.",
+                    "type": "string",
+                    "enum": [
+                        "CSV",
+                        "ORC",
+                        "Parquet"
+                    ]
+                },
+                "Prefix": {
+                    "description": "The prefix to use when exporting data. The prefix is prepended to all results.",
+                    "type": "string"
+                }
+            },
+            "required": [
+                "BucketArn",
+                "Format"
+            ]
+        },
+        "AccelerateConfiguration": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "AccelerationStatus": {
+                    "description": "Configures the transfer acceleration state for an Amazon S3 bucket.",
+                    "type": "string",
+                    "enum": [
+                        "Enabled",
+                        "Suspended"
+                    ]
+                }
+            },
+            "required": [
+                "AccelerationStatus"
+            ]
+        },
+        "AnalyticsConfiguration": {
+            "description": "Specifies the configuration and any analyses for the analytics filter of an Amazon S3 bucket.",
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "TagFilters": {
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "$ref": "#/definitions/TagFilter"
+                    }
+                },
+                "StorageClassAnalysis": {
+                    "$ref": "#/definitions/StorageClassAnalysis"
+                },
+                "Id": {
+                    "description": "The ID that identifies the analytics configuration.",
+                    "type": "string"
+                },
+                "Prefix": {
+                    "description": "The prefix that an object must have to be included in the analytics results.",
+                    "type": "string"
+                }
+            },
+            "required": [
+                "StorageClassAnalysis",
+                "Id"
+            ]
+        },
+        "StorageClassAnalysis": {
+            "description": "Specifies data related to access patterns to be collected and made available to analyze the tradeoffs between different storage classes for an Amazon S3 bucket.",
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "DataExport": {
+                    "$ref": "#/definitions/DataExport"
+                }
+            }
+        },
+        "DataExport": {
+            "description": "Specifies how data related to the storage class analysis for an Amazon S3 bucket should be exported.",
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "Destination": {
+                    "$ref": "#/definitions/Destination"
+                },
+                "OutputSchemaVersion": {
+                    "description": "The version of the output schema to use when exporting data.",
+                    "type": "string",
+                    "const": "V_1"
+                }
+            },
+            "required": [
+                "Destination",
+                "OutputSchemaVersion"
+            ]
+        },
+        "BucketEncryption": {
+            "description": "Specifies default encryption for a bucket using server-side encryption with either Amazon S3-managed keys (SSE-S3) or AWS KMS-managed keys (SSE-KMS).",
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "ServerSideEncryptionConfiguration": {
+                    "description": "Specifies the default server-side-encryption configuration.",
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "$ref": "#/definitions/ServerSideEncryptionRule"
+                    }
+                }
+            },
+            "required": [
+                "ServerSideEncryptionConfiguration"
+            ]
+        },
+        "ServerSideEncryptionRule": {
+            "description": "Specifies the default server-side encryption configuration.",
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "BucketKeyEnabled": {
+                    "description": "Specifies whether Amazon S3 should use an S3 Bucket Key with server-side encryption using KMS (SSE-KMS) for new objects in the bucket. Existing objects are not affected. Setting the BucketKeyEnabled element to true causes Amazon S3 to use an S3 Bucket Key. By default, S3 Bucket Key is not enabled.",
+                    "type": "boolean"
+                },
+                "ServerSideEncryptionByDefault": {
+                    "$ref": "#/definitions/ServerSideEncryptionByDefault"
+                }
+            }
+        },
+        "ServerSideEncryptionByDefault": {
+            "description": "Specifies the default server-side encryption to apply to new objects in the bucket. If a PUT Object request doesn't specify any server-side encryption, this default encryption will be applied.",
+            "type": "object",
+            "properties": {
+                "KMSMasterKeyID": {
+                    "description": "\"KMSMasterKeyID\" can only be used when you set the value of SSEAlgorithm as aws:kms.",
+                    "type": "string"
+                },
+                "SSEAlgorithm": {
+                    "type": "string",
+                    "enum": [
+                        "aws:kms",
+                        "AES256"
+                    ]
+                }
+            },
+            "additionalProperties": false,
+            "required": [
+                "SSEAlgorithm"
+            ]
+        },
+        "CorsConfiguration": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "CorsRules": {
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "$ref": "#/definitions/CorsRule",
+                        "maxLength": 100
+                    }
+                }
+            },
+            "required": [
+                "CorsRules"
+            ]
+        },
+        "CorsRule": {
+            "type": "object",
+            "description": "A set of origins and methods (cross-origin access that you want to allow). You can add up to 100 rules to the configuration.",
+            "additionalProperties": false,
+            "properties": {
+                "AllowedHeaders": {
+                    "description": "Headers that are specified in the Access-Control-Request-Headers header.",
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "type": "string"
+                    }
+                },
+                "AllowedMethods": {
+                    "description": "An HTTP method that you allow the origin to execute.",
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "type": "string",
+                        "enum": [
+                            "GET",
+                            "PUT",
+                            "HEAD",
+                            "POST",
+                            "DELETE"
+                        ]
+                    }
+                },
+                "AllowedOrigins": {
+                    "description": "One or more origins you want customers to be able to access the bucket from.",
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "type": "string"
+                    }
+                },
+                "ExposedHeaders": {
+                    "description": "One or more headers in the response that you want customers to be able to access from their applications (for example, from a JavaScript XMLHttpRequest object).",
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "type": "string"
+                    }
+                },
+                "Id": {
+                    "description": "A unique identifier for this rule.",
+                    "type": "string",
+                    "maxLength": 255
+                },
+                "MaxAge": {
+                    "description": "The time in seconds that your browser is to cache the preflight response for the specified resource.",
+                    "type": "integer",
+                    "minimum": 0
+                }
+            },
+            "required": [
+                "AllowedMethods",
+                "AllowedOrigins"
+            ]
+        },
+        "IntelligentTieringConfiguration": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "Id": {
+                    "description": "The ID used to identify the S3 Intelligent-Tiering configuration.",
+                    "type": "string"
+                },
+                "Prefix": {
+                    "description": "An object key name prefix that identifies the subset of objects to which the rule applies.",
+                    "type": "string"
+                },
+                "Status": {
+                    "description": "Specifies the status of the configuration.",
+                    "type": "string",
+                    "enum": [
+                        "Disabled",
+                        "Enabled"
+                    ]
+                },
+                "TagFilters": {
+                    "description": "A container for a key-value pair.",
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "$ref": "#/definitions/TagFilter"
+                    }
+                },
+                "Tierings": {
+                    "description": "Specifies a list of S3 Intelligent-Tiering storage class tiers in the configuration. At least one tier must be defined in the list. At most, you can specify two tiers in the list, one for each available AccessTier: ARCHIVE_ACCESS and DEEP_ARCHIVE_ACCESS.",
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "$ref": "#/definitions/Tiering"
+                    }
+                }
+            },
+            "required": [
+                "Id",
+                "Status",
+                "Tierings"
+            ]
+        },
+        "Tiering": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "AccessTier": {
+                    "description": "S3 Intelligent-Tiering access tier. See Storage class for automatically optimizing frequently and infrequently accessed objects for a list of access tiers in the S3 Intelligent-Tiering storage class.",
+                    "type": "string",
+                    "enum": [
+                        "ARCHIVE_ACCESS",
+                        "DEEP_ARCHIVE_ACCESS"
+                    ]
+                },
+                "Days": {
+                    "description": "The number of consecutive days of no access after which an object will be eligible to be transitioned to the corresponding tier. The minimum number of days specified for Archive Access tier must be at least 90 days and Deep Archive Access tier must be at least 180 days. The maximum can be up to 2 years (730 days).",
+                    "type": "integer"
+                }
+            },
+            "required": [
+                "AccessTier",
+                "Days"
+            ]
+        },
+        "InventoryConfiguration": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "Destination": {
+                    "$ref": "#/definitions/Destination"
+                },
+                "Enabled": {
+                    "description": "Specifies whether the inventory is enabled or disabled.",
+                    "type": "boolean"
+                },
+                "Id": {
+                    "description": "The ID used to identify the inventory configuration.",
+                    "type": "string"
+                },
+                "IncludedObjectVersions": {
+                    "description": "Object versions to include in the inventory list.",
+                    "type": "string",
+                    "enum": [
+                        "All",
+                        "Current"
+                    ]
+                },
+                "OptionalFields": {
+                    "description": "Contains the optional fields that are included in the inventory results.",
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "type": "string",
+                        "enum": [
+                            "Size",
+                            "LastModifiedDate",
+                            "StorageClass",
+                            "ETag",
+                            "IsMultipartUploaded",
+                            "ReplicationStatus",
+                            "EncryptionStatus",
+                            "ObjectLockRetainUntilDate",
+                            "ObjectLockMode",
+                            "ObjectLockLegalHoldStatus",
+                            "IntelligentTieringAccessTier",
+                            "BucketKeyStatus"
+                        ]
+                    }
+                },
+                "Prefix": {
+                    "description": "The prefix that is prepended to all inventory results.",
+                    "type": "string"
+                },
+                "ScheduleFrequency": {
+                    "description": "Specifies the schedule for generating inventory results.",
+                    "type": "string",
+                    "enum": [
+                        "Daily",
+                        "Weekly"
+                    ]
+                }
+            },
+            "required": [
+                "Destination",
+                "Enabled",
+                "Id",
+                "IncludedObjectVersions",
+                "ScheduleFrequency"
+            ]
+        },
+        "LifecycleConfiguration": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "Rules": {
+                    "description": "A lifecycle rule for individual objects in an Amazon S3 bucket.",
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "$ref": "#/definitions/Rule"
+                    }
+                }
+            },
+            "required": [
+                "Rules"
+            ]
+        },
+        "Rule": {
+            "type": "object",
+            "description": "You must specify at least one of the following properties: AbortIncompleteMultipartUpload, ExpirationDate, ExpirationInDays, NoncurrentVersionExpirationInDays, NoncurrentVersionTransition, NoncurrentVersionTransitions, Transition, or Transitions.",
+            "additionalProperties": false,
+            "properties": {
+                "AbortIncompleteMultipartUpload": {
+                    "$ref": "#/definitions/AbortIncompleteMultipartUpload"
+                },
+                "ExpirationDate": {
+                    "$ref": "#/definitions/iso8601UTC"
+                },
+                "ExpirationInDays": {
+                    "type": "integer"
+                },
+                "ExpiredObjectDeleteMarker": {
+                    "type": "boolean"
+                },
+                "Id": {
+                    "type": "string",
+                    "maxLength": 255
+                },
+                "NoncurrentVersionExpirationInDays": {
+                    "type": "integer"
+                },
+                "NoncurrentVersionExpiration": {
+                    "$ref": "#/definitions/NoncurrentVersionExpiration"
+                },
+                "NoncurrentVersionTransition": {
+                    "$ref": "#/definitions/NoncurrentVersionTransition"
+                },
+                "NoncurrentVersionTransitions": {
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "$ref": "#/definitions/NoncurrentVersionTransition"
+                    }
+                },
+                "Prefix": {
+                    "type": "string"
+                },
+                "Status": {
+                    "type": "string",
+                    "enum": [
+                        "Enabled",
+                        "Disabled"
+                    ]
+                },
+                "TagFilters": {
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "$ref": "#/definitions/TagFilter"
+                    }
+                },
+                "ObjectSizeGreaterThan": {
+                    "type": "string",
+                    "maxLength": 20,
+                    "pattern": "[0-9]+"
+                },
+                "ObjectSizeLessThan": {
+                    "type": "string",
+                    "maxLength": 20,
+                    "pattern": "[0-9]+"
+                },
+                "Transition": {
+                    "$ref": "#/definitions/Transition"
+                },
+                "Transitions": {
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "$ref": "#/definitions/Transition"
+                    }
+                }
+            },
+            "required": [
+                "Status"
+            ]
+        },
+        "AbortIncompleteMultipartUpload": {
+            "description": "Specifies the days since the initiation of an incomplete multipart upload that Amazon S3 will wait before permanently removing all parts of the upload.",
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "DaysAfterInitiation": {
+                    "description": "Specifies the number of days after which Amazon S3 aborts an incomplete multipart upload.",
+                    "type": "integer",
+                    "minimum": 0
+                }
+            },
+            "required": [
+                "DaysAfterInitiation"
+            ]
+        },
+        "iso8601UTC": {
+            "description": "The date value in ISO 8601 format. The timezone is always UTC. (YYYY-MM-DDThh:mm:ssZ)",
+            "type": "string",
+            "pattern": "^([0-2]\\d{3})-(0[0-9]|1[0-2])-([0-2]\\d|3[01])T([01]\\d|2[0-4]):([0-5]\\d):([0-6]\\d)((\\.\\d{3})?)Z$"
+        },
+        "NoncurrentVersionExpiration": {
+            "type": "object",
+            "description": "Container for the expiration rule that describes when noncurrent objects are expired. If your bucket is versioning-enabled (or versioning is suspended), you can set this action to request that Amazon S3 expire noncurrent object versions at a specific period in the object's lifetime",
+            "additionalProperties": false,
+            "properties": {
+                "NoncurrentDays": {
+                    "description": "Specified the number of days an object is noncurrent before Amazon S3 can perform the associated action",
+                    "type": "integer"
+                },
+                "NewerNoncurrentVersions": {
+                    "description": "Specified the number of newer noncurrent and current versions that must exists before performing the associated action",
+                    "type": "integer"
+                }
+            },
+            "required": [
+                "NoncurrentDays"
+            ]
+        },
+        "NoncurrentVersionTransition": {
+            "type": "object",
+            "description": "Container for the transition rule that describes when noncurrent objects transition to the STANDARD_IA, ONEZONE_IA, INTELLIGENT_TIERING, GLACIER_IR, GLACIER, or DEEP_ARCHIVE storage class. If your bucket is versioning-enabled (or versioning is suspended), you can set this action to request that Amazon S3 transition noncurrent object versions to the STANDARD_IA, ONEZONE_IA, INTELLIGENT_TIERING, GLACIER_IR, GLACIER, or DEEP_ARCHIVE storage class at a specific period in the object's lifetime.",
+            "additionalProperties": false,
+            "properties": {
+                "StorageClass": {
+                    "description": "The class of storage used to store the object.",
+                    "type": "string",
+                    "enum": [
+                        "DEEP_ARCHIVE",
+                        "GLACIER",
+                        "Glacier",
+                        "GLACIER_IR",
+                        "INTELLIGENT_TIERING",
+                        "ONEZONE_IA",
+                        "STANDARD_IA"
+                    ]
+                },
+                "TransitionInDays": {
+                    "description": "Specifies the number of days an object is noncurrent before Amazon S3 can perform the associated action.",
+                    "type": "integer"
+                },
+                "NewerNoncurrentVersions": {
+                    "description": "Specified the number of newer noncurrent and current versions that must exists before performing the associated action",
+                    "type": "integer"
+                }
+            },
+            "required": [
+                "StorageClass",
+                "TransitionInDays"
+            ]
+        },
+        "Transition": {
+            "type": "object",
+            "properties": {
+                "StorageClass": {
+                    "type": "string",
+                    "enum": [
+                        "DEEP_ARCHIVE",
+                        "GLACIER",
+                        "Glacier",
+                        "GLACIER_IR",
+                        "INTELLIGENT_TIERING",
+                        "ONEZONE_IA",
+                        "STANDARD_IA"
+                    ]
+                },
+                "TransitionDate": {
+                    "$ref": "#/definitions/iso8601UTC"
+                },
+                "TransitionInDays": {
+                    "type": "integer"
+                }
+            },
+            "additionalProperties": false,
+            "description": "You must specify at least one of \"TransitionDate\" and \"TransitionInDays\"",
+            "required": [
+                "StorageClass"
+            ]
+        },
+        "LoggingConfiguration": {
+            "type": "object",
+            "properties": {
+                "DestinationBucketName": {
+                    "type": "string",
+                    "description": "The name of an Amazon S3 bucket where Amazon S3 store server access log files. You can store log files in any bucket that you own. By default, logs are stored in the bucket where the LoggingConfiguration property is defined."
+                },
+                "LogFilePrefix": {
+                    "type": "string"
+                }
+            },
+            "additionalProperties": false
+        },
+        "MetricsConfiguration": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "AccessPointArn": {
+                    "type": "string"
+                },
+                "Id": {
+                    "type": "string"
+                },
+                "Prefix": {
+                    "type": "string"
+                },
+                "TagFilters": {
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "$ref": "#/definitions/TagFilter"
+                    }
+                }
+            },
+            "required": [
+                "Id"
+            ]
+        },
+        "NotificationConfiguration": {
+            "description": "Describes the notification configuration for an Amazon S3 bucket.",
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "EventBridgeConfiguration": {
+                    "$ref": "#/definitions/EventBridgeConfiguration"
+                },
+                "LambdaConfigurations": {
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "$ref": "#/definitions/LambdaConfiguration"
+                    }
+                },
+                "QueueConfigurations": {
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "$ref": "#/definitions/QueueConfiguration"
+                    }
+                },
+                "TopicConfigurations": {
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "$ref": "#/definitions/TopicConfiguration"
+                    }
+                }
+            }
+        },
+        "EventBridgeConfiguration": {
+            "type": "object",
+            "description": "Describes the Amazon EventBridge notification configuration for an Amazon S3 bucket.",
+            "additionalProperties": false,
+            "properties": {
+                "EventBridgeEnabled": {
+                    "description": "Specifies whether to send notifications to Amazon EventBridge when events occur in an Amazon S3 bucket.",
+                    "type": "boolean",
+                    "default": "true"
+                }
+            },
+            "required": [
+                "EventBridgeEnabled"
+            ]
+        },
+        "LambdaConfiguration": {
+            "type": "object",
+            "description": "Describes the AWS Lambda functions to invoke and the events for which to invoke them.",
+            "additionalProperties": false,
+            "properties": {
+                "Event": {
+                    "description": "The Amazon S3 bucket event for which to invoke the AWS Lambda function.",
+                    "type": "string"
+                },
+                "Filter": {
+                    "description": "The filtering rules that determine which objects invoke the AWS Lambda function.",
+                    "$ref": "#/definitions/NotificationFilter"
+                },
+                "Function": {
+                    "description": "The Amazon Resource Name (ARN) of the AWS Lambda function that Amazon S3 invokes when the specified event type occurs.",
+                    "type": "string"
+                }
+            },
+            "required": [
+                "Function",
+                "Event"
+            ]
+        },
+        "QueueConfiguration": {
+            "type": "object",
+            "description": "The Amazon Simple Queue Service queues to publish messages to and the events for which to publish messages.",
+            "additionalProperties": false,
+            "properties": {
+                "Event": {
+                    "description": "The Amazon S3 bucket event about which you want to publish messages to Amazon SQS.",
+                    "type": "string"
+                },
+                "Filter": {
+                    "description": "The filtering rules that determine which objects trigger notifications.",
+                    "$ref": "#/definitions/NotificationFilter"
+                },
+                "Queue": {
+                    "description": "The Amazon Resource Name (ARN) of the Amazon SQS queue to which Amazon S3 publishes a message when it detects events of the specified type.",
+                    "type": "string"
+                }
+            },
+            "required": [
+                "Event",
+                "Queue"
+            ]
+        },
+        "TopicConfiguration": {
+            "type": "object",
+            "description": "The topic to which notifications are sent and the events for which notifications are generated.",
+            "additionalProperties": false,
+            "properties": {
+                "Event": {
+                    "description": "The Amazon S3 bucket event about which to send notifications.",
+                    "type": "string"
+                },
+                "Filter": {
+                    "description": "The filtering rules that determine for which objects to send notifications.",
+                    "$ref": "#/definitions/NotificationFilter"
+                },
+                "Topic": {
+                    "description": "The Amazon Resource Name (ARN) of the Amazon SNS topic to which Amazon S3 publishes a message when it detects events of the specified type.",
+                    "type": "string"
+                }
+            },
+            "required": [
+                "Event",
+                "Topic"
+            ]
+        },
+        "NotificationFilter": {
+            "type": "object",
+            "description": "Specifies object key name filtering rules.",
+            "additionalProperties": false,
+            "properties": {
+                "S3Key": {
+                    "$ref": "#/definitions/S3KeyFilter"
+                }
+            },
+            "required": [
+                "S3Key"
+            ]
+        },
+        "S3KeyFilter": {
+            "type": "object",
+            "description": "A container for object key name prefix and suffix filtering rules.",
+            "additionalProperties": false,
+            "properties": {
+                "Rules": {
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "$ref": "#/definitions/FilterRule"
+                    }
+                }
+            },
+            "required": [
+                "Rules"
+            ]
+        },
+        "FilterRule": {
+            "type": "object",
+            "description": "Specifies the Amazon S3 object key name to filter on and whether to filter on the suffix or prefix of the key name.",
+            "additionalProperties": false,
+            "properties": {
+                "Name": {
+                    "type": "string",
+                    "maxLength": 1024
+                },
+                "Value": {
+                    "type": "string"
+                }
+            },
+            "required": [
+                "Value",
+                "Name"
+            ]
+        },
+        "ObjectLockConfiguration": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "ObjectLockEnabled": {
+                    "type": "string",
+                    "const": "Enabled"
+                },
+                "Rule": {
+                    "$ref": "#/definitions/ObjectLockRule"
+                }
+            }
+        },
+        "ObjectLockRule": {
+            "type": "object",
+            "description": "The Object Lock rule in place for the specified object.",
+            "additionalProperties": false,
+            "properties": {
+                "DefaultRetention": {
+                    "$ref": "#/definitions/DefaultRetention"
+                }
+            }
+        },
+        "DefaultRetention": {
+            "type": "object",
+            "description": "The default retention period that you want to apply to new objects placed in the specified bucket.",
+            "additionalProperties": false,
+            "properties": {
+                "Years": {
+                    "type": "integer"
+                },
+                "Days": {
+                    "type": "integer"
+                },
+                "Mode": {
+                    "type": "string",
+                    "enum": [
+                        "COMPLIANCE",
+                        "GOVERNANCE"
+                    ]
+                }
+            }
+        },
+        "OwnershipControls": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "Rules": {
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "$ref": "#/definitions/OwnershipControlsRule"
+                    }
+                }
+            },
+            "required": [
+                "Rules"
+            ]
+        },
+        "OwnershipControlsRule": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "ObjectOwnership": {
+                    "description": "Specifies an object ownership rule.",
+                    "type": "string",
+                    "enum": [
+                        "ObjectWriter",
+                        "BucketOwnerPreferred",
+                        "BucketOwnerEnforced"
+                    ]
+                }
+            }
+        },
+        "PublicAccessBlockConfiguration": {
+            "description": "Configuration that defines how Amazon S3 handles public access.",
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "BlockPublicAcls": {
+                    "type": "boolean",
+                    "description": "Specifies whether Amazon S3 should block public access control lists (ACLs) for this bucket and objects in this bucket. Setting this element to TRUE causes the following behavior:\n- PUT Bucket acl and PUT Object acl calls fail if the specified ACL is public.\n - PUT Object calls fail if the request includes a public ACL.\nEnabling this setting doesn't affect existing policies or ACLs."
+                },
+                "BlockPublicPolicy": {
+                    "type": "boolean",
+                    "description": "Specifies whether Amazon S3 should block public bucket policies for this bucket. Setting this element to TRUE causes Amazon S3 to reject calls to PUT Bucket policy if the specified bucket policy allows public access.\nEnabling this setting doesn't affect existing bucket policies."
+                },
+                "IgnorePublicAcls": {
+                    "type": "boolean",
+                    "description": "Specifies whether Amazon S3 should ignore public ACLs for this bucket and objects in this bucket. Setting this element to TRUE causes Amazon S3 to ignore all public ACLs on this bucket and objects in this bucket.\nEnabling this setting doesn't affect the persistence of any existing ACLs and doesn't prevent new public ACLs from being set."
+                },
+                "RestrictPublicBuckets": {
+                    "type": "boolean",
+                    "description": "Specifies whether Amazon S3 should restrict public bucket policies for this bucket. Setting this element to TRUE restricts access to this bucket to only AWS services and authorized users within this account if the bucket has a public policy.\nEnabling this setting doesn't affect previously stored bucket policies, except that public and cross-account access within any public bucket policy, including non-public delegation to specific accounts, is blocked."
+                }
+            }
+        },
+        "ReplicationConfiguration": {
+            "type": "object",
+            "description": "A container for replication rules. You can add up to 1,000 rules. The maximum size of a replication configuration is 2 MB.",
+            "additionalProperties": false,
+            "properties": {
+                "Role": {
+                    "description": "The Amazon Resource Name (ARN) of the AWS Identity and Access Management (IAM) role that Amazon S3 assumes when replicating objects.",
+                    "type": "string"
+                },
+                "Rules": {
+                    "description": "A container for one or more replication rules.",
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "$ref": "#/definitions/ReplicationRule",
+                        "maxLength": 1000,
+                        "minLength": 1
+                    }
+                }
+            },
+            "required": [
+                "Role",
+                "Rules"
+            ]
+        },
+        "ReplicationRule": {
+            "type": "object",
+            "description": "Specifies which Amazon S3 objects to replicate and where to store the replicas.",
+            "additionalProperties": false,
+            "properties": {
+                "DeleteMarkerReplication": {
+                    "$ref": "#/definitions/DeleteMarkerReplication"
+                },
+                "Destination": {
+                    "$ref": "#/definitions/ReplicationDestination"
+                },
+                "Filter": {
+                    "$ref": "#/definitions/ReplicationRuleFilter"
+                },
+                "Id": {
+                    "description": "A unique identifier for the rule.",
+                    "type": "string",
+                    "maxLength": 255
+                },
+                "Prefix": {
+                    "description": "An object key name prefix that identifies the object or objects to which the rule applies.",
+                    "type": "string",
+                    "maxLength": 1024
+                },
+                "Priority": {
+                    "type": "integer"
+                },
+                "SourceSelectionCriteria": {
+                    "$ref": "#/definitions/SourceSelectionCriteria"
+                },
+                "Status": {
+                    "description": "Specifies whether the rule is enabled.",
+                    "type": "string",
+                    "enum": [
+                        "Disabled",
+                        "Enabled"
+                    ]
+                }
+            },
+            "required": [
+                "Destination",
+                "Status"
+            ]
+        },
+        "DeleteMarkerReplication": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "Status": {
+                    "type": "string",
+                    "enum": [
+                        "Disabled",
+                        "Enabled"
+                    ]
+                }
+            }
+        },
+        "ReplicationDestination": {
+            "type": "object",
+            "description": "Specifies which Amazon S3 bucket to store replicated objects in and their storage class.",
+            "additionalProperties": false,
+            "properties": {
+                "AccessControlTranslation": {
+                    "$ref": "#/definitions/AccessControlTranslation"
+                },
+                "Account": {
+                    "type": "string"
+                },
+                "Bucket": {
+                    "type": "string"
+                },
+                "EncryptionConfiguration": {
+                    "$ref": "#/definitions/EncryptionConfiguration"
+                },
+                "Metrics": {
+                    "$ref": "#/definitions/Metrics"
+                },
+                "ReplicationTime": {
+                    "$ref": "#/definitions/ReplicationTime"
+                },
+                "StorageClass": {
+                    "description": "The storage class to use when replicating objects, such as S3 Standard or reduced redundancy.",
+                    "type": "string",
+                    "enum": [
+                        "DEEP_ARCHIVE",
+                        "GLACIER",
+                        "GLACIER_IR",
+                        "INTELLIGENT_TIERING",
+                        "ONEZONE_IA",
+                        "REDUCED_REDUNDANCY",
+                        "STANDARD",
+                        "STANDARD_IA"
+                    ]
+                }
+            },
+            "required": [
+                "Bucket"
+            ]
+        },
+        "AccessControlTranslation": {
+            "type": "object",
+            "description": "Specify this only in a cross-account scenario (where source and destination bucket owners are not the same), and you want to change replica ownership to the AWS account that owns the destination bucket. If this is not specified in the replication configuration, the replicas are owned by same AWS account that owns the source object.",
+            "additionalProperties": false,
+            "properties": {
+                "Owner": {
+                    "type": "string",
+                    "const": "Destination"
+                }
+            },
+            "required": [
+                "Owner"
+            ]
+        },
+        "EncryptionConfiguration": {
+            "type": "object",
+            "description": "Specifies encryption-related information for an Amazon S3 bucket that is a destination for replicated objects.",
+            "additionalProperties": false,
+            "properties": {
+                "ReplicaKmsKeyID": {
+                    "description": "Specifies the ID (Key ARN or Alias ARN) of the customer managed customer master key (CMK) stored in AWS Key Management Service (KMS) for the destination bucket.",
+                    "type": "string"
+                }
+            },
+            "required": [
+                "ReplicaKmsKeyID"
+            ]
+        },
+        "Metrics": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "EventThreshold": {
+                    "$ref": "#/definitions/ReplicationTimeValue"
+                },
+                "Status": {
+                    "type": "string",
+                    "enum": [
+                        "Disabled",
+                        "Enabled"
+                    ]
+                }
+            },
+            "required": [
+                "Status"
+            ]
+        },
+        "ReplicationTimeValue": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "Minutes": {
+                    "type": "integer"
+                }
+            },
+            "required": [
+                "Minutes"
+            ]
+        },
+        "ReplicationTime": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "Status": {
+                    "type": "string",
+                    "enum": [
+                        "Disabled",
+                        "Enabled"
+                    ]
+                },
+                "Time": {
+                    "$ref": "#/definitions/ReplicationTimeValue"
+                }
+            },
+            "required": [
+                "Status",
+                "Time"
+            ]
+        },
+        "ReplicationRuleFilter": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "And": {
+                    "$ref": "#/definitions/ReplicationRuleAndOperator"
+                },
+                "Prefix": {
+                    "type": "string"
+                },
+                "TagFilter": {
+                    "$ref": "#/definitions/TagFilter"
+                }
+            }
+        },
+        "ReplicationRuleAndOperator": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "Prefix": {
+                    "type": "string"
+                },
+                "TagFilters": {
+                    "type": "array",
+                    "uniqueItems": true,
+                    "insertionOrder": true,
+                    "items": {
+                        "$ref": "#/definitions/TagFilter"
+                    }
+                }
+            }
+        },
+        "SourceSelectionCriteria": {
+            "description": "A container that describes additional filters for identifying the source objects that you want to replicate.",
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "ReplicaModifications": {
+                    "description": "A filter that you can specify for selection for modifications on replicas.",
+                    "$ref": "#/definitions/ReplicaModifications"
+                },
+                "SseKmsEncryptedObjects": {
+                    "description": "A container for filter information for the selection of Amazon S3 objects encrypted with AWS KMS.",
+                    "$ref": "#/definitions/SseKmsEncryptedObjects"
+                }
+            }
+        },
+        "ReplicaModifications": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "Status": {
+                    "description": "Specifies whether Amazon S3 replicates modifications on replicas.",
+                    "type": "string",
+                    "enum": [
+                        "Enabled",
+                        "Disabled"
+                    ]
+                }
+            },
+            "required": [
+                "Status"
+            ]
+        },
+        "SseKmsEncryptedObjects": {
+            "type": "object",
+            "description": "A container for filter information for the selection of S3 objects encrypted with AWS KMS.",
+            "additionalProperties": false,
+            "properties": {
+                "Status": {
+                    "description": "Specifies whether Amazon S3 replicates objects created with server-side encryption using a customer master key (CMK) stored in AWS Key Management Service.",
+                    "type": "string",
+                    "enum": [
+                        "Disabled",
+                        "Enabled"
+                    ]
+                }
+            },
+            "required": [
+                "Status"
+            ]
+        },
+        "Tag": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "Key": {
+                    "type": "string",
+                    "minLength": 1,
+                    "maxLength": 128
+                },
+                "Value": {
+                    "type": "string",
+                    "minLength": 1,
+                    "maxLength": 256
+                }
+            },
+            "required": [
+                "Value",
+                "Key"
+            ]
+        },
+        "VersioningConfiguration": {
+            "description": "Describes the versioning state of an Amazon S3 bucket.",
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "Status": {
+                    "description": "The versioning state of the bucket.",
+                    "type": "string",
+                    "default": "Suspended",
+                    "enum": [
+                        "Enabled",
+                        "Suspended"
+                    ]
+                }
+            },
+            "required": [
+                "Status"
+            ]
+        },
+        "WebsiteConfiguration": {
+            "type": "object",
+            "description": "Specifies website configuration parameters for an Amazon S3 bucket.",
+            "additionalProperties": false,
+            "properties": {
+                "ErrorDocument": {
+                    "description": "The name of the error document for the website.",
+                    "type": "string"
+                },
+                "IndexDocument": {
+                    "description": "The name of the index document for the website.",
+                    "type": "string"
+                },
+                "RoutingRules": {
+                    "type": "array",
+                    "insertionOrder": true,
+                    "items": {
+                        "$ref": "#/definitions/RoutingRule"
+                    }
+                },
+                "RedirectAllRequestsTo": {
+                    "$ref": "#/definitions/RedirectAllRequestsTo"
+                }
+            }
+        },
+        "RoutingRule": {
+            "description": "Specifies the redirect behavior and when a redirect is applied.",
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "RedirectRule": {
+                    "description": "Container for redirect information. You can redirect requests to another host, to another page, or with another protocol. In the event of an error, you can specify a different error code to return.",
+                    "$ref": "#/definitions/RedirectRule"
+                },
+                "RoutingRuleCondition": {
+                    "$ref": "#/definitions/RoutingRuleCondition"
+                }
+            },
+            "required": [
+                "RedirectRule"
+            ]
+        },
+        "RedirectRule": {
+            "type": "object",
+            "description": "Specifies how requests are redirected. In the event of an error, you can specify a different error code to return.",
+            "additionalProperties": false,
+            "properties": {
+                "HostName": {
+                    "description": "The host name to use in the redirect request.",
+                    "type": "string"
+                },
+                "HttpRedirectCode": {
+                    "description": "The HTTP redirect code to use on the response. Not required if one of the siblings is present.",
+                    "type": "string"
+                },
+                "Protocol": {
+                    "description": "Protocol to use when redirecting requests. The default is the protocol that is used in the original request.",
+                    "enum": [
+                        "http",
+                        "https"
+                    ],
+                    "type": "string"
+                },
+                "ReplaceKeyPrefixWith": {
+                    "description": "The object key prefix to use in the redirect request.",
+                    "type": "string"
+                },
+                "ReplaceKeyWith": {
+                    "description": "The specific object key to use in the redirect request.d",
+                    "type": "string"
+                }
+            }
+        },
+        "RoutingRuleCondition": {
+            "description": "A container for describing a condition that must be met for the specified redirect to apply.You must specify at least one of HttpErrorCodeReturnedEquals and KeyPrefixEquals",
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "KeyPrefixEquals": {
+                    "description": "The object key name prefix when the redirect is applied.",
+                    "type": "string"
+                },
+                "HttpErrorCodeReturnedEquals": {
+                    "description": "The HTTP error code when the redirect is applied. ",
+                    "type": "string"
+                }
+            }
+        },
+        "RedirectAllRequestsTo": {
+            "description": "Specifies the redirect behavior of all requests to a website endpoint of an Amazon S3 bucket.",
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "HostName": {
+                    "description": "Name of the host where requests are redirected.",
+                    "type": "string"
+                },
+                "Protocol": {
+                    "description": "Protocol to use when redirecting requests. The default is the protocol that is used in the original request.",
+                    "type": "string",
+                    "enum": [
+                        "http",
+                        "https"
+                    ]
+                }
+            },
+            "required": [
+                "HostName"
+            ]
+        },
+        "Arn": {
+            "description": "the Amazon Resource Name (ARN) of the specified bucket.",
+            "type": "string"
+        }
+    },
+    "tagging": {
+        "taggable": true,
+        "tagOnCreate": true,
+        "tagUpdatable": true,
+        "cloudFormationSystemTags": true,
+        "tagProperty": "/properties/Tags"
+    },
+    "createOnlyProperties": [
+        "/properties/BucketName",
+        "/properties/ObjectLockEnabled"
+    ],
+    "primaryIdentifier": [
+        "/properties/BucketName"
+    ],
+    "readOnlyProperties": [
+        "/properties/Arn",
+        "/properties/DomainName",
+        "/properties/DualStackDomainName",
+        "/properties/RegionalDomainName",
+        "/properties/WebsiteURL"
+    ],
+    "handlers": {
+        "create": {
+            "permissions": [
+                "s3:CreateBucket",
+                "s3:PutBucketTagging",
+                "s3:PutAnalyticsConfiguration",
+                "s3:PutEncryptionConfiguration",
+                "s3:PutBucketCORS",
+                "s3:PutInventoryConfiguration",
+                "s3:PutLifecycleConfiguration",
+                "s3:PutMetricsConfiguration",
+                "s3:PutBucketNotification",
+                "s3:PutBucketReplication",
+                "s3:PutBucketWebsite",
+                "s3:PutAccelerateConfiguration",
+                "s3:PutBucketPublicAccessBlock",
+                "s3:PutReplicationConfiguration",
+                "s3:PutObjectAcl",
+                "s3:PutBucketObjectLockConfiguration",
+                "s3:GetBucketAcl",
+                "s3:ListBucket",
+                "iam:PassRole",
+                "s3:DeleteObject",
+                "s3:PutBucketLogging",
+                "s3:PutBucketVersioning",
+                "s3:PutObjectLockConfiguration",
+                "s3:PutBucketOwnershipControls",
+                "s3:PutBucketIntelligentTieringConfiguration"
+            ]
+        },
+        "read": {
+            "permissions": [
+                "s3:GetAccelerateConfiguration",
+                "s3:GetLifecycleConfiguration",
+                "s3:GetBucketPublicAccessBlock",
+                "s3:GetAnalyticsConfiguration",
+                "s3:GetBucketCORS",
+                "s3:GetEncryptionConfiguration",
+                "s3:GetInventoryConfiguration",
+                "s3:GetBucketLogging",
+                "s3:GetMetricsConfiguration",
+                "s3:GetBucketNotification",
+                "s3:GetBucketVersioning",
+                "s3:GetReplicationConfiguration",
+                "S3:GetBucketWebsite",
+                "s3:GetBucketPublicAccessBlock",
+                "s3:GetBucketObjectLockConfiguration",
+                "s3:GetBucketTagging",
+                "s3:GetBucketOwnershipControls",
+                "s3:GetIntelligentTieringConfiguration",
+                "s3:ListBucket"
+            ]
+        },
+        "update": {
+            "permissions": [
+                "s3:PutBucketAcl",
+                "s3:PutBucketTagging",
+                "s3:PutAnalyticsConfiguration",
+                "s3:PutEncryptionConfiguration",
+                "s3:PutBucketCORS",
+                "s3:PutInventoryConfiguration",
+                "s3:PutLifecycleConfiguration",
+                "s3:PutMetricsConfiguration",
+                "s3:PutBucketNotification",
+                "s3:PutBucketReplication",
+                "s3:PutBucketWebsite",
+                "s3:PutAccelerateConfiguration",
+                "s3:PutBucketPublicAccessBlock",
+                "s3:PutReplicationConfiguration",
+                "s3:PutBucketOwnershipControls",
+                "s3:PutBucketIntelligentTieringConfiguration",
+                "s3:DeleteBucketWebsite",
+                "s3:PutBucketLogging",
+                "s3:PutBucketVersioning",
+                "s3:PutObjectLockConfiguration",
+                "s3:DeleteBucketAnalyticsConfiguration",
+                "s3:DeleteBucketCors",
+                "s3:DeleteBucketMetricsConfiguration",
+                "s3:DeleteBucketEncryption",
+                "s3:DeleteBucketLifecycle",
+                "s3:DeleteBucketReplication",
+                "iam:PassRole"
+            ]
+        },
+        "delete": {
+            "permissions": [
+                "s3:DeleteBucket"
+            ]
+        },
+        "list": {
+            "permissions": [
+                "s3:ListAllMyBuckets"
+            ]
+        }
+    }
+}
diff --git a/src/rpdk/core/data/managed-upload-infrastructure.yaml b/src/rpdk/core/data/managed-upload-infrastructure.yaml
index bc4dfe8..873b052 100644
--- a/src/rpdk/core/data/managed-upload-infrastructure.yaml
+++ b/src/rpdk/core/data/managed-upload-infrastructure.yaml
@@ -32,12 +32,10 @@ Resources:
     DeletionPolicy: Retain
     UpdateReplacePolicy: Retain
     Properties:
-      AccessControl: LogDeliveryWrite
       BucketEncryption:
         ServerSideEncryptionConfiguration:
         - ServerSideEncryptionByDefault:
-            SSEAlgorithm: aws:kms
-            KMSMasterKeyID: !Ref EncryptionKey
+            SSEAlgorithm: AES256
       LifecycleConfiguration:
         Rules:
           - Status: Enabled
@@ -80,6 +78,17 @@ Resources:
               Bool:
                 "aws:SecureTransport": "false"
             Principal: "*"
+          - Sid: S3 Server Access Logs Policy
+            Effect: Allow
+            Principal:
+              Service: logging.s3.amazonaws.com
+            Action: s3:PutObject
+            Resource: !Sub "arn:${AWS::Partition}:s3:::${AccessLogsBucket}/ArtifactBucket*"
+            Condition:
+              ArnLike:
+                aws:SourceArn: !Sub "arn:${AWS::Partition}:s3:::${ArtifactBucket}"
+              StringEquals:
+                aws:SourceAccount: !Ref AWS::AccountId
 
   EncryptionKey:
     Type: AWS::KMS::Key
@@ -114,10 +123,6 @@ Resources:
               - resources.cloudformation.amazonaws.com
               - hooks.cloudformation.amazonaws.com
           Action: sts:AssumeRole
-          Condition:
-            StringEquals:
-              aws:SourceAccount:
-                Ref: AWS::AccountId
       Path: "/"
       Policies:
       - PolicyName: LogAndMetricsDeliveryRolePolicy
diff --git a/src/rpdk/core/hook/init_hook.py b/src/rpdk/core/hook/init_hook.py
index 279bf1c..d43b6b3 100644
--- a/src/rpdk/core/hook/init_hook.py
+++ b/src/rpdk/core/hook/init_hook.py
@@ -1,12 +1,18 @@
 import logging
 import re
 
-from rpdk.core.exceptions import WizardAbortError, WizardValidationError
+from rpdk.core.data_loaders import resource_json
+from rpdk.core.exceptions import (
+    CLIMisconfiguredError,
+    WizardAbortError,
+    WizardValidationError,
+)
 from rpdk.core.plugin_registry import get_plugin_choices
 from rpdk.core.utils.init_utils import input_with_validation, print_error
 
 LOG = logging.getLogger(__name__)
 HOOK_TYPE_NAME_REGEX = r"^[a-zA-Z0-9]{2,64}::[a-zA-Z0-9]{2,64}::[a-zA-Z0-9]{2,64}$"
+HOOK_PLUGINS = ("java", "python37", "python38", "python39")
 
 
 def init_hook(args, project):
@@ -31,7 +37,21 @@ def init_hook(args, project):
     }
 
     project.init_hook(type_name, language, settings)
-    project.generate(args.endpoint_url, args.region, args.target_schemas, args.profile)
+    try:
+        project.generate(
+            args.endpoint_url, args.region, False, args.target_schemas, args.profile
+        )
+    except CLIMisconfiguredError as e:
+        LOG.debug(
+            "Error when initializing hook project, attempting local project generation",
+            exc_info=e,
+        )
+        example_target = resource_json(
+            __name__, "../data/examples/hook/targets/aws-s3-bucket.json"
+        )
+        project.generate(
+            args.endpoint_url, args.region, True, [example_target], args.profile
+        )
     # Reload the generated example schema
     project.load_configuration_schema()
     # generate the docs based on the example schema loaded
@@ -77,7 +97,7 @@ def validate_type_name(value):
 
 class ValidatePluginChoice:
     def __init__(self, choices):
-        self.choices = tuple(choices)
+        self.choices = tuple(filter(lambda l: l in HOOK_PLUGINS, choices))
         self.max = len(self.choices)
 
         pretty = "\n".join(
diff --git a/src/rpdk/core/submit.py b/src/rpdk/core/submit.py
index d9b521f..27364c6 100644
--- a/src/rpdk/core/submit.py
+++ b/src/rpdk/core/submit.py
@@ -12,6 +12,10 @@ LOG = logging.getLogger(__name__)
 def submit(args):
     project = Project()
     project.load()
+    # Use CLI override opposed to config file if use-docker or no-docker switch used
+    if args.use_docker or args.no_docker:
+        project.settings["use_docker"] = args.use_docker
+        project.settings["no_docker"] = args.no_docker
     project.submit(
         args.dry_run,
         args.endpoint_url,
@@ -51,3 +55,18 @@ def setup_subparser(subparsers, parents):
         help="Register the type without an explicit execution role "
         "(Will not be able to invoke AWS APIs).",
     )
+
+    nodocker_group = parser.add_mutually_exclusive_group()
+    nodocker_group.add_argument(
+        "--use-docker",
+        action="store_true",
+        help="""Use docker for platform-independent packaging.
+            This is highly recommended unless you are experienced
+            with cross-platform packaging.""",
+    )
+    nodocker_group.add_argument(
+        "--no-docker",
+        action="store_true",
+        help="""Generally not recommended unless you are experienced
+            with cross-platform packaging.""",
+    )
diff --git a/src/rpdk/core/type_name_resolver.py b/src/rpdk/core/type_name_resolver.py
index a4de7b3..1837187 100644
--- a/src/rpdk/core/type_name_resolver.py
+++ b/src/rpdk/core/type_name_resolver.py
@@ -27,7 +27,7 @@ class TypeNameResolver:
         LOG.debug("Resolving the following type names: %s", str(type_names))
 
         if not any(contains_wildcard(tn) for tn in type_names):
-            return type_names
+            return sorted(type_names)
 
         req = self._create_list_types_request(type_names)
 
@@ -37,6 +37,9 @@ class TypeNameResolver:
     def resolve_type_names_locally(type_names, local_info):
         LOG.debug("Resolving the following type names: %s", str(type_names))
 
+        if not any(contains_wildcard(tn) for tn in type_names):
+            return sorted(type_names)
+
         if not local_info:
             raise InvalidTypeSchemaError(
                 "Type info must be provided for local resolving"
@@ -87,6 +90,15 @@ class TypeNameResolver:
     def list_types(self, **kwargs):
         kwargs["PaginationConfig"] = {"PageSize": REGISTRY_RESULTS_PAGE_SIZE}
 
+        def type_enabled(type_summary):
+            if (
+                kwargs["Visibility"] == REGISTRY_VISIBILITY_PRIVATE
+                or "PublisherId" not in type_summary
+            ):
+                return True
+
+            return type_summary.get("IsActivated")
+
         try:
             paginator = self.cfn_client.get_paginator("list_types")
 
@@ -96,7 +108,7 @@ class TypeNameResolver:
                     {
                         type_summary["TypeName"]: type_summary
                         for type_summary in page["TypeSummaries"]
-                        if self._type_enabled(type_summary, kwargs)
+                        if type_enabled(type_summary)
                     }
                 )
 
@@ -124,13 +136,3 @@ class TypeNameResolver:
             req["Filters"] = {"TypeNamePrefix": prefix}
 
         return req
-
-    @staticmethod
-    def _type_enabled(type_summary, request):
-        if (
-            request["Visibility"] == REGISTRY_VISIBILITY_PRIVATE
-            or "PublisherId" not in type_summary
-        ):
-            return True
-
-        return type_summary.get("IsActivated")


Test patch:
diff --git a/tests/hook/test_init_hook.py b/tests/hook/test_init_hook.py
index 85bdb28..2a1cedc 100644
--- a/tests/hook/test_init_hook.py
+++ b/tests/hook/test_init_hook.py
@@ -9,7 +9,8 @@ from rpdk.core.hook.init_hook import (
     input_typename,
     validate_type_name,
 )
-from tests.test_init import PROMPT
+
+PROMPT = "java"
 
 
 def test_input_typename():
@@ -36,7 +37,7 @@ def test_input_language_one_plugin():
 
 
 def test_input_language_several_plugins():
-    validator = ValidatePluginChoice(["1", PROMPT, "2"])
+    validator = ValidatePluginChoice(["python38", PROMPT, "python39"])
     patch_validator = patch(
         "rpdk.core.hook.init_hook.validate_plugin_choice", validator
     )
@@ -70,7 +71,7 @@ def test_validate_plugin_choice_greater_than_choice():
 
 
 def test_validate_plugin_choice_valid():
-    choices = ["1", PROMPT, "2"]
+    choices = ["python38", PROMPT, "python39"]
     validator = ValidatePluginChoice(choices)
     assert validator("2") == PROMPT
 
diff --git a/tests/test_init.py b/tests/test_init.py
index c76e757..7406fa5 100644
--- a/tests/test_init.py
+++ b/tests/test_init.py
@@ -128,7 +128,9 @@ def test_init_hook_method_interactive():
             "profile": DEFAULT_PROFILE,
         },
     )
-    mock_project.generate.assert_called_once_with(None, None, [], DEFAULT_PROFILE)
+    mock_project.generate.assert_called_once_with(
+        None, None, False, [], DEFAULT_PROFILE
+    )
 
 
 def test_init_resource_method_noninteractive():
@@ -222,7 +224,9 @@ def test_init_hook_method_noninteractive():
             "profile": DEFAULT_PROFILE,
         },
     )
-    mock_project.generate.assert_called_once_with(None, None, [], DEFAULT_PROFILE)
+    mock_project.generate.assert_called_once_with(
+        None, None, False, [], DEFAULT_PROFILE
+    )
 
 
 def test_init_resource_method_noninteractive_invalid_type_name():
@@ -328,7 +332,9 @@ def test_init_hook_method_noninteractive_invalid_type_name():
             "profile": DEFAULT_PROFILE,
         },
     )
-    mock_project.generate.assert_called_once_with(None, None, [], DEFAULT_PROFILE)
+    mock_project.generate.assert_called_once_with(
+        None, None, False, [], DEFAULT_PROFILE
+    )
 
 
 def test_init_hook_method_noninteractive_target_schemas():
@@ -383,6 +389,7 @@ def test_init_hook_method_noninteractive_target_schemas():
     mock_project.generate.assert_called_once_with(
         None,
         None,
+        False,
         ["/files/target-schema.json", "/files/other-target-schema.json"],
         DEFAULT_PROFILE,
     )
diff --git a/tests/test_project.py b/tests/test_project.py
index 965fd54..58e66e7 100644
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -2451,7 +2451,7 @@ def test__load_target_info_for_hooks(project):
     }
 
     mock_loader.assert_called_once_with(
-        set(test_type_info.keys()),
+        sorted(test_type_info.keys()),
         local_schemas=[
             "/files/target-schema.json",
             "/files/target-schema-not-for-this-project.json",