

# Creating resource types using the CloudFormation CLI
<a name="resource-types"></a>

If you use third-party resources in your infrastructure and applications, you can now model and automate those resources by developing them as *resource types* for use within CloudFormation. A resource type includes a resource type specification and handlers that control API interactions with the underlying AWS or third-party services. These interactions include create, read, update, delete, and list (CRUDL) operations for resources. Use resource types to model and provision resources using CloudFormation.

Resource types are treated as first-class citizens within CloudFormation: you can use CloudFormation capabilities to create, provision, and manage these custom resources in a safe and repeatable manner, just as you would any AWS resource. Using resource types for third-party resources provides you a way to reliably manage these resources using a single tool, without having to resort to time-consuming and error-prone methods like manual configuration or custom scripts.

You can create resource types and make them available for use within the AWS account in which they're registered.

**Note**  
Make sure your Lambda runtimes are up-to-date to avoid using a deprecated version. For more information, see [Updating Lambda runtimes for resource types and hooks](runtime-update.md).

## Using the CloudFormation CLI to create resource types
<a name="resource-types-rpdk"></a>

Use the [CloudFormation Command Line Interface (CLI)](https://github.com/aws-cloudformation/aws-cloudformation-rpdk) to develop your resource types. The CloudFormation CLI is an open-source project that provides a consistent way to model and provision both AWS and third-party resources using CloudFormation. It includes commands to enable each step of creating your resource types.

There are three major steps in developing resource types:
+ Model

  Create and validate a schema that serves as the canonical definition of your resource type.

  Use the `init` command to generate your resource project, including an example resource schema. Edit the example schema to define the actual model of your resource type. This includes resource properties and their attributes, as well specifying resource event handlers and any permissions needed for each.

  As you iterate on your resource model, you can use the `validate` command to validate your schema against the [Resource type definition schema](https://github.com/aws-cloudformation/cloudformation-cli/blob/master/src/rpdk/core/data/schema/provider.definition.schema.v1.json) and fix any issues.
+ Develop

  Add logic that controls what happens to the resource at each stage in its lifecycle, and then test the resource locally to ensure it works as expected.

  Implement the resource provisioning actions that the CloudFormation CLI stubbed out when you initially generated your resource project.

  If you make changes to your resource schema, use the `generate` command to generate the language-specific data model, contract test, and unit test stubs based on the current state of the resource schema. (If you use the Java add-in for the CloudFormation CLI, this is done for you automatically.) 

  When you're ready to test the resource behavior, the CloudFormation CLI provides two commands for testing: 
  + Use the `invoke` command to test a single handler.
  + Use the `test` command to run the entire suite of resource contract tests locally, using the AWS SAM Command Line Interface (SAM CLI), to make sure the handlers you've written comply with expected handler behavior at each stage of the resource lifecycle.
+ Register

  Register the resource type with the CloudFormation registry in order to make it available for use in CloudFormation templates.

  Use the `submit` command to register the resource type with CloudFormation and make it available for use in CloudFormation operations. Registration includes:
  + Validating the resource schema.
  + Packaging up the resource project files and uploading them to CloudFormation.
  + Registering the resource definition in your account, in the specified Region.

  You can register multiple versions of a resource type, and specify which version you want users to use by default.

# Modeling resource types to use with CloudFormation
<a name="resource-type-model"></a>

The first step in creating a resource type is *modeling* that resource, which involves crafting a schema that defines the resource, its properties, and their attributes. When you initially create your resource type project using the CloudFormation CLI `init` command, one of the files created is an example resource schema. Use this schema file as a starting point for defining the shape and semantics of your resource type.

**Note**  
When naming your extension, we recommend that you don't use the following namespaces: `aws`, `amzn`, `alexa`, `amazon`, `awsquickstart`. CloudFormation doesn't block private registration using `cfn submit` for types whose names include these namespaces, but you won't be able to publish these types.

In order to be considered valid, your resource type's schema must adhere to the [Resource type definition schema](https://github.com/aws-cloudformation/aws-cloudformation-rpdk/blob/master/src/rpdk/core/data/schema/provider.definition.schema.v1.json). This meta-schema provides a means of validating your resource specification during resource development.

The Resource Type Definition Schema is a *meta-schema* that extends [draft-07](https://json-schema.org/draft-07) of the [JSON Schema](https://json-schema.org/). To simplify authoring resource specifications, the Resource Type Definition Schema constrains the scope of the full JSON Schema standard in terms of how certain validations can be expressed, and encourages consistent modeling for all resource schemas. (For full details on how the Resource Type Definition Schema differs from the full JSON schema, see [Divergence From JSON Schema](https://github.com/aws-cloudformation/cloudformation-resource-schema/blob/master/README.md#divergence-from-json-schema).)

Once you have defined your resource schema, you can use the CloudFormation CLI ` validate` command to verify that the resource schema is valid.

In terms of testing, the resource schema also determines:
+ What unit test stubs are generated in your resource package, and what contract tests are appropriate to run for the resource. When you run the CloudFormation CLI ` generate` command, the CloudFormation CLI generates empty unit tests based on the properties of the resource and their attributes.
+ Which contract tests are appropriate for CloudFormation CLI to run for your resources. When you run the ` test` command, the CloudFormation CLI runs the appropriate contract tests, based on which handlers are included in your resource schema.

**Note**  
Make sure your Lambda runtimes are up-to-date to avoid using a deprecated version. For more information, see [Updating Lambda runtimes for resource types and hooks](runtime-update.md).

## Defining property attributes
<a name="resource-type-model-setting-properties"></a>

Certain properties of a resource may have special meaning when used in different contexts. For example, a given resource property may be read-only when read back for state changes, but can be specified when used as the target of a \$1ref from a related resource. Because of this semantic difference in how this property metadata should be interpreted, certain property attributes are defined at the resource level, rather than at a property level.

These attributes include:
+ `primaryIdentifier`
+ `additionalIdentifiers`
+ `createOnlyProperties`
+ `readOnlyProperties`
+ `writeOnlyProperties`

For reference information on resource schema elements, see [Resource type schema](resource-type-schema.md).

## How to define a minimal resource
<a name="resource-type-howto-minimal"></a>

The example below displays a minimal resource type definition. In this case, the resource consists of a single optional property, `Name`, which is also specified as its primary (and only) identifier.

Note that this resource schema would require a `handlers` section with the create, read, and update handlers specified in order for the resource to actually be provisioned within a CloudFormation account.

```
{
    "typeName": "myORG::myService::myResource",
    "properties": {
        "Name": {
            "description": "The name of the resource.",
            "type": "string",
            "pattern": "^[a-zA-Z0-9_-]{0,64}$",
            "maxLength": 64
        }
    },
    "createOnlyProperties": [
        "/properties/Name"
    ],
    "identifiers": [
        [
            "/properties/Name"
        ]
    ],
    "additionalProperties": false
}
```

## Defining the account-level configuration of an extension
<a name="resource-type-howto-configuration"></a>

There might be cases where your extension includes properties that the user must specify for all instances of the extension in a given account and Region. In such cases, you can define those properties in a *configuration definition* that the user then sets at the Region level. For example, if your extension needs to access a third-party web service, you can include a configuration for the user to specify their credentials for that service.

When the user sets the configuration, CloudFormation validates it against the configuration definition, and then saves this information at the Region level. From then on, CloudFormation can access that configuration during operations involving any instances of that extension in the Region. Configurations are available to CloudFormation during all resource operations, including `read` and `list` events that don't explicitly involve a stack template.

**Note**  
Configuration definitions aren't compatible with [module](modules.md) extensions.

Your configuration definition must validate against the [provider configuration definition meta-schema](https://github.com/aws-cloudformation/cloudformation-cli/blob/master/src/rpdk/core/data/schema/provider.configuration.definition.schema.v1.json).

The `CloudFormation` property name is reserved, and can't be used to define any properties in your configuration definition.

Use the `typeConfiguration` element of the [provider definition meta-schema](https://github.com/aws-cloudformation/cloudformation-cli/blob/master/src/rpdk/core/data/schema/provider.definition.schema.v1.json) to include the configuration definition as part of your extension's schema.

**Important**  
It's strongly recommended that you use dynamic references to restrict sensitive configuration definitions, such as third-party credentials, as in the example below. For more details on dynamic references, see [Using dynamic references to specify template values](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html) in the *AWS CloudFormation User Guide*.

### Example: Defining a configuration definition to specify third-party credentials
<a name="resource-type-howto-configuration-example"></a>

The following example illustrates how you might model third-party credentials in an extension. The schema below for the `MyOrg::MyService::Resource` resource type includes a `typeConfiguration` section. The configuration definition includes a required property, `ServiceCredentials`, of type `Credentials`. As defined in the `definitions` section, the `Credentials` type includes two properties for the user to specify their credentials for a third-party service: `ApiKey` and `ApplicationKey`.

In this example, both properties must be dynamic references, as represented by the regex pattern for each property. By using dynamic references here, CloudFormation never stores the actual credential values, but instead retrieves them from AWS Secrets Manager or Systems Manager Parameter Store only when necessary. For more information about dynamic references, including how CloudFormation distinguishes which service to retrieve values from, see [Using dynamic references to specify template values](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html) in the *AWS CloudFormation User Guide*.

To see how users set configuration data for their extensions, see [Configuring extensions at the account level](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/registry-private.html#registry-set-configuration) in the *AWS CloudFormation User Guide*.

```
{
    "typeName": "MyOrg::MyService::Resource",
    "description": "Example resource type that requires third-party credentials",
    "additionalProperties": false,
    "typeConfiguration": {
        "properties": {
            "ServiceCredentials": {
                "$ref": "#/definitions/Credentials"
            }
        },
        "additionalProperties": false,
        "required": [
            "ServiceCredentials"
        ]
    },
    "definitions": {
        "Credentials": {
            "type": "object",
            "properties": {
                "ApiKey": {
                    "description": "Third-party API key",
                    "type": "string",
                    "pattern": "{{resolve:.*:[a-zA-Z0-9_.-/]+}}"
                },
                "ApplicationKey": {
                    "description": "Third-party application key",
                    "type": "string",
                    "pattern": "{{resolve:.*:[a-zA-Z0-9_.-/]+}}"
                }
            },
            "additionalProperties": false
        }
    },
    "properties": {
        "Id": {
            "type": "string"
        },
        "Name": {
            "type": "string"
        }
    },
    "primaryIdentifier": [
        "/properties/Id"
    ],
    "additionalIdentifiers": [
        ["/properties/Name"]
    ],
    "handlers": {
    }
}
```

# Resource type schema
<a name="resource-type-schema"></a>

To be considered valid, your resource type's schema must adhere to the [Resource provider definition schema](https://github.com/aws-cloudformation/aws-cloudformation-rpdk/blob/master/src/rpdk/core/data/schema/provider.definition.schema.v1.json). This meta-schema provides a means of validating your resource specification during resource development.

## Syntax
<a name="resource-type-schema-syntax"></a>

Below is the structure for a typical resource type schema. For the complete meta-schema definition, see the [Resource Provider Definition Schema](https://github.com/aws-cloudformation/cloudformation-cli/blob/master/src/rpdk/core/data/schema/provider.definition.schema.v1.json) on [GitHub](https://github.com).

```
{
    "typeName": "string",
    "description": "string",
    "sourceUrl": "string",
    "documentationUrl": "string",
    "replacementStrategy": "create_then_delete" | "delete_then_create",
    "taggable": true | false,
    "tagging": {
      "taggable": true | false,
      "tagOnCreate": true | false,
      "tagUpdatable": true | false,
      "cloudFormationSystemTags": true | false,
      "tagProperty": "json-pointer",
    },
    "definitions": {
        "definitionName": {
          . . .
        }
    },
    "properties": {
         "propertyName": {
            "description": "string",
            "type": "string",
             . . . 
            },
        },
    },
    "required": [
        "propertyName"
    ],
    "propertyTransform": {
      { 
        "/properties/propertyName": "transform"
      }  
    },
    "handlers": {
        "create": {
            "permissions": [
                "permission"
            ],
            "timeoutInMinutes": integer
        },
        "read": {
            "permissions": [
                "permission"
            ],
            "timeoutInMinutes": integer
        },
        "update": {
            "permissions": [
                "permission"
            ],
            "timeoutInMinutes": integer
        },
        "delete": {
            "permissions": [
                "permission"
            ],
            "timeoutInMinutes": integer
        },
        "list": {
            "permissions": [
                "permission"
            ],
            "timeoutInMinutes": integer
        }
    },
    "readOnlyProperties": [
        "/properties/propertyName"
    ],
    "writeOnlyProperties": [
        "/properties/propertyName"
    ],
    "conditionalCreateOnlyProperties": [
        "/properties/propertyName"
    ],
    "nonPublicProperties": [
        "/properties/propertyName"
    ],
    "nonPublicDefinitions": [
        "/properties/propertyName"
    ],
    "createOnlyProperties": [
        "/properties/propertyName"
    ],
    "deprecatedProperties": [
        "/properties/propertyName"
    ],
    "primaryIdentifier": [
        "/properties/propertyName"
    ],
    "additionalIdentifiers": [
        [ "/properties/propertyName" ]
    ],
    "typeConfiguration": {
      . . . 
    },
    "resourceLink": {
      "templateUri": "string",
      "mappings": "json-pointer"
    },
}
```

## Properties
<a name="resource-type-schema-properties"></a>

Below are the properties for a typical resource type schema.

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

`description`  <a name="schema-properties-description"></a>
A short description of the resource. This will be displayed in the CloudFormation console.  
*Required*: Yes

`sourceUrl`  <a name="schema-properties-sourceUrl"></a>
The URL of the source code for this resource, if public.

`documentationUrl`  <a name="schema-properties-documentationurl"></a>
The URL of a page providing detailed documentation for this resource.  
While the resource schema itself should include complete and accurate property descriptions, the documentationURL property enables you to provide users with documentation that describes and explains the resource in more detail, including examples, use cases, and other detailed information.

`replacementStrategy`  <a name="schema-properties-replacementstrategy"></a>
The order of replacement when a resource update necessitates replacing the existing resource with a new resource. For example, updating a resource property that is listed in `createonlyProperties` results in a new resource being created to replace the existing resource.  
By default, when updating a resource that requires replacement, CloudFormation first creates the new resource, and then delete the old resource. However, some resources can only exist one at a time in a given account/region. For these resources, this attribute can be used to instruct CloudFormation to delete the existing resource before creating its replacement.  
For more information on how resources are updated, see [Update behaviors of stack resources](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html) in the *CloudFormation User Guide*.  
*Valid values*: `create_then_delete` \$1 `delete_then_create`

`taggable`  <a name="schema-properties-taggable"></a>
Deprecated. Use the `tagging` element instead.

`tagging`  <a name="schema-properties-tagging"></a>
Contains properties that specify the resource type's support for tags.  
If your resource type does not support tagging, you must set `taggable` to `false` or your resource type will fail contract testing.    
`taggable`  <a name="schema-properties-tagging-taggable"></a>
Whether this resource type supports tagging.  
If your resource type does not support tagging, you must set `taggable` to `false` or your resource type will fail contract testing.  
The default is `true`.  
`tagOnCreate`  <a name="schema-properties-tagging-tagoncreate"></a>
Whether this resource type supports tagging resources upon creation.  
The default is `true`.  
`tagUpdatable`  <a name="schema-properties-tagging-tagupdatable"></a>
Whether this resource type supports updating tags during resource update operations.  
The default is `true`.  
`cloudFormationSystemTags`  <a name="schema-properties-tagging-cloudformationsystemtags"></a>
Whether this resource type supports CloudFormation system tags.  
The default is `true`.  
`tagProperty`  <a name="schema-properties-tagging-tagproperty"></a>
A reference to where you have defined the `Tags` property in this resource type schema.  
The default is `/properties/Tags`.

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

`properties`  <a name="schema-properties-properties"></a>
The properties of the resource.  
All properties of a resource must be expressed in the schema. Arbitrary inputs aren't allowed. A resource must contain at least one property.  
Nested properties are not allowed. Instead, define any nested properties in the `definitions` element, and use a `$ref` pointer to reference them in the desired property.  
*Required*: Yes    
`propertyName`  <a name="schema-properties-propertyname"></a>  
`insertionOrder`  <a name="schema-properties-properties-insertionorder"></a>
For properties of type `array`, set to `true` to specify that the order in which array items are specified must be honored, and that changing the order of the array will indicate a change in the property.  
The default is `true`.  
`dependencies`  <a name="schema-properties-properties-dependencies"></a>
Any properties that are required if this property is specified.  
`patternProperties`  <a name="schema-properties-properties-patternproperties"></a>
Use to specify a specification for key-value pairs.  

```
"type": "object",
"propertyNames": {
   "format": "regex"
}
```  
`properties`  <a name="schema-properties-properties-properties"></a>
*Minimum*: 1    
`patternProperties`  
*Pattern:* `^[A-Za-z0-9]{1,64}$`  
Specifies a pattern that properties must match to be valid.  
`allOf`  <a name="schema-properties-properties-allof"></a>
The property must contain all of the data structures define here.  
Contains a single schema. A list of schemas is not allowed.  
*Minimum*: 1  
`anyOf`  <a name="schema-properties-properties-anyof"></a>
The property can contain any number of the data structures define here.  
Contains a single schema. A list of schemas is not allowed.  
*Minimum*: 1  
`oneOf`  <a name="schema-properties-properties-oneof"></a>
The property must contain only one of the data structures define here.  
Contains a single schema. A list of schemas is not allowed.  
*Minimum*: 1  
`items`  <a name="schema-properties-properties-items"></a>
For properties of type array, defines the data structure of each array item.  
Contains a single schema. A list of schemas is not allowed.
In addition, the following elements, defined in [draft-07](https://json-schema.org/draft-07) of the [JSON Schema](https://json-schema.org/), are allowed in the `properties` object:  
+ \$1ref
+ \$1comment
+ title
+ description
+ examples
+ default
+ multipleOf
+ maximum
+ exclusiveMaximum
+ minimum
+ exclusiveMinimum
+ minLength
+ pattern
+ maxItems
+ minItems
+ uniqueItems
+ contains
+ maxProperties
+ required
+ const
+ enum
+ type
+ format

`propertyTransform`  <a name="schema-properties-propertytransform"></a>
One or more transforms to apply to the property value when comparing for drift. For more information, see [Preventing false drift detection results for resource types](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-model-false-drift.html).

`remote`  <a name="schema-properties-remote"></a>
Reserved for CloudFormation use.

`readOnlyProperties`  <a name="schema-properties-readonlyproperties"></a>
Resource properties that can be returned by a `read` or `list` request, but can't be set by the user.  
*Type*: List of JSON pointers

`writeOnlyProperties`  <a name="schema-properties-writeonlyproperties"></a>
Resource properties that can be specified by the user, but can't be returned by a `read` or `list` request. Write-only properties are often used to contain passwords, secrets, or other sensitive data.  
*Type*: List of JSON pointers

`conditionalCreateOnlyProperties`  <a name="schema-properties-conditionalcreateonlyproperties"></a>
A list of JSON pointers for properties that can only be updated under certain conditions. For example, you can upgrade the engine version of an RDS DBInstance but you cannot downgrade it. When updating this property for a resource in a CloudFormation stack, the resource will be replaced if it cannot be updated.  
*Type*: List of JSON pointers

`nonPublicProperties`  <a name="schema-properties-nonpublicproperties"></a>
A list of JSON pointers for properties that are hidden. These properties will still be used but will not be visible  
*Type*: List of JSON pointers

`nonPublicDefinitions`  <a name="schema-properties-nonpublicdefinitions"></a>
A list of JSON pointers for definitions that are hidden. These definitions will still be used but will not be visible  
*Type*: List of JSON pointers

`createOnlyProperties`  <a name="schema-properties-createonlyproperties"></a>
Resource properties that can be specified by the user only during resource creation.  
Any property not explicitly listed in the `createOnlyProperties` element can be specified by the user during a resource update operation.
*Type*: List of JSON pointers

`deprecatedProperties`  <a name="schema-properties-deprecatedproperties"></a>
Resource properties that have been deprecated by the underlying service provider. These properties are still accepted in create and update operations. However they may be ignored, or converted to a consistent model on application. Deprecated properties are not guaranteed to be returned by read operations.  
*Type*: List of JSON pointers

`primaryIdentifier`  <a name="schema-properties-primaryidentifier"></a>
The uniquely identifier for an instance of this resource type. An identifier is a non-zero-length list of JSON pointers to properties that form a single key. An identifier can be a single or multiple properties to support composite-key identifiers.  
*Type*: List of JSON pointers  
*Required*: Yes

`additionalIdentifiers`  <a name="schema-properties-additionalidentifiers"></a>
An optional list of lists of supplementary identifiers, each of which uniquely identifies an instance of this resource type. An identifier can be a single property, or multiple properties to construct composite-key identifiers.  
*Type*: List of lists  
*Minimum*: 1

`handlers`  <a name="schema-properties-handlers"></a>
Specifies the provisioning operations which can be performed on this resource type. The handlers specified determine what provisioning actions CloudFormation takes with respect to the resource during various stack operations.  
+ If the resource type doesn't contain `create`, `read`, and `delete` handlers, CloudFormation can't actually provision the resource.
+ If the resource type doesn't contain an `update` handler, CloudFormation can't update the resource during stack update operations, and will instead replace it.
If your resource type calls AWS APIs in any of its handlers, you must create an *[IAM execution role](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html)* that includes the necessary permissions to call those AWS APIs, and provision that execution role in your account. For more information, see [Accessing AWS APIs from a Resource Type](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-develop.html#resource-type-develop-executionrole).    
`create`  <a name="schema-properties-handlers-create"></a>  
permissions  <a name="schema-properties-handlers-create-permissions"></a>
A string array that specifies the AWS permissions needed to invoke the create handler.  
You must specify at least one permission for each handler.  
*Required*: Yes  
timeoutInMinutes  <a name="schema-properties-handlers-create-timeoutinminutes"></a>
An integer specifying the timeout for the entire operation to be interpreted by the invoker of the handler, in minutes.   
*Minimum*: 2  
*Maximum*: 2160  
*Default*: 120  
*Required*: No  
`read`  <a name="schema-properties-handlers-read"></a>  
`permissions`  <a name="schema-properties-handlers-read-permissions"></a>
A string array that specifies the AWS permissions needed to invoke the read handler.  
You must specify at least one permission for each handler.  
*Required*: Yes  
timeoutInMinutes  <a name="schema-properties-handlers-read-timeoutinminutes"></a>
An integer specifying the timeout for the entire operation to be interpreted by the invoker of the handler, in minutes.   
*Minimum*: 2  
*Maximum*: 2160  
*Default*: 120  
*Required*: No  
`update`  <a name="schema-properties-handlers-update"></a>  
permissions  <a name="schema-properties-handlers-update-permissions"></a>
A string array that specifies the AWS permissions needed to invoke the update handler.  
You must specify at least one permission for each handler.  
*Required*: Yes  
timeoutInMinutes  <a name="schema-properties-handlers-update-timeoutinminutes"></a>
An integer specifying the timeout for the entire operation to be interpreted by the invoker of the handler, in minutes.   
*Minimum*: 2  
*Maximum*: 2160  
*Default*: 120  
*Required*: No  
`delete`  <a name="schema-properties-handlers-delete"></a>  
permissions  <a name="schema-properties-handlers-delete-permissions"></a>
A string array that specifies the AWS permissions needed to invoke the delete handler.  
You must specify at least one permission for each handler.  
*Required*: Yes  
timeoutInMinutes  <a name="schema-properties-handlers-delete-timeoutinminutes"></a>
An integer specifying the timeout for the entire operation to be interpreted by the invoker of the handler, in minutes.   
*Minimum*: 2  
*Maximum*: 2160  
*Default*: 120  
*Required*: No  
`list`  <a name="schema-properties-handlers-list"></a>
The `list` handler must at least return the resource's primary identifier.    
permissions  <a name="schema-properties-handlers-list-permissions"></a>
A string array that specifies the AWS permissions needed to invoke the list handler.  
You must specify at least one permission for each handler.  
*Required*: Yes  
timeoutInMinutes  <a name="schema-properties-handlers-list-timeoutinminutes"></a>
An integer specifying the timeout for the entire operation to be interpreted by the invoker of the handler, in minutes.   
*Minimum*: 2  
*Maximum*: 2160  
*Default*: 120  
*Required*: No

`allOf`  <a name="schema-properties-allof"></a>
The resource must contain all of the data structures defined here.  
*Minimum*: 1

`anyOf`  <a name="schema-properties-anyof"></a>
The resource can contain any number of the data structures define here.  
*Minimum*: 1

`oneOf`  <a name="schema-properties-oneof"></a>
The resource must contain only one of the data structures define here.  
*Minimum*: 1

`resourceLink`  <a name="schema-properties-resourcelink"></a>
A template-able link to a resource instance. External service links must be absolute, HTTPS URIs.    
templateUri  <a name="schema-properties-resourcelink-templateuri"></a>
*Required*: Yes  
*Pattern*: `^(/|https:)`  
mappings  <a name="schema-properties-resourcelink-mappings"></a>
*Required*: Yes  
*Type*: List of JSON pointers

`typeConfiguration`  <a name="schema-properties-typeconfiguration"></a>
A type configuration schema that defines any properties that the user must specify for all instances of the extension in a given account and Region. For example, if your extension needs to access a third-party web service, you can include a configuration schema for the user to specify their credentials for that service.  
Your configuration definition must validate against the [provider configuration definition meta-schema](https://github.com/aws-cloudformation/cloudformation-cli/blob/master/src/rpdk/core/data/schema/provider.configuration.definition.schema.v1.json).  
When the user specifices a resource, they can sets the configuration. CloudFormation validates it against the configuration definition, and then saves this information at the Region level. From then on, CloudFormation can access that configuration schema during operations involving any instances of that extension in the Region. Configurations are available to CloudFormation during all resource operations, including `read` and `list` events that don't explicitly involve a stack template.  
The `CloudFormation` property name is reserved, and cannot be used to define any properties in your configuration definition.  
For more information, see [Defining the account-level configuration of an extension](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-model.html#resource-type-howto-configuration).

# Patterns for modeling your resource types
<a name="resource-type-howtos"></a>

Use the following patterns to model the data structures of your resource types using the Resource Provider Schema.

## How to specify a property as dependent on another
<a name="resource-type-howto-dependencies"></a>

Use the `dependencies` element to specify if a property is required in order for another property to be specified. In the following example, if the user specifies a value for the `ResponseCode` property, they must also specify a value for `ResponsePagePath`, and vice versa. (Note that, as a best practice, this is also called out in the `description` of each property.)

```
"properties": {
"CustomErrorResponse": {
    "additionalProperties": false,
    "dependencies": {
        "ResponseCode": [
            "ResponsePagePath"
        ],
        "ResponsePagePath": [
            "ResponseCode"
        ]
    },
    "properties": {
        "ResponseCode": {
            "description": "The HTTP status code that you want CloudFront to return to the viewer along with the custom error page. If you specify a value for ResponseCode, you must also specify a value for ResponsePagePath.",
            "type": "integer"
        },
        "ResponsePagePath": {
            "description": "The path to the custom error page that you want CloudFront to return to a viewer when your origin returns the HTTP status code specified by ErrorCode. If you specify a value for ResponsePagePath, you must also specify a value for ResponseCode.",
            "type": "string"
        }
        . . . 
    },
    "type": "object" 
},
},
. . .
```

## How to define nested properties
<a name="resource-type-howto-nested-properties"></a>

It's considered a best practice is to use the `definitions` section to define schema elements that may be used at multiple points in your resource type schema. You can then use a JSON pointer to reference that element at the appropriate places in your resource type schema.

For example, define the reused element in the `definitions` section:

```
"definitions": {
    "AccountId": {
        "pattern": "^[0-9]{12}$",
        "type": "string"
    },
    . . . 
},
. . .
```

And then reference that definition where appropriate:

```
"AwsAccountNumber": {
    "description": "An AWS account that is included in the TrustedSigners complex type for this distribution.",
    "$ref": "#/definitions/AccountId"
},
    . . .
```

## Advanced: How to encapsulate complex logic
<a name="resource-type-howto-logic"></a>

Use the `allOf`, `oneOf`, or `anyOf` elements to encapsulate complex logic in your resource type schema.

In the example below, if `whitelist` is specified for the Forward property in your resource, then the `WhitelistedNames` property must also be specified.

```
"properties": {
"Cookies": {
    "oneOf": [
        {
            "additionalProperties": false,
            "properties": {
               "Forward": {
                    "description": "Specifies which cookies to forward to the origin for this cache behavior.",
                    "enum": [
                        "all",
                        "none"
                    ],
                    "type": "string"
                }
            },
            "required": [
                "Forward"
                ]
        },
     ],
        {
            "additionalProperties": false,
            "properties": {
                "Forward": {
                    "description": "Specifies which cookies to forward to the origin for this cache behavior.",
                    "enum": [
                        "whitelist"
                    ],
                    "type": "string"
                },
                "WhitelistedNames": {
                    "description": "Required if you specify whitelist for the value of Forward.",
                    "items": {
                       "type": "string"
                    },
                    "minItems": 1,
                    "type": "array"
                }
            },
            "required": [
                "Forward",
                "WhitelistedNames"
            ]
        },
    "type": "object"
    }
}
. . .
```

# Preventing false drift detection results for resource types
<a name="resource-type-model-false-drift"></a>

When CloudFormation performs drift detection on a resource, it looks up the value for each resource property as specified in the stack template, and compares that value with the current resource property value returned by the resource `read` handler. A resource is then considered to have drifted if one or more of its properties have been deleted, or had their value changed. In some cases, however, the resource may not be able to return the exact same value in the `read` handler as was specified in the stack template, even though the value is essentially the same and shouldn't be considered as drifted.

To prevent these cases from being incorrectly reported as drifted resources, you can specify a *property transform* in your resource schema. The property transform provides a way for CloudFormation to accurately compare the resource property value specified in the template with the value returned from the `read` handler. During drift detection, if CloudFormation finds a property where the template value differs from the value returned by the `read` handler, it determines if a property transform has been defined for that property in the resource schema. If it has, CloudFormation applies that property transform to the value specified in the template, and then compares it to the `read` handler value again. If these two values match, the property isn't considered to have drifted, and is marked as `IN_SYNC`.

For more information about drift detection, see [Detecting unmanaged configuration changes to stacks and resources](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-stack-drift.html) in the *CloudFormation User Guide*.

## Defining a property transform for drift detection operations
<a name="resource-type-model-false-drift-property-transform"></a>

Use the `propertyTransform` element to define a property transform for a given resource property.

```
"propertyTransform": { 
  "property_path": "transform"
}
```

Where:
+ *property\$1path* is the path to the resource property in the resource schema.
+ *transform* is the transform to perform on the resource property value specified in the stack template.

  Property transforms are written in [JSONata](https://docs.jsonata.org/overview.html), an open-source, lightweight query and transformation language for JSON data.

For example, consider the `[AWS::Route53::HostedZone](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53-hostedzone.html#cfn-route53-hostedzone-name) resource`. For the `[Name](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53-hostedzone.html#cfn-route53-hostedzone-name)` property, users can specify a domain name with or without a trailing `.` in their templates. However, assume the Route 53 service always returns the domain name with a trailing `.`. This means that if a user specified a domain name without the trailing `.` in their template, created the stack, and then performed drift detection on the stack, CloudFormation would erroneously report the `AWS::Route53::HostedZone` resource as drifted. To prevent this from happening, the resource developer would add a `propertyTransform` element to the resource schema to enable CloudFormation to determine if both property values were actually the same:

```
"propertyTransform": { 
  "/properties/Name": "$join([Name, \".\"])"
}
```

### Specifying multiple transforms for a property
<a name="resource-type-model-false-drift-property-transform-mulitple"></a>

You can specify multiple transforms for CloudFormation to attempt by using the `$OR` operator. If you specify multiple transforms, CloudFormation tries them all, in the order they're specified, until it finds one that results in the property values matching, or it has tried them all.

For example, for the following property transform, CloudFormation would attempt two transforms to determine whether the property value has actually drifted:
+ Append `.` to the template property value, and determine if the updated value now matches the property value returned by the resource `read` handler. If it does, CloudFormation reports the property as `IN_SYNC`. If not, CloudFormation performs the next transform.
+ Append the string `test` to the template property value, and determine if the updated value now matches the property value returned by the resource `read` handler. If it does, CloudFormation reports the property as `IN_SYNC`. If not, CloudFormation reports the property, and the resource, as `MODIFIED`.

```
"propertyTransform": { 
  "/properties/Name": "$join([Name, \".\"]) $OR $join([Name, \"test\"])"
}
```

# Developing resource types for CloudFormation templates
<a name="resource-type-develop"></a>

Once you've modeled your resource type, and validated its schema, the next step is to develop the resource. Developing the resource consists of two main steps:
+ Implementing the appropriate event handlers for your resource.
+ Testing the resource locally to ensure it works as expected.

## Implementing resource handlers
<a name="resource-type-develop-implement-handlers"></a>

When you `generate` your resource package, the CloudFormation CLI stubs out empty handler functions, each of which each corresponds to a specific event in the resource lifecycle. You add logic to these handlers to control what happens to your resource type at each stage of its lifecycle.
+ `create`: CloudFormation invokes this handler when the resource is initially created during stack create operations.
+ `read`: CloudFormation invokes this handler as part of a stack update operation when detailed information about the resource's current state is required.
+ `update`: CloudFormation invokes this handler when the resource is updated as part of a stack update operation.
+ `delete`: CloudFormation invokes this handler when the resource is deleted, either when the resource is deleted from the stack as part of a stack update operation, or the stack itself is deleted.
+ `list`: CloudFormation invokes this handler when summary information about multiple resources of this resource type is required.

You can only specify a single handler for each event.

Which handlers you implement for a resource determine what provisioning actions CloudFormation takes with respect to the resource during various stack operations:
+ If the resource type contains both `create` and `update` handlers, CloudFormation invokes the appropriate handler during stack create and update operation.
+ If the resource type doesn't contain an `update` handler, CloudFormation can't update the resource during stack update operations, and will instead replace it. CloudFormation invokes the `create` handler to creates a new resource, then deletes the old resource by invoking the `delete` handler.
+ If the resource type doesn't contain `create`, `read`, and `delete` handlers, CloudFormation can't actually provision the resource.

Use the resource schema to specify which handlers you have implemented. If you choose not to implement a specific handler, remove it from the `handlers` section of the resource schema.

### Accessing AWS APIs from a resource type
<a name="resource-type-develop-executionrole"></a>

If your resource type calls AWS APIs in any of its handlers, you must create an *[IAM execution role](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html)* that includes the necessary permissions to call those AWS APIs, and provision that execution role in your account. CloudFormation then assumes that execution role to provide your resource type with the appropriate credentials.

When you call `generate`, the CloudFormation CLI automatically generates an execution role template, `resource-role.yaml`, as part of generating the code files for the resource type package. This template is based on the permissions specified for each handler in the `[handlers](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-schema.html#schema-properties-handlers)` section of the [Resource type schema](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-schema.html). When you use `submit` to register the resource type, the CloudFormation CLI attempts to create or update an execution role based on the template, and then passes this execution role to CloudFormation as part of the registration.

For more information about the permissions available per AWS service, see [Actions, resources, and condition keys for AWS services](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_actions-resources-contextkeys.html) in the *[AWS Identity and Access Management User Guide](https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction.html)*.

## Monitoring runtime logging for resource types
<a name="resource-type-develop-log"></a>

When you register a resource type using `cfn submit`, CloudFormation creates a CloudWatch log group for the resource type in your account. This enables you to access the logs for your resource to help you diagnose any faults. The log group is named according to the following pattern:

`/my-resource-type-stack-ResourceHandler-string`

Where:
+ *my-resource-type* is the three-part resource type name.
+ *string* is a unique string generated by CloudFormation.

Now, when you initiate stack operations for stacks that contain the resource type, CloudFormation delivers log events emitted by the resource type to this log group.

# Testing resource types using contract tests
<a name="resource-type-test"></a>

As you model and develop your resource type, you should have the CloudFormation CLI perform tests to ensure that the resource type is behaving as expected during each event in the resource lifecycle. The CloudFormation CLI performs a suite of tests called contract tests to enforce CloudFormation’s [handler contract](resource-type-test-contract.md). Developing and registering your resource type in CloudFormation signifies an agreement that your resource is compliant and doesn't break any framework expectations. All resources that fail contract tests are blocked from publishing into our registry.

## Testing resource types locally using AWS SAM
<a name="resource-type-develop-test"></a>

Once you've implemented the desired handlers for your resource, you can also test the resource locally using the AWS SAM command line interface (CLI), to make sure your resource behaves as expected, debug what's wrong, and fix any issues.

To start testing, use the AWS SAM CLI to start the Local Lambda service. Run the following command in a terminal separate from your resource type workspace, or as a background process.

```
$ sam local start-lambda
```

If you have functions defined in your AWS SAM template, it will provide an endpoint to invoke these functions locally. This is especially helpful because it allows for remote debugging to step through resource type invocations in real time.

```
Starting the Local Lambda Service. You can now invoke your Lambda Functions defined in your template through the endpoint.
2020-01-15 15:27:19  * Running on http://127.0.0.1:3001/ (Press CTRL+C to quit)
```

Alternatively, you can also specify using the public Lambda service and invoke functions deployed in your account. Be aware, however, that using the local service allows for more iteration. To specify a debug port for remote debugging, use the `-d` option:

```
$ sam local start-lambda -d PORT
```

Once you have the Lambda service started, use the `[test](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-cli-test.html)` command to perform contract tests:

```
$ cfn test
```

The CloudFormation CLI selects the appropriate contract tests to execute, based on the handlers specified in your resource type schema. If a test fails, the CloudFormation CLI outputs a detailed trace of the failure, including the related assertion failure and mismatched values.

For more information about testing using AWS SAM CLI, see [Testing and debugging serverless applications ](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-test-and-debug.html) in the *AWS Serverless Application Model Developer Guide*.

## How the CloudFormation CLI constructs and executes contract tests
<a name="resource-type-test-how"></a>

The CloudFormation CLI uses [PyTest](https://docs.pytest.org/en/latest/), an open-source testing framework, to execute the contract tests.

The tests themselves are located in the [suite](https://github.com/aws-cloudformation/cloudformation-cli/tree/master/src/rpdk/core/contract/suite) folder of the CloudFormation CLI repository on GitHub. Each test is adorned with the appropriate `pytest` markers. For example, tests applicable to the `create` handler are adorned with the `@pytest.mark.create` marker. This enables the CloudFormation CLI to execute only those tests appropriate for a resource type, based on the handlers specified in the resource type's schema. For example, suppose a resource type's schema specified `create`, `read`, and `delete` handlers. In this case, the CloudFormation CLI would not perform any test marked with only the `@pytest.mark.update` or `@pytest.mark.list`, since those handlers weren't implemented.

To test `create` and `update` handlers, the CloudFormation CLI uses the resource type's schema and [Hypothesis](https://hypothesis.readthedocs.io/en/latest/), an open-source Python library used to generate testing strategies. The resource type schema is walked to generate a strategy for valid resource models, and the strategy is used to generate models for tests.

Tests create, update, and delete resources to test various aspects of the resource handler contract during handler operations. The CloudFormation CLI uses PyTest `fixtures` to decrease the amount of time the contract tests take to perform. Using fixtures enable the tests within a test module to share resources, rather than have to create a new resource for each test. Currently, the contract tests employ the following fixtures:
+ `created_resource` in the `[handler\$1create](https://github.com/aws-cloudformation/cloudformation-cli/blob/master/src/rpdk/core/contract/suite/resource/handler_create.py)` test module.
+ `updated_resource` in the `[handler\$1update](https://github.com/aws-cloudformation/cloudformation-cli/blob/master/src/rpdk/core/contract/suite/resource/handler_update.py)` test module.
+ `deleted_resource` in `[handler\$1delete](https://github.com/aws-cloudformation/cloudformation-cli/blob/master/src/rpdk/core/contract/suite/resource/handler_delete.py)` test module.

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

By default, the CloudFormation CLI performs resource contract tests using input properties generated from the patterns you define in your resource type schema. However, most resources are complex enough that the input properties for creating or updating those resources requires an understanding of the resource being provisioned. To address this, you can specify the input the CloudFormation CLI uses when performing its contract tests.

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

  Using an `overrides` file provides a light-weight way of specifying input data for certain specific properties for the CloudFormation CLI to use during both create and update operations testing.
+ Input files

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

### Specifying input data using an override file
<a name="resource-type-test-overrides"></a>

Using an override file enables you to overwrite input values for specific resource properties. Input values specified in the override file are used in contract testing for both create and update operations. You can only specify a single override file, and only specify a single input value for each resource property. For any properties for which you don't specify values, the CloudFormation CLI uses generated input.

Because the input data specified in the `overrides.json` file is used by the CloudFormation CLI during testing of create and update operations, you can't include input values for create-only properties in the file, as this would lead to contract test failures during update operations. For more information, see [createOnlyProperties](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-schema.html#schema-properties-createonlyproperties).

To override the input data used for specific properties during contract testing, add an `overrides.json` file to the root directory of your resource type project. The `overrides.json` file should contain only the resource properties to be used in testing. Use the following syntax:

```
{
    "CREATE": {
        "property_name": "property_value" # optional_comment
    }
}
```

For example:

```
{
    "CREATE": {
        "SubnetId": "subnet-0bc6136e" # This should be a real subnet that exists in the account you're testing against.
    }
}
```

You can also use output values from other stacks when specifying input data. For example, suppose you had a stack that contained an export value named `SubnetExport`:

```
Resources:
    VPC:
        Type: "AWS::EC2::VPC"
        Properties:
            CidrBlock: "10.0.0.0/16"
    Subnet:
        Type: "AWS::EC2::Subnet"
        Properties:
           CidrBlock: "10.0.0.0/24"
           VpcId: !Ref VPC
Outputs:
    SubnetId:
        Value: !Ref Subnet
        Export:
            Name: SubnetExport
```

You could then specify that export value as input data using the export value name using the following syntax:

```
{
  "CREATE": {
    "SubnetId": "{{SubnetExport}}"
  }
}
```

For more information, see [Outputs](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html).

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

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

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

Specify which kind of input data a file contains by using the following naming conventions, where **n** is an integer:
+ `inputs_n_create.json`: Use files with `_create.json` for specifying inputs for creating the resource. This includes input values for create-only properties. For more information, see [createOnlyProperties](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-schema.html#schema-properties-createonlyproperties).
+ `inputs_n_update.json`: Use files with `_update.json` for specifying inputs for updating the resource.
+ `inputs_n_invalid.json`: Use files with `_invalid.json` for specifying invalid inputs to test when creating or updating the resource.

To specify multiple sets of input data for contract tests, increment the integer in the file names to order your input data sets. For example, your first set of input files should be named `inputs_1_create.json`, `inputs_1_update.json`, and `inputs_1_invalid.json`. Your next set would be named `inputs_2_create.json`, `inputs_2_update.json`, and `inputs_2_invalid.json`, and so on.

Each input file is a JSON file containing only the resource properties to be used in testing. Below is an example of an input file data set.

```
{
   "AlarmName": "Name",
   "AlarmDescription": "TestAlarmDimensions Description",
   "Namespace": "CloudWatchNamespace",
   "MetricName": "Fault",
   "Dimensions": [
      {
         "Name": "MethodName",
         "Value": "Value"
      }
   ],
   "Statistic": "Average",
   "Period": 60,
   "EvaluationPeriods": 5,
   "Threshold": 0.01,
   "ComparisonOperator": "GreaterThanOrEqualToThreshold"
}
```

If you specify an `inputs` folder, the CloudFormation CLI uses only the input data included in that folder. Therefore, you must specify create, update, and invalid data files for the CloudFormation CLI to successfully complete the contract tests.

If you specify both input files and an overrides files, the CloudFormation CLI ignores the overrides file and uses the input data specified in the `inputs` folder.

You can also use output values from other stacks when specifying input data. For example, suppose you had a stack that contained an export value named `SubnetExport`:

```
Resources:
    VPC:
        Type: "AWS::EC2::VPC"
        Properties:
            CidrBlock: "10.0.0.0/16"
    Subnet:
        Type: "AWS::EC2::Subnet"
        Properties:
           CidrBlock: "10.0.0.0/24"
           VpcId: !Ref VPC
Outputs:
    SubnetId:
        Value: !Ref Subnet
        Export:
            Name: SubnetExport
```

You could then specify that export value as input data using the export value name using the following syntax:

```
{
   "SubnetId": "{{SubnetExport}}",
   . . . 
}
```

For more information, see [Outputs](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html).

## Viewing contract test logs
<a name="debug-contract-test"></a>

It's important to note that contract tests aren't run during private type registration (unless the resource contains one of the following namespaces: `aws`, `amzn`, `alexa`, `amazon`, `awsquickstart`), but failing contract tests does block a publisher's ability to publish their type. This is because public resources are consumed by other external customers and need to maintain a high quality bar. For this reason, it's important to debug your contract test failures early on in the resource development process.

Running contract tests generates two types of logs. Using both helps expedite the debugging process.
+ `Lambda logs` show logs from your handlers and provide more details on the input and output for each handler call.
+ `Test logs` show the result of running the test suite, including which tests have failed or passed, in addition to a traceback if a test has failed.

Because there are multiple ways to invoke contract tests against your resource, there are different places to find logs depending on which operation you are using.
+ If you run contract tests locally, logs are divided into the following two sections:
  + Lambda logs are in the terminal tab in which you ran `sam local start-lambda`.
  + Test logs are in the terminal tab in which you ran `cfn test`.
+ If you run contract tests through the type registration (`cfn submit`), logs are uploaded in two areas. Contract tests are only run during type registration if your type name includes one of the following namespaces: `aws`, `amzn`, `alexa`, `amazon`, `awsquickstart`.
  + Lambda logs are delivered to a CloudWatch log group in your account. The log group adheres to the following naming pattern:

    `<Hyphenated TypeName>-ContractTests-<RegistrationToken>`

    For example, `aws-cloudwatch-alarm-ContractTests-ca7096d7-ccb3-4c7d-ad51-78d0a1a300ca`.
  + To receive test logs in an Amazon S3 bucket, you have to modify the IAM role that's created by CloudFormation, which adheres to the following naming pattern:

    `CloudFormationManagedUplo-LogAndMetricsDeliveryRol-<RandomId>`

    Add the following inline policy:

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

****  

    ```
    {
        "Version":"2012-10-17",		 	 	 
        "Statement": [
            {
                "Action": [
                    "s3:PutObject"
                ],
                "Resource": [
                    "*"
                ],
                "Effect": "Allow"
            },
            {
                "Action": [
                    "kms:Encrypt",
                    "kms:Decrypt",
                    "kms:ReEncrypt*",
                    "kms:GenerateDataKey*",
                    "kms:DescribeKey"
                ],
                "Resource": "*",
                "Effect": "Allow"
            }
        ]
    }
    ```

------

    Also add the following trust policy:

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

****  

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

------

    Invoking `cfn submit --role-arn <arn for above IAM role>` uploads your test logs to an Amazon S3 bucket named `cloudformationmanageduploadinfrast-artifactbucket-<RandomId>` under the following path:

    `CloudFormation/ContractTestResults/<TypeName>/<ContractTestInvocationToken>.zip`

    Download the zip file to see your test logs.
+ If you run contract tests against your registered type through the `TestType`, both logs are condensed and uploaded to an Amazon S3 bucket in your account. You must specify the [https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_TestType.html#API_TestType_RequestParameters](https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_TestType.html#API_TestType_RequestParameters) parameter when invoking `TestType` to receive logs in your account.

## Testing resource types manually
<a name="manual-testing"></a>

Running contract tests with the `cfn-test` command uses the AWS SAM CLI, so it's possible to attach a debugger from your IDE by specifying a port when you start the local Lambda service. However, we don't suggest this approach because the debugger detaches after each individual handler invocation completes.

Instead, you can mimic the scenarios modeled in contract tests by invoking the handlers with the [https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-invoke.html](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-invoke.html) command. This allows you to step through each handler invocation without interruption. You first need to define test templates that AWS SAM can run against the resource handlers. Create the test templates in a separate folder in the resource directory and name the folder `sam-tests`.

The test templates must adhere to the following format:

```
{
  "credentials": {
    "accessKeyId": "<Access Key Id>",
    "secretAccessKey": "<Secret Access key>",
    "sessionToken": "<Session Token>"
  },
  "action": "<Action>",
  "request": {
    "clientRequestToken": "<Random UUID>",
    "desiredResourceState": <ResourceModel json>,
    "logicalResourceIdentifier": "<Logical Id>"
  },
  "callbackContext": <CallbackContext json>
}
```
+ For `credentials`, use temporary credentials for an IAM role (such as `PowerUserAccess` or `Developer`) in your personal AWS account. Replace the `accessKeyId`, `secretAccessKey`, and `sessionToken` with their corresponding values. For instructions on how to copy IAM role credentials from the AWS access portal, see [Manual credential refresh](https://docs.aws.amazon.com/singlesignon/latest/userguide/howtogetcredentials.html#how-to-get-temp-credentials-manual) in the *AWS IAM Identity Center User Guide*. The settings for the IAM role you choose determine [how long the temporary credentials are valid](https://docs.aws.amazon.com/singlesignon/latest/userguide/howtosessionduration.html). 

  Using the AWS CLI, you can call an [AWS STS API](https://docs.aws.amazon.com/STS/latest/APIReference/) like `AssumeRole` or `GetFederationToken` and then capture the resulting output. For more information, see [Using temporary credentials with AWS resources](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html) in the *IAM User Guide*. 
**Note**  
In the past, it was common practice to use persistent credentials, such as IAM user credentials or even root credentials, but this is not recommended. For more information, see [Security best practices in IAM](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html) in the *IAM User Guide*. 
+ For `action`, specify the handler you want to test. Allowed values: `CREATE`, `READ`, `DELETE`, `UPDATE`, `LIST`.
+ For `clientRequestToken`, specify a random UUID string. To retrieve this, use any UUID generator tool.
+ For `desiredResourceState`, specify the properties of the resource required for the request that follow the resource schema.
+ For `logicalResourceIdentifier`, specify a logical ID to assign to your resource instance. You can use this in subsequent handler invocations for the same resource.
+ For `callbackContext`, the request begins with a null value for callback context. For handlers with stabilization logic, the subsequent requests have callback context from the previous request's response.

Once you've written the input files, do the following to debug your handlers:

1. Ensure that Docker is downloaded and installed on your machine, and that you've added the resource directory to Docker.

1. In one terminal, start the local Lambda service by running `sam local start-lambda`.

1. In your IDE, create a remote configuration and add a port number. Add a breakpoint in the appropriate handler you are invoking.

1. In another terminal, invoke the handler by running `sam local invoke TestEntrypoint --event sam-tests/<input filename> -d <PORT number>`.

1. Step through the code to debug any handler errors.

For more information about testing using the AWS SAM CLI, see [Testing and Debugging Serverless Applications](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-test-and-debug.html) in the *AWS Serverless Application Model Developer Guide*.

# Resource type handler contract
<a name="resource-type-test-contract"></a>

The resource type handler contract specifies the expected and required behavior to which a resource must adhere in each given event handler. It defines a set of specific, unambiguous rules with which `create`, `read`, `update`, `delete` and `list` resource handlers must comply. Following the contract will allow customers to interact with all resource types under a uniform set of behaviors and expectations, and prevents creation of unintended or duplicate resources.

A resource implementation MUST pass all resource contract tests to be registered.

Assuming no other concurrent interaction on the resource, the handlers must comply with the following contract.

All terminology in the handler contract requirements adheres to the [RFC 2119 specification](https://www.ietf.org/rfc/rfc2119.txt).

## Create handlers
<a name="resource-type-test-contract-create"></a>

CloudFormation invokes the `create` handler when the resource is created during a stack create operation.

### Input assumptions
<a name="resource-type-test-contract-create-in"></a>

The `create` handler can make the following assumptions about input submitted to it:
+ The input to a `create` handler MUST be valid against the resource schema.

### Output requirements
<a name="resource-type-test-contract-create-out"></a>

The `create` handler must adhere to the following requirements regarding its output:
+ A `create` handler MUST always return a ProgressEvent object within 60 seconds. For more information, see [ProgressEvent Object Schema](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-progressevent.html).

  In every ProgressEvent object, the `create` handler MUST return a model which conforms to the shape of the resource schema. For more information, see [Returned models must conform to the shape of the schema](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html#resource-type-test-contract-additional-shape).

  Every model MUST include the primaryIdentifier. The only exception is if the first progress event is `FAILED`, and the resource hasn't yet been created. In this case, a subsequent `read` call MUST return `NotFound`.
+ A `create` handler MUST NOT return `SUCCESS` until it has applied all properties included in the `create` request. For more information, see [Update, create, and delete handlers must satisfy desired-state stabilization](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html#resource-type-test-contract-additional-stabilization).
  + A `create` handler MUST return IN\$1PROGRESS if it hasn't yet reached the desired-state.

    A `create` handler SHOULD return a model containing all properties set so far and nothing more during each IN\$1PROGRESS event.
  + A `create` handler MUST return FAILED progress event if it can't reach the desired-state within the timeout specified in the resource schema.

    The progress event MUST return an error message and the most applicable error code. For more information, see [Handler error codes](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract-errors.html).
  + A `create` handler MAY return SUCCESS once it reaches the desired-state.

    Once the desired state has been reached, a `create` handler MAY perform runtime-state stabilization. For more information, see [Update and create handlers should satisfy runtime-state stabilization](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html#resource-type-test-contract-additional-runtime).

    When the `create` handler returns SUCCESS, it MUST return a ProgressEvent object containing a model that satisfies the following requirements:
    + All properties specified in the `create` request MUST be present in the model returned, and they MUST match exactly, with the exception of properties defined as writeOnlyProperties in the resource schema.
    + The model MUST contain all properties that have values, including any properties that have default values, and any readOnlyProperties as defined in the resource schema.
    + The model MUST NOT return any properties that are null or don't have values.
+ After a `create` operation returns SUCCESS, a subsequent `read` request MUST succeed when passed in the primaryIdentifier or any additionalIdentifiers associated with the provisioned resource instance.
+ After a `create` operation returns SUCCESS, a subsequent `list` operation MUST return the primaryIdentifier associated with the provisioned resource instance.

  If the `list` operation is paginated, the entire `list` operation is defined as all `list` requests until the `nextToken` is `null`.
+ A `create` handler MUST be idempotent. A `create` handler MUST NOT create multiple resources given the same idempotency token.
+ A `create` handler MUST return FAILED with an AlreadyExists error code if the resource already existed before the create request.

## Update handlers
<a name="resource-type-test-contract-update"></a>

CloudFormation invokes the `update` handler when the resource is updated during an update operation.

### Input assumptions
<a name="resource-type-test-contract-update-in"></a>

The `update` handler can make the following assumptions about input submitted to it:
+ The input to an `update` handler MUST be valid against the resource schema.
+ Any `createOnlyProperties` specified in update handler input MUST NOT be different from their previous state.
+ The input to an `update` handler MUST contain either the `primaryIdentifier` or an `additionalIdentifier`.

### Output requirements
<a name="resource-type-test-contract-update-out"></a>

The `update` handler must adhere to the following requirements:
+ An `update` handler MUST always return a ProgressEvent object within 60 seconds. For more information, see [ProgressEvent Object Schema](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-progressevent.html).

  In every ProgressEvent object, the `update` handler MUST return a model which conforms to the shape of the resource schema. For more information, see [Returned models must conform to the shape of the schema](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html#resource-type-test-contract-additional-shape).

  Every model MUST include the primaryIdentifier.

  The primaryIdentifier returned in every progress event must match the primaryIdentifier passed into the request.
+ An `update` handler MUST NOT return `SUCCESS` until it has applied all properties included in the `update` request. For more information, see [Update, create, and delete handlers must satisfy desired-state stabilization](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html#resource-type-test-contract-additional-stabilization).
  + An `update` handler MUST return IN\$1PROGRESS if it hasn't yet reached the desired-state.

    An `update` handler SHOULD return a model containing all properties set so far and nothing more during each IN\$1PROGRESS event.
  + An `update` handler MUST return FAILED progress event if it can't reach the desired-state within the timeout specified in the resource schema.

    The progress event MUST return an error message and the most applicable error code. For more information, see [Handler error codes](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract-errors.html).
  + An `update` handler MAY return SUCCESS once it reaches the desired-state.

    Once the desired state has been reached, an `update` handler MAY perform runtime-state stabilization. For more information, see [Update and create handlers should satisfy runtime-state stabilization](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html#resource-type-test-contract-additional-runtime).

    When the `update` handler returns SUCCESS, it MUST return a ProgressEvent object containing a model that satisfies the following requirements:
    + All properties specified in the `update` request MUST be present in the model returned, and they MUST match exactly, with the exception of properties defined as writeOnlyProperties in the resource schema.
    + The model MUST contain all properties that have values, including any properties that have default values, and any readOnlyProperties as defined in the resource schema.
    + The model MUST NOT return any properties that are null or don't have values.

  All list or collection properties MUST be applied in full. The successful outcome MUST be replacement of the previous properties, if any.
+ An `update` handler MUST return FAILED with a `NotFound` error code if the resource didn't exist before the `update` request.
+ An `update` handler MUST NOT create a new physical resource.

## Delete handlers
<a name="resource-type-test-contract-delete"></a>

CloudFormation invokes the `delete` handler when the resource, or entire stack, is deleted during a stack delete operation.

### Input assumptions
<a name="resource-type-test-contract-delete-in"></a>

The `delete` handler can make the following assumptions about input submitted to it:
+ The input to a `delete` handler MUST contain either the `primaryIdentifier` or an `additionalIdentifier`. Any other properties MAY NOT be included in the request.

### Output requirements
<a name="resource-type-test-contract-delete-out"></a>

The `delete` handler must adhere to the following requirements:
+ A `delete` handler MUST always return a ProgressEvent object within 60 seconds. For more information, see [ProgressEvent Object Schema](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-progressevent.html).
+ A `delete` handler MUST NOT return `SUCCESS` until the resource has reached the desired state for deletion. For more information, see [Update, create, and delete handlers must satisfy desired-state stabilization](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html#resource-type-test-contract-additional-stabilization).
  + A `delete` handler MUST return IN\$1PROGRESS if it hasn't yet reached the desired state.
  + A `delete` handler MUST return FAILED progress event if it can't reach the desired-state within the timeout specified in the resource schema.

    The progress event MUST return an error message and the most applicable error code. For more information, see [Handler error codes](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract-errors.html).
  + A `delete` handler MUST return SUCCESS once it reaches the desired state. (This is because there is no runtime-state stabilization for delete requests.)

    When the `delete` handler returns SUCCESS, the ProgressEvent object MUST NOT contain a model.
+ A `delete` handler MUST return FAILED with a `NotFound` error code if the resource didn't exist before the delete request.
+ Once a `delete` operation successfully completes, any subsequent `update`, `delete`, or `read` request for the deleted resource instance MUST return `FAILED` with a `NotFound` error code.
+ Once a `delete` operation successfully completes, any subsequent `list` operation MUST NOT return the primaryIdentifier associated with the deleted resource instance.

  If the `list` operation is paginated, the 'list operation' is defined as all `list` calls until the `nextToken` is `null`.
+ Once a `delete` operation successfully completes, a subsequent `create` request with the same primaryIdentifier or additionalIdentifiers MUST NOT return `FAILED` with an `AlreadyExists` error code.
+ Once a `delete` operation successfully completes, the resource SHOULD NOT be billable to the client.

## Read handlers
<a name="resource-type-test-contract-read"></a>

CloudFormation invokes the `read` handler when detailed information about the resource needed during a stack update operation.

### Input assumptions
<a name="resource-type-test-contract-read-in"></a>

The `read` handler can make the following assumptions about input submitted to it:
+ The input to a `read` handler MUST contain either the `primaryIdentifier` or an `additionalIdentifier`. Any other properties MAY NOT be included in the request.

### Output requirements
<a name="resource-type-test-contract-read-out"></a>

The `read` handler must adhere to the following requirements regarding its output:
+ A `read` handler MUST always return a ProgressEvent object within 30 seconds. For more information, see [ProgressEvent Object Schema](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-progressevent.html).

  A `read` handler MUST always return a status of `SUCCESS` or `FAILED`; it MUST NOT return a status of `IN_PROGRESS`.
+ A `read` handler MUST return a model representation that conforms to the shape of the resource schema.
  + The model MUST contain all properties that have values, including any properties that have default values and any `readOnlyProperties` as defined in the resource schema.
  + The model MUST NOT return any properties that are null or don't have values.
+ A `read` handler MUST return `FAILED` with a `NotFound` error code if the resource doesn't exist.

## List handlers
<a name="resource-type-test-contract-list"></a>

CloudFormation invokes the `list` handler when summary information about multiple resources of this resource type is required.
+ A `list` handler MUST always return a ProgressEvent object within 30 seconds. For more information, see [ProgressEvent Object Schema](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-progressevent.html).

  A `list` handler MUST always return a status of `SUCCESS` or `FAILED`; it MUST NOT return a status of `IN_PROGRESS`.
+ A `list` handler MUST return an array of primary identifiers.

  When passed in a `read` request, each `primaryIdentifier` MUST NOT return `FAILED` with `NotFound` error code.
+ A `list` request MUST support pagination by returning a `NextToken`.

  The `NextToken` returned MUST be able to be used in a subsequent `list` request to retrieve the next set of results from the service.

  The `NextToken` MUST be null when all results have been returned.
+ A `list` request MUST return an empty array if there are no resources found.
+ A `list` handler MAY accept a set of properties conforming to the shape of the resource schema as filter criteria.

  The filter should use `AND(&)` when multiple properties are passed in.

## Additional requirements
<a name="resource-type-test-contract-additional"></a>

The following requirements also apply to resource handlers.

### Returned models must conform to the shape of the schema
<a name="resource-type-test-contract-additional-shape"></a>

A model returned in a [ProgressEvent](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-progressevent.html) object MUST always conform to the shape of the resource schema. This means that each property that's returned MUST adhere to its own individual restrictions: correct data type, regex, length, etc. However, the model returned MAY NOT contain all properties defined as required in the json-schema.

More specifically, contract tests validate models based on json-schema [Validation keywords](https://json-schema.org/draft-07/json-schema-validation.html#rfc.section.6).
+ ALL Validation Keywords for the following MUST be observed:
  + Any Instance Type (Section 6.1)
  + Numeric Instances (Section 6.2)
  + Strings (Section 6.3)
  + Arrays (Section 6.4)
+ All Validation Keywords for Objects (Section 6.5) MUST be observed EXCEPT for:
  + required (Section 6.5.3)
  + dependencies (Section 6.5.7)
  + propertyNames (Section 6.5.8)
+ Contract tests won't validate Validation Keywords for:
  + Applying Subschemas Conditionally (Section 6.6)
  + Applying Subschemas With Boolean Logic (Section 6.7)

### Update, create, and delete handlers must satisfy desired-state stabilization
<a name="resource-type-test-contract-additional-stabilization"></a>

Stabilization is the process of waiting for a resource to be in a particular state. Note that reaching the desired-state is mandatory for all handlers before returning SUCCESS.

#### Create and update handlers
<a name="resource-type-test-contract-additional-stabilization-create"></a>

For Create and Update handlers, desired-state stabilization is satisfied when all properties specified in the request are applied as requested. This is verified by calling the Read handler.

In many cases, the desired-state is reached immediately upon completion of a Create/Update API call. However, in some cases, multiple API calls and or wait periods may be required in order to reach this state.

##### Eventual consistency in desired-state stabilization
<a name="resource-type-test-contract-additional-stabilization-consistency"></a>

Eventual consistency means that the result of an API command you run might not be immediately visible to all subsequent commands you run. Handling API eventual consistency is required as part of desired-state stabilization. This is because a subsequent Read call might fail with a NotFound error code.

Amazon EC2 resources are a great example of this. For more information, see [Eventual Consistency](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/query-api-troubleshooting.html#eventual-consistency) in the *Amazon Elastic Compute Cloud API Reference.*

##### Examples of desired-state stabilization
<a name="resource-type-test-contract-additional-stabilization-examples"></a>

For a simple example of desired-state stabilization, consider the implementation of the `create` handler for the `AWS::Logs::MetricFilter` resource: immediately after the handler code completes the call to the `PutMetricFilter` method, the `AWS::Logs::MetricFilter` has achieved its desired state. You can examine the code for this resource in its open-source repository at [github.com/aws-cloudformation/aws-cloudformation-resource-providers-logs](https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-logs).

A more complex example is the implementation of the `update` handler for the `AWS::Kinesis::Stream` resource. The `update` handler must make multiple API calls during an update, including `AddTagsToStream` or `RemoveTagsFromStream`, `UpdateShardCount`, `IncreaseRetentionPeriod` or `DecreaseRetentionPeriod`, and `StartStreamEncryption` or `StopStreamEncryption`. Meanwhile, each API call will set the `StreamStatus` to `UPDATING`, during which time other API operations can't be performed or the API will throw a `ResourceInUseException`. Therefore, to reach the desired state, the handler will need to wait for the `StreamStatus` to become `ACTIVE` in between each API operation.

#### Delete handlers
<a name="resource-type-test-contract-additional-stabilization-delete"></a>

Usually, the definition of *deleted* is obvious. A `delete` API operation will result in the resource being purged from the database, and the resource is no longer describable to the user.

However, sometimes, a deletion will result in the resource leaving an *audit trail*, in which the resource can still be described by service API operations, but can no longer be interacted with by the user. For example, when you delete a CloudFormation stack, it's assigned a status of `DELETE_COMPLETE`, but it can still be returned from a `[DescribeStacks](https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_DescribeStacks.html)` API call. For resources like this, the desired-state for deletion is when the resource has reached a *terminal, inoperable, and irrecoverable state*. If the resource can continue to be mutated by the user through another API call, then it isn't *deleted*, it's *updated*.

There is no difference between desired-state stabilization and runtime-state stabilization for a delete handler. By definition, once a resource has reached the desired-state for deletion, a subsequent `read` call MUST return `FAILED` with a `NotFound` error code, and a subsequent `create` call with the same primaryIdentifier or additionalIdentifiers MUST NOT return `FAILED` with an `AlreadyExists` error code. Additional restrictions are defined in the contract above.

So in the case of a CloudFormation stack, a `read` handler MUST return `FAILED` with a `NotFound` error code if the stack is `DELETE_COMPLETE`, even though it's audit trail can still be accessed by the DescribeStacks API.

### Update and create handlers should satisfy runtime-state stabilization
<a name="resource-type-test-contract-additional-runtime"></a>

*Runtime-state stabilization* is a process of waiting for the resource to be "ready" to use. Generally, runtime-state stabilization is done by continually describing the resource until it reaches a particular state, though it can take many forms.

Runtime-state stabilization can mean different things for different resources, but the following are common requirements:
+ *Additional mutating API calls can be made on the resource*

  Some resources can't be modified while they're in a particular state.
+ *Dependent resources can consume the resource*

  There may be other resources which need to consume the resource in some way, but can't until it is in a particular state.
+ *Users can interact with the resource*

  Customers may not be able to use the resource until it is in a particular status. This usually overlaps with the dependent resources requirement, although there could be different qualifications, depending on the resources.

While desired-state stabilization is mandatory, runtime-state stabilization is optional but encouraged. Users have come to expect that once a resource is COMPLETE, they will be able to use it.

#### Examples of run-time stabilization
<a name="resource-type-test-contract-additional-runtime-examples"></a>

For a simple example of run-time stabilization, consider the implementation of the `create` handler for the `AWS::KinesisFirehose::DeliveryStream` resource. The `create` handler invokes only a single API, `CreateDeliveryStream`, in order for the resource to reach its desired state. Immediately after this API call is made, a `read` request will return the correct desired state. However, the resource still has not reached run-time stabilization because it can't be used by the customer or downstream resources until the `DeliveryStreamStatus` is `ACTIVE`.

For a more complex example, consider the implementation of the `update` handler for the `AWS::Kinesis::Stream` resource once again. Once the `update` handler has made its final call, to `StartStreamEncryption` or `StopStreamEncryption` as described in [Examples of desired-state stabilization](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html#resource-type-test-contract-additional-stabilization-examples), the resource has reached its desired state. However, like the other API calls on the Kinesis resource, the `StreamStatus` will again be set to `UPDATING`. During this period, it has reached its desired state, and customers can even continue using the stream. But it hasn't yet achieved runtime-stabilization, because additional API calls cannot be made on the resource until the `StreamStatus` gets set to `ACTIVE`.

### Handlers must not leak resources
<a name="resource-type-test-contract-additional-leaking"></a>

*Resource leaking* refers to when a handler loses track of the existence of a resource. This happens most often in the following cases:
+ A `create` handler isn't idempotent. Re-invoking the handler with the same idempotencyToken will cause another resource to be created, and the handler is only tracking a single resource.
+ A `create` handler creates the resource, but is unable to communicate an identifier for that resource back to CloudFormation. A subsequent `delete` call doesn't have enough information to delete the resource.
+ A bug in the `delete` handler causes the resource to not actually be deleted, but the `delete` handler reports that the resource was successfully deleted.

# Contract tests for resource types
<a name="contract-tests"></a>

As part of testing your resource, the CloudFormation CLI performs a suite of tests, each written to test a requirement contained in the [resource type handler contract](resource-type-test-contract.md). Each handler invocation is expected to follow the general requirements for that handler listed in the contract. This topic lists tests that explicitly test some more specific requirements.

## `create` handler tests
<a name="contract-tests-create"></a>

The CloudFormation CLI performs the following contract tests for `create` handlers.


| Test | Description | 
| --- | --- | 
|  `contract_create_create`  |  Creates a resource, waits for the resource creation to complete, and then creates the resource again with the expectation that the second create operation will fail with the `AlreadyExists` error code. This test isn't run for resources if the primary identifier or any additional identifiers are read-only.  | 
|  `contract_create_read`  |  Creates a resource, waits for the resource creation to complete, and then reads the created resource to ensure that the input to the `create` handler is equal to the output from the `read` handler. The comparison ignores any read-only/generated properties in the `read` output, as create input can't specify these. It also ignores any write-only properties in the create input, as these are removed from read output to avoid security issues.  | 
|  `contract_create_delete`  |  Creates a resource, waits for the resource creation to complete, and then deletes the created resource. It also checks if the create input is equal to the create output (which is then used for delete input), with the exception of readOnly and writeOnly properties.  | 
|  `contract_create_list`  |  Creates a resource, waits for the resource creation to complete, and then lists out the resources with the expectation that the created resource exists in the returned list.  | 

## `update` handler tests
<a name="contract-tests-update"></a>

The CloudFormation CLI performs the following contract tests for `update` handlers.


| Test | Description | 
| --- | --- | 
|  `contract_update_read`  |  Creates a resource, updates the resource, and then reads the resource to check that the update was made by comparing the read output with the update input. The comparison excludes read-only and write-only properties because they can't be included in the update input and read output, respectively.  | 
|  `contract_update_list`  |  Creates a resource, updates the resource, and then lists the resource to check that the updated resource exists in the returned list.  | 
|  `contract_update_without_create`  |  Updates a resource without creating it first. The test expects the update operation to fail with the `NotFound` error code.  | 

## `delete` handler tests
<a name="contract-tests-delete"></a>

The CloudFormation CLI performs the following contract tests for `delete` handlers.


| Test | Description | 
| --- | --- | 
|  `contract_delete_create`  |  Creates a resource, deletes the resource, and then creates the resource again with the expectation that the deletion was successful and a new resource can be created. The CloudFormation CLI performs this contract test for resources with create-only primary identifiers.  | 
|  `contract_delete_update`  |  Creates a resource, deletes the resource, and then updates the resource with the expectation that the update operation will fail with the `NotFound` error code.  | 
|  `contract_delete_read`  |  Creates a resource, deletes the resource, and then reads the resource with the expectation that the read operation will fail with the `NotFound` error code.  | 
|  `contract_delete_list`  |  Creates a resource, deletes the resource, and then lists the resource with the expectation that the returned list doesn't contain the deleted resource.  | 
|  `contract_delete_delete`  |  Creates a resource, deletes the resource, and then deletes the resource again with the expectation that the second delete operation will fail with the `NotFound` error code.  | 

# Resource type handler error codes
<a name="resource-type-test-contract-errors"></a>

One of the following error codes MUST be returned from the handler whenever there is a [progress event](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-progressevent.html) with an operation status of `FAILED`.
+ `AccessDenied`

  The customer has insufficient permissions to perform this request.

  *Type:* Terminal
+ `AlreadyExists`

  The specified resource already existed before the execution of this handler. This error is applicable to `create` handlers only.

  *Type:* Terminal
+ `GeneralServiceException`

  The downstream service generated an error that doesn't map to any other handler error code.

  *Type:* Terminal
+ `InternalFailure`

  An unexpected error occurred within the handler.

  *Type:* Terminal
+ `InvalidCredentials`

  The credentials provided by the user are invalid.

  *Type:* Terminal
+ `InvalidRequest`

  Invalid input from the user has generated a generic exception.

  *Type:* Terminal
+ `NetworkFailure`

  The request couldn't be completed due to networking issues, such as a failure to receive a response from the server.

  *Type:* Retriable
+ `NotFound`

  The specified resource doesn't exist, or is in a terminal, inoperable, and irrecoverable state.

  *Type:* Terminal
+ `NotStabilized`

  The downstream resource failed to complete all of its ready-state checks.

  *Type:* Terminal
+ `NotUpdatable`

  The user has requested an update to a property defined in the resource type schema as a [create-only property](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-schema.html#schema-properties-createonlyproperties). This error is applicable to `update` handlers only.

  *Type:* Terminal
+ `ResourceConflict`

  The resource is temporarily unavailable to be acted upon. For example, if the resource is currently undergoing an operation and can't be acted upon until that operation is finished.

  *Type:* Retriable
+ `ServiceInternalError`

  The downstream service returned an internal error, typically with a `5XX` HTTP Status code.

  *Type:* Retriable
+ `ServiceLimitExceeded`

  A non-transient resource limit was reached on the service side.

  *Type:* Terminal
+ `Throttling`

  The request was throttled by a downstream service.

  *Type:* Retriable

# ProgressEvent object schema
<a name="resource-type-test-progressevent"></a>

A `ProgressEvent` is a JSON object which represents the current operation status of the handler, the current live state of the resource, and any additional resource information the handler wishes to communicate to the CloudFormation CLI. Each handler MUST communicate a progress event to the CloudFormation CLI under certain circumstances, and SHOULD communicate a progress event under others. For more information, see [Handler Communication Contract](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html#resource-type-test-contract-communication).

A handler MAY use progress events on a re-invocation to continue work from where it left off. For a detailed discussion of this, see [Progress chaining, stabilization and callback pattern](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-develop-stabilize.html).

## Syntax
<a name="resource-type-test-progressevent-syntax"></a>

Below is the syntax for the ProgressEvent object.

```
{
    "OperationStatus": "string",
    "HandlerErrorCode": "string",
    "Message": "string",
    "CallbackContext": "string",
    "CallbackDelaySeconds": "string",
    "ResourceModel": "string",
    "ResourceModels": [
        "string"
    ],
    "NextToken": "string",
    }
```

## Properties
<a name="resource-type-schema-properties"></a>

`OperationStatus`  <a name="progressevent-properties-OperationStatus"></a>
Indicates whether the handler has reached a terminal state or is still computing and requires more time to complete.  
Values: `PENDING` \$1 `IN_PROGRESS` \$1 `SUCCESS` \$1 `FAILED`  
 *Required*: No

`HandlerErrorCode`  <a name="progressevent-properties-HandlerErrorCode"></a>
A handler error code should be provided when the event operation status is `FAILED` or `IN_PROGRESS`.  
For a list of handler error codes, see [Handler Error Codes](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract-errors.html).  
 *Required*: Conditional. A handler error codes MUST be returned from the handler whenever there is a progress event with an operation status of `FAILED`.

`Message`  <a name="progressevent-properties-Message"></a>
Information which can be shown to users to indicate the nature of a progress transition or callback delay.  
 *Required*: No

`CallbackContext`  <a name="progressevent-properties-CallbackContext"></a>
Arbitrary information which the handler can return in an event with operation status of `IN_PROGRESS`, to allow the passing through of additional state or metadata between subsequent retries. For example, to pass through a resource identifier which can be used to continue polling for stabilization.  
For more detailed examples, see [Progress chaining, stabilization and callback pattern](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-develop-stabilize.html).  
*Required*: No

`CallbackDelaySeconds`  <a name="progressevent-properties-CallbackDelaySeconds"></a>
A callback will be scheduled with an initial delay of no less than the number of seconds specified.  
Set this value to less than 0 to indicate no callback should be made.  
*Required*: No

`ResourceModel`  <a name="progressevent-properties-ResourceModel"></a>
Resource model returned by a `read` or `list` operation response for synchronous results, or for final response validation/confirmation by `create`, `update`, and `delete` operations.  
*Required*: No

`ResourceModels`  <a name="progressevent-properties-ResourceModels"></a>
List of resource models returned by a `list` operation response for synchronous results.  
 *Required*: Conditional. Required for List handlers.

`NextToken`  <a name="progressevent-properties-NextToken"></a>
Token used to request additional pages of resources from a `list` operation response.  
*Required*: Conditional. Required for List handlers.

# Progress chaining, stabilization, and callback pattern
<a name="resource-type-develop-stabilize"></a>

Often when you develop CloudFormation resource types, when interacting with web service APIs you need to chain them in sequence to apply the desired state. CloudFormation provides a framework to write these chain patterns. The framework does a lot of the heavy lifting needed to handle error conditions, throttle when calling downstream API, and more. The framework provides callbacks that the handler can use to inspect and change the behavior when making these service calls.

Most web service API calls follows a typical pattern:

1. Initiate the call context for the API.

1. Transform the incoming resource model properties to the underlying service API request.

1. Make the service call.

1. (Optional) Handle errors.

1. (Optional) Handle stabilization (if you need resource to be in a specific state before you apply the next state).

1. Finalize progress to the next part of the call chain, or indicate successful completion.

In writing the handler, you don't need to do anything special with replay/continuation semantics. The framework ensures that the call chain is effectively resumed from where it was halted. This is essentially useful when the wait time for resource stabilization runs into minutes or even hours.

## Sample: Amazon Kinesis Data Streams integration
<a name="resource-type-develop-stabilize-example"></a>

The following resource model is an example integration for a Kinesis Data Streams operation.

```
{
    "typeName": "AWS::Kinesis::Stream",
    "description": "Resource Type definition for AWS::Kinesis::Stream",
    "definitions": {
        ...
    },
    "properties": {
        "Arn": {
            "type": "string"
        },
        "Name": {
            "type": "string",
            "pattern": "[a-zA-Z0-9_.-]+"
        },
        "RetentionPeriodHours": {
            "type": "integer",
            "minimum": 24,
            "maximum": 168
        },
        "ShardCount": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100000
        },
        "StreamEncryption": {
            "$ref": "#/definitions/AWSKinesisStreamStreamEncryption"
        },
        "Tags": {
            "type": "array",
            "uniqueItems": true,
            "items": {
                "$ref": "#/definitions/Tag"
            },
            "maximum": 50
        }
    }
    ...
}
```

And a sample CloudFormation template for creating this resource in a stack:

```
---
AWSTemplateFormatVersion: '2010-09-09'
Description: AWS MetricFilter
Resources:
  KinesisStream:
    Type: AWS::Kinesis::Stream
    Properties:
      ShardCount: 100
      RetentionPeriodHours: 36
      Tags:
      - Key: '1'
        Value: one
      - Key: '2'
        Value: two
      StreamEncryption:
        EncryptionType: KMS
        KeyId: alias/KinesisEncryption
```

For Kinesis, the stream must first be created with a name and shard count, then tags can be applied, followed by encryption. After creating a stream, but before any other configuration can be applied, the stream must be in an `ACTIVE` state.

Here is the example of the using the progress-chaining and callback pattern to apply state consistently. Note that much of the error handling is delegated to the framework. The CloudFormation CLI provides some error handling on interpreting errors that can be retried after a delay. The framework provides a fluent API that guides the developer with the right set of calls with strong typing and code completion capabilities in IDEs.

```
public class CreateHandler extends BaseKinesisHandler {
    //
    // The handler is provide with a AmazonWebServicesClientProxy that provides 
    // the framework for making calls that returns a ProgressEvent, 
    // which can then be chained to perform the next task.
    //
    protected ProgressEvent<ResourceModel, CallbackContext> 
        handleRequest(final AmazonWebServicesClientProxy proxy,
                      final ResourceHandlerRequest<ResourceModel> request,
                      final CallbackContext callbackContext,
                      final ProxyClient<KinesisClient> client,
                      final Logger logger) {

        ResourceModel model = request.getDesiredResourceState();
        if (model.getName() == null || model.getName().isEmpty()) {
            model.setName(
               IdentifierUtils.generateResourceIdentifier(
                  "stream-", request.getClientRequestToken(), 128));
        }
        //
        // 1) initiate the call context, we are making createStream API call 
        // 
        return proxy.initiate(
            "kinesis:CreateStream", client, model, callbackContext)
            
            //
            // 2) transform Resource model properties to CreateStreamRequest API
            //
            .request((m) ->
                CreateStreamRequest.builder()
                    .streamName(m.getName()).shardCount(m.getShardCount()).build())
                    
            //
            // 3) Make a service call. Handler does not worry about credentials, they 
            //    are auto injected
            //
            .call((r, c) -> 
                c.injectCredentialsAndInvokeV2(r, c.client()::createStream))

            //
            // provide stabilization callback. The callback is provided with 
            // the following parameters 
            //   a. CreateStreamRequest the we transformed in request()
            //   b. CreateStreamResponse that the service returned with successful call
            //   c. ProxyClient<Kinesis>, we provided in initiate call 
            //   d. ResourceModel we provided in initiate call 
            //   f. CallbackContext callback context.
            // 
            //
            .stabilize((_request, _response, _client, _model, _context) -> 
                 isStreamActive(client1, _model, context))
                 
            //
            // Once ACTIVE return progress
            //     
            .progress()
            
            //
            // we then chain to next state, setting tags on the resource.
            // we receive ProgressEvent object from .progress().
            //
            .then(r -> {
                Set<Tag> tags = model.getTags();
                if (tags != null && !tags.isEmpty()) {
                    return setTags(proxy, client, model, callbackContext, false, logger);
                }
                return r;
            })
            
            //
            // we then setRetention...
            //
            .then(r -> {
                Integer retention = model.getRetentionPeriodHours();
                if (retention != null) {
                    return handleRetention(proxy, client, model, DEFAULT_RETENTION, retention, callbackContext, logger);
                }
                return r;
            })
            
            ... // other steps
            
            //
            // finally we wait for Kinesis stream to be ACTIVE
            //
            .then((r) -> waitForActive(proxy, client, model, callbackContext))
            
            //
            // we then delegate to ReadHandler to read the live state and send
            // back successful response.
            //
            .then((r) -> new ReadHandler()
                .handleRequest(proxy, request, callbackContext, client, logger));
    }
}
```

## How to make other calls
<a name="resource-type-develop-stabilize-other-calls"></a>

The same pattern shown here for `CreateStreamRequest` is followed with others as well. The following is `handleRetention` code:

```
protected ProgressEvent<ResourceModel, CallbackContext> 
    handleRetention(final AmazonWebServicesClientProxy proxy,
                    final ProxyClient<KinesisClient> client,
                    final ResourceModel model,
                    final int previous,
                    final int current,
                    final CallbackContext callbackContext,
                    final Logger logger) {
       
       if (current > previous) {
            //
            // 1) initiate the call context, we are making IncreaseRetentionPeriod API call 
            // 
            return proxy.initiate(
                "kinesis:IncreaseRetentionPeriod:" + getClass().getSimpleName(), 
                client, model, callbackContext)
                //
                // 2) transform Resource model properties to IncreaseStreamRetentionPeriodRequest API
                //
                .request((m) ->
                   IncreaseStreamRetentionPeriodRequest.builder()
                       .retentionPeriodHours(current)
                       .streamName(m.getName()).build())
                //
                // 3) Make a service call. Handler does not worry about credentials, they 
                //    are auto injected
            
                // Add important comments like shown below
                // https://docs.aws.amazon.com/kinesis/latest/APIReference/API_IncreaseStreamRetentionPeriod.html
                // When applying change if stream is not ACTIVE, we get ResourceInUse.
                // We filter this expection back off and then re-try to set this.
               
                //
                // set new retention period
                //
                .call((r, c) -> c.injectCredentialsAndInvokeV2(r, c.client()::increaseStreamRetentionPeriod))
                
                //
                // Filter ResoureceInUse or LimitExceeded. 
                // Currently LimitExceeded is issued even for throttles
                //
                .exceptFilter(this::filterException).progress();
        } else {
            return proxy.initiate("kinesis:DecreaseRetentionPeriod:" + getClass().getSimpleName(), client, model, callbackContext)
                //
                // convert to API model
                //
                .request(m -> DecreaseStreamRetentionPeriodRequest.builder().retentionPeriodHours(current).streamName(m.getName())
                    .build())
                ... // snipped for brevity
                .exceptFilter(this::filterException).progress();
        }
}

protected boolean filterException(AwsRequest request,
                                  Exception e,
                                  ProxyClient<KinesisClient> client,
                                  ResourceModel model,
                                  CallbackContext context) {
    return e instanceof ResourceInUseException || 
           e instanceof LimitExceededException;
}
```

# Walkthrough: Develop a resource type
<a name="resource-type-walkthrough"></a>

In this walkthrough, we'll use the CloudFormation CLI to create a sample resource type, `Example::Testing::WordPress`. This includes modeling the schema, developing the handlers to test those handlers, all the way to performing a dry run to get the resource type ready to submit to the CloudFormation registry. We'll be coding our new resource type in Java, and using the `us-west-2` Region.

**Note**  
This walkthrough may reference sample resources that have been deleted. For a resource creation workflow, including a walkthrough of an example resource type in Python, see the [Resource Types walkthrough](https://catalog.workshops.aws/cfn101/en-US/advanced/resource-types) in the [CloudFormation Workshop](https://catalog.workshops.aws/cfn101/en-US/).

## 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:

[Set up your environment for extension development and install the CLI](what-is-cloudformation-cli.md#resource-type-setup)

## Create the resource type development project
<a name="resource-type-walkthrough-model"></a>

Before we can actually design and implement our resource type, we'll need to generate a new resource type project, and then import it into our IDE.

**Note**  
This walkthrough uses the Community Edition of the [IntelliJ IDEA](https://www.jetbrains.com/idea/).

### Initiate the project
<a name="resource-type-walkthrough-model-initiate"></a>

1. Use the `init` command to create your resource type project and generate the files it requires.

   ```
   $ cfn init
   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`.

   ```
   Enter resource type identifier (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:

   ```
   One language plugin found, defaulting to java
   ```

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
   Initialized a new project in /workplace/tobflem/example-testing-wordpress
   ```

Initiating the project includes generating the files needed to develop the resource type. For example:

```
$ ls -1
README.md
example-testing-wordpress.json
lombok.config
pom.xml
rpdk.log
src
target
template.yml
```

### Import the project into your IDE
<a name="resource-type-walkthrough-model-import"></a>

In order to guarantee that any project dependencies are correctly resolved, you must import the generated project into your IDE with Maven support.

For example, if you are using IntelliJ IDEA, you would need to do the following:

1. From the **File** menu, choose **New**, then choose **Project From Existing Sources**.

1. Navigate to the project directory.

1. In the **Import Project** dialog box, choose **Import project from external model** and then choose **Maven**.

1. Choose **Next** and accept any defaults to complete importing the project.

## Model the resource type
<a name="resource-type-walkthrough-model"></a>

When you initiate the resource type project, an example resource type schema file is included to help start you modeling your resource type. This is a JSON file named for your resource, and contains an example of a typical resource type schema. In the case of our example resource, the schema file is named `example-testing-wordpress.json`.

1. In your IDE, open `example-testing-wordpress.json`.

1. Paste the following schema in place of the default example schema currently in the file.

   This schema defines a resource, `Example::Testing::WordPress`, that provisions a WordPress site. The resource itself contains four properties, only two of which can be set by users: `Name`, and `SubnetId`. The other two properties, `InstanceId` and `PublicIp`, are read-only, meaning they can't be set by users, but will be assigned during resource creation. Both of these properties also serve as identifiers for the resource when it's provisioned.

   As we'll see later in the walkthrough, creating a WordPress site actually requires more information than represented in our resource model. However, we'll be handling that information on behalf of the user in the code for the resource `create` handler.

   ```
   {
     "typeName": "Example::Testing::WordPress",
     "description": "An example resource that creates a website based on WordPress 5.2.2.",
     "sourceUrl": "https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-walkthrough.html",
     "properties": {
       "Name": {
         "description": "A name associated with the website.",
         "type": "string",
         "pattern": "^[a-zA-Z0-9]{1,219}\\Z",
         "minLength": 1,
         "maxLength": 219
       },
       "SubnetId": {
         "description": "A subnet in which to host the website.",
         "pattern": "^(subnet-[a-f0-9]{13})|(subnet-[a-f0-9]{8})\\Z",
         "type": "string"
       },
       "InstanceId": {
         "description": "The ID of the instance that backs the WordPress site.",
         "type": "string"
       },
       "PublicIp": {
         "description": "The public IP for the WordPress site.",
         "type": "string"
       }
     },
     "required": [
       "Name",
       "SubnetId"
     ],
   "handlers": {
       "create": {
         "permissions": [
           "ec2:AuthorizeSecurityGroupIngress",
           "ec2:CreateSecurityGroup",
           "ec2:DeleteSecurityGroup",
           "ec2:DescribeInstances",
           "ec2:DescribeSubnets",
           "ec2:CreateTags",
           "ec2:RunInstances"
         ]
       },
       "read": {
         "permissions": [
           "ec2:DescribeInstances"
         ]
       },
       "delete": {
         "permissions": [
           "ec2:DeleteSecurityGroup",
           "ec2:DescribeInstances",
           "ec2:TerminateInstances"
         ]
       }
     },
     "additionalProperties": false,
     "primaryIdentifier": [
       "/properties/PublicIp",
       "/properties/InstanceId"
     ],
     "readOnlyProperties": [
       "/properties/PublicIp",
       "/properties/InstanceId"
     ]
   }
   ```

1. Update the auto-generated files in the resource type package so that they reflect the changes we've made to the resource type schema.

   When we first initiated the resource type project, the CloudFormation CLI generated supporting files and code for our resource type. Since we've made changes to the resource type schema, we'll need to regenerate that code to ensure that it reflects the updated schema. To do this, we use the generate command:

   ```
   $ cfn generate
   Generated files for Example::Testing::WordPress
   ```
**Note**  
When using Maven, as part of the build process the `generate` command is automatically run before the code is compiled. So your changes will never get out of sync with the generated code.  
Be aware the CloudFormation CLI must be in a location Maven/the system can find. For more information, see [Set up your environment for extension development and install the CLI](what-is-cloudformation-cli.md#resource-type-setup).

## Implement the Resource handler
<a name="resource-type-walkthrough-implement"></a>

Now that we have our resource type schema specified, we can start implementing the behavior we want the resource type to exhibit during each resource operation. To do this, we'll have to implement the various event handlers, including:
+ Adding any necessary dependencies 
+ Writing code to implement the various resource operation handlers.

### Add dependencies
<a name="resource-type-walkthrough-implement-dependencies"></a>

To actually make WordPress handlers that call the associated Amazon EC2 API operations, we need to declare the Amazon EC2 SDK as a dependency in Maven's pom.xml file. To enable this, we need to add a dependency on the [AWS SDK for Java](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/package-summary.html) to the project.

1. In your IDE, open the project's `pom.xml` file.

1. Add the following dependency in the `dependencies` section.

   ```
   <dependency>
     <groupId>com.amazonaws</groupId>
     <artifactId>aws-java-sdk-ec2</artifactId>
     <version>1.11.606</version>
   </dependency>
   ```

   This artifact will be added by Maven from the [Maven Repository](https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-logs).

For more information on how to add dependencies, see the [Maven documentation](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html).

**Note**  
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.

### 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.

To create a WordPress site, our resource `create` handler will have to accomplish the following:
+ Gather and define inputs that we'll need to create the underlying AWS resources on behalf of the user. These are details we're managing for them, since this is a very high-level resource type.
+ Create an Amazon EC2 instance using a special AMI vended by Bitnami from the AMI Marketplace that bootstraps WordPress.
+ Create a security group that the instance will belong to so you can access the WordPress site from your browser.
+ Change the security group rules to dictate what the networking rules are for web access to the WordPress site.
+ If something goes wrong with creating the resource, attempt to delete the security group.

#### Define the CallbackContext
<a name="resource-type-walkthrough-implement-create-handler-context"></a>

Because our create handler is more complex than simply calling a single API, it takes some time to complete. However, each handler times out after one minute. To work around this issue, we'll write our handlers as state machines. A handler can exit with one of three states: `SUCCESS`, `IN_PROGRESS`, and `FAILED`. To wait on stabilization of underlying resources, we can return an `IN_PROGRESS` state with a CallbackContext. The CallbackContext will hold details about the current state of the execution. When we return an `IN_PROGRESS` state and a CallbackContext, CloudFormation will re-invoke the handler and pass the CallbackContext in with the request. You can then make decisions based on what is included in the context.

The CallbackContext is modeled as a POJO so you can define what information you want to pass between state transitions explicitly.

1. In your IDE, open the `CallbackContext.java` file, located in the `src/main/java/com/example/testing/wordpress` folder.

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

   ```
   package com.example.testing.wordpress;
   
   import com.amazonaws.services.ec2.model.Instance;
   
   import java.util.List;
   
   import lombok.AllArgsConstructor;
   import lombok.Builder;
   import lombok.Data;
   import lombok.NoArgsConstructor;
   
   @Builder(toBuilder = true)
   @Data
   @NoArgsConstructor
   @AllArgsConstructor
   public class CallbackContext {
       private Instance instance;
       private Integer stabilizationRetriesRemaining;
       private List<String> instanceSecurityGroups;
   }
   ```

#### 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/CreateHandler.java` folder.

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

   ```
   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 java.util.List;
   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";
       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";
       private static final int NUMBER_OF_STATE_POLL_RETRIES = 60;
       private static final int POLL_RETRY_DELAY_IN_MS = 5000;
       private static final String TIMED_OUT_MESSAGE = "Timed out waiting for instance to become available.";
   
       private AmazonWebServicesClientProxy clientProxy;
       private AmazonEC2 ec2Client;
   
       @Override
       public ProgressEvent<ResourceModel, CallbackContext> handleRequest(
           final AmazonWebServicesClientProxy proxy,
           final ResourceHandlerRequest<ResourceModel> request,
           final CallbackContext callbackContext,
           final Logger logger) {
   
           final ResourceModel model = request.getDesiredResourceState();
   
           clientProxy = proxy;
           ec2Client = AmazonEC2ClientBuilder.standard().withRegion(SUPPORTED_REGION).build();
           final CallbackContext currentContext = callbackContext == null ?
                                                  CallbackContext.builder().stabilizationRetriesRemaining(NUMBER_OF_STATE_POLL_RETRIES).build() :
                                                  callbackContext;
   
           // This Lambda will continually be re-invoked with the current state of the instance, finally succeeding when state stabilizes.
           return createInstanceAndUpdateProgress(model, currentContext);
       }
   
       private ProgressEvent<ResourceModel, CallbackContext> createInstanceAndUpdateProgress(ResourceModel model, CallbackContext callbackContext) {
           // This Lambda will continually be re-invoked with the current state of the instance, finally succeeding when state stabilizes.
           final Instance instanceStateSoFar = callbackContext.getInstance();
   
           if (callbackContext.getStabilizationRetriesRemaining() == 0) {
               throw new RuntimeException(TIMED_OUT_MESSAGE);
           }
   
           if (instanceStateSoFar == null) {
               return ProgressEvent.<ResourceModel, CallbackContext>builder()
                   .resourceModel(model)
                   .status(OperationStatus.IN_PROGRESS)
                   .callbackContext(CallbackContext.builder()
                                                   .instance(createEC2Instance(model))
                                                   .stabilizationRetriesRemaining(NUMBER_OF_STATE_POLL_RETRIES)
                                                   .build())
                   .build();
           } else if (instanceStateSoFar.getState().getName().equals(AVAILABLE_INSTANCE_STATE)) {
               model.setInstanceId(instanceStateSoFar.getInstanceId());
               model.setPublicIp(instanceStateSoFar.getPublicIpAddress());
               return ProgressEvent.<ResourceModel, CallbackContext>builder()
                   .resourceModel(model)
                   .status(OperationStatus.SUCCESS)
                   .build();
   
           } else {
               try {
                   Thread.sleep(POLL_RETRY_DELAY_IN_MS);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
               return ProgressEvent.<ResourceModel, CallbackContext>builder()
                   .resourceModel(model)
                   .status(OperationStatus.IN_PROGRESS)
                   .callbackContext(CallbackContext.builder()
                                                   .instance(updatedInstanceProgress(instanceStateSoFar.getInstanceId()))
                                                   .stabilizationRetriesRemaining(callbackContext.getStabilizationRetriesRemaining() - 1)
                                                   .build())
                   .build();
           }
       }
   
       private Instance createEC2Instance(ResourceModel model) {
           final String securityGroupId = createSecurityGroupForInstance(model);
           final RunInstancesRequest runInstancesRequest = new RunInstancesRequest()
               .withInstanceType(INSTANCE_TYPE)
               .withImageId(WORDPRESS_AMI_ID)
               .withNetworkInterfaces(new InstanceNetworkInterfaceSpecification()
                                          .withAssociatePublicIpAddress(true)
                                          .withDeviceIndex(0)
                                          .withGroups(securityGroupId)
                                          .withSubnetId(model.getSubnetId()))
               .withMaxCount(1)
               .withMinCount(1)
               .withTagSpecifications(buildTagFromSiteName(model.getName()));
   
           try {
               return clientProxy.injectCredentialsAndInvoke(runInstancesRequest, ec2Client::runInstances)
                                 .getReservation()
                                 .getInstances()
                                 .stream()
                                 .findFirst()
                                 .orElse(new Instance());
           } catch (Throwable e) {
               attemptToCleanUpSecurityGroup(securityGroupId);
               throw new RuntimeException(e);
           }
       }
   
       private String createSecurityGroupForInstance(ResourceModel model) {
           String vpcId;
           try {
               vpcId = getVpcIdFromSubnetId(model.getSubnetId());
           } catch (Throwable e) {
               throw new RuntimeException(e);
           }
   
           final String securityGroupName = model.getName() + "-" + UUID.randomUUID().toString();
   
           final CreateSecurityGroupRequest createSecurityGroupRequest = new CreateSecurityGroupRequest()
               .withGroupName(securityGroupName)
               .withDescription("Created for the test WordPress blog: " + model.getName())
               .withVpcId(vpcId);
   
           final String securityGroupId =
               clientProxy.injectCredentialsAndInvoke(createSecurityGroupRequest, ec2Client::createSecurityGroup)
                          .getGroupId();
   
           final AuthorizeSecurityGroupIngressRequest authorizeSecurityGroupIngressRequest = new AuthorizeSecurityGroupIngressRequest()
               .withGroupId(securityGroupId)
               .withIpPermissions(openHTTP(), openHTTPS());
   
           clientProxy.injectCredentialsAndInvoke(authorizeSecurityGroupIngressRequest, ec2Client::authorizeSecurityGroupIngress);
   
           return securityGroupId;
       }
   
       private String getVpcIdFromSubnetId(String subnetId) throws Throwable {
           final DescribeSubnetsRequest describeSubnetsRequest = new DescribeSubnetsRequest()
               .withSubnetIds(subnetId);
   
           final DescribeSubnetsResult describeSubnetsResult =
               clientProxy.injectCredentialsAndInvoke(describeSubnetsRequest, ec2Client::describeSubnets);
   
           return describeSubnetsResult.getSubnets()
                                       .stream()
                                       .map(Subnet::getVpcId)
                                       .findFirst()
                                       .orElseThrow(() -> {
                                           throw new RuntimeException("Subnet " + subnetId + " not found");
                                       });
       }
   
       private IpPermission openHTTP() {
           return new IpPermission().withIpProtocol("tcp")
                                    .withFromPort(80)
                                    .withToPort(80)
                                    .withIpv4Ranges(new IpRange().withCidrIp("0.0.0.0/0"));
       }
   
       private IpPermission openHTTPS() {
           return new IpPermission().withIpProtocol("tcp")
                                    .withFromPort(443)
                                    .withToPort(443)
                                    .withIpv4Ranges(new IpRange().withCidrIp("0.0.0.0/0"));
       }
   
       private TagSpecification buildTagFromSiteName(String siteName) {
           return new TagSpecification()
               .withResourceType("instance")
               .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);
       }
   }
   ```

#### Update the Create handler unit test
<a name="resource-type-walkthrough-implement-create-handler-unit"></a>

Because our resource type is a high-level abstraction, a lot of implementation behavior isn't apparent by the name alone. As such, we'll need to make some additions to our unit tests so that we're not calling the live API operations that are necessary to create the WordPress site.

1. In your IDE, open the `CreateHandlerTest.java` file, located in the `src/test/java/com/example/testing/wordpress` folder.

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

   ```
   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 org.junit.jupiter.api.BeforeEach;
   import org.junit.jupiter.api.Test;
   import org.junit.jupiter.api.extension.ExtendWith;
   import org.mockito.Mock;
   import org.mockito.junit.jupiter.MockitoExtension;
   
   
   import 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 CreateHandlerTest {
       private static String EXPECTED_TIMEOUT_MESSAGE = "Timed out waiting for instance to become available.";
   
       @Mock
       private AmazonWebServicesClientProxy proxy;
   
       @Mock
       private Logger logger;
   
       @BeforeEach
       public void setup() {
           proxy = mock(AmazonWebServicesClientProxy.class);
           logger = mock(Logger.class);
       }
   
       @Test
       public void testSuccessState() {
           final InstanceState inProgressState = new InstanceState().withName("running");
           final GroupIdentifier group = new GroupIdentifier().withGroupId("sg-1234");
           final Instance instance = new Instance().withInstanceId("i-1234").withState(inProgressState).withPublicIpAddress("54.0.0.0").withSecurityGroups(group);
   
           final CreateHandler handler = new CreateHandler();
   
           final ResourceModel model = ResourceModel.builder()
                                                    .name("MyWordPressSite")
                                                    .subnetId("subnet-1234")
                                                    .build();
   
           final ResourceModel desiredOutputModel = ResourceModel.builder()
                                                                 .instanceId("i-1234")
                                                                 .publicIp("54.0.0.0")
                                                                 .name("MyWordPressSite")
                                                                 .subnetId("subnet-1234")
                                                                 .build();
   
           final ResourceHandlerRequest<ResourceModel> request = ResourceHandlerRequest.<ResourceModel>builder()
               .desiredResourceState(model)
               .build();
   
           final CallbackContext context = CallbackContext.builder()
                                                          .stabilizationRetriesRemaining(1)
                                                          .instance(instance)
                                                          .build();
   
           final ProgressEvent<ResourceModel, CallbackContext> response
               = handler.handleRequest(proxy, request, context, logger);
   
           assertThat(response).isNotNull();
           assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
           assertThat(response.getCallbackContext()).isNull();
           assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
           assertThat(response.getResourceModel()).isEqualTo(desiredOutputModel);
           assertThat(response.getResourceModels()).isNull();
           assertThat(response.getMessage()).isNull();
           assertThat(response.getErrorCode()).isNull();
       }
   
       @Test
       public void testInProgressStateInstanceCreationNotInvoked() {
           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());
   
           final CreateHandler handler = new CreateHandler();
   
           final ResourceModel model = ResourceModel.builder().name("MyWordPressSite").subnetId("subnet-1234").build();
   
           final ResourceHandlerRequest<ResourceModel> request = ResourceHandlerRequest.<ResourceModel>builder()
               .desiredResourceState(model)
               .build();
   
           final ProgressEvent<ResourceModel, CallbackContext> response
               = handler.handleRequest(proxy, request, null, logger);
   
           final CallbackContext desiredOutputContext = CallbackContext.builder()
                                                                       .stabilizationRetriesRemaining(60)
                                                                       .instance(instance)
                                                                       .build();
           assertThat(response).isNotNull();
           assertThat(response.getStatus()).isEqualTo(OperationStatus.IN_PROGRESS);
           assertThat(response.getCallbackContext()).isEqualToComparingFieldByField(desiredOutputContext);
           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 testInProgressStateInstanceCreationInvoked() {
           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);
           final DescribeInstancesResult describeInstancesResult =
               new DescribeInstancesResult().withReservations(new Reservation().withInstances(instance));
   
           doReturn(describeInstancesResult).when(proxy).injectCredentialsAndInvoke(any(DescribeInstancesRequest.class), any());
   
           final CreateHandler handler = new CreateHandler();
   
           final ResourceModel model = ResourceModel.builder().name("MyWordPressSite").subnetId("subnet-1234").build();
   
           final ResourceHandlerRequest<ResourceModel> request = ResourceHandlerRequest.<ResourceModel>builder()
               .desiredResourceState(model)
               .build();
   
           final CallbackContext context = CallbackContext.builder()
                                                          .stabilizationRetriesRemaining(60)
                                                          .instance(instance)
                                                          .build();
   
           final ProgressEvent<ResourceModel, CallbackContext> response
               = handler.handleRequest(proxy, request, context, logger);
   
           final CallbackContext desiredOutputContext = CallbackContext.builder()
                                                                       .stabilizationRetriesRemaining(59)
                                                                       .instance(instance)
                                                                       .build();
   
           assertThat(response).isNotNull();
           assertThat(response.getStatus()).isEqualTo(OperationStatus.IN_PROGRESS);
           assertThat(response.getCallbackContext()).isEqualToComparingFieldByField(desiredOutputContext);
           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 testStabilizationTimeout() {
           final CreateHandler handler = new CreateHandler();
   
           final ResourceModel model = ResourceModel.builder().name("MyWordPressSite").subnetId("subnet-1234").build();
   
           final ResourceHandlerRequest<ResourceModel> request = ResourceHandlerRequest.<ResourceModel>builder()
               .desiredResourceState(model)
               .build();
   
           final CallbackContext context = CallbackContext.builder()
                                                          .stabilizationRetriesRemaining(0)
                                                          .instance(new Instance().withState(new InstanceState().withName("in-progress")))
                                                          .build();
   
           try {
               handler.handleRequest(proxy, request, context, logger);
           } catch (RuntimeException e) {
               assertThat(e.getMessage()).isEqualTo(EXPECTED_TIMEOUT_MESSAGE);
           }
       }
   }
   ```

### Implement the Delete handler
<a name="resource-type-walkthrough-implement-delete-handler"></a>

We'll also need to implement a delete handler. At a high level, the delete handler needs to accomplish the following:

1. Find the security groups attached to the Amazon EC2 instance that's hosting the WordPress page.

1. Delete the instance.

1. Delete the security groups.

Again, we'll implement the delete handler as a state machine.

#### Code the Delete handler
<a name="resource-type-walkthrough-implement-delete-handler-code"></a>

1. In your IDE, open the `DeleteHandler.java` file, located in the `src/main/java/com/example/testing/wordpress` folder.

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

   ```
   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 java.util.List;
   import java.util.stream.Collectors;
   
   public class DeleteHandler extends BaseHandler<CallbackContext> {
       private static final String SUPPORTED_REGION = "us-west-2";
       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;
       private static final String TIMED_OUT_MESSAGE = "Timed out waiting for instance to terminate.";
   
       private AmazonWebServicesClientProxy clientProxy;
       private AmazonEC2 ec2Client;
   
       @Override
       public ProgressEvent<ResourceModel, CallbackContext> handleRequest (
           final AmazonWebServicesClientProxy proxy,
           final ResourceHandlerRequest<ResourceModel> request,
           final CallbackContext callbackContext,
           final Logger logger) {
   
           final ResourceModel model = request.getDesiredResourceState();
   
           clientProxy = proxy;
           ec2Client = AmazonEC2ClientBuilder.standard().withRegion(SUPPORTED_REGION).build();
           final CallbackContext currentContext = callbackContext == null ?
                                                  CallbackContext.builder().stabilizationRetriesRemaining(NUMBER_OF_STATE_POLL_RETRIES).build() :
                                                  callbackContext;
   
           // This Lambda will continually be re-invoked with the current state of the instance, finally succeeding when state stabilizes.
           return deleteInstanceAndUpdateProgress(model, currentContext);
       }
   
       private ProgressEvent<ResourceModel, CallbackContext> deleteInstanceAndUpdateProgress(ResourceModel model, CallbackContext callbackContext) {
   
           if (callbackContext.getStabilizationRetriesRemaining() == 0) {
               throw new RuntimeException(TIMED_OUT_MESSAGE);
           }
   
           if (callbackContext.getInstanceSecurityGroups() == null) {
               final Instance currentInstanceState = currentInstanceState(model.getInstanceId());
   
               if (DELETED_INSTANCE_STATE.equals(currentInstanceState.getState().getName())) {
                   return ProgressEvent.<ResourceModel, CallbackContext>builder()
                       .status(OperationStatus.FAILED)
                       .errorCode(HandlerErrorCode.NotFound)
                       .build();
               }
   
               final List<String> instanceSecurityGroups = currentInstanceState
                   .getSecurityGroups()
                   .stream()
                   .map(GroupIdentifier::getGroupId)
                   .collect(Collectors.toList());
   
               return ProgressEvent.<ResourceModel, CallbackContext>builder()
                   .resourceModel(model)
                   .status(OperationStatus.IN_PROGRESS)
                   .callbackContext(CallbackContext.builder()
                                                   .stabilizationRetriesRemaining(NUMBER_OF_STATE_POLL_RETRIES)
                                                   .instanceSecurityGroups(instanceSecurityGroups)
                                                   .build())
                   .build();
           }
   
           if (callbackContext.getInstance() == null) {
               return ProgressEvent.<ResourceModel, CallbackContext>builder()
                   .resourceModel(model)
                   .status(OperationStatus.IN_PROGRESS)
                   .callbackContext(CallbackContext.builder()
                                                   .instance(deleteInstance(model.getInstanceId()))
                                                   .instanceSecurityGroups(callbackContext.getInstanceSecurityGroups())
                                                   .stabilizationRetriesRemaining(NUMBER_OF_STATE_POLL_RETRIES)
                                                   .build())
                   .build();
           } 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 {
               try {
                   Thread.sleep(POLL_RETRY_DELAY_IN_MS);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
               return ProgressEvent.<ResourceModel, CallbackContext>builder()
                   .resourceModel(model)
                   .status(OperationStatus.IN_PROGRESS)
                   .callbackContext(CallbackContext.builder()
                                                   .instance(currentInstanceState(model.getInstanceId()))
                                                   .instanceSecurityGroups(callbackContext.getInstanceSecurityGroups())
                                                   .stabilizationRetriesRemaining(callbackContext.getStabilizationRetriesRemaining() - 1)
                                                   .build())
                   .build();
           }
   
       }
   
       private Instance deleteInstance(String instanceId) {
           final TerminateInstancesRequest terminateInstancesRequest = new TerminateInstancesRequest().withInstanceIds(instanceId);
           return clientProxy.injectCredentialsAndInvoke(terminateInstancesRequest, ec2Client::terminateInstances)
                             .getTerminatingInstances()
                             .stream()
                             .map(instance -> new Instance().withState(instance.getCurrentState()).withInstanceId(instance.getInstanceId()))
                             .findFirst()
                             .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);
       }
   }
   ```

#### Update the Delete handler unit test
<a name="resource-type-walkthrough-implement-delete-handler-unit"></a>

We'll also need to update the unit test for the delete handler.

1. In your IDE, open the `DeleteHandlerTest.java` file, located in the `src/test/java/com/example/testing/wordpress` folder.

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

   ```
   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 org.junit.jupiter.api.BeforeEach;
   import org.junit.jupiter.api.Test;
   import org.junit.jupiter.api.extension.ExtendWith;
   import org.mockito.Mock;
   import org.mockito.junit.jupiter.MockitoExtension;
   
   import java.util.Arrays;
   
   
   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 DeleteHandlerTest {
       private static String EXPECTED_TIMEOUT_MESSAGE = "Timed out waiting for instance to terminate.";
   
       @Mock
       private AmazonWebServicesClientProxy proxy;
   
       @Mock
       private Logger logger;
   
       @BeforeEach
       public void setup() {
           proxy = mock(AmazonWebServicesClientProxy.class);
           logger = mock(Logger.class);
       }
   
       @Test
       public void testSuccessState() {
           final DeleteSecurityGroupResult deleteSecurityGroupResult = new DeleteSecurityGroupResult();
           doReturn(deleteSecurityGroupResult).when(proxy).injectCredentialsAndInvoke(any(DeleteSecurityGroupRequest.class), any());
   
           final DeleteHandler handler = new DeleteHandler();
   
           final ResourceModel model = ResourceModel.builder().instanceId("i-1234").build();
   
           final ResourceHandlerRequest<ResourceModel> request = ResourceHandlerRequest.<ResourceModel>builder()
               .desiredResourceState(model)
               .build();
   
           final CallbackContext context = CallbackContext.builder()
                                                          .stabilizationRetriesRemaining(1)
                                                          .instanceSecurityGroups(Arrays.asList("sg-1234"))
                                                          .instance(new Instance().withState(new InstanceState().withName("terminated")))
                                                          .build();
   
           final ProgressEvent<ResourceModel, CallbackContext> response
               = handler.handleRequest(proxy, request, context, 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 testHandlerInvokedWhenInstanceIsAlreadyTerminated() {
           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());
   
           final DeleteHandler handler = new DeleteHandler();
   
           final ResourceModel model = ResourceModel.builder().instanceId("i-1234").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()).isEqualTo(HandlerErrorCode.NotFound);
       }
   
       @Test
       public void testInProgressStateSecurityGroupsNotGathered() {
           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());
   
           final DeleteHandler handler = new DeleteHandler();
   
           final ResourceModel model = ResourceModel.builder().instanceId("i-1234").build();
   
           final ResourceHandlerRequest<ResourceModel> request = ResourceHandlerRequest.<ResourceModel>builder()
               .desiredResourceState(model)
               .build();
   
           final ProgressEvent<ResourceModel, CallbackContext> response
               = handler.handleRequest(proxy, request, null, logger);
   
           final CallbackContext desiredOutputContext = CallbackContext.builder()
                                                                       .stabilizationRetriesRemaining(60)
                                                                       .instanceSecurityGroups(Arrays.asList("sg-1234"))
                                                                       .build();
           assertThat(response).isNotNull();
           assertThat(response.getStatus()).isEqualTo(OperationStatus.IN_PROGRESS);
           assertThat(response.getCallbackContext()).isEqualToComparingFieldByField(desiredOutputContext);
           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 testInProgressStateSecurityGroupsGathered() {
           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());
   
           final DeleteHandler handler = new DeleteHandler();
   
           final ResourceModel model = ResourceModel.builder().instanceId("i-1234").build();
   
           final ResourceHandlerRequest<ResourceModel> request = ResourceHandlerRequest.<ResourceModel>builder()
               .desiredResourceState(model)
               .build();
   
           final CallbackContext context = CallbackContext.builder()
                                                          .stabilizationRetriesRemaining(60)
                                                          .instanceSecurityGroups(Arrays.asList("sg-1234"))
                                                          .build();
   
           final ProgressEvent<ResourceModel, CallbackContext> response
               = handler.handleRequest(proxy, request, context, logger);
   
           final CallbackContext desiredOutputContext = CallbackContext.builder()
                                                                       .stabilizationRetriesRemaining(60)
                                                                       .instanceSecurityGroups(context.getInstanceSecurityGroups())
                                                                       .instance(new Instance().withState(inProgressState))
                                                                       .build();
   
           assertThat(response).isNotNull();
           assertThat(response.getStatus()).isEqualTo(OperationStatus.IN_PROGRESS);
           assertThat(response.getCallbackContext()).isEqualToComparingFieldByField(desiredOutputContext);
           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 testInProgressStateInstanceTerminationInvoked() {
           final InstanceState inProgressState = new InstanceState().withName("in-progress");
           final GroupIdentifier group = new GroupIdentifier().withGroupId("sg-1234");
           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());
   
           final DeleteHandler handler = new DeleteHandler();
   
           final ResourceModel model = ResourceModel.builder().instanceId("i-1234").build();
   
           final ResourceHandlerRequest<ResourceModel> request = ResourceHandlerRequest.<ResourceModel>builder()
               .desiredResourceState(model)
               .build();
   
           final CallbackContext context = CallbackContext.builder()
                                                          .stabilizationRetriesRemaining(60)
                                                          .instance(new Instance().withState(inProgressState).withSecurityGroups(group))
                                                          .instanceSecurityGroups(Arrays.asList("sg-1234"))
                                                          .build();
   
           final ProgressEvent<ResourceModel, CallbackContext> response
               = handler.handleRequest(proxy, request, context, logger);
   
           final CallbackContext desiredOutputContext = CallbackContext.builder()
                                                                       .stabilizationRetriesRemaining(59)
                                                                       .instanceSecurityGroups(context.getInstanceSecurityGroups())
                                                                       .instance(new Instance().withState(inProgressState).withSecurityGroups(group))
                                                                       .build();
   
           assertThat(response).isNotNull();
           assertThat(response.getStatus()).isEqualTo(OperationStatus.IN_PROGRESS);
           assertThat(response.getCallbackContext()).isEqualToComparingFieldByField(desiredOutputContext);
           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 testStabilizationTimeout() {
           final DeleteHandler handler = new DeleteHandler();
   
           final ResourceModel model = ResourceModel.builder().instanceId("i-1234").build();
   
           final ResourceHandlerRequest<ResourceModel> request = ResourceHandlerRequest.<ResourceModel>builder()
               .desiredResourceState(model)
               .build();
   
           final CallbackContext context = CallbackContext.builder()
                                                          .stabilizationRetriesRemaining(0)
                                                          .instanceSecurityGroups(Arrays.asList("sg-1234"))
                                                          .instance(new Instance().withState(new InstanceState().withName("terminated")))
                                                          .build();
   
           try {
               handler.handleRequest(proxy, request, context, logger);
           } catch (RuntimeException e) {
               assertThat(e.getMessage()).isEqualTo(EXPECTED_TIMEOUT_MESSAGE);
           }
       }
   }
   ```

## 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.

### Create the SAM test files
<a name="resource-type-walkthrough-test-files"></a>

1. Create two files:
   + `package-root/sam-tests/create.json`
   + `package-root/sam-tests/delete.json`

   Where *package-root* is the root of the resource project. For our walkthrough example, the files would be:
   + `example-testing-wordpress/sam-tests/create.json`
   + `example-testing-wordpress/sam-tests/delete.json`

1. In `example-testing-wordpress/sam-tests/create.json`, paste the following test.

   Add the necessary information, such as credentials and log group name, and remove any comments in the file before testing.

   For `credentials`, use temporary credentials for an IAM role (such as `PowerUserAccess` or `Developer`) in your personal AWS account. Specify the `accessKeyId`, `secretAccessKey`, and `sessionToken` with their corresponding values. For instructions on how to copy IAM role credentials from the AWS access portal, see [Manual credential refresh](https://docs.aws.amazon.com/singlesignon/latest/userguide/howtogetcredentials.html#how-to-get-temp-credentials-manual) in the *AWS IAM Identity Center User Guide*. The settings for the IAM role you choose determine [how long the temporary credentials are valid](https://docs.aws.amazon.com/singlesignon/latest/userguide/howtosessionduration.html). 

   Using the AWS CLI, you can call an [AWS STS API](https://docs.aws.amazon.com/STS/latest/APIReference/) like `AssumeRole` or `GetFederationToken` and then capture the resulting output. For more information, see [Using temporary credentials with AWS resources](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html) in the *IAM User Guide*. 
**Note**  
In the past, it was common practice to use persistent credentials, such as IAM user credentials or even root credentials, but this is not recommended. For more information, see [Security best practices in IAM](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html) in the *IAM User Guide*. 

   ```
   {
       "credentials": {
           # Real credentials need to go here.
           "accessKeyId": "",
           "secretAccessKey": "",
           "sessionToken": ""
       },
       "action": "CREATE",
       "request": {
           "clientRequestToken": "4b90a7e4-b790-456b-a937-0cfdfa211dfe", # Can be any UUID.
           "desiredResourceState": {
               "Name": "MyBlog",
               "SubnetId": "subnet-0bc6136e" # This should be a real subnet that exists in the account you're testing against.
           },
           "logicalResourceIdentifier": "MyResource"
       },
       "callbackContext": null
   }
   ```

1. In `example-testing-wordpress/sam-tests/delete.json`, paste the following test.

   Add the necessary information, such as credentials and log group name, and remove any comments in the file before testing.

   For `credentials`, use temporary credentials for an IAM role (such as `PowerUserAccess` or `Developer`) in your personal AWS account. Specify the `accessKeyId`, `secretAccessKey`, and `sessionToken` with their corresponding values. For instructions on how to copy IAM role credentials from the AWS access portal, see [Manual credential refresh](https://docs.aws.amazon.com/singlesignon/latest/userguide/howtogetcredentials.html#how-to-get-temp-credentials-manual) in the *AWS IAM Identity Center User Guide*. The settings for the IAM role you choose determine [how long the temporary credentials are valid](https://docs.aws.amazon.com/singlesignon/latest/userguide/howtosessionduration.html). 

   Using the AWS CLI, you can call an [AWS STS API](https://docs.aws.amazon.com/STS/latest/APIReference/) like `AssumeRole` or `GetFederationToken` and then capture the resulting output. For more information, see [Using temporary credentials with AWS resources](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html) in the *IAM User Guide*. 
**Note**  
In the past, it was common practice to use persistent credentials, such as IAM user credentials or even root credentials, but this is not recommended. For more information, see [Security best practices in IAM](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html) in the *IAM User Guide*. 

   ```
   {
       "credentials": {
           # Real credentials need to go here.
           "accessKeyId": "",
           "secretAccessKey": "",
           "sessionToken": ""
       },
       "action": "DELETE",
       "request": {
           "clientRequestToken": "4b90a7e4-b790-456b-a937-0cfdfa211dfe",  # Can be any UUID.
           "desiredResourceState": {
               "Name": "MyBlog",
               "InstanceId": "i-0167b19dd4c1efbf3", # This should be the instance ID that was created in the "create.json" test.
               "SubnetId": "subnet-0bc6136e" # This should be a real subnet that exists in the account you're testing against.
           },
           "logicalResourceIdentifier": "MyResource"
       },
       "callbackContext": null
   }
   ```

### Test the Create handler
<a name="resource-type-walkthrough-test-create"></a>

Once you've created the `example-testing-wordpress/sam-tests/create.json` test file, you can use it to test your create handler.

Ensure Docker is running on your computer.

1. Invoke the SAM function from the resource package root directory using the following commands.

   ```
   $ sam local invoke TestEntrypoint --event sam-tests/create.json
   ```
**Note**  
Occasionally these tests will fail with a retry-able error. In such a case, run the tests again to determine whether the issue was transient.

   Because the create handler was written as a state machine, invoking the tests will return an output that represents a state. For example:

   ```
   {
       "callbackDelaySeconds": 0,
       "resourceModel": {
           "SubnetId": "subnet-0bc6136e",
           "Name": "MyBlog"
       },
       "callbackContext": {
           "instance": {
               "subnetId": "subnet-0bc6136e",
               "virtualizationType": "hvm",
               "capacityReservationSpecification": {
                   "capacityReservationPreference": "open"
               },
               "amiLaunchIndex": 0,
               "elasticInferenceAcceleratorAssociations": [],
               "sourceDestCheck": true,
               "stateReason": {
                   "code": "pending",
                   "message": "pending"
               },
               "instanceId": "i-0b6978477c0e9d358",
               "vpcId": "vpc-eb80788e",
               "hypervisor": "xen",
               "rootDeviceName": "/dev/sda1",
               "productCodes": [],
               "state": {
                   "code": 0,
                   "name": "pending"
               },
               "architecture": "x86_64",
               "ebsOptimized": false,
               "imageId": "ami-04fb0368671b6f138",
               "blockDeviceMappings": [],
               "stateTransitionReason": "",
               "clientToken": "207dc686-e95c-4df9-8fcb-ee22bbdde963",
               "instanceType": "m4.large",
               "cpuOptions": {
                   "threadsPerCore": 2,
                   "coreCount": 1
               },
               "monitoring": {
                   "state": "disabled"
               },
               "publicDnsName": "",
               "privateIpAddress": "172.0.0.133",
               "rootDeviceType": "ebs",
               "tags": [
                   {
                       "value": "MyBlog",
                       "key": "Name"
                   }
               ],
               "launchTime": 1567718644000,
               "elasticGpuAssociations": [],
               "licenses": [],
               "networkInterfaces": [
                   {
                       "networkInterfaceId": "eni-0e450b35a159b60fe",
                       "privateIpAddresses": [
                           {
                               "privateIpAddress": "172.0.0.133",
                               "primary": true
                           }
                       ],
                       "subnetId": "subnet-0bc6136e",
                       "description": "",
                       "groups": [
                           {
                               "groupName": "MyBlog-cbb70fca-4704-430b-b67b-7d6d550e0592",
                               "groupId": "sg-063679dc7681610c3"
                           }
                       ],
                       "ipv6Addresses": [],
                       "ownerId": "671472782477",
                       "sourceDestCheck": true,
                       "privateIpAddress": "172.0.0.133",
                       "interfaceType": "interface",
                       "macAddress": "02:e1:4b:d1:f7:40",
                       "attachment": {
                           "attachmentId": "eni-attach-0a01c63e4b45c4a5d",
                           "deleteOnTermination": true,
                           "deviceIndex": 0,
                           "attachTime": 1567718644000,
                           "status": "attaching"
                       },
                       "vpcId": "vpc-eb80788e",
                       "status": "in-use"
                   }
               ],
               "privateDnsName": "ip-172-0-0-133.us-west-2.compute.internal",
               "securityGroups": [
                   {
                       "groupName": "MyBlog-cbb70fca-4704-430b-b67b-7d6d550e0592",
                       "groupId": "sg-063679dc7681610c3"
                   }
               ],
               "placement": {
                   "groupName": "",
                   "tenancy": "default",
                   "availabilityZone": "us-west-2b"
               }
           },
           "stabilizationRetriesRemaining": 60
       },
       "status": "IN_PROGRESS"
   }
   ```

1. From the test response, copy the contents of the `callbackContext`, and paste it into the `callbackContext` section of the `example-testing-wordpress/sam-tests/create.json` file.

1. Invoke the `TestEntrypoint` function again.

   ```
   $ sam local invoke TestEntrypoint --event sam-tests/create.json
   ```

   If the resource has yet to complete provisioning, the test returns a response with a `status` of `IN_PROGRESS`. Once the resource has completed provisioning, the test returns a response with a `status` of `SUCCESS`. For example: 

   ```
   {
       "callbackDelaySeconds": 0,
       "resourceModel": {
           "InstanceId": "i-0b6978477c0e9d358",
           "PublicIp": "34.211.69.121",
           "SubnetId": "subnet-0bc6136e",
           "Name": "MyBlog"
       },
       "status": "SUCCESS"
   }
   ```

1. Repeat the previous two steps until the resource has completed.

When the resource completes provisioning, the test response contains both its `PublicIp` and `InstanceId`:
+ You can use the `PublicIp` value to navigate to the WordPress site.
+ You can use the `InstanceId` value to test the delete handler, as described below.

### Test the Delete handler
<a name="resource-type-walkthrough-test-delete"></a>

Once you've created the `example-testing-wordpress/sam-tests/delete.json` test file, you can use it to test your delete handler.

Ensure Docker is running on your computer.

1. Invoke the `TestEntrypoint` function from the resource package root directory using the following commands.

   ```
   $ sam local invoke TestEntrypoint --event sam-tests/delete.json
   ```
**Note**  
Occasionally these tests will fail with a retriable error. In such a case, run the tests again to determine whether the issue was transient.

   As with the create handler, the delete handler was written as a state machine, so invoking the test will return an output that represents a state.

1. From the test response, copy the contents of the `callbackContext`, and paste it into the `callbackContext` section of the `example-testing-wordpress/sam-tests/delete.json` file.

1. Invoke the `TestEntrypoint` function again.

   ```
   $ sam local invoke TestEntrypoint --event sam-tests/delete.json
   ```

   If the resource has yet to complete provisioning, the test returns a response with a `status` of `IN_PROGRESS`. Once the resource has completed provisioning, the test returns a response with a `status` of `SUCCESS`.

1. Repeat the previous two steps until the resource has completed.

### Performing resource contract tests
<a name="resource-type-walkthrough-test-contract"></a>

Resource contract tests verify that the resource type schema you've defined properly catches property values that will fail when passed to the underlying APIs called from within your resource handlers. This provides a way of validating user input before passing it to the resource handlers. For example, in the `Example::Testing::WordPress` resource type provide schema (in the `example-testing-wordpress.json` file), we specified regex patterns for the `Name` and `SubnetId` properties, and set the maximum length of `Name` as 219 characters. Contract tests are intended to stress and validate those input definitions.

#### Specify resource contract test override values
<a name="resource-type-walkthrough-test-contract-override"></a>

The CloudFormation CLI performs resource contract tests using input that's generated from the patterns you define in your resource's property definitions. However, some inputs can't be randomly generated. For example, the `Example::Testing::WordPress` resource requires an actual subnet ID for testing, not just a string that matches the appearance of a subnet ID. To test this property, we need to include a file with actual values for the resource contract tests to use `overrides.json` at the root of our project that looks like this:

1. Navigate to the root of your project.

1. Create a file named `overrides.json`.

1. Include the following override, specifying an actual subnet ID to use when performing resource contract tests.

   ```
   {
       "CREATE": {
           "/SubnetId": "subnet-0bc6136e" # This should be a real subnet that exists in the account you're testing against.
       }
   }
   ```

#### Run the resource contract tests
<a name="resource-type-walkthrough-test-contract-run"></a>

To run resource contract tests, you'll need two shell sessions.

1. In a new session, run the following command:

   ```
   $ sam local start-lambda
   ```

1. From the resource package root directory, in a session that's aware of the CloudFormation CLI, run the `test` command:

   ```
   $ cfn test
   ```

   The session that's running `sam local start-lambda` will display information about the status of your tests.

## Submit the resource type
<a name="resource-type-walkthrough-submit"></a>

Once you have completed implementing and testing your resource provided, the final step is to submit it to the CloudFormation registry. This makes it available for use in stack operations in the account and region in which it was submitted.
+ In a terminal, run the `submit` command to register the resource type in the us-west-2 region.

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

  ```
  Validating your resource specification...
  Packaging Java project
  Creating managed upload infrastructure stack
  Managed upload infrastructure stack was successfully created
  Registration in progress with token: 3c27b9e6-dca4-4892-ba4e-3c0example
  ```

Resource type registration is an asynchronous operation. You can use the supplied registration token to track the progress of your type registration request using the [DescribeTypeRegistration](https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_DescribeTypeRegistration.html) action of the CloudFormation API.

**Note**  
If you update your resource type, you can submit a new version of that resource type. Every time you submit your resource type, CloudFormation generates a new version of that resource type.  
To set the default version of a resource type, use `[set-type-default-version](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/set-type-default-version.html)`. For example:   

```
$ aws cloudformation set-type-default-version \
  --type "RESOURCE" \
  --type-name "Example::Testing::WordPress" \
  --version-id "00000002"
```
To retrieve information about the versions of a resource type, use `[list-type-versions](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/list-type-versions.html)`. For example:   

```
$ aws cloudformation list-type-versions \
  --type "RESOURCE" \
  --type-name "Example::Testing::WordPress"
```

## Provision the resource in a CloudFormation stack
<a name="resource-type-walkthrough-provision"></a>

Once the registration request for your resource type has completed successfully, you can create a stack including resources of that type.

**Note**  
Use [DescribeTypeRegistration](https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_DescribeTypeRegistration.html) to determine when your resource type is successfully registration registered with a status of `COMPLETE`. You should also see your new resource type listed in the CloudFormation console.

1. Save the following JSON as a stack template, with the name `stack.json`.

   ```
   {
       "AWSTemplateFormatVersion": "2010-09-09",
       "Description": "WordPress stack",
       "Resources": {
           "MyWordPressSite": {
               "Type": "Example::Testing::WordPress",
               "Properties": {
                   "SubnetId": "subnet-0bc6136e", ## Note that this should be replaced with a subnet that exists in your account.
                   "Name": "MyWebsite"
               }
           }
       }
   }
   ```

1. Use the template to create a stack.
**Note**  
This resource uses an official WordPress image on AWS Marketplace. In order to create the stack, you'll first need to visit the [AWS Marketplace](https://aws.amazon.com/marketplace/pp?sku=7eyp7o9i35afqvpvvh5gujt8w) and accept the terms and subscribe.

   Navigate to the folder in which you saved the `stack.json` file, and create a stack named `wordpress`.

   ```
   $ aws cloudformation create-stack 
     --region us-west-2 \
     --template-body "file://stack.json" \
     --stack-name "wordpress"
   ```

   As CloudFormation creates the stack, it should invoke your resource type create handler to provision a resource of type `Example::Testing::WordPress` as part of the `wordpress` stack.

As a final test of the resource type delete handler, you can delete the `wordpress` stack.

```
$ aws cloudformation delete-stack \
  --region us-west-2 \
  --stack-name wordpress
```

# Resource type development troubleshooting
<a name="resource-type-troubleshooting"></a>

This topic contains common issues regarding resource type development, and suggested solutions for those issues..

## Updates
<a name="resource-type-troubleshooting-updates"></a>
+ **Problem:** My service API implements Update actions as an Upsert, can I implement my CloudFormation resource type in this way?

  **Solution:** No, CloudFormation requires that `update` actions to a non-existing resource always throw a `ResourceNotFoundException`.

## Schema development
<a name="resource-type-troubleshooting-schema"></a>
+ **Problem:** How can I re-use existing schemas, or establish relationships to other resource types in my schema?

  **Solution:** Relationships and re-use are established using JSON Pointers. These are implemented using the `$ref` keyword in your resource type schema. Refer to [Modeling resource types to use with CloudFormation](resource-type-model.md) for more information.

## Permissions and authorization
<a name="resource-type-troubleshooting-permissions"></a>
+ **Problem:** Why am I getting an `AccessDeniedException` for my AWS API calls?

  **Solution:** If you are seeing errors in your logs related to AccessDeniedException for a Lambda Execution Role like:

  `A downstream service error occurred in a CREATE action on a AWS::MyService::MyResource: com.amazonaws.services.logs.model.AWSLogsException: User: arn:aws:sts::111122223333:assumed-role/UluruResourceHandlerLambdaExecutionRole-123456789012pdx/AWS-MYService-MyResource-Handler-1h344teffe is not authorized to perform: some:ApiCall on resource: some-resource (Service: AWSLogs; Status Code: 400; Error Code: AccessDeniedException; Request ID: 36af0cec-a96a-11e9-b204-ddabexample)`

  This is an indication that you are attempting to create and invoke AWS APIs using the default client, which is injected with environment credentials.

  For resource types, you should use the passed-in `AmazonWebServicesClientProxy` object to make AWS API calls, as in the following example.

  ```
  SesClient client = ClientBuilder.getClient();
  final CreateConfigurationSetRequest createConfigurationSetRequest =
      CreateConfigurationSetRequest.builder()
          .configurationSet(ConfigurationSet.builder()
              .name(model.getName())
              .build())
          .build();
  proxy.injectCredentialsAndInvokeV2(createConfigurationSetRequest, client::createConfigurationSet);
  ```
+ **Problem:** How do I specify credentials for non-AWS API calls?

  **Solution:** For non-AWS API calls which require authentication and authorization, you should create properties in your resource type which contain the credentials. Define these properties in the resource type schema as `writeOnlyProperties`.

  Users can then provide their own credentials through their CloudFormation templates. We encourage the use of [dynamic references](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html) in CloudFormation templates, which can use AWS Secrets Manager to fetch credentials at runtime.

## Resource type development
<a name="resource-type-troubleshooting-dev"></a>
+ **Problem:** Can I share functionality between resource by adding common functionality to the `BaseHandler`?

  **Solution:** Because the `BaseHandler` is code-generated, it cannot be edited.
+ **Problem:** For Java development, is there a way to include multiple resources in a single maven project?

  **Solution:** Not currently. For security and manageability, the CloudFormation Registry registers each resource type as a separate, versioned, type. You could still share code through a shared package. Ideally, the wrapper layer does most of the boilerplate. If you see a need for more boilerplate, we would like to know how we can improve for that use case rather than combine types in a package, so please reach out to the team.
+ **Problem:** Will `software.amazon.cloudformation.proxy.Logger` have debug/info/warning/error levels/?

  **Solution:** Currently, all log messages are emitted to CloudWatch, which has no built-in concept of log levels.
+ **Problem:** Does CloudFormation have a sandbox environment I can use to test and experiment with my extensions?

  **Solution:** Using extensions from your private registry in stacks created in your account is the same as using a sandbox environment.

## Testing
<a name="resource-type-troubleshooting-test"></a>
+ **Problem:** For testing, when should I use `sam local invoke`, `cfn test` and `mvn test`?

  **Solution:** Use the various test capabilities for the test scenarios described below.
  + `sam local invoke`: Creating custom integration test by passing in custom CloudFormation payloads and isolate specific handlers.
  + `cfn test`: Contract tests meant to cycle through CRUDL actions and ideally leave in a clean state (if tests pass).
  + `mvn test`: Used for Unit testing. The goal is to confirm that each method or unit of the resource performs as intended. It also helps to ensure that you have enough code coverage. We expect unit tests to mock dependencies and not create real resources.
+ **Problem:** How do I get the latest changes for a contract test?

  **Solution:** Run `pip install cloudformation-cli cloudformation-cli-<language>-plugin --upgrade`.
+ **Problem:** Where can I find the code for contract tests?

  **Solution:** You can find the code for the test suite at [https://github.com/aws-cloudformation/cloudformation-cli/tree/master/src/rpdk/core/contract/suite](https://github.com/aws-cloudformation/cloudformation-cli/tree/master/src/rpdk/core/contract/suite).
+ **Problem:** How do I check which version of contract tests I’m running?

  **Solution:** Run `pip freeze`. The output shows the CloudFormation CLI version. The list of all releases for the CloudFormation CLI can be found at [https://github.com/aws-cloudformation/cloudformation-cli/releases](https://github.com/aws-cloudformation/cloudformation-cli/releases).
+ **Problem:** I found a bug in the contract tests. How do I report it?

  **Solution:** File an issue at [https://github.com/aws-cloudformation/cloudformation-cli/issues](https://github.com/aws-cloudformation/cloudformation-cli/issues).
+ **Problem:** How can I contribute to the development of contract tests?

  **Solution:** Follow [these steps](https://github.com/aws-cloudformation/cloudformation-cli#development) to install a virtual development environment on your local machine.
+ **Problem:** Contract tests time out on my local machine. How do I resolve this issue?

  **Solution:** Contract tests assert that `create`, `update`, and `delete` handlers return progress events every 60 seconds, and that `read` and `list` handlers complete in 30 seconds. If you see errors for tests timing out, you can increase the timeout by running contract tests with the following argument: `cfn test --enforce-timeout <value>`. The value specified is the new timeout threshold for the `read` and `list` handlers. The new threshold for the `create`, `update`, and `delete` handlers is double the value specified.
+ **Problem:** How do I run one contract test at a time?

  **Solution:** You can run one contract test by running the following command: `cfn test -- -k <test-name>`.
+ **Problem:** How do I define input files for contract tests?

  **Solution:** [Specifying input data for use in contract tests](resource-type-test.md#resource-type-test-input-data) describes how to pass an input for contract tests and what each file should contain.
+ **Problem:** How do I fix the `json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)` error?

  **Solution:** This exception message indicates that you might be running out of memory or time. Add the following lines under `Globals` in the `template.yaml` file in your code package.

  ```
  Globals:
    Function:
      Timeout: 180
      MemorySize: 256
  ```
+ **Problem:** Why do create operations fail when the `primaryIdentifier` is null?

  **Solution:** The `create` handler should return the `primaryIdentifier` in the model. If the `primaryIdentifier` for your resource is a read-only property, you can update the returned model with the property or invoke the `read` handler in your `create` handler.
+ **Problem:** Some tests fail because the input is not equal to the output. How do I fix this?

  **Solution:** Input-output comparisons check that all properties are exactly equal, except for read-only properties, write-only properties, defaults, `insertionOrder`, and `uniqueItems`.
+ **Problem:** Can the `update` handler update create-only properties?

  **Solution:** No. The `update` handler cannot update create-only properties. These properties should remain the same in `update` models.
+ **Problem:** Why does my `delete`/`read` handler fail with a `NotFound` or `InvalidRequest` error code?

  **Solution:** Ensure that your input to these handlers is correct. The `delete` and `read` handlers should be successfully invoked with just the `primaryIdentifier`. The `read` handler can also be invoked with `additionalIdentifiers`.
+ **Problem:** Why do delete operations fail when `resourceModel` isn't null?

  **Solution:** The `delete` handler shouldn't return a model when successful. You need to redact the model from the returned progress event.
+ **Problem:** All of my properties are marked as create-only properties. Why do the `update` contract tests keep running and failing?

  **Solution:** Remove the `update` block from the `handlers` section in your schema file.
+ **Problem:** How do I fix permission issues during test execution?

  **Solution:** Check the exception message to see which permissions are missing. Add these permissions to `ExecutionRole` in the `resource-role.yaml` file in your code package.

## Deployment
<a name="resource-type-troubleshooting-deploy"></a>
+ **Problem:** Is the resource type interface guaranteed to be stable?

  **Solution:** The communication protocol between CloudFormation and your resource type package is subject to change. However this will be done in a backwards-compatible way, using versioned interfaces. This will be invisible to you as a developer and is managed as part of the CloudFormation managed platform.

  The interface that your handlers implement inside your package is expected to be stable. We may introduce improvements, such as security fixes or other changes to the package, but these will be done with versioned dependency or CloudFormation CLI updates. You aren't required to upgrade your packages to publish them, only to incorporate these improvements.