diff --git a/aws-iot-domainconfiguration/.rpdk-config b/aws-iot-domainconfiguration/.rpdk-config
new file mode 100644
index 0000000..b456e6a
--- /dev/null
+++ b/aws-iot-domainconfiguration/.rpdk-config
@@ -0,0 +1,16 @@
+{
+ "typeName": "AWS::IoT::DomainConfiguration",
+ "language": "java",
+ "runtime": "java8",
+ "entrypoint": "com.amazonaws.iot.domainconfiguration.HandlerWrapper::handleRequest",
+ "testEntrypoint": "com.amazonaws.iot.domainconfiguration.HandlerWrapper::testEntrypoint",
+ "settings": {
+ "namespace": [
+ "com",
+ "amazonaws",
+ "iot",
+ "domainconfiguration"
+ ],
+ "protocolVersion": "2.0.0"
+ }
+}
diff --git a/aws-iot-domainconfiguration/README.md b/aws-iot-domainconfiguration/README.md
new file mode 100644
index 0000000..f04eba0
--- /dev/null
+++ b/aws-iot-domainconfiguration/README.md
@@ -0,0 +1,17 @@
+# AWS::IoT::DomainConfiguration
+
+Congratulations on starting development! Next steps:
+
+1. Write the JSON schema describing your resource, `aws-iot-domainconfiguration.json`
+2. The RPDK will automatically generate the correct resource model from the
+ schema whenever the project is built via Maven. You can also do this manually
+ with the following command: `cfn generate`
+3. Implement your resource handlers
+
+
+Please don't modify files under `target/generated-sources/rpdk`, as they will be
+automatically overwritten.
+
+The code use [Lombok](https://projectlombok.org/), and [you may have to install
+IDE integrations](https://projectlombok.org/) to enable auto-complete for
+Lombok-annotated classes.
diff --git a/aws-iot-domainconfiguration/aws-iot-domainconfiguration.json b/aws-iot-domainconfiguration/aws-iot-domainconfiguration.json
new file mode 100644
index 0000000..9ad1e5e
--- /dev/null
+++ b/aws-iot-domainconfiguration/aws-iot-domainconfiguration.json
@@ -0,0 +1,180 @@
+{
+ "typeName": "AWS::IoT::DomainConfiguration",
+ "description": "Create and manage a Domain Configuration",
+ "sourceUrl": "https://github.com/aws-cloudformation/aws-cloudformation-rpdk.git",
+ "definitions": {
+ "AuthorizerConfig": {
+ "type": "object",
+ "properties": {
+ "AllowAuthorizerOverride": {
+ "type": "boolean"
+ },
+ "DefaultAuthorizerName": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 128,
+ "pattern": "^[\\w=,@-]+$"
+ }
+ },
+ "additionalProperties": false
+ },
+ "ServerCertificateSummary": {
+ "type": "object",
+ "properties": {
+ "ServerCertificateArn": {
+ "type": "string",
+ "pattern": "^arn:aws(-cn|-us-gov|-iso-b|-iso)?:acm:[a-z]{2}-(gov-|iso-|isob-)?[a-z]{4,9}-\\d{1}:\\d{12}:certificate/[a-zA-Z0-9/-]+$",
+ "minLength": 1,
+ "maxLength": 2048
+ },
+ "ServerCertificateStatus": {
+ "type": "string",
+ "enum": [
+ "INVALID",
+ "VALID"
+ ]
+ },
+ "ServerCertificateStatusDetail": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+ },
+ "Tags": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "Key": {
+ "type": "string"
+ },
+ "Value": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "Key",
+ "Value"
+ ]
+ }
+ }
+ },
+ "properties": {
+ "DomainConfigurationName": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 128,
+ "pattern": "^[\\w.-]+$"
+ },
+ "AuthorizerConfig": {
+ "$ref": "#/definitions/AuthorizerConfig"
+ },
+ "DomainName": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 253
+ },
+ "ServerCertificateArns": {
+ "type": "array",
+ "minItems": 0,
+ "maxItems": 1,
+ "items": {
+ "type": "string",
+ "pattern": "^arn:aws(-cn|-us-gov|-iso-b|-iso)?:acm:[a-z]{2}-(gov-|iso-|isob-)?[a-z]{4,9}-\\d{1}:\\d{12}:certificate/[a-zA-Z0-9/-]+$",
+ "minLength": 1,
+ "maxLength": 2048
+ }
+ },
+ "ServiceType": {
+ "type": "string",
+ "enum": [
+ "DATA",
+ "CREDENTIAL_PROVIDER",
+ "JOBS"
+ ]
+ },
+ "ValidationCertificateArn": {
+ "type": "string",
+ "pattern": "^arn:aws(-cn|-us-gov|-iso-b|-iso)?:acm:[a-z]{2}-(gov-|iso-|isob-)?[a-z]{4,9}-\\d{1}:\\d{12}:certificate/[a-zA-Z0-9/-]+$"
+ },
+ "Arn": {
+ "type": "string"
+ },
+ "DomainConfigurationStatus": {
+ "type": "string",
+ "enum": [
+ "ENABLED",
+ "DISABLED"
+ ]
+ },
+ "DomainType": {
+ "type": "string",
+ "enum": [
+ "ENDPOINT",
+ "AWS_MANAGED",
+ "CUSTOMER_MANAGED"
+ ]
+ },
+ "ServerCertificates": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ServerCertificateSummary"
+ }
+ },
+ "Tags": {
+ "$ref": "#/definitions/Tags"
+ }
+ },
+ "additionalProperties": false,
+ "required": [],
+ "createOnlyProperties": [
+ "/properties/DomainConfigurationName",
+ "/properties/DomainName",
+ "/properties/ServiceType",
+ "/properties/ValidationCertificateArn",
+ "/properties/ServerCertificateArns"
+ ],
+ "readOnlyProperties": [
+ "/properties/Arn",
+ "/properties/DomainType",
+ "/properties/ServerCertificates"
+ ],
+ "writeOnlyProperties": [
+ "/properties/ServerCertificateArns"
+ ],
+ "primaryIdentifier": [
+ "/properties/DomainConfigurationName"
+ ],
+ "handlers": {
+ "create": {
+ "permissions": [
+ "iot:CreateDomainConfiguration",
+ "iot:UpdateDomainConfiguration",
+ "iot:DescribeDomainConfiguration"
+ ]
+ },
+ "read": {
+ "permissions": [
+ "iot:DescribeDomainConfiguration"
+ ]
+ },
+ "update": {
+ "permissions": [
+ "iot:UpdateDomainConfiguration",
+ "iot:DescribeDomainConfiguration"
+ ]
+ },
+ "delete": {
+ "permissions": [
+ "iot:DeleteDomainConfiguration",
+ "iot:UpdateDomainConfiguration"
+ ]
+ },
+ "list": {
+ "permissions": [
+ "iot:ListDomainConfigurations"
+ ]
+ }
+ }
+}
diff --git a/aws-iot-domainconfiguration/lombok.config b/aws-iot-domainconfiguration/lombok.config
new file mode 100644
index 0000000..7a21e88
--- /dev/null
+++ b/aws-iot-domainconfiguration/lombok.config
@@ -0,0 +1 @@
+lombok.addLombokGeneratedAnnotation = true
diff --git a/aws-iot-domainconfiguration/pom.xml b/aws-iot-domainconfiguration/pom.xml
new file mode 100644
index 0000000..c77cce0
--- /dev/null
+++ b/aws-iot-domainconfiguration/pom.xml
@@ -0,0 +1,213 @@
+
+
+ 4.0.0
+
+ com.amazonaws.iot.domainconfiguration
+ aws-iot-domainconfiguration-handler
+ aws-iot-domainconfiguration-handler
+ 1.0-SNAPSHOT
+ jar
+
+
+ 1.8
+ 1.8
+ UTF-8
+ UTF-8
+
+
+
+
+ central
+ https://repo.maven.apache.org/maven2
+
+
+
+
+
+
+ software.amazon.cloudformation
+ aws-cloudformation-rpdk-java-plugin
+ 2.0.2
+
+
+
+ software.amazon.awssdk
+ iot
+ 2.14.9
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.4
+ provided
+
+
+
+
+ org.assertj
+ assertj-core
+ 3.12.2
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.5.0-M1
+ test
+
+
+
+ org.mockito
+ mockito-core
+ 2.26.0
+ test
+
+
+
+ org.mockito
+ mockito-junit-jupiter
+ 2.26.0
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+
+ -Xlint:all,-options,-processing
+ -Werror
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 2.3
+
+ false
+
+
+
+ package
+
+ shade
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 1.6.0
+
+
+ generate
+ generate-sources
+
+ exec
+
+
+ cfn
+ generate
+ ${project.basedir}
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 3.0.0
+
+
+ add-source
+ generate-sources
+
+ add-source
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 2.4
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.4
+
+
+ **/BaseConfiguration*
+ **/BaseHandler*
+ **/HandlerWrapper*
+ **/ResourceModel*
+
+
+
+
+
+ prepare-agent
+
+
+
+ report
+ test
+
+ report
+
+
+
+ jacoco-check
+
+ check
+
+
+
+
+ PACKAGE
+
+
+ BRANCH
+ COVEREDRATIO
+ 0.8
+
+
+ INSTRUCTION
+ COVEREDRATIO
+ 0.8
+
+
+
+
+
+
+
+
+
+
+
+ ${project.basedir}
+
+ aws-iot-domainconfiguration.json
+
+
+
+
+
diff --git a/aws-iot-domainconfiguration/resource-role.yaml b/aws-iot-domainconfiguration/resource-role.yaml
new file mode 100644
index 0000000..90bcf54
--- /dev/null
+++ b/aws-iot-domainconfiguration/resource-role.yaml
@@ -0,0 +1,35 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Description: >
+ This CloudFormation template creates a role assumed by CloudFormation
+ during CRUDL operations to mutate resources on behalf of the customer.
+
+Resources:
+ ExecutionRole:
+ Type: AWS::IAM::Role
+ Properties:
+ MaxSessionDuration: 8400
+ AssumeRolePolicyDocument:
+ Version: '2012-10-17'
+ Statement:
+ - Effect: Allow
+ Principal:
+ Service: resources.cloudformation.amazonaws.com
+ Action: sts:AssumeRole
+ Path: "/"
+ Policies:
+ - PolicyName: ResourceTypePolicy
+ PolicyDocument:
+ Version: '2012-10-17'
+ Statement:
+ - Effect: Allow
+ Action:
+ - "iot:CreateDomainConfiguration"
+ - "iot:DeleteDomainConfiguration"
+ - "iot:DescribeDomainConfiguration"
+ - "iot:ListDomainConfigurations"
+ - "iot:UpdateDomainConfiguration"
+ Resource: "*"
+Outputs:
+ ExecutionRoleArn:
+ Value:
+ Fn::GetAtt: ExecutionRole.Arn
diff --git a/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/CallbackContext.java b/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/CallbackContext.java
new file mode 100644
index 0000000..a76912e
--- /dev/null
+++ b/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/CallbackContext.java
@@ -0,0 +1,19 @@
+package com.amazonaws.iot.domainconfiguration;
+
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.AllArgsConstructor;
+
+/**
+ * Context used for CloudFormation handlers when creating/updating/deleting domain configuration request.
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class CallbackContext {
+ private boolean domainConfigurationDisabled;
+ private boolean createOrUpdateInProgress;
+ private int retryCount;
+}
diff --git a/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/ClientBuilder.java b/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/ClientBuilder.java
new file mode 100644
index 0000000..abbc73f
--- /dev/null
+++ b/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/ClientBuilder.java
@@ -0,0 +1,24 @@
+package com.amazonaws.iot.domainconfiguration;
+
+import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
+import software.amazon.awssdk.core.retry.RetryPolicy;
+import software.amazon.awssdk.services.iot.IotClient;
+
+class ClientBuilder {
+ private static volatile IotClient iotClient;
+
+ static IotClient getClient() {
+ if (iotClient != null) {
+ return iotClient;
+ }
+
+ synchronized (ClientBuilder.class) {
+ iotClient = IotClient.builder()
+ .overrideConfiguration(ClientOverrideConfiguration.builder()
+ .retryPolicy(RetryPolicy.builder().numRetries(3).build())
+ .build())
+ .build();
+ return iotClient;
+ }
+ }
+}
diff --git a/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/Configuration.java b/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/Configuration.java
new file mode 100644
index 0000000..785202d
--- /dev/null
+++ b/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/Configuration.java
@@ -0,0 +1,8 @@
+package com.amazonaws.iot.domainconfiguration;
+
+class Configuration extends BaseConfiguration {
+
+ public Configuration() {
+ super("aws-iot-domainconfiguration.json");
+ }
+}
diff --git a/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/CreateHandler.java b/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/CreateHandler.java
new file mode 100644
index 0000000..6881606
--- /dev/null
+++ b/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/CreateHandler.java
@@ -0,0 +1,157 @@
+package com.amazonaws.iot.domainconfiguration;
+
+import com.amazonaws.util.StringUtils;
+import software.amazon.awssdk.services.iot.IotClient;
+import software.amazon.awssdk.services.iot.model.CreateDomainConfigurationRequest;
+import software.amazon.awssdk.services.iot.model.CreateDomainConfigurationResponse;
+import software.amazon.awssdk.services.iot.model.InternalException;
+import software.amazon.awssdk.services.iot.model.InvalidRequestException;
+import software.amazon.awssdk.services.iot.model.LimitExceededException;
+import software.amazon.awssdk.services.iot.model.ResourceAlreadyExistsException;
+import software.amazon.awssdk.services.iot.model.Tag;
+import software.amazon.awssdk.services.iot.model.ThrottlingException;
+import software.amazon.awssdk.services.iot.model.UpdateDomainConfigurationRequest;
+import software.amazon.cloudformation.exceptions.CfnAlreadyExistsException;
+import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
+import software.amazon.cloudformation.exceptions.CfnNotFoundException;
+import software.amazon.cloudformation.exceptions.CfnResourceConflictException;
+import software.amazon.cloudformation.exceptions.CfnServiceInternalErrorException;
+import software.amazon.cloudformation.exceptions.CfnServiceLimitExceededException;
+import software.amazon.cloudformation.exceptions.CfnThrottlingException;
+import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
+import software.amazon.cloudformation.proxy.Logger;
+import software.amazon.cloudformation.proxy.ProgressEvent;
+import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
+import software.amazon.cloudformation.resource.IdentifierUtils;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class CreateHandler extends BaseHandler {
+ private static final int MAX_DOMAIN_CONFIG_NAME = 128;
+ private static final String OPERATION = "CreateDomainConfiguration";
+
+ private IotClient iotClient;
+
+ public CreateHandler() {
+ iotClient = ClientBuilder.getClient();
+ }
+
+ public CreateHandler(IotClient iotClient) {
+ this.iotClient = iotClient;
+ }
+
+ /**
+ * Return the domain configuration name if specified in the model or auto-generate one based on the request and
+ * the resource's logical ID.
+ *
+ * @param model The desired resource state
+ * @param request The resource handler request (used to get logical ID and request token)
+ * @return template name to use in create request
+ */
+ private static String getDomainConfigurationName(final ResourceModel model,
+ final ResourceHandlerRequest request) {
+ return StringUtils.isNullOrEmpty(model.getDomainConfigurationName())
+ ? IdentifierUtils.generateResourceIdentifier(
+ request.getLogicalResourceIdentifier(),
+ request.getClientRequestToken(),
+ MAX_DOMAIN_CONFIG_NAME)
+ : model.getDomainConfigurationName();
+ }
+
+ /**
+ * Get the converted tags of the request model or return null if none are present
+ * @param model
+ * @return A collection of tags or null
+ */
+ private static Collection getTags(ResourceModel model) {
+ final List modelTags = model.getTags();
+ return Objects.isNull(modelTags)
+ ? null
+ : modelTags.stream()
+ .map(tag -> Tag.builder().key(tag.getKey()).value(tag.getValue()).build())
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public ProgressEvent handleRequest(
+ final AmazonWebServicesClientProxy proxy,
+ final ResourceHandlerRequest request,
+ final CallbackContext callbackContext,
+ final Logger logger) {
+
+ final ResourceModel model = request.getDesiredResourceState();
+ final String domainConfigName = getDomainConfigurationName(model, request);
+ model.setDomainConfigurationName(domainConfigName);
+
+ if (callbackContext != null && callbackContext.isCreateOrUpdateInProgress()) {
+ int currentRetryCount = callbackContext.getRetryCount();
+ try {
+ final ProgressEvent readResponse = (new ReadHandler(iotClient))
+ .handleRequest(proxy, request, CallbackContext.builder().build(), logger);
+ logger.log(String.format("%s [%s] created successfully", ResourceModel.TYPE_NAME, domainConfigName));
+ return readResponse;
+ } catch (final CfnNotFoundException e) {
+ if(currentRetryCount >= ResourceUtil.MAX_RETRIES)
+ throw new CfnResourceConflictException(model.getDomainName(), model.getArn(),
+ "Unable to create the resource", e);
+ else
+ return ProgressEvent.defaultInProgressHandler(
+ CallbackContext.builder().createOrUpdateInProgress(true).retryCount(currentRetryCount + 1).build(),
+ ResourceUtil.DELAY_CONSTANT, model);
+ }
+ }
+
+ final CreateDomainConfigurationRequest domainRequest = CreateDomainConfigurationRequest.builder()
+ .domainConfigurationName(domainConfigName)
+ .domainName(model.getDomainName())
+ .authorizerConfig(ResourceUtil.getSdkAuthorizerConfig(model))
+ .serverCertificateArns(model.getServerCertificateArns())
+ .serviceType(model.getServiceType())
+ .tags(getTags(model))
+ .validationCertificateArn(model.getValidationCertificateArn())
+ .build();
+
+ try {
+ CreateDomainConfigurationResponse response = proxy.injectCredentialsAndInvokeV2(domainRequest,
+ iotClient::createDomainConfiguration);
+ logger.log(String.format("%s [%s] created. Waiting for %d seconds before returning success",
+ ResourceModel.TYPE_NAME, domainConfigName, ResourceUtil.DELAY_CONSTANT));
+
+ // Since we have a property that only shows up in updates, we need to handle it in create as well as
+ // there is no support for updateOnlyProperties.
+ if (model.getDomainConfigurationStatus() != null) {
+ UpdateDomainConfigurationRequest updateRequest = UpdateDomainConfigurationRequest.builder()
+ .domainConfigurationName(domainConfigName)
+ .domainConfigurationStatus(model.getDomainConfigurationStatus())
+ .build();
+ proxy.injectCredentialsAndInvokeV2(updateRequest, iotClient::updateDomainConfiguration);
+ logger.log(String.format("%s [%s] updated during creation with status [%s]",
+ ResourceModel.TYPE_NAME,
+ domainConfigName,
+ model.getDomainConfigurationStatus()));
+
+ }
+ return ProgressEvent.defaultInProgressHandler(
+ CallbackContext.builder().createOrUpdateInProgress(true).retryCount(1).build(),
+ ResourceUtil.DELAY_CONSTANT,
+ ResourceModel.builder().
+ domainConfigurationName(response.domainConfigurationName())
+ .arn(response.domainConfigurationArn())
+ .build());
+
+ } catch (final ResourceAlreadyExistsException e) {
+ throw new CfnAlreadyExistsException(ResourceModel.TYPE_NAME, domainConfigName);
+ } catch (final InvalidRequestException e) {
+ throw new CfnInvalidRequestException(domainRequest.toString(), e);
+ } catch (final LimitExceededException e) {
+ throw new CfnServiceLimitExceededException(ResourceModel.TYPE_NAME, e.toString());
+ } catch (final InternalException e) {
+ throw new CfnServiceInternalErrorException(OPERATION, e);
+ } catch (final ThrottlingException e) {
+ throw new CfnThrottlingException(OPERATION, e);
+ }
+ }
+}
diff --git a/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/DeleteHandler.java b/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/DeleteHandler.java
new file mode 100644
index 0000000..2b5192a
--- /dev/null
+++ b/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/DeleteHandler.java
@@ -0,0 +1,99 @@
+package com.amazonaws.iot.domainconfiguration;
+
+import org.apache.commons.lang3.StringUtils;
+import software.amazon.awssdk.services.iot.IotClient;
+import software.amazon.awssdk.services.iot.model.DeleteDomainConfigurationRequest;
+import software.amazon.awssdk.services.iot.model.DomainConfigurationStatus;
+import software.amazon.awssdk.services.iot.model.InternalFailureException;
+import software.amazon.awssdk.services.iot.model.InvalidRequestException;
+import software.amazon.awssdk.services.iot.model.ResourceNotFoundException;
+import software.amazon.awssdk.services.iot.model.ServiceUnavailableException;
+import software.amazon.awssdk.services.iot.model.ThrottlingException;
+import software.amazon.awssdk.services.iot.model.UnauthorizedException;
+import software.amazon.awssdk.services.iot.model.UpdateDomainConfigurationRequest;
+import software.amazon.awssdk.services.iot.model.UpdateDomainConfigurationResponse;
+import software.amazon.cloudformation.exceptions.CfnAccessDeniedException;
+import software.amazon.cloudformation.exceptions.CfnGeneralServiceException;
+import software.amazon.cloudformation.exceptions.CfnInternalFailureException;
+import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
+import software.amazon.cloudformation.exceptions.CfnNotFoundException;
+import software.amazon.cloudformation.exceptions.CfnThrottlingException;
+import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
+import software.amazon.cloudformation.proxy.Logger;
+import software.amazon.cloudformation.proxy.ProgressEvent;
+import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
+
+public class DeleteHandler extends BaseHandler {
+ private static final String DELETE_OPERATION = "DeleteDomainConfiguration";
+ private static final String UPDATE_OPERATION = "UpdateDomainConfiguration";
+
+ private IotClient iotClient;
+
+ public DeleteHandler() {
+ iotClient = ClientBuilder.getClient();
+ }
+
+ public DeleteHandler(IotClient iotClient) {
+ this.iotClient = iotClient;
+ }
+
+ @Override
+ public ProgressEvent handleRequest(
+ final AmazonWebServicesClientProxy proxy,
+ final ResourceHandlerRequest request,
+ final CallbackContext callbackContext,
+ final Logger logger) {
+
+ final ResourceModel model = request.getDesiredResourceState();
+ final String domainConfigName = model.getDomainConfigurationName();
+
+ String operation = DELETE_OPERATION;
+
+ final DeleteDomainConfigurationRequest deleteRequest = DeleteDomainConfigurationRequest.builder()
+ .domainConfigurationName(domainConfigName)
+ .build();
+ boolean previouslyDisabled = callbackContext != null && callbackContext.isDomainConfigurationDisabled();
+ try {
+ if(!previouslyDisabled &&
+ !StringUtils.equals(DomainConfigurationStatus.DISABLED.toString(), model.getDomainConfigurationStatus())) {
+ operation = UPDATE_OPERATION;
+ final UpdateDomainConfigurationRequest updateRequest = UpdateDomainConfigurationRequest.builder()
+ .domainConfigurationName(model.getDomainConfigurationName())
+ .authorizerConfig(ResourceUtil.getSdkAuthorizerConfig(model))
+ .domainConfigurationStatus(DomainConfigurationStatus.DISABLED.toString())
+ .removeAuthorizerConfig(false)
+ .build();
+ UpdateDomainConfigurationResponse response = proxy.injectCredentialsAndInvokeV2(updateRequest, iotClient::updateDomainConfiguration);
+ logger.log(String.format("%s [%s] set as %s before deleting",
+ ResourceModel.TYPE_NAME, model.getDomainConfigurationName(), DomainConfigurationStatus.DISABLED));
+
+ return ProgressEvent.defaultInProgressHandler(
+ CallbackContext.builder().domainConfigurationDisabled(true).build(),
+ ResourceUtil.DELAY_CONSTANT,
+ ResourceModel.builder()
+ .arn(response.domainConfigurationArn())
+ .domainConfigurationName(response.domainConfigurationName())
+ .build());
+ }
+
+ proxy.injectCredentialsAndInvokeV2(deleteRequest, iotClient::deleteDomainConfiguration);
+ logger.log(String.format("%s [%s] deleted successfully", ResourceModel.TYPE_NAME, domainConfigName));
+
+ } catch (final ThrottlingException e) {
+ throw new CfnThrottlingException(operation, e);
+ } catch (final UnauthorizedException e) {
+ throw new CfnAccessDeniedException(operation, e);
+ } catch (final ServiceUnavailableException e) {
+ throw new CfnGeneralServiceException(operation, e);
+ } catch (final InternalFailureException e) {
+ throw new CfnInternalFailureException(e);
+ } catch (final InvalidRequestException e) {
+ throw new CfnInvalidRequestException(String.format("Request: %s \n Message: %s", deleteRequest.toString(),
+ e.getMessage()), e);
+ } catch (final ResourceNotFoundException e) {
+ throw new CfnNotFoundException(ResourceModel.TYPE_NAME, model.getDomainConfigurationName());
+ }
+
+ return ProgressEvent.defaultSuccessHandler(null);
+ }
+}
diff --git a/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/ListHandler.java b/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/ListHandler.java
new file mode 100644
index 0000000..639ef6c
--- /dev/null
+++ b/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/ListHandler.java
@@ -0,0 +1,73 @@
+package com.amazonaws.iot.domainconfiguration;
+
+import software.amazon.awssdk.services.iot.IotClient;
+import software.amazon.awssdk.services.iot.model.InternalFailureException;
+import software.amazon.awssdk.services.iot.model.InvalidRequestException;
+import software.amazon.awssdk.services.iot.model.ListDomainConfigurationsRequest;
+import software.amazon.awssdk.services.iot.model.ListDomainConfigurationsResponse;
+import software.amazon.awssdk.services.iot.model.ThrottlingException;
+import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
+import software.amazon.cloudformation.exceptions.CfnServiceInternalErrorException;
+import software.amazon.cloudformation.exceptions.CfnThrottlingException;
+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 java.util.List;
+import java.util.stream.Collectors;
+
+public class ListHandler extends BaseHandler {
+ private static final String OPERATION = "ListProvisioningTemplates";
+
+ private IotClient iotClient;
+
+ public ListHandler() {
+ iotClient = ClientBuilder.getClient();
+ }
+
+ public ListHandler(IotClient iotClient) {
+ this.iotClient = iotClient;
+ }
+
+ @Override
+ public ProgressEvent handleRequest(
+ final AmazonWebServicesClientProxy proxy,
+ final ResourceHandlerRequest request,
+ final CallbackContext callbackContext,
+ final Logger logger) {
+
+ final ListDomainConfigurationsRequest domainRequest = ListDomainConfigurationsRequest.builder()
+ .pageSize(50)
+ .marker(request.getNextToken())
+ .build();
+
+ try {
+ final ListDomainConfigurationsResponse response = proxy.injectCredentialsAndInvokeV2(
+ domainRequest,
+ iotClient::listDomainConfigurations);
+
+ final List models = response.domainConfigurations().stream()
+ .map(domainConfig-> ResourceModel.builder()
+ .domainConfigurationName(domainConfig.domainConfigurationName())
+ .arn(domainConfig.domainConfigurationArn())
+ .serviceType(domainConfig.serviceTypeAsString())
+ .build())
+ .collect(Collectors.toList());
+
+ return ProgressEvent.builder()
+ .resourceModels(models)
+ .nextToken(response.nextMarker())
+ .status(OperationStatus.SUCCESS)
+ .build();
+
+ } catch (final InternalFailureException e) {
+ throw new CfnServiceInternalErrorException(OPERATION, e);
+ } catch (final InvalidRequestException e) {
+ throw new CfnInvalidRequestException(domainRequest.toString(), e);
+ } catch (final ThrottlingException e) {
+ throw new CfnThrottlingException(OPERATION, e);
+ }
+ }
+}
diff --git a/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/ReadHandler.java b/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/ReadHandler.java
new file mode 100644
index 0000000..0a214d3
--- /dev/null
+++ b/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/ReadHandler.java
@@ -0,0 +1,92 @@
+package com.amazonaws.iot.domainconfiguration;
+
+import software.amazon.awssdk.services.iot.IotClient;
+import software.amazon.awssdk.services.iot.model.DescribeDomainConfigurationRequest;
+import software.amazon.awssdk.services.iot.model.DescribeDomainConfigurationResponse;
+import software.amazon.awssdk.services.iot.model.InternalFailureException;
+import software.amazon.awssdk.services.iot.model.InvalidRequestException;
+import software.amazon.awssdk.services.iot.model.ResourceNotFoundException;
+import software.amazon.awssdk.services.iot.model.ServerCertificateSummary;
+import software.amazon.awssdk.services.iot.model.ThrottlingException;
+import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
+import software.amazon.cloudformation.exceptions.CfnNotFoundException;
+import software.amazon.cloudformation.exceptions.CfnServiceInternalErrorException;
+import software.amazon.cloudformation.exceptions.CfnThrottlingException;
+import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
+import software.amazon.cloudformation.proxy.Logger;
+import software.amazon.cloudformation.proxy.ProgressEvent;
+import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ReadHandler extends BaseHandler {
+ private static final String OPERATION = "DescribeProvisioningTemplate";
+
+ private IotClient iotClient;
+
+ public ReadHandler() {
+ iotClient = ClientBuilder.getClient();
+ }
+
+ public ReadHandler(IotClient iotClient) {
+ this.iotClient = iotClient;
+ }
+
+ /**
+ * Convert the SDK's authorizer config to the type expected by CloudFormation
+ * @param certs The SDK AuthorizerConfig
+ * @return A converted AuthorizerConfig or null if none was present in the response
+ */
+ private static List getServerCertificates(List certs) {
+ if (certs != null) {
+ return certs.stream()
+ .map(cert -> com.amazonaws.iot.domainconfiguration.ServerCertificateSummary.builder()
+ .serverCertificateArn(cert.serverCertificateArn())
+ .serverCertificateStatus(cert.serverCertificateStatusAsString())
+ .serverCertificateStatusDetail(cert.serverCertificateStatusDetail())
+ .build())
+ .collect(Collectors.toList());
+ }
+ return null;
+ }
+
+ @Override
+ public ProgressEvent handleRequest(
+ final AmazonWebServicesClientProxy proxy,
+ final ResourceHandlerRequest request,
+ final CallbackContext callbackContext,
+ final Logger logger) {
+
+ final ResourceModel model = request.getDesiredResourceState();
+ final DescribeDomainConfigurationRequest domainRequest = DescribeDomainConfigurationRequest.builder()
+ .domainConfigurationName(model.getDomainConfigurationName())
+ .build();
+
+ try {
+ final DescribeDomainConfigurationResponse response = proxy.injectCredentialsAndInvokeV2(
+ domainRequest,
+ iotClient::describeDomainConfiguration);
+
+ return ProgressEvent.defaultSuccessHandler(ResourceModel.builder()
+ .arn(response.domainConfigurationArn())
+ .domainConfigurationName(response.domainConfigurationName())
+ .domainConfigurationStatus(response.domainConfigurationStatusAsString())
+ .domainName(response.domainName())
+ .domainType(response.domainTypeAsString())
+ .serviceType(response.serviceTypeAsString())
+ .authorizerConfig(ResourceUtil.getResourceModelAuthorizerConfig(response.authorizerConfig()))
+ .serverCertificates(getServerCertificates(response.serverCertificates()))
+ .build());
+
+ } catch (final InternalFailureException e) {
+ throw new CfnServiceInternalErrorException(OPERATION, e);
+ } catch (final InvalidRequestException e) {
+ throw new CfnInvalidRequestException(domainRequest.toString(), e);
+ } catch (final ResourceNotFoundException e) {
+ throw new CfnNotFoundException(ResourceModel.TYPE_NAME, domainRequest.domainConfigurationName());
+ } catch (final ThrottlingException e) {
+ throw new CfnThrottlingException(OPERATION, e);
+ }
+ }
+}
diff --git a/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/ResourceUtil.java b/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/ResourceUtil.java
new file mode 100644
index 0000000..8512aa8
--- /dev/null
+++ b/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/ResourceUtil.java
@@ -0,0 +1,41 @@
+package com.amazonaws.iot.domainconfiguration;
+
+import software.amazon.awssdk.services.iot.model.AuthorizerConfig;
+
+public final class ResourceUtil {
+
+ public static final int DELAY_CONSTANT = 35;
+ public static final int MAX_RETRIES = 3;
+
+ /**
+ * Convert the resource model's authorizer config to the type expected by the SDK
+ * @param model The resource model
+ * @return A converted AuthorizerConfig or null if none was present in the model
+ */
+ public static AuthorizerConfig getSdkAuthorizerConfig(ResourceModel model) {
+ final com.amazonaws.iot.domainconfiguration.AuthorizerConfig config = model.getAuthorizerConfig();
+ if (config != null) {
+ return AuthorizerConfig.builder()
+ .allowAuthorizerOverride(config.getAllowAuthorizerOverride())
+ .defaultAuthorizerName(config.getDefaultAuthorizerName())
+ .build();
+ }
+ return null;
+ }
+
+ /**
+ * Convert the SDK's authorizer config to the type expected by CloudFormation
+ * @param config The SDK AuthorizerConfig
+ * @return A converted AuthorizerConfig or null if none was present in the response
+ */
+ public static com.amazonaws.iot.domainconfiguration.AuthorizerConfig getResourceModelAuthorizerConfig(AuthorizerConfig config) {
+ if (config != null) {
+ return com.amazonaws.iot.domainconfiguration.AuthorizerConfig.builder()
+ .allowAuthorizerOverride(config.allowAuthorizerOverride())
+ .defaultAuthorizerName(config.defaultAuthorizerName())
+ .build();
+ }
+ return null;
+ }
+
+}
diff --git a/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/UpdateHandler.java b/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/UpdateHandler.java
new file mode 100644
index 0000000..56e06ae
--- /dev/null
+++ b/aws-iot-domainconfiguration/src/main/java/com/amazonaws/iot/domainconfiguration/UpdateHandler.java
@@ -0,0 +1,119 @@
+package com.amazonaws.iot.domainconfiguration;
+
+import org.apache.commons.lang3.StringUtils;
+import software.amazon.awssdk.services.iot.IotClient;
+import software.amazon.awssdk.services.iot.model.CertificateValidationException;
+import software.amazon.awssdk.services.iot.model.InternalFailureException;
+import software.amazon.awssdk.services.iot.model.InvalidRequestException;
+import software.amazon.awssdk.services.iot.model.ResourceNotFoundException;
+import software.amazon.awssdk.services.iot.model.ThrottlingException;
+import software.amazon.awssdk.services.iot.model.UpdateDomainConfigurationRequest;
+import software.amazon.awssdk.services.iot.model.UpdateDomainConfigurationResponse;
+import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
+import software.amazon.cloudformation.exceptions.CfnNotFoundException;
+import software.amazon.cloudformation.exceptions.CfnNotUpdatableException;
+import software.amazon.cloudformation.exceptions.CfnServiceInternalErrorException;
+import software.amazon.cloudformation.exceptions.CfnThrottlingException;
+import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
+import software.amazon.cloudformation.proxy.Logger;
+import software.amazon.cloudformation.proxy.ProgressEvent;
+import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
+
+import java.util.List;
+
+public class UpdateHandler extends BaseHandler {
+ private static final String OPERATION = "UpdateDomainConfiguration";
+
+ private IotClient iotClient;
+
+ public UpdateHandler() {
+ iotClient = ClientBuilder.getClient();
+ }
+
+ public UpdateHandler(IotClient iotClient) {
+ this.iotClient = iotClient;
+ }
+
+ private boolean areServerCertificatesUnchanged(List newModelCerts, List prevModelCerts) {
+ if (newModelCerts.size() != prevModelCerts.size()) return false;
+ return newModelCerts.containsAll(prevModelCerts);
+ }
+
+ private void validatePropertiesAreUpdatable(ResourceModel prevModel, ResourceModel newModel) {
+ if (!StringUtils.equals(newModel.getDomainConfigurationName(), prevModel.getDomainConfigurationName())) {
+ throw new CfnNotUpdatableException(ResourceModel.TYPE_NAME, "DomainConfigurationName");
+ }
+ if (!StringUtils.equals(newModel.getDomainName(), prevModel.getDomainName())) {
+ throw new CfnNotUpdatableException(ResourceModel.TYPE_NAME, "DomainName");
+ }
+ if (!StringUtils.equals(newModel.getServiceType(), prevModel.getServiceType())) {
+ throw new CfnNotUpdatableException(ResourceModel.TYPE_NAME, "ServiceType");
+ }
+ if (!StringUtils.equals(newModel.getValidationCertificateArn(), prevModel.getValidationCertificateArn())) {
+ throw new CfnNotUpdatableException(ResourceModel.TYPE_NAME, "ValidationCertificateArn");
+ }
+ if (!areServerCertificatesUnchanged(newModel.getServerCertificateArns(), prevModel.getServerCertificateArns())) {
+ throw new CfnNotUpdatableException(ResourceModel.TYPE_NAME, "ValidationCertificateArn");
+ }
+ }
+
+ @Override
+ public ProgressEvent handleRequest(
+ final AmazonWebServicesClientProxy proxy,
+ final ResourceHandlerRequest request,
+ final CallbackContext callbackContext,
+ final Logger logger) {
+
+ final ResourceModel model = request.getDesiredResourceState();
+
+ final ProgressEvent readResponse = (new ReadHandler(iotClient))
+ .handleRequest(proxy, request, CallbackContext.builder().build(), logger);
+
+ if (callbackContext != null && callbackContext.isCreateOrUpdateInProgress()) {
+ logger.log(String.format("%s [%s] updated successfully", ResourceModel.TYPE_NAME, model.getDomainConfigurationName()));
+ return readResponse;
+ }
+
+ final ResourceModel prevModel = request.getPreviousResourceState();
+
+ validatePropertiesAreUpdatable(prevModel, model);
+
+ // Determine if we need to set the removeAuthorizerConfig flag by comparing the original state with the
+ // desired state.
+ boolean removeAuthorizerConfig = false;
+ if (prevModel.getAuthorizerConfig() != null && model.getAuthorizerConfig() == null) {
+ removeAuthorizerConfig = true;
+ }
+
+ final UpdateDomainConfigurationRequest domainRequest = UpdateDomainConfigurationRequest.builder()
+ .domainConfigurationName(model.getDomainConfigurationName())
+ .authorizerConfig(ResourceUtil.getSdkAuthorizerConfig(model))
+ .domainConfigurationStatus(model.getDomainConfigurationStatus())
+ .removeAuthorizerConfig(removeAuthorizerConfig)
+ .build();
+
+ try {
+ UpdateDomainConfigurationResponse response = proxy.injectCredentialsAndInvokeV2(domainRequest,
+ iotClient::updateDomainConfiguration);
+ logger.log(String.format("%s [%s] updated. Waiting for %d seconds before returning success.",
+ ResourceModel.TYPE_NAME, model.getDomainConfigurationName(),ResourceUtil.DELAY_CONSTANT));
+
+ return ProgressEvent.defaultInProgressHandler(
+ CallbackContext.builder().createOrUpdateInProgress(true).build(),
+ ResourceUtil.DELAY_CONSTANT,
+ ResourceModel.builder().
+ domainConfigurationName(response.domainConfigurationName())
+ .arn(response.domainConfigurationArn())
+ .build());
+ } catch (final ResourceNotFoundException e) {
+ throw new CfnNotFoundException(ResourceModel.TYPE_NAME, model.getDomainConfigurationName());
+ } catch (final InvalidRequestException|CertificateValidationException e) {
+ throw new CfnInvalidRequestException(domainRequest.toString(), e);
+ } catch (final ThrottlingException e) {
+ throw new CfnThrottlingException(OPERATION, e);
+ } catch (final InternalFailureException e) {
+ throw new CfnServiceInternalErrorException(OPERATION, e);
+ }
+
+ }
+}
diff --git a/aws-iot-domainconfiguration/src/test/java/com/amazonaws/iot/domainconfiguration/CreateHandlerTest.java b/aws-iot-domainconfiguration/src/test/java/com/amazonaws/iot/domainconfiguration/CreateHandlerTest.java
new file mode 100644
index 0000000..7bc7bca
--- /dev/null
+++ b/aws-iot-domainconfiguration/src/test/java/com/amazonaws/iot/domainconfiguration/CreateHandlerTest.java
@@ -0,0 +1,211 @@
+package com.amazonaws.iot.domainconfiguration;
+
+import org.junit.jupiter.api.Assertions;
+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.MockitoAnnotations;
+import org.mockito.junit.jupiter.MockitoExtension;
+import software.amazon.awssdk.services.iot.model.DescribeDomainConfigurationRequest;
+import software.amazon.awssdk.services.iot.model.DomainConfigurationStatus;
+import software.amazon.awssdk.services.iot.model.InternalException;
+import software.amazon.awssdk.services.iot.model.InvalidRequestException;
+import software.amazon.awssdk.services.iot.model.LimitExceededException;
+import software.amazon.awssdk.services.iot.model.ResourceAlreadyExistsException;
+import software.amazon.awssdk.services.iot.model.ThrottlingException;
+import software.amazon.cloudformation.exceptions.CfnAlreadyExistsException;
+import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
+import software.amazon.cloudformation.exceptions.CfnServiceInternalErrorException;
+import software.amazon.cloudformation.exceptions.CfnServiceLimitExceededException;
+import software.amazon.cloudformation.exceptions.CfnThrottlingException;
+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 static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class CreateHandlerTest extends DomainConfigurationTestBase {
+ private CreateHandler handler;
+ private final ResourceModel expectedResponse = ResourceModel.builder()
+ .domainConfigurationName(DOMAIN_CONFIG_NAME)
+ .arn(DOMAIN_CONFIG_ARN)
+ .build();
+
+ @Mock
+ private AmazonWebServicesClientProxy proxy;
+
+ @Mock
+ private Logger logger;
+
+ @BeforeEach
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ handler = new CreateHandler();
+ }
+
+ @Test
+ public void handleRequest_SimpleCreateInProgress() {
+ final ResourceModel model = defaultModelBuilder()
+ .domainConfigurationStatus(null)
+ .build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+
+ when(proxy.injectCredentialsAndInvokeV2(any(), any()))
+ .thenReturn(DEFAULT_CREATE_DOMAIN_CONFIGURATION_RESPONSE);
+
+ final ProgressEvent response
+ = handler.handleRequest(proxy, request, null, logger);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getStatus()).isEqualTo(OperationStatus.IN_PROGRESS);
+ assertThat(response.getCallbackContext()).isNotNull();
+ assertThat(response.getCallbackDelaySeconds()).isEqualTo(ResourceUtil.DELAY_CONSTANT);
+ assertThat(response.getResourceModel()).isEqualTo(DEFAULT_RESOURCE_MODEL);
+ assertThat(response.getResourceModels()).isNull();
+ assertThat(response.getMessage()).isNull();
+ assertThat(response.getErrorCode()).isNull();
+ assertThat(response.getResourceModel().getDomainConfigurationName()).isEqualTo(DOMAIN_CONFIG_NAME);
+
+ verify(proxy, times(1)).injectCredentialsAndInvokeV2(any(), any());
+ }
+
+ @Test
+ public void handleRequest_SimpleCreateSuccess() {
+ final ResourceModel model = defaultModelBuilder()
+ .domainConfigurationStatus(null)
+ .build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+
+ when(proxy.injectCredentialsAndInvokeV2(any(DescribeDomainConfigurationRequest.class), any()))
+ .thenReturn(DEFAULT_DESCRIBE_DOMAIN_CONFIGURATION_RESPONSE);
+
+ final ProgressEvent response = handler.handleRequest(proxy, request,
+ CallbackContext.builder().createOrUpdateInProgress(true).retryCount(1).build(), logger);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
+ assertThat(response.getCallbackContext()).isNull();
+ assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
+ assertThat(response.getResourceModel()).isNotNull();
+ assertThat(response.getResourceModels()).isNull();
+ assertThat(response.getMessage()).isNull();
+ assertThat(response.getErrorCode()).isNull();
+ assertThat(response.getResourceModel().getDomainConfigurationName()).isEqualTo(DOMAIN_CONFIG_NAME);
+
+ verify(proxy, times(1)).injectCredentialsAndInvokeV2(any(), any());
+ }
+
+ @Test
+ public void handleRequest_InvokesTwice() {
+ final ResourceModel model = defaultModelBuilder()
+ .domainConfigurationStatus(DomainConfigurationStatus.ENABLED.toString())
+ .build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+ when(proxy.injectCredentialsAndInvokeV2(any(), any()))
+ .thenReturn(DEFAULT_CREATE_DOMAIN_CONFIGURATION_RESPONSE);
+
+ final ProgressEvent response
+ = handler.handleRequest(proxy, request, null, logger);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getStatus()).isEqualTo(OperationStatus.IN_PROGRESS);
+ assertThat(response.getCallbackContext()).isNotNull();
+ assertThat(response.getCallbackDelaySeconds()).isEqualTo(ResourceUtil.DELAY_CONSTANT);
+ assertThat(response.getResourceModel()).isEqualTo(DEFAULT_RESOURCE_MODEL);
+ assertThat(response.getResourceModels()).isNull();
+ assertThat(response.getMessage()).isNull();
+ assertThat(response.getErrorCode()).isNull();
+ assertThat(response.getResourceModel().getDomainConfigurationName()).isEqualTo(DOMAIN_CONFIG_NAME);
+
+ verify(proxy, times(2)).injectCredentialsAndInvokeV2(any(), any());
+ }
+
+ @Test
+ public void handleRequest_GeneratesName() {
+ final ResourceModel model = defaultModelBuilder().domainConfigurationName(null).build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+ when(proxy.injectCredentialsAndInvokeV2(any(), any()))
+ .thenReturn(GENERATED_CREATE_DOMAIN_CONFIGURATION_RESPONSE);
+
+ final ProgressEvent response
+ = handler.handleRequest(proxy, request, null, logger);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getStatus()).isEqualTo(OperationStatus.IN_PROGRESS);
+ assertThat(response.getResourceModel().getDomainConfigurationName()).isNotNull();
+ assertThat(response.getResourceModel().getDomainConfigurationName()).isNotEqualTo(DOMAIN_CONFIG_NAME);
+ }
+
+ @Test
+ public void handleRequest_ResourceConflictFails() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+ doThrow(ResourceAlreadyExistsException.builder().resourceId(DOMAIN_CONFIG_NAME).build())
+ .when(proxy)
+ .injectCredentialsAndInvokeV2(any(), any());
+
+ Assertions.assertThrows(CfnAlreadyExistsException.class, () -> handler.handleRequest(proxy, request, null, logger));
+ }
+
+ @Test
+ public void handleRequest_InvalidRequestFails() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+ doThrow(InvalidRequestException.builder().build())
+ .when(proxy)
+ .injectCredentialsAndInvokeV2(any(), any());
+
+ Assertions.assertThrows(CfnInvalidRequestException.class, () -> handler.handleRequest(proxy, request, null, logger));
+ }
+
+ @Test
+ public void handleRequest_LimitExceededFails() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+ doThrow(LimitExceededException.builder().build())
+ .when(proxy)
+ .injectCredentialsAndInvokeV2(any(), any());
+
+ Assertions.assertThrows(CfnServiceLimitExceededException.class, () -> handler.handleRequest(proxy, request, null, logger));
+ }
+
+ @Test
+ public void handleRequest_InternalExceptionFails() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+ doThrow(InternalException.builder().build())
+ .when(proxy)
+ .injectCredentialsAndInvokeV2(any(), any());
+
+ Assertions.assertThrows(CfnServiceInternalErrorException.class, () -> handler.handleRequest(proxy, request, null, logger));
+ }
+
+ @Test
+ public void handleRequest_ThrottlingFails() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+ doThrow(ThrottlingException.builder().build())
+ .when(proxy)
+ .injectCredentialsAndInvokeV2(any(), any());
+
+ Assertions.assertThrows(CfnThrottlingException.class, () -> handler.handleRequest(proxy, request, null, logger));
+ }
+}
diff --git a/aws-iot-domainconfiguration/src/test/java/com/amazonaws/iot/domainconfiguration/DeleteHandlerTest.java b/aws-iot-domainconfiguration/src/test/java/com/amazonaws/iot/domainconfiguration/DeleteHandlerTest.java
new file mode 100644
index 0000000..d66e11a
--- /dev/null
+++ b/aws-iot-domainconfiguration/src/test/java/com/amazonaws/iot/domainconfiguration/DeleteHandlerTest.java
@@ -0,0 +1,182 @@
+package com.amazonaws.iot.domainconfiguration;
+
+import org.junit.jupiter.api.Assertions;
+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.MockitoAnnotations;
+import org.mockito.junit.jupiter.MockitoExtension;
+import software.amazon.awssdk.services.iot.model.InternalFailureException;
+import software.amazon.awssdk.services.iot.model.InvalidRequestException;
+import software.amazon.awssdk.services.iot.model.ResourceNotFoundException;
+import software.amazon.awssdk.services.iot.model.ServiceUnavailableException;
+import software.amazon.awssdk.services.iot.model.ThrottlingException;
+import software.amazon.awssdk.services.iot.model.UnauthorizedException;
+import software.amazon.awssdk.services.iot.model.UpdateDomainConfigurationRequest;
+import software.amazon.cloudformation.exceptions.CfnAccessDeniedException;
+import software.amazon.cloudformation.exceptions.CfnGeneralServiceException;
+import software.amazon.cloudformation.exceptions.CfnInternalFailureException;
+import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
+import software.amazon.cloudformation.exceptions.CfnNotFoundException;
+import software.amazon.cloudformation.exceptions.CfnThrottlingException;
+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 static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class DeleteHandlerTest extends DomainConfigurationTestBase {
+ private DeleteHandler handler;
+
+ @Mock
+ private AmazonWebServicesClientProxy proxy;
+
+ @Mock
+ private Logger logger;
+
+ @BeforeEach
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ handler = new DeleteHandler();
+ }
+
+ @Test
+ public void handleRequest_SimpleSuccess() {
+ final ResourceModel model = defaultModelBuilder().domainConfigurationStatus("DISABLED").build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+ final ProgressEvent response
+ = handler.handleRequest(proxy, request, null, logger);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
+ assertThat(response.getCallbackContext()).isNull();
+ assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
+ assertThat(response.getResourceModel()).isNull();
+ assertThat(response.getResourceModels()).isNull();
+ assertThat(response.getMessage()).isNull();
+ assertThat(response.getErrorCode()).isNull();
+ }
+
+ @Test
+ public void handleRequest_DisableBeforeDelete() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+ when(proxy.injectCredentialsAndInvokeV2(any(), any()))
+ .thenReturn(DEFAULT_UPDATE_DOMAIN_CONFIGURATION_RESPONSE);
+
+ final ProgressEvent response
+ = handler.handleRequest(proxy, request, null, logger);
+
+ verify(proxy, times(1)).injectCredentialsAndInvokeV2(any(UpdateDomainConfigurationRequest.class), any());
+
+ assertThat(response).isNotNull();
+ assertThat(response.getStatus()).isEqualTo(OperationStatus.IN_PROGRESS);
+ assertThat(response.getCallbackContext()).isNotNull();
+ assertThat(response.getCallbackDelaySeconds()).isEqualTo(ResourceUtil.DELAY_CONSTANT);
+ assertThat(response.getResourceModel()).isEqualTo(DEFAULT_RESOURCE_MODEL);
+ assertThat(response.getResourceModels()).isNull();
+ assertThat(response.getMessage()).isNull();
+ assertThat(response.getErrorCode()).isNull();
+ }
+
+ @Test
+ public void handleRequest_InProgressDeleteSucceeds() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+ final ProgressEvent response = handler.handleRequest(proxy, request,
+ CallbackContext.builder().domainConfigurationDisabled(true).build(), logger);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
+ assertThat(response.getCallbackContext()).isNull();
+ assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
+ assertThat(response.getResourceModel()).isNull();
+ assertThat(response.getResourceModels()).isNull();
+ assertThat(response.getMessage()).isNull();
+ assertThat(response.getErrorCode()).isNull();
+ }
+
+ @Test
+ public void handleRequest_ResourceNotFoundFails() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+ doThrow(ResourceNotFoundException.builder().build())
+ .when(proxy)
+ .injectCredentialsAndInvokeV2(any(), any());
+
+ Assertions.assertThrows(CfnNotFoundException.class, () -> handler.handleRequest(proxy, request, null, logger));
+ }
+
+ @Test
+ public void handleRequest_CfnInternalFailureException() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+ doThrow(InternalFailureException.builder().build())
+ .when(proxy)
+ .injectCredentialsAndInvokeV2(any(), any());
+
+ Assertions.assertThrows(CfnInternalFailureException.class, () -> handler.handleRequest(proxy, request, null, logger));
+ }
+
+ @Test
+ public void handleRequest_CfnInvalidRequestException() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+ doThrow(InvalidRequestException.builder().build())
+ .when(proxy)
+ .injectCredentialsAndInvokeV2(any(), any());
+
+ Assertions.assertThrows(CfnInvalidRequestException.class, () -> handler.handleRequest(proxy, request, null, logger));
+ }
+
+ @Test
+ public void handleRequest_CfnGeneralServiceExceptionUnavailable() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+ doThrow(ServiceUnavailableException.builder().build())
+ .when(proxy)
+ .injectCredentialsAndInvokeV2(any(), any());
+
+ Assertions.assertThrows(CfnGeneralServiceException.class, () -> handler.handleRequest(proxy, request, null, logger));
+ }
+
+ @Test
+ public void handleRequest_CfnThrottlingException() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+ doThrow(ThrottlingException.builder().build())
+ .when(proxy)
+ .injectCredentialsAndInvokeV2(any(), any());
+
+ Assertions.assertThrows(CfnThrottlingException.class, () -> handler.handleRequest(proxy, request, null, logger));
+ }
+
+ @Test
+ public void handleRequest_CfnAccessDeniedException() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+ doThrow(UnauthorizedException.builder().build())
+ .when(proxy)
+ .injectCredentialsAndInvokeV2(any(), any());
+
+ Assertions.assertThrows(CfnAccessDeniedException.class, () -> handler.handleRequest(proxy, request, null, logger));
+ }
+}
diff --git a/aws-iot-domainconfiguration/src/test/java/com/amazonaws/iot/domainconfiguration/DomainConfigurationTestBase.java b/aws-iot-domainconfiguration/src/test/java/com/amazonaws/iot/domainconfiguration/DomainConfigurationTestBase.java
new file mode 100644
index 0000000..8a64c1f
--- /dev/null
+++ b/aws-iot-domainconfiguration/src/test/java/com/amazonaws/iot/domainconfiguration/DomainConfigurationTestBase.java
@@ -0,0 +1,107 @@
+package com.amazonaws.iot.domainconfiguration;
+
+import org.junit.jupiter.api.BeforeEach;
+import software.amazon.awssdk.services.iot.IotClient;
+import software.amazon.awssdk.services.iot.model.AuthorizerConfig;
+import software.amazon.awssdk.services.iot.model.CreateDomainConfigurationResponse;
+import software.amazon.awssdk.services.iot.model.DescribeDomainConfigurationResponse;
+import software.amazon.awssdk.services.iot.model.DomainConfigurationStatus;
+import software.amazon.awssdk.services.iot.model.DomainType;
+import software.amazon.awssdk.services.iot.model.ServerCertificateSummary;
+import software.amazon.awssdk.services.iot.model.ServiceType;
+import software.amazon.awssdk.services.iot.model.UpdateDomainConfigurationResponse;
+import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
+
+import java.util.Collections;
+
+import static org.mockito.Mockito.mock;
+
+public class DomainConfigurationTestBase {
+ protected final static String DOMAIN_CONFIG_NAME = "SampleDomainConfig";
+ protected final static String DOMAIN_CONFIG_NAME_GENERATED = "GeneratedDomainConfig";
+ protected final static String DOMAIN_CONFIG_NAME_UPDATED = "SampleDomainConfigUpdated";
+ protected final static String DOMAIN_CONFIG_ARN = "arn:aws:iot:us-east-1:0123456789:domainconfiguration/SampleDomain/2e8nm";
+ protected final static String DOMAIN_NAME = "example.com";
+
+ protected final static String DEFAULT_AUTHORIZER_NAME = "authorizer";
+ protected final static AuthorizerConfig AUTHORIZER_CONFIG = AuthorizerConfig.builder()
+ .defaultAuthorizerName(DEFAULT_AUTHORIZER_NAME)
+ .allowAuthorizerOverride(false)
+ .build();
+
+ protected final static String SERVER_CERT_ARN = "arn:aws:iam::01234567890:certificate/Something";
+ protected final static String CERT_STATUS = "VALID";
+ protected final static String CERT_DETAIL = "Details";
+ protected final static ServerCertificateSummary SERVER_CERTIFICATE_SUMMARY = ServerCertificateSummary.builder()
+ .serverCertificateArn(SERVER_CERT_ARN)
+ .serverCertificateStatus(CERT_STATUS)
+ .serverCertificateStatusDetail(CERT_DETAIL)
+ .build();
+
+ protected final static DescribeDomainConfigurationResponse DEFAULT_DESCRIBE_DOMAIN_CONFIGURATION_RESPONSE = DescribeDomainConfigurationResponse.builder()
+ .domainConfigurationName(DOMAIN_CONFIG_NAME)
+ .domainConfigurationArn(DOMAIN_CONFIG_ARN)
+ .domainName(DOMAIN_NAME)
+ .authorizerConfig(AUTHORIZER_CONFIG)
+ .domainConfigurationStatus("ENABLED")
+ .serverCertificates(SERVER_CERTIFICATE_SUMMARY)
+ .domainType(DomainType.ENDPOINT)
+ .serviceType(ServiceType.CREDENTIAL_PROVIDER)
+ .build();
+
+ protected final static CreateDomainConfigurationResponse DEFAULT_CREATE_DOMAIN_CONFIGURATION_RESPONSE = CreateDomainConfigurationResponse.builder()
+ .domainConfigurationName(DOMAIN_CONFIG_NAME)
+ .domainConfigurationArn(DOMAIN_CONFIG_ARN)
+ .build();
+
+ protected final static CreateDomainConfigurationResponse GENERATED_CREATE_DOMAIN_CONFIGURATION_RESPONSE = CreateDomainConfigurationResponse.builder()
+ .domainConfigurationName(DOMAIN_CONFIG_NAME_GENERATED)
+ .domainConfigurationArn(DOMAIN_CONFIG_ARN)
+ .build();
+
+ protected final static ResourceModel DEFAULT_RESOURCE_MODEL = ResourceModel.builder()
+ .domainConfigurationName(DOMAIN_CONFIG_NAME)
+ .arn(DOMAIN_CONFIG_ARN)
+ .build();
+
+ protected final static UpdateDomainConfigurationResponse DEFAULT_UPDATE_DOMAIN_CONFIGURATION_RESPONSE = UpdateDomainConfigurationResponse.builder()
+ .domainConfigurationName(DOMAIN_CONFIG_NAME)
+ .domainConfigurationArn(DOMAIN_CONFIG_ARN)
+ .build();
+
+ protected final static String REQUEST_TOKEN = "RequestToken";
+ protected final static String LOGICAL_ID = "LogicalResourceId";
+
+ protected IotClient iotClient;
+
+ @BeforeEach
+ public void setup() {
+ iotClient = mock(IotClient.class);
+ }
+
+ protected ResourceModel.ResourceModelBuilder defaultModelBuilder() {
+ return ResourceModel.builder()
+ .domainName(DOMAIN_NAME)
+ .domainConfigurationName(DOMAIN_CONFIG_NAME)
+ .arn(DOMAIN_CONFIG_ARN)
+ .serverCertificates(Collections.singletonList(com.amazonaws.iot.domainconfiguration.ServerCertificateSummary.builder()
+ .serverCertificateArn(SERVER_CERT_ARN)
+ .serverCertificateStatus(CERT_STATUS)
+ .serverCertificateStatusDetail(CERT_DETAIL)
+ .build()))
+ .authorizerConfig(com.amazonaws.iot.domainconfiguration.AuthorizerConfig.builder()
+ .allowAuthorizerOverride(false)
+ .defaultAuthorizerName(DEFAULT_AUTHORIZER_NAME)
+ .build())
+ .serviceType(ServiceType.CREDENTIAL_PROVIDER.toString())
+ .domainType(DomainType.ENDPOINT.toString())
+ .domainConfigurationStatus(DomainConfigurationStatus.ENABLED.toString());
+ }
+
+ protected ResourceHandlerRequest.ResourceHandlerRequestBuilder defaultRequestBuilder(ResourceModel model) {
+ return ResourceHandlerRequest.builder()
+ .clientRequestToken(REQUEST_TOKEN)
+ .logicalResourceIdentifier(LOGICAL_ID)
+ .desiredResourceState(model);
+ }
+}
diff --git a/aws-iot-domainconfiguration/src/test/java/com/amazonaws/iot/domainconfiguration/ListHandlerTest.java b/aws-iot-domainconfiguration/src/test/java/com/amazonaws/iot/domainconfiguration/ListHandlerTest.java
new file mode 100644
index 0000000..c33e86f
--- /dev/null
+++ b/aws-iot-domainconfiguration/src/test/java/com/amazonaws/iot/domainconfiguration/ListHandlerTest.java
@@ -0,0 +1,145 @@
+package com.amazonaws.iot.domainconfiguration;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.jupiter.MockitoExtension;
+import software.amazon.awssdk.services.iot.model.DomainConfigurationSummary;
+import software.amazon.awssdk.services.iot.model.InternalFailureException;
+import software.amazon.awssdk.services.iot.model.InvalidRequestException;
+import software.amazon.awssdk.services.iot.model.ListDomainConfigurationsRequest;
+import software.amazon.awssdk.services.iot.model.ListDomainConfigurationsResponse;
+import software.amazon.awssdk.services.iot.model.ThrottlingException;
+import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
+import software.amazon.cloudformation.exceptions.CfnServiceInternalErrorException;
+import software.amazon.cloudformation.exceptions.CfnThrottlingException;
+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 java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class ListHandlerTest extends DomainConfigurationTestBase {
+ private ListHandler handler;
+
+ @Mock
+ private AmazonWebServicesClientProxy proxy;
+
+ @Mock
+ private Logger logger;
+
+ @Captor
+ ArgumentCaptor domainRequestCaptor;
+
+ @BeforeEach
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ handler = new ListHandler();
+ }
+
+ @Test
+ public void handleRequest_SimpleSuccess() {
+ final ResourceHandlerRequest request = defaultRequestBuilder(null).build();
+
+ when(proxy.injectCredentialsAndInvokeV2(any(), any())).thenReturn(ListDomainConfigurationsResponse.builder()
+ .nextMarker(null)
+ .domainConfigurations(DomainConfigurationSummary.builder()
+ .domainConfigurationArn(DOMAIN_CONFIG_ARN)
+ .domainConfigurationName(DOMAIN_CONFIG_NAME)
+ .build())
+ .build());
+
+ final ProgressEvent response =
+ handler.handleRequest(proxy, request, null, logger);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
+ assertThat(response.getCallbackContext()).isNull();
+ assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
+ assertThat(response.getResourceModel()).isNull();
+ assertThat(response.getResourceModels()).isNotNull();
+ assertThat(response.getMessage()).isNull();
+ assertThat(response.getErrorCode()).isNull();
+
+ List models = response.getResourceModels();
+ assertThat(models.size()).isEqualTo(1);
+
+ ResourceModel model = models.get(0);
+ assertThat(model.getDomainConfigurationName()).isEqualTo(DOMAIN_CONFIG_NAME);
+ assertThat(model.getArn()).isEqualTo(DOMAIN_CONFIG_ARN);
+ }
+
+ @Test
+ public void handleRequest_PassedNextToken() {
+ final String nextToken = "NEXT";
+ final ResourceHandlerRequest request = defaultRequestBuilder(null)
+ .nextToken(nextToken)
+ .build();
+
+ when(proxy.injectCredentialsAndInvokeV2(any(), any())).thenReturn(ListDomainConfigurationsResponse.builder()
+ .nextMarker(null)
+ .domainConfigurations(DomainConfigurationSummary.builder()
+ .domainConfigurationArn(DOMAIN_CONFIG_ARN)
+ .domainConfigurationName(DOMAIN_NAME)
+ .build())
+ .build());
+
+ handler.handleRequest(proxy, request, null, logger);
+
+ verify(proxy).injectCredentialsAndInvokeV2(domainRequestCaptor.capture(), any());
+ assertThat(domainRequestCaptor.getValue().marker()).isEqualTo(nextToken);
+ }
+
+ @Test
+ public void handleRequest_InternalFailure() {
+ final ResourceHandlerRequest request = defaultRequestBuilder(null).build();
+
+ doThrow(InternalFailureException.builder().build())
+ .when(proxy)
+ .injectCredentialsAndInvokeV2(any(), any());
+
+ Assertions.assertThrows(CfnServiceInternalErrorException.class, () -> {
+ handler.handleRequest(proxy, request, null, logger);
+ });
+ }
+
+ @Test
+ public void handleRequest_InvalidRequest() {
+ final ResourceHandlerRequest request = defaultRequestBuilder(null).build();
+
+ doThrow(InvalidRequestException.builder().build())
+ .when(proxy)
+ .injectCredentialsAndInvokeV2(any(), any());
+
+ Assertions.assertThrows(CfnInvalidRequestException.class, () -> {
+ handler.handleRequest(proxy, request, null, logger);
+ });
+ }
+
+ @Test
+ public void handleRequest_ThrottlingExceptio() {
+ final ResourceHandlerRequest request = defaultRequestBuilder(null).build();
+
+ doThrow(ThrottlingException.builder().build())
+ .when(proxy)
+ .injectCredentialsAndInvokeV2(any(), any());
+
+ Assertions.assertThrows(CfnThrottlingException.class, () -> {
+ handler.handleRequest(proxy, request, null, logger);
+ });
+ }
+}
diff --git a/aws-iot-domainconfiguration/src/test/java/com/amazonaws/iot/domainconfiguration/ReadHandlerTest.java b/aws-iot-domainconfiguration/src/test/java/com/amazonaws/iot/domainconfiguration/ReadHandlerTest.java
new file mode 100644
index 0000000..86b0f9c
--- /dev/null
+++ b/aws-iot-domainconfiguration/src/test/java/com/amazonaws/iot/domainconfiguration/ReadHandlerTest.java
@@ -0,0 +1,120 @@
+package com.amazonaws.iot.domainconfiguration;
+
+import org.junit.jupiter.api.Assertions;
+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.MockitoAnnotations;
+import org.mockito.junit.jupiter.MockitoExtension;
+import software.amazon.awssdk.services.iot.model.DescribeDomainConfigurationResponse;
+import software.amazon.awssdk.services.iot.model.DomainType;
+import software.amazon.awssdk.services.iot.model.InternalFailureException;
+import software.amazon.awssdk.services.iot.model.InvalidRequestException;
+import software.amazon.awssdk.services.iot.model.ResourceNotFoundException;
+import software.amazon.awssdk.services.iot.model.ServiceType;
+import software.amazon.awssdk.services.iot.model.ThrottlingException;
+import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
+import software.amazon.cloudformation.exceptions.CfnNotFoundException;
+import software.amazon.cloudformation.exceptions.CfnServiceInternalErrorException;
+import software.amazon.cloudformation.exceptions.CfnThrottlingException;
+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 static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class ReadHandlerTest extends DomainConfigurationTestBase {
+ private ReadHandler handler;
+
+ @Mock
+ private AmazonWebServicesClientProxy proxy;
+
+ @Mock
+ private Logger logger;
+
+ @BeforeEach
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ handler = new ReadHandler();
+ }
+
+ @Test
+ public void handleRequest_SimpleSuccess() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+ when(proxy.injectCredentialsAndInvokeV2(any(), any())).thenReturn(DescribeDomainConfigurationResponse.builder()
+ .domainConfigurationName(DOMAIN_CONFIG_NAME)
+ .domainConfigurationArn(DOMAIN_CONFIG_ARN)
+ .domainName(DOMAIN_NAME)
+ .authorizerConfig(AUTHORIZER_CONFIG)
+ .domainConfigurationStatus("ENABLED")
+ .serverCertificates(SERVER_CERTIFICATE_SUMMARY)
+ .domainType(DomainType.ENDPOINT)
+ .serviceType(ServiceType.CREDENTIAL_PROVIDER)
+ .build());
+
+ final ProgressEvent response
+ = handler.handleRequest(proxy, request, null, logger);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
+ assertThat(response.getCallbackContext()).isNull();
+ assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
+ assertThat(response.getResourceModel()).isEqualTo(request.getDesiredResourceState());
+ assertThat(response.getResourceModels()).isNull();
+ assertThat(response.getMessage()).isNull();
+ assertThat(response.getErrorCode()).isNull();
+ }
+
+ @Test
+ public void handleRequest_InternalFailure() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+ doThrow(InternalFailureException.builder().build()).when(proxy).injectCredentialsAndInvokeV2(any(), any());
+
+ Assertions.assertThrows(CfnServiceInternalErrorException.class, () ->
+ handler.handleRequest(proxy, request, null, logger));
+ }
+
+ @Test
+ public void handleRequest_InvalidRequest() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+ doThrow(InvalidRequestException.builder().build()).when(proxy).injectCredentialsAndInvokeV2(any(), any());
+
+ Assertions.assertThrows(CfnInvalidRequestException.class, () ->
+ handler.handleRequest(proxy, request, null, logger));
+ }
+
+ @Test
+ public void handleRequest_ResourceNotFound() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+ doThrow(ResourceNotFoundException.builder().build()).when(proxy).injectCredentialsAndInvokeV2(any(), any());
+
+ Assertions.assertThrows(CfnNotFoundException.class, () ->
+ handler.handleRequest(proxy, request, null, logger));
+ }
+
+ @Test
+ public void handleRequest_ThrottlingException() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).build();
+
+ doThrow(ThrottlingException.builder().build()).when(proxy).injectCredentialsAndInvokeV2(any(), any());
+
+ Assertions.assertThrows(CfnThrottlingException.class, () ->
+ handler.handleRequest(proxy, request, null, logger));
+ }
+}
diff --git a/aws-iot-domainconfiguration/src/test/java/com/amazonaws/iot/domainconfiguration/UpdateHandlerTest.java b/aws-iot-domainconfiguration/src/test/java/com/amazonaws/iot/domainconfiguration/UpdateHandlerTest.java
new file mode 100644
index 0000000..fca5018
--- /dev/null
+++ b/aws-iot-domainconfiguration/src/test/java/com/amazonaws/iot/domainconfiguration/UpdateHandlerTest.java
@@ -0,0 +1,194 @@
+package com.amazonaws.iot.domainconfiguration;
+
+import org.junit.jupiter.api.Assertions;
+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.MockitoAnnotations;
+import org.mockito.junit.jupiter.MockitoExtension;
+import software.amazon.awssdk.services.iot.model.CertificateValidationException;
+import software.amazon.awssdk.services.iot.model.DescribeDomainConfigurationRequest;
+import software.amazon.awssdk.services.iot.model.DomainConfigurationStatus;
+import software.amazon.awssdk.services.iot.model.InternalFailureException;
+import software.amazon.awssdk.services.iot.model.InvalidRequestException;
+import software.amazon.awssdk.services.iot.model.ResourceNotFoundException;
+import software.amazon.awssdk.services.iot.model.ThrottlingException;
+import software.amazon.awssdk.services.iot.model.UpdateDomainConfigurationRequest;
+import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
+import software.amazon.cloudformation.exceptions.CfnNotFoundException;
+import software.amazon.cloudformation.exceptions.CfnNotUpdatableException;
+import software.amazon.cloudformation.exceptions.CfnServiceInternalErrorException;
+import software.amazon.cloudformation.exceptions.CfnThrottlingException;
+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 static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class UpdateHandlerTest extends DomainConfigurationTestBase {
+ private UpdateHandler handler;
+
+ @Mock
+ private AmazonWebServicesClientProxy proxy;
+
+ @Mock
+ private Logger logger;
+
+ @BeforeEach
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ handler = new UpdateHandler();
+ }
+
+ @Test
+ public void handleRequest_SimpleInProgress() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceModel prevModel = defaultModelBuilder().domainConfigurationStatus(DomainConfigurationStatus.DISABLED.toString()).build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).previousResourceState(prevModel).build();
+
+ when(proxy.injectCredentialsAndInvokeV2(any(), any())).thenAnswer(invocationOnMock -> {
+ if (invocationOnMock.getArguments()[0] instanceof UpdateDomainConfigurationRequest)
+ return DEFAULT_UPDATE_DOMAIN_CONFIGURATION_RESPONSE;
+ else if (invocationOnMock.getArguments()[0] instanceof DescribeDomainConfigurationRequest)
+ return DEFAULT_DESCRIBE_DOMAIN_CONFIGURATION_RESPONSE;
+ return null;
+ });
+
+ final ProgressEvent response
+ = handler.handleRequest(proxy, request, null, logger);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getStatus()).isEqualTo(OperationStatus.IN_PROGRESS);
+ assertThat(response.getCallbackContext()).isNotNull();
+ assertThat(response.getCallbackDelaySeconds()).isEqualTo(ResourceUtil.DELAY_CONSTANT);
+ assertThat(response.getResourceModel()).isEqualTo(DEFAULT_RESOURCE_MODEL);
+ assertThat(response.getResourceModels()).isNull();
+ assertThat(response.getMessage()).isNull();
+ assertThat(response.getErrorCode()).isNull();
+ }
+
+ @Test
+ public void handleRequest_SimpleSuccess() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceModel prevModel = defaultModelBuilder().domainConfigurationStatus(DomainConfigurationStatus.DISABLED.toString()).build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).previousResourceState(prevModel).build();
+
+ when(proxy.injectCredentialsAndInvokeV2(any(DescribeDomainConfigurationRequest.class), any()))
+ .thenReturn(DEFAULT_DESCRIBE_DOMAIN_CONFIGURATION_RESPONSE);
+
+ final ProgressEvent response = handler.handleRequest(proxy, request,
+ CallbackContext.builder().createOrUpdateInProgress(true).build(), 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 handleRequest_ResourceNotUpdatable() {
+ final ResourceModel model = defaultModelBuilder().domainConfigurationName(DOMAIN_CONFIG_NAME_UPDATED).build();
+ final ResourceModel prevModel = defaultModelBuilder().build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).previousResourceState(prevModel).build();
+
+ when(proxy.injectCredentialsAndInvokeV2(any(DescribeDomainConfigurationRequest.class), any()))
+ .thenReturn(DEFAULT_DESCRIBE_DOMAIN_CONFIGURATION_RESPONSE);
+
+
+
+ Assertions.assertThrows(CfnNotUpdatableException.class, () -> handler.handleRequest(proxy, request, null, logger));
+ }
+
+ @Test
+ public void handleRequest_ResourceNotFound() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceModel prevModel = defaultModelBuilder().domainConfigurationStatus(DomainConfigurationStatus.DISABLED.toString()).build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).previousResourceState(prevModel).build();
+
+ doThrow(ResourceNotFoundException.builder().build())
+ .when(proxy)
+ .injectCredentialsAndInvokeV2(any(), any());
+
+ Assertions.assertThrows(CfnNotFoundException.class, () -> handler.handleRequest(proxy, request, null, logger));
+ }
+
+ @Test
+ public void handleRequest_InvalidRequest() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceModel prevModel = defaultModelBuilder().domainConfigurationStatus(DomainConfigurationStatus.DISABLED.toString()).build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).previousResourceState(prevModel).build();
+
+ when(proxy.injectCredentialsAndInvokeV2(any(), any())).thenAnswer(invocationOnMock -> {
+ if (invocationOnMock.getArguments()[0] instanceof UpdateDomainConfigurationRequest)
+ throw InvalidRequestException.builder().build();
+ else if (invocationOnMock.getArguments()[0] instanceof DescribeDomainConfigurationRequest)
+ return DEFAULT_DESCRIBE_DOMAIN_CONFIGURATION_RESPONSE;
+ return null;
+ });
+
+ Assertions.assertThrows(CfnInvalidRequestException.class, () -> handler.handleRequest(proxy, request, null, logger));
+ }
+
+ @Test
+ public void handleRequest_CertificateValidation() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceModel prevModel = defaultModelBuilder().domainConfigurationStatus(DomainConfigurationStatus.DISABLED.toString()).build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).previousResourceState(prevModel).build();
+
+ when(proxy.injectCredentialsAndInvokeV2(any(), any())).thenAnswer(invocationOnMock -> {
+ if (invocationOnMock.getArguments()[0] instanceof UpdateDomainConfigurationRequest)
+ throw CertificateValidationException.builder().build();
+ else if (invocationOnMock.getArguments()[0] instanceof DescribeDomainConfigurationRequest)
+ return DEFAULT_DESCRIBE_DOMAIN_CONFIGURATION_RESPONSE;
+ return null;
+ });
+
+ Assertions.assertThrows(CfnInvalidRequestException.class, () -> handler.handleRequest(proxy, request, null, logger));
+ }
+
+ @Test
+ public void handleRequest_Throttling() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceModel prevModel = defaultModelBuilder().domainConfigurationStatus(DomainConfigurationStatus.DISABLED.toString()).build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).previousResourceState(prevModel).build();
+
+ when(proxy.injectCredentialsAndInvokeV2(any(), any())).thenAnswer(invocationOnMock -> {
+ if (invocationOnMock.getArguments()[0] instanceof UpdateDomainConfigurationRequest)
+ throw ThrottlingException.builder().build();
+ else if (invocationOnMock.getArguments()[0] instanceof DescribeDomainConfigurationRequest)
+ return DEFAULT_DESCRIBE_DOMAIN_CONFIGURATION_RESPONSE;
+ return null;
+ });
+
+ Assertions.assertThrows(CfnThrottlingException.class, () -> handler.handleRequest(proxy, request, null, logger));
+ }
+
+ @Test
+ public void handleRequest_InternalFailure() {
+ final ResourceModel model = defaultModelBuilder().build();
+ final ResourceModel prevModel = defaultModelBuilder().domainConfigurationStatus(DomainConfigurationStatus.DISABLED.toString()).build();
+ final ResourceHandlerRequest request = defaultRequestBuilder(model).previousResourceState(prevModel).build();
+
+ when(proxy.injectCredentialsAndInvokeV2(any(), any())).thenAnswer(invocationOnMock -> {
+ if (invocationOnMock.getArguments()[0] instanceof UpdateDomainConfigurationRequest)
+ throw InternalFailureException.builder().build();
+ else if (invocationOnMock.getArguments()[0] instanceof DescribeDomainConfigurationRequest)
+ return DEFAULT_DESCRIBE_DOMAIN_CONFIGURATION_RESPONSE;
+ return null;
+ });
+
+ Assertions.assertThrows(CfnServiceInternalErrorException.class, () -> handler.handleRequest(proxy, request, null, logger));
+ }
+}
diff --git a/aws-iot-domainconfiguration/template.yml b/aws-iot-domainconfiguration/template.yml
new file mode 100644
index 0000000..7ba3bae
--- /dev/null
+++ b/aws-iot-domainconfiguration/template.yml
@@ -0,0 +1,24 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Transform: AWS::Serverless-2016-10-31
+Description: AWS SAM template for the AWS::IoT::DomainConfiguration resource type
+
+Globals:
+ Function:
+ Timeout: 60 # docker start-up times can be long for SAM CLI
+
+Resources:
+ TypeFunction:
+ Type: AWS::Serverless::Function
+ Properties:
+ Handler: com.amazonaws.iot.domainconfiguration.HandlerWrapper::handleRequest
+ Runtime: java8
+ CodeUri: ./target/aws-iot-domainconfiguration-handler-1.0-SNAPSHOT.jar
+ MemorySize: 256
+
+ TestEntrypoint:
+ Type: AWS::Serverless::Function
+ Properties:
+ Handler: com.amazonaws.iot.domainconfiguration.HandlerWrapper::testEntrypoint
+ Runtime: java8
+ CodeUri: ./target/aws-iot-domainconfiguration-handler-1.0-SNAPSHOT.jar
+ MemorySize: 256