From 002661d77b528733d744a060fba28596e2da1507 Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Thu, 13 Jan 2022 14:30:06 +0530 Subject: [PATCH 01/23] pom version changes --- aws-ssm-parameter/.rpdk-config | 4 +- aws-ssm-parameter/docs/README.md | 131 +++++++++++++++++++++++++++++++ aws-ssm-parameter/docs/tags.md | 32 ++++++++ aws-ssm-parameter/pom.xml | 77 ++++++++++++++---- 4 files changed, 226 insertions(+), 18 deletions(-) create mode 100644 aws-ssm-parameter/docs/README.md create mode 100644 aws-ssm-parameter/docs/tags.md diff --git a/aws-ssm-parameter/.rpdk-config b/aws-ssm-parameter/.rpdk-config index 136a7167..0767480e 100644 --- a/aws-ssm-parameter/.rpdk-config +++ b/aws-ssm-parameter/.rpdk-config @@ -1,4 +1,5 @@ { + "artifact_type": "RESOURCE", "typeName": "AWS::SSM::Parameter", "language": "java", "runtime": "java8", @@ -12,5 +13,6 @@ "parameter" ], "protocolVersion": "2.0.0" - } + }, + "executableEntrypoint": "com.amazonaws.ssm.parameter.HandlerWrapperExecutable" } diff --git a/aws-ssm-parameter/docs/README.md b/aws-ssm-parameter/docs/README.md new file mode 100644 index 00000000..c3ac4402 --- /dev/null +++ b/aws-ssm-parameter/docs/README.md @@ -0,0 +1,131 @@ +# AWS::SSM::Parameter + +Resource Type definition for AWS::SSM::Parameter + +## Syntax + +To declare this entity in your AWS CloudFormation template, use the following syntax: + +### JSON + +
+{
+    "Type" : "AWS::SSM::Parameter",
+    "Properties" : {
+        "Description" : String,
+        "Policies" : String,
+        "AllowedPattern" : String,
+        "Tier" : String,
+        "Tags" : Tags,
+        "DataType" : String,
+    }
+}
+
+ +### YAML + +
+Type: AWS::SSM::Parameter
+Properties:
+    Description: String
+    Policies: String
+    AllowedPattern: String
+    Tier: String
+    Tags: Tags
+    DataType: String
+
+ +## Properties + +#### Description + +The information about the parameter. + +_Required_: No + +_Type_: String + +_Maximum_: 1024 + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### Policies + +The policies attached to the parameter. + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### AllowedPattern + +The regular expression used to validate the parameter value. + +_Required_: No + +_Type_: String + +_Maximum_: 1024 + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### Tier + +The corresponding tier of the parameter. + +_Required_: No + +_Type_: String + +_Allowed Values_: Standard | Advanced | Intelligent-Tiering + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### Tags + +A key-value pair to associate with a resource. + +_Required_: No + +_Type_: Tags + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### DataType + +The corresponding DataType of the parameter. + +_Required_: No + +_Type_: String + +_Allowed Values_: text | aws:ec2:image + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +## Return Values + +### Ref + +When you pass the logical ID of this resource to the intrinsic `Ref` function, Ref returns the Name. + +### Fn::GetAtt + +The `Fn::GetAtt` intrinsic function returns a value for a specified attribute of this type. The following are the available attributes and sample return values. + +For more information about using the `Fn::GetAtt` intrinsic function, see [Fn::GetAtt](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html). + +#### Name + +The name of the parameter. + +#### Type + +The type of the parameter. + +#### Value + +The value associated with the parameter. + diff --git a/aws-ssm-parameter/docs/tags.md b/aws-ssm-parameter/docs/tags.md new file mode 100644 index 00000000..1a93d58b --- /dev/null +++ b/aws-ssm-parameter/docs/tags.md @@ -0,0 +1,32 @@ +# AWS::SSM::Parameter Tags + +A key-value pair to associate with a resource. + +## Syntax + +To declare this entity in your AWS CloudFormation template, use the following syntax: + +### JSON + +
+{
+    "^([\p{L}\p{Z}\p{N}_.:/=+\-@]*)$" : String
+}
+
+ +### YAML + +
+^([\p{L}\p{Z}\p{N}_.:/=+\-@]*)$: String
+
+ +## Properties + +#### \^([\p{L}\p{Z}\p{N}_.:/=+\-@]*)$ + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + diff --git a/aws-ssm-parameter/pom.xml b/aws-ssm-parameter/pom.xml index 7dbb2295..4cb64da3 100644 --- a/aws-ssm-parameter/pom.xml +++ b/aws-ssm-parameter/pom.xml @@ -12,8 +12,9 @@ jar - 1.8 - 1.8 + 1.8 + ${java.version} + ${java.version} UTF-8 UTF-8 @@ -35,48 +36,67 @@ software.amazon.awssdk ssm - 2.15.28 software.amazon.cloudformation aws-cloudformation-rpdk-java-plugin - [2.0.0, 3.0.0) + [2.0.6, 3.0.0) org.projectlombok lombok - 1.18.4 + 1.18.12 provided + + + org.apache.logging.log4j + log4j-api + 2.17.0 + + + + + org.apache.logging.log4j + log4j-core + 2.17.0 + org.assertj assertj-core - 3.12.2 + 3.16.1 test + + + org.apache.logging.log4j + log4j-slf4j-impl + 2.17.0 + + org.junit.jupiter junit-jupiter - 5.5.0-M1 + 5.6.2 test org.mockito mockito-core - 2.26.0 + 3.6.0 test org.mockito mockito-junit-jupiter - 2.26.0 + 3.6.0 test @@ -97,7 +117,7 @@ org.apache.maven.plugins maven-shade-plugin - 2.3 + 3.2.4 false @@ -113,7 +133,7 @@ org.codehaus.mojo exec-maven-plugin - 1.6.0 + 3.0.0 generate @@ -132,7 +152,7 @@ org.codehaus.mojo build-helper-maven-plugin - 3.0.0 + 3.2.0 add-source @@ -151,23 +171,21 @@ org.apache.maven.plugins maven-resources-plugin - 2.4 + 3.1.0 maven-surefire-plugin - 3.0.0-M3 + 3.0.0-M5 org.jacoco jacoco-maven-plugin - 0.8.4 + 0.8.6 **/BaseConfiguration* - **/BaseHandler* **/HandlerWrapper* **/ResourceModel* - **/Configuration* @@ -210,6 +228,31 @@ + + + org.apache.maven.plugins + maven-antrun-plugin + 1.8 + + + + + shade-package-post + + + + + + + + + run + + + + From 41748fff1872df7e76827ba3cd0753a1d3682ce5 Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Sat, 15 Jan 2022 12:42:21 +0530 Subject: [PATCH 02/23] amend schema --- aws-ssm-parameter/aws-ssm-parameter.json | 85 ++++++++---------------- 1 file changed, 26 insertions(+), 59 deletions(-) diff --git a/aws-ssm-parameter/aws-ssm-parameter.json b/aws-ssm-parameter/aws-ssm-parameter.json index 61d9b4a2..a28bf7b6 100644 --- a/aws-ssm-parameter/aws-ssm-parameter.json +++ b/aws-ssm-parameter/aws-ssm-parameter.json @@ -5,72 +5,45 @@ "properties": { "Type": { "type": "string", - "description": "The type of the parameter.", - "enum": [ - "String", - "StringList", - "SecureString" - ] - }, - "Value": { - "type": "string", - "description": "The value associated with the parameter.", - "minLength": 1, - "maxLength": 32768 + "description": "The type of parameter." }, "Description": { "type": "string", - "description": "The information about the parameter.", - "minLength": 0, - "maxLength": 1024 + "description": "Information about the parameter." }, "Policies": { "type": "string", - "description": "The policies attached to the parameter." + "description": "Information about the policies assigned to a parameter." }, "AllowedPattern": { "type": "string", - "description": "The regular expression used to validate the parameter value.", - "minLength": 0, - "maxLength": 1024 + "description": "A regular expression used to validate the parameter value." }, "Tier": { "type": "string", - "description": "The corresponding tier of the parameter.", - "enum": [ - "Standard", - "Advanced", - "Intelligent-Tiering" - ] + "description": "The parameter tier." }, - "Tags": { - "type": "object", - "description": "A key-value pair to associate with a resource.", - "patternProperties": { - "^([\\p{L}\\p{Z}\\p{N}_.:/=+\\-@]*)$": { - "type": "string" - } - }, - "additionalProperties": false + "Value": { + "type": "string", + "description": "The parameter value." }, "DataType": { "type": "string", - "description": "The corresponding DataType of the parameter.", - "enum": [ - "text", - "aws:ec2:image" - ] + "description": "The data type of the parameter, such as text or aws:ec2:image. The default is text." + }, + "Tags": { + "type": "object", + "description": "Optional metadata that you assign to a resource in the form of an arbitrary set of tags (key-value pairs)" }, "Name": { "type": "string", - "description": "The name of the parameter.", - "minLength": 1, - "maxLength": 2048 + "description": "The name of the parameter." } }, + "taggable": false, "required": [ - "Value", - "Type" + "Type", + "Value" ], "createOnlyProperties": [ "/properties/Name" @@ -78,32 +51,26 @@ "primaryIdentifier": [ "/properties/Name" ], - "readOnlyProperties": [ - "/properties/Name", - "/properties/Type", - "/properties/Value" - ], "handlers": { "create": { "permissions": [ - "ssm:PutParameter", - "ssm:GetParameters" - ], - "timeoutInMinutes": 5 + "ssm:GetParameters", + "ssm:PutParameter" + ] }, "read": { "permissions": [ - "ssm:GetParameters" + "ssm:GetParameters", + "ssm:ListTagsForResource" ] }, "update": { "permissions": [ "ssm:PutParameter", + "ssm:GetParameters", "ssm:AddTagsToResource", - "ssm:RemoveTagsFromResource", - "ssm:GetParameters" - ], - "timeoutInMinutes": 5 + "ssm:RemoveTagsFromResource" + ] }, "delete": { "permissions": [ @@ -116,4 +83,4 @@ ] } } -} +} \ No newline at end of file From 95b2be421c2bc5dc4304d3568472f64e7ccbaa94 Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Tue, 18 Jan 2022 17:09:10 +0530 Subject: [PATCH 03/23] rearranged code spacing --- .../ssm/parameter/BaseHandlerStd.java | 142 +-- .../ssm/parameter/Configuration.java | 29 +- .../amazonaws/ssm/parameter/Constants.java | 22 +- .../ssm/parameter/CreateHandler.java | 167 ++-- .../ssm/parameter/DeleteHandler.java | 78 +- .../amazonaws/ssm/parameter/ListHandler.java | 35 +- .../amazonaws/ssm/parameter/ReadHandler.java | 64 +- .../ssm/parameter/SSMClientBuilder.java | 33 +- .../amazonaws/ssm/parameter/Translator.java | 134 +-- .../ssm/parameter/UpdateHandler.java | 210 ++--- .../ssm/parameter/AbstractTestBase.java | 166 ++-- .../ssm/parameter/CreateHandlerTest.java | 740 ++++++++------- .../ssm/parameter/DeleteHandlerTest.java | 363 ++++---- .../ssm/parameter/ListHandlerTest.java | 136 ++- .../ssm/parameter/ReadHandlerTest.java | 201 ++-- .../ssm/parameter/UpdateHandlerTest.java | 868 +++++++++--------- 16 files changed, 1685 insertions(+), 1703 deletions(-) diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java index 58653a80..cb8c8dc4 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java @@ -7,88 +7,88 @@ import software.amazon.awssdk.services.ssm.model.PutParameterRequest; import software.amazon.awssdk.services.ssm.model.PutParameterResponse; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; -import software.amazon.cloudformation.proxy.ResourceHandlerRequest; -import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.Logger; import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; import software.amazon.cloudformation.proxy.delay.Constant; import java.time.Duration; import java.util.Set; public abstract class BaseHandlerStd extends BaseHandler { - protected static final Set THROTTLING_ERROR_CODES = ImmutableSet.of( - "ThrottlingException", - "TooManyUpdates"); + protected static final Set THROTTLING_ERROR_CODES = ImmutableSet.of( + "ThrottlingException", + "TooManyUpdates"); - @Override - public ProgressEvent handleRequest(final AmazonWebServicesClientProxy proxy, - final ResourceHandlerRequest request, - final CallbackContext callbackContext, - final Logger logger) { - return handleRequest( - proxy, - request, - callbackContext != null ? callbackContext : new CallbackContext(), - proxy.newProxy(SSMClientBuilder::getClient), - logger); - } + @Override + public ProgressEvent handleRequest(final AmazonWebServicesClientProxy proxy, + final ResourceHandlerRequest request, + final CallbackContext callbackContext, + final Logger logger) { + return handleRequest( + proxy, + request, + callbackContext != null ? callbackContext : new CallbackContext(), + proxy.newProxy(SSMClientBuilder::getClient), + logger); + } - protected abstract ProgressEvent handleRequest( - AmazonWebServicesClientProxy proxy, - ResourceHandlerRequest request, - CallbackContext callbackContext, - ProxyClient client, - Logger logger); + protected abstract ProgressEvent handleRequest( + AmazonWebServicesClientProxy proxy, + ResourceHandlerRequest request, + CallbackContext callbackContext, + ProxyClient client, + Logger logger); - protected Constant getBackOffDelay(final ResourceModel model) { - if(model.getDataType() != null && model.getDataType() == Constants.AWS_EC2_IMAGE_DATATYPE) { - return Constant.of() - .timeout(Duration.ofMinutes(5)) - .delay(Duration.ofSeconds(30)) - .build(); - } else { - return Constant.of() - .timeout(Duration.ofMinutes(5)) - .delay(Duration.ofSeconds(5)) - .build(); - } - } + protected Constant getBackOffDelay(final ResourceModel model) { + if (model.getDataType() != null && model.getDataType() == Constants.AWS_EC2_IMAGE_DATATYPE) { + return Constant.of() + .timeout(Duration.ofMinutes(5)) + .delay(Duration.ofSeconds(30)) + .build(); + } else { + return Constant.of() + .timeout(Duration.ofMinutes(5)) + .delay(Duration.ofSeconds(5)) + .build(); + } + } - /** - * If your resource requires some form of stabilization (e.g. service does not provide strong - * consistency), you will need to ensure that your code accounts for any potential issues, so that - * a subsequent read/update requests will not cause any conflicts (e.g. - * NotFoundException/InvalidRequestException) for more information -> - * https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html - * - * @param putParameterRequest the aws service request to create a resource - * @param putParameterResponse the aws service response to create a resource - * @param proxyClient the aws service client to make the call - * @param resourceModel resource model - * @param callbackContext callback context - * @return boolean state of stabilized or not - */ - protected static boolean stabilize( - final PutParameterRequest putParameterRequest, - final PutParameterResponse putParameterResponse, - final ProxyClient proxyClient, - final ResourceModel resourceModel, - final CallbackContext callbackContext - ) { - final GetParametersResponse response; - try { - response = proxyClient.injectCredentialsAndInvokeV2(Translator.getParametersRequest(resourceModel), proxyClient.client()::getParameters); - } catch (final InternalServerErrorException exception) { - return false; - } + /** + * If your resource requires some form of stabilization (e.g. service does not provide strong + * consistency), you will need to ensure that your code accounts for any potential issues, so that + * a subsequent read/update requests will not cause any conflicts (e.g. + * NotFoundException/InvalidRequestException) for more information -> + * https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html + * + * @param putParameterRequest the aws service request to create a resource + * @param putParameterResponse the aws service response to create a resource + * @param proxyClient the aws service client to make the call + * @param resourceModel resource model + * @param callbackContext callback context + * @return boolean state of stabilized or not + */ + protected static boolean stabilize( + final PutParameterRequest putParameterRequest, + final PutParameterResponse putParameterResponse, + final ProxyClient proxyClient, + final ResourceModel resourceModel, + final CallbackContext callbackContext + ) { + final GetParametersResponse response; + try { + response = proxyClient.injectCredentialsAndInvokeV2(Translator.getParametersRequest(resourceModel), proxyClient.client()::getParameters); + } catch (final InternalServerErrorException exception) { + return false; + } - // if invalid parameters list is not empty return false as the validation for - // DataType has not been completed and the parameter has not been created yet. - if(response == null || response.invalidParameters().size() != 0) { - return false; - } - return (response.parameters() != null && - response.parameters().get(0).version() == putParameterResponse.version()); - } + // if invalid parameters list is not empty return false as the validation for + // DataType has not been completed and the parameter has not been created yet. + if (response == null || response.invalidParameters().size() != 0) { + return false; + } + return (response.parameters() != null && + response.parameters().get(0).version() == putParameterResponse.version()); + } } diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Configuration.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Configuration.java index 919beb3c..3ad833c5 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Configuration.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Configuration.java @@ -4,19 +4,20 @@ class Configuration extends BaseConfiguration { - public Configuration() { - super("aws-ssm-parameter.json"); - } + public Configuration() { + super("aws-ssm-parameter.json"); + } - /** - * Providers should implement this method if their resource has a 'Tags' property to define resource-level tags - * @return - */ - public Map resourceDefinedTags(final ResourceModel resourceModel) { - if (resourceModel.getTags() == null) { - return null; - } else { - return resourceModel.getTags(); - } - } + /** + * Providers should implement this method if their resource has a 'Tags' property to define resource-level tags + * + * @return + */ + public Map resourceDefinedTags(final ResourceModel resourceModel) { + if (resourceModel.getTags() == null) { + return null; + } else { + return resourceModel.getTags(); + } + } } diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Constants.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Constants.java index 948bee85..4f4c9cd4 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Constants.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Constants.java @@ -1,17 +1,17 @@ package com.amazonaws.ssm.parameter; public class Constants { - // ParameterName limit is 1024 chars. To make sure that we never hit that limit with auto name generation, - // lets have a number less than the allowed limit i.e 1000. - // The total of following three variables should be less than 1000. - // This can be increased in the future. - public static final int ALLOWED_LOGICAL_RESOURCE_ID_LENGTH = 500; - public static final String CF_PARAMETER_NAME_PREFIX = "CFN"; - public static final int GUID_LENGTH = 12; + // ParameterName limit is 1024 chars. To make sure that we never hit that limit with auto name generation, + // lets have a number less than the allowed limit i.e 1000. + // The total of following three variables should be less than 1000. + // This can be increased in the future. + public static final int ALLOWED_LOGICAL_RESOURCE_ID_LENGTH = 500; + public static final String CF_PARAMETER_NAME_PREFIX = "CFN"; + public static final int GUID_LENGTH = 12; - public static final int ERROR_STATUS_CODE_400 = 400; - public static final int ERROR_STATUS_CODE_500 = 500; + public static final int ERROR_STATUS_CODE_400 = 400; + public static final int ERROR_STATUS_CODE_500 = 500; - public static final Integer MAX_RESULTS = 50; - public static final String AWS_EC2_IMAGE_DATATYPE = "aws:ec2:image"; + public static final Integer MAX_RESULTS = 50; + public static final String AWS_EC2_IMAGE_DATATYPE = "aws:ec2:image"; } diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java index a9005699..d1fa97af 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java @@ -2,13 +2,12 @@ import com.amazonaws.AmazonServiceException; import org.apache.commons.lang3.RandomStringUtils; - import software.amazon.awssdk.services.ssm.SsmClient; -import software.amazon.awssdk.services.ssm.model.ParameterAlreadyExistsException; import software.amazon.awssdk.services.ssm.model.InternalServerErrorException; +import software.amazon.awssdk.services.ssm.model.ParameterAlreadyExistsException; +import software.amazon.awssdk.services.ssm.model.ParameterType; import software.amazon.awssdk.services.ssm.model.PutParameterRequest; import software.amazon.awssdk.services.ssm.model.PutParameterResponse; -import software.amazon.awssdk.services.ssm.model.ParameterType; import software.amazon.cloudformation.exceptions.CfnAlreadyExistsException; import software.amazon.cloudformation.exceptions.CfnGeneralServiceException; import software.amazon.cloudformation.exceptions.CfnServiceInternalErrorException; @@ -16,102 +15,102 @@ import software.amazon.cloudformation.exceptions.TerminalException; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; import software.amazon.cloudformation.proxy.HandlerErrorCode; -import software.amazon.cloudformation.proxy.ResourceHandlerRequest; -import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.Logger; import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; import java.util.HashMap; import java.util.Map; import java.util.Random; public class CreateHandler extends BaseHandlerStd { - private static final String OPERATION = "PutParameter"; - private static final String RETRY_MESSAGE = "Detected retryable error, retrying. Exception message: %s"; - private Logger logger; + private static final String OPERATION = "PutParameter"; + private static final String RETRY_MESSAGE = "Detected retryable error, retrying. Exception message: %s"; + private Logger logger; - @Override - protected ProgressEvent handleRequest( - final AmazonWebServicesClientProxy proxy, - final ResourceHandlerRequest request, - final CallbackContext callbackContext, - final ProxyClient proxyClient, - final Logger logger) { - this.logger = logger; - final ResourceModel model = request.getDesiredResourceState(); + @Override + protected ProgressEvent handleRequest( + final AmazonWebServicesClientProxy proxy, + final ResourceHandlerRequest request, + final CallbackContext callbackContext, + final ProxyClient proxyClient, + final Logger logger) { + this.logger = logger; + final ResourceModel model = request.getDesiredResourceState(); - // Set model primary ID if absent - if(model.getName() == null) { - model.setName(generateParameterName( - request.getLogicalResourceIdentifier(), - request.getClientRequestToken() - )); - } + // Set model primary ID if absent + if (model.getName() == null) { + model.setName(generateParameterName( + request.getLogicalResourceIdentifier(), + request.getClientRequestToken() + )); + } - if(model.getType().equalsIgnoreCase(ParameterType.SECURE_STRING.toString())) { - String message = String.format("SSM Parameters of type %s cannot be created using CloudFormation", ParameterType.SECURE_STRING); - return ProgressEvent.defaultFailureHandler(new TerminalException(message), - HandlerErrorCode.InvalidRequest); - } + if (model.getType().equalsIgnoreCase(ParameterType.SECURE_STRING.toString())) { + String message = String.format("SSM Parameters of type %s cannot be created using CloudFormation", ParameterType.SECURE_STRING); + return ProgressEvent.defaultFailureHandler(new TerminalException(message), + HandlerErrorCode.InvalidRequest); + } - Map consolidatedTagList = new HashMap<>(); - if (request.getDesiredResourceTags() != null) { - consolidatedTagList.putAll(request.getDesiredResourceTags()); - } - if (request.getSystemTags() != null) { - consolidatedTagList.putAll(request.getSystemTags()); - } + Map consolidatedTagList = new HashMap<>(); + if (request.getDesiredResourceTags() != null) { + consolidatedTagList.putAll(request.getDesiredResourceTags()); + } + if (request.getSystemTags() != null) { + consolidatedTagList.putAll(request.getSystemTags()); + } - return proxy.initiate("aws-ssm-parameter::resource-create", proxyClient, model, callbackContext) - .translateToServiceRequest((resourceModel) -> Translator.createPutParameterRequest(resourceModel, consolidatedTagList)) - .backoffDelay(getBackOffDelay(model)) - .makeServiceCall(this::createResource) - .stabilize(BaseHandlerStd::stabilize) - .progress() - .then(progress -> new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, logger)); - } + return proxy.initiate("aws-ssm-parameter::resource-create", proxyClient, model, callbackContext) + .translateToServiceRequest((resourceModel) -> Translator.createPutParameterRequest(resourceModel, consolidatedTagList)) + .backoffDelay(getBackOffDelay(model)) + .makeServiceCall(this::createResource) + .stabilize(BaseHandlerStd::stabilize) + .progress() + .then(progress -> new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, logger)); + } - private PutParameterResponse createResource(final PutParameterRequest putParameterRequest, - final ProxyClient proxyClient) { - try { - return proxyClient.injectCredentialsAndInvokeV2(putParameterRequest, proxyClient.client()::putParameter); - } catch (final ParameterAlreadyExistsException exception) { - throw new CfnAlreadyExistsException(ResourceModel.TYPE_NAME, putParameterRequest.name()); - } catch (final InternalServerErrorException exception) { - throw new CfnServiceInternalErrorException(OPERATION, exception); - } catch (final AmazonServiceException exception) { - final Integer errorStatus = exception.getStatusCode(); - final String errorCode = exception.getErrorCode(); - if (errorStatus >= Constants.ERROR_STATUS_CODE_400 && errorStatus < Constants.ERROR_STATUS_CODE_500) { - if (THROTTLING_ERROR_CODES.contains(errorCode)) { - logger.log(String.format(RETRY_MESSAGE, exception.getMessage())); - throw new CfnThrottlingException(OPERATION, exception); - } - } - throw new CfnGeneralServiceException(OPERATION, exception); - } - } + private PutParameterResponse createResource(final PutParameterRequest putParameterRequest, + final ProxyClient proxyClient) { + try { + return proxyClient.injectCredentialsAndInvokeV2(putParameterRequest, proxyClient.client()::putParameter); + } catch (final ParameterAlreadyExistsException exception) { + throw new CfnAlreadyExistsException(ResourceModel.TYPE_NAME, putParameterRequest.name()); + } catch (final InternalServerErrorException exception) { + throw new CfnServiceInternalErrorException(OPERATION, exception); + } catch (final AmazonServiceException exception) { + final Integer errorStatus = exception.getStatusCode(); + final String errorCode = exception.getErrorCode(); + if (errorStatus >= Constants.ERROR_STATUS_CODE_400 && errorStatus < Constants.ERROR_STATUS_CODE_500) { + if (THROTTLING_ERROR_CODES.contains(errorCode)) { + logger.log(String.format(RETRY_MESSAGE, exception.getMessage())); + throw new CfnThrottlingException(OPERATION, exception); + } + } + throw new CfnGeneralServiceException(OPERATION, exception); + } + } - // We support this special use case of auto-generating names only for CloudFormation. - // Name format: Prefix - logical resource id - randomString - private String generateParameterName(final String logicalResourceId, final String clientRequestToken) { - StringBuilder sb = new StringBuilder(); - int endIndex = logicalResourceId.length() > Constants.ALLOWED_LOGICAL_RESOURCE_ID_LENGTH - ? Constants.ALLOWED_LOGICAL_RESOURCE_ID_LENGTH : logicalResourceId.length(); + // We support this special use case of auto-generating names only for CloudFormation. + // Name format: Prefix - logical resource id - randomString + private String generateParameterName(final String logicalResourceId, final String clientRequestToken) { + StringBuilder sb = new StringBuilder(); + int endIndex = logicalResourceId.length() > Constants.ALLOWED_LOGICAL_RESOURCE_ID_LENGTH + ? Constants.ALLOWED_LOGICAL_RESOURCE_ID_LENGTH : logicalResourceId.length(); - sb.append(Constants.CF_PARAMETER_NAME_PREFIX); - sb.append("-"); - sb.append(logicalResourceId.substring(0, endIndex)); - sb.append("-"); + sb.append(Constants.CF_PARAMETER_NAME_PREFIX); + sb.append("-"); + sb.append(logicalResourceId.substring(0, endIndex)); + sb.append("-"); - sb.append(RandomStringUtils.random( - Constants.GUID_LENGTH, - 0, - 0, - true, - true, - null, - new Random(clientRequestToken.hashCode()))); - return sb.toString(); - } + sb.append(RandomStringUtils.random( + Constants.GUID_LENGTH, + 0, + 0, + true, + true, + null, + new Random(clientRequestToken.hashCode()))); + return sb.toString(); + } } diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/DeleteHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/DeleteHandler.java index 36f895fd..c14849be 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/DeleteHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/DeleteHandler.java @@ -4,57 +4,57 @@ import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.awssdk.services.ssm.model.DeleteParameterRequest; import software.amazon.awssdk.services.ssm.model.DeleteParameterResponse; -import software.amazon.awssdk.services.ssm.model.ParameterNotFoundException; import software.amazon.awssdk.services.ssm.model.InternalServerErrorException; +import software.amazon.awssdk.services.ssm.model.ParameterNotFoundException; import software.amazon.cloudformation.exceptions.CfnGeneralServiceException; 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.ProxyClient; import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; public class DeleteHandler extends BaseHandlerStd { - private static final String OPERATION = "DeleteParameter"; - private static final String RETRY_MESSAGE = "Detected retryable error, retrying. Exception message: %s"; - private Logger logger; + private static final String OPERATION = "DeleteParameter"; + private static final String RETRY_MESSAGE = "Detected retryable error, retrying. Exception message: %s"; + private Logger logger; - @Override - protected ProgressEvent handleRequest( - final AmazonWebServicesClientProxy proxy, - final ResourceHandlerRequest request, - final CallbackContext callbackContext, - final ProxyClient proxyClient, - final Logger logger) { - this.logger = logger; - final ResourceModel model = request.getDesiredResourceState(); + @Override + protected ProgressEvent handleRequest( + final AmazonWebServicesClientProxy proxy, + final ResourceHandlerRequest request, + final CallbackContext callbackContext, + final ProxyClient proxyClient, + final Logger logger) { + this.logger = logger; + final ResourceModel model = request.getDesiredResourceState(); - return proxy.initiate("aws-ssm-parameter::resource-delete", proxyClient, model, callbackContext) - .translateToServiceRequest(Translator::deleteParameterRequest) - .makeServiceCall(this::deleteResource) - .done((deleteParameterRequest, deleteParameterResponse, _client, _model, _callbackContext) -> ProgressEvent.defaultSuccessHandler(null)); - } + return proxy.initiate("aws-ssm-parameter::resource-delete", proxyClient, model, callbackContext) + .translateToServiceRequest(Translator::deleteParameterRequest) + .makeServiceCall(this::deleteResource) + .done((deleteParameterRequest, deleteParameterResponse, _client, _model, _callbackContext) -> ProgressEvent.defaultSuccessHandler(null)); + } - private DeleteParameterResponse deleteResource(final DeleteParameterRequest deleteParameterRequest, - final ProxyClient proxyClient) { - try { - return proxyClient.injectCredentialsAndInvokeV2(deleteParameterRequest, proxyClient.client()::deleteParameter); - } catch (final ParameterNotFoundException exception) { - throw new CfnNotFoundException(ResourceModel.TYPE_NAME, deleteParameterRequest.name()); - } catch (final InternalServerErrorException exception) { - throw new CfnServiceInternalErrorException(OPERATION, exception); - } catch (final AmazonServiceException exception) { - final Integer errorStatus = exception.getStatusCode(); - final String errorCode = exception.getErrorCode(); - if (errorStatus >= Constants.ERROR_STATUS_CODE_400 && errorStatus < Constants.ERROR_STATUS_CODE_500) { - if (THROTTLING_ERROR_CODES.contains(errorCode)) { - logger.log(String.format(RETRY_MESSAGE, exception.getMessage())); - throw new CfnThrottlingException(OPERATION, exception); - } - } - throw new CfnGeneralServiceException(OPERATION, exception); - } - } + private DeleteParameterResponse deleteResource(final DeleteParameterRequest deleteParameterRequest, + final ProxyClient proxyClient) { + try { + return proxyClient.injectCredentialsAndInvokeV2(deleteParameterRequest, proxyClient.client()::deleteParameter); + } catch (final ParameterNotFoundException exception) { + throw new CfnNotFoundException(ResourceModel.TYPE_NAME, deleteParameterRequest.name()); + } catch (final InternalServerErrorException exception) { + throw new CfnServiceInternalErrorException(OPERATION, exception); + } catch (final AmazonServiceException exception) { + final Integer errorStatus = exception.getStatusCode(); + final String errorCode = exception.getErrorCode(); + if (errorStatus >= Constants.ERROR_STATUS_CODE_400 && errorStatus < Constants.ERROR_STATUS_CODE_500) { + if (THROTTLING_ERROR_CODES.contains(errorCode)) { + logger.log(String.format(RETRY_MESSAGE, exception.getMessage())); + throw new CfnThrottlingException(OPERATION, exception); + } + } + throw new CfnGeneralServiceException(OPERATION, exception); + } + } } diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ListHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ListHandler.java index 9677c60b..b9f5713d 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ListHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ListHandler.java @@ -4,31 +4,32 @@ import software.amazon.awssdk.services.ssm.model.DescribeParametersResponse; 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.ProxyClient; -import software.amazon.cloudformation.proxy.OperationStatus; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; import java.util.List; import java.util.stream.Collectors; public class ListHandler extends BaseHandlerStd { - protected ProgressEvent handleRequest( - final AmazonWebServicesClientProxy proxy, - final ResourceHandlerRequest request, - final CallbackContext callbackContext, - final ProxyClient proxyClient, - final Logger logger) { - final DescribeParametersResponse describeParametersResponse = proxy.injectCredentialsAndInvokeV2(Translator.describeParametersRequest(request.getNextToken()), proxyClient.client()::describeParameters); + protected ProgressEvent handleRequest( + final AmazonWebServicesClientProxy proxy, + final ResourceHandlerRequest request, + final CallbackContext callbackContext, + final ProxyClient proxyClient, + final Logger logger) { + final DescribeParametersResponse describeParametersResponse = proxy.injectCredentialsAndInvokeV2(Translator.describeParametersRequest(request.getNextToken()), + proxyClient.client()::describeParameters); - final List models = describeParametersResponse - .parameters() - .stream().map(parameterMetadata -> ResourceModel.builder().name(parameterMetadata.name()).build()).collect(Collectors.toList()); + final List models = describeParametersResponse + .parameters() + .stream().map(parameterMetadata -> ResourceModel.builder().name(parameterMetadata.name()).build()).collect(Collectors.toList()); - return ProgressEvent.builder() - .resourceModels(models) - .nextToken(describeParametersResponse.nextToken()) - .status(OperationStatus.SUCCESS) - .build(); - } + return ProgressEvent.builder() + .resourceModels(models) + .nextToken(describeParametersResponse.nextToken()) + .status(OperationStatus.SUCCESS) + .build(); + } } diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ReadHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ReadHandler.java index b662d2a1..e08e0553 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ReadHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ReadHandler.java @@ -8,43 +8,43 @@ import software.amazon.cloudformation.exceptions.CfnNotFoundException; import software.amazon.cloudformation.exceptions.CfnServiceInternalErrorException; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; -import software.amazon.cloudformation.proxy.ResourceHandlerRequest; -import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.Logger; import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; public class ReadHandler extends BaseHandlerStd { - private static final String OPERATION = "ReadParameter"; + private static final String OPERATION = "ReadParameter"; - @Override - protected ProgressEvent handleRequest( - final AmazonWebServicesClientProxy proxy, - final ResourceHandlerRequest request, - final CallbackContext callbackContext, - final ProxyClient proxyClient, - final Logger logger) { - return proxy.initiate("aws-ssm-parameter::resource-read", proxyClient, request.getDesiredResourceState(), callbackContext) - .translateToServiceRequest(Translator::getParametersRequest) - .makeServiceCall(this::ReadResource) - .done((getParametersRequest, getParametersResponse, proxyInvocation, resourceModel, context) -> { - if(getParametersResponse.parameters().size() == 0) { - throw new CfnNotFoundException(ResourceModel.TYPE_NAME, request.getDesiredResourceState().getName()); - } - final Parameter parameter = getParametersResponse.parameters().stream().findFirst().get(); + @Override + protected ProgressEvent handleRequest( + final AmazonWebServicesClientProxy proxy, + final ResourceHandlerRequest request, + final CallbackContext callbackContext, + final ProxyClient proxyClient, + final Logger logger) { + return proxy.initiate("aws-ssm-parameter::resource-read", proxyClient, request.getDesiredResourceState(), callbackContext) + .translateToServiceRequest(Translator::getParametersRequest) + .makeServiceCall(this::ReadResource) + .done((getParametersRequest, getParametersResponse, proxyInvocation, resourceModel, context) -> { + if (getParametersResponse.parameters().size() == 0) { + throw new CfnNotFoundException(ResourceModel.TYPE_NAME, request.getDesiredResourceState().getName()); + } + final Parameter parameter = getParametersResponse.parameters().stream().findFirst().get(); - return ProgressEvent.defaultSuccessHandler(ResourceModel.builder() - .name(parameter.name()) - .type(parameter.typeAsString()) - .value(parameter.value()).build()); - }); - } + return ProgressEvent.defaultSuccessHandler(ResourceModel.builder() + .name(parameter.name()) + .type(parameter.typeAsString()) + .value(parameter.value()).build()); + }); + } - private GetParametersResponse ReadResource(final GetParametersRequest getParametersRequest, - final ProxyClient proxyClient) { - try{ - return proxyClient.injectCredentialsAndInvokeV2(getParametersRequest, proxyClient.client()::getParameters); - } catch (final InternalServerErrorException exception) { - throw new CfnServiceInternalErrorException(OPERATION, exception); - } - } + private GetParametersResponse ReadResource(final GetParametersRequest getParametersRequest, + final ProxyClient proxyClient) { + try { + return proxyClient.injectCredentialsAndInvokeV2(getParametersRequest, proxyClient.client()::getParameters); + } catch (final InternalServerErrorException exception) { + throw new CfnServiceInternalErrorException(OPERATION, exception); + } + } } diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/SSMClientBuilder.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/SSMClientBuilder.java index cb46ec9b..5f2e2897 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/SSMClientBuilder.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/SSMClientBuilder.java @@ -7,20 +7,21 @@ import software.amazon.cloudformation.LambdaWrapper; public class SSMClientBuilder { - private static final RetryPolicy RETRY_POLICY = - RetryPolicy.builder() - .numRetries(16) - .retryCondition(RetryCondition.defaultRetryCondition()) - .build(); - /** - * Builds and returns SsmClient with configuration overrides. - * - * @return Configured SsmClient. - */ - public static SsmClient getClient() { - return SsmClient.builder() - .httpClient(LambdaWrapper.HTTP_CLIENT) - .overrideConfiguration(ClientOverrideConfiguration.builder().retryPolicy(RETRY_POLICY).build()) - .build(); - } + private static final RetryPolicy RETRY_POLICY = + RetryPolicy.builder() + .numRetries(16) + .retryCondition(RetryCondition.defaultRetryCondition()) + .build(); + + /** + * Builds and returns SsmClient with configuration overrides. + * + * @return Configured SsmClient. + */ + public static SsmClient getClient() { + return SsmClient.builder() + .httpClient(LambdaWrapper.HTTP_CLIENT) + .overrideConfiguration(ClientOverrideConfiguration.builder().retryPolicy(RETRY_POLICY).build()) + .build(); + } } diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Translator.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Translator.java index b1b64dac..ce101aeb 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Translator.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Translator.java @@ -10,83 +10,83 @@ import software.amazon.awssdk.services.ssm.model.Tag; import java.util.Collections; -import java.util.Map; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; public class Translator { - static PutParameterRequest createPutParameterRequest(final ResourceModel model, - final Map tags) { - return PutParameterRequest.builder() - .description(model.getDescription()) - .name(model.getName()) - .value(model.getValue()) - .type(model.getType()) - .overwrite(Boolean.FALSE) - .allowedPattern(model.getAllowedPattern()) - .policies(model.getPolicies()) - .tier(model.getTier()) - .tags(translateTagsToSdk(tags)) - .dataType(model.getDataType()) - .build(); - } + static PutParameterRequest createPutParameterRequest(final ResourceModel model, + final Map tags) { + return PutParameterRequest.builder() + .description(model.getDescription()) + .name(model.getName()) + .value(model.getValue()) + .type(model.getType()) + .overwrite(Boolean.FALSE) + .allowedPattern(model.getAllowedPattern()) + .policies(model.getPolicies()) + .tier(model.getTier()) + .tags(translateTagsToSdk(tags)) + .dataType(model.getDataType()) + .build(); + } - static PutParameterRequest updatePutParameterRequest(final ResourceModel model) { - return PutParameterRequest.builder() - .description(model.getDescription()) - .name(model.getName()) - .value(model.getValue()) - .type(model.getType()) - .overwrite(Boolean.TRUE) - .allowedPattern(model.getAllowedPattern()) - .policies(model.getPolicies()) - .tier(model.getTier()) - .dataType(model.getDataType()) - .build(); - } + static PutParameterRequest updatePutParameterRequest(final ResourceModel model) { + return PutParameterRequest.builder() + .description(model.getDescription()) + .name(model.getName()) + .value(model.getValue()) + .type(model.getType()) + .overwrite(Boolean.TRUE) + .allowedPattern(model.getAllowedPattern()) + .policies(model.getPolicies()) + .tier(model.getTier()) + .dataType(model.getDataType()) + .build(); + } - static GetParametersRequest getParametersRequest(final ResourceModel model) { - return GetParametersRequest.builder() - .names(model.getName()) - .withDecryption(Boolean.FALSE) - .build(); - } + static GetParametersRequest getParametersRequest(final ResourceModel model) { + return GetParametersRequest.builder() + .names(model.getName()) + .withDecryption(Boolean.FALSE) + .build(); + } - static DescribeParametersRequest describeParametersRequest(final String nextToken) { - return DescribeParametersRequest.builder() - .nextToken(nextToken) - .maxResults(Constants.MAX_RESULTS) - .build(); - } + static DescribeParametersRequest describeParametersRequest(final String nextToken) { + return DescribeParametersRequest.builder() + .nextToken(nextToken) + .maxResults(Constants.MAX_RESULTS) + .build(); + } - static DeleteParameterRequest deleteParameterRequest(final ResourceModel model) { - return DeleteParameterRequest.builder() - .name(model.getName()) - .build(); - } + static DeleteParameterRequest deleteParameterRequest(final ResourceModel model) { + return DeleteParameterRequest.builder() + .name(model.getName()) + .build(); + } - static RemoveTagsFromResourceRequest removeTagsFromResourceRequest(final String parameterName, List tagsToRemove) { - return RemoveTagsFromResourceRequest.builder() - .resourceId(parameterName) - .resourceType(ResourceTypeForTagging.PARAMETER) - .tagKeys(tagsToRemove.stream().map(tag -> tag.key()).collect(Collectors.toList())) - .build(); - } + static RemoveTagsFromResourceRequest removeTagsFromResourceRequest(final String parameterName, List tagsToRemove) { + return RemoveTagsFromResourceRequest.builder() + .resourceId(parameterName) + .resourceType(ResourceTypeForTagging.PARAMETER) + .tagKeys(tagsToRemove.stream().map(tag -> tag.key()).collect(Collectors.toList())) + .build(); + } - static AddTagsToResourceRequest addTagsToResourceRequest(final String parameterName, List tagsToAdd) { - return AddTagsToResourceRequest.builder() - .resourceId(parameterName) - .resourceType(ResourceTypeForTagging.PARAMETER) - .tags(tagsToAdd) - .build(); - } + static AddTagsToResourceRequest addTagsToResourceRequest(final String parameterName, List tagsToAdd) { + return AddTagsToResourceRequest.builder() + .resourceId(parameterName) + .resourceType(ResourceTypeForTagging.PARAMETER) + .tags(tagsToAdd) + .build(); + } - // Translate tags - static List translateTagsToSdk(final Map tags) { - return Optional.of(tags.entrySet()).orElse(Collections.emptySet()) - .stream() - .map(tag -> Tag.builder().key(tag.getKey()).value(tag.getValue()).build()) - .collect(Collectors.toList()); - } + // Translate tags + static List translateTagsToSdk(final Map tags) { + return Optional.of(tags.entrySet()).orElse(Collections.emptySet()) + .stream() + .map(tag -> Tag.builder().key(tag.getKey()).value(tag.getValue()).build()) + .collect(Collectors.toList()); + } } diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java index 0db9bcdd..6c9cee6d 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java @@ -9,8 +9,8 @@ import software.amazon.awssdk.services.ssm.model.InternalServerErrorException; import software.amazon.awssdk.services.ssm.model.ParameterAlreadyExistsException; import software.amazon.awssdk.services.ssm.model.ParameterType; -import software.amazon.awssdk.services.ssm.model.PutParameterResponse; import software.amazon.awssdk.services.ssm.model.PutParameterRequest; +import software.amazon.awssdk.services.ssm.model.PutParameterResponse; import software.amazon.awssdk.services.ssm.model.Tag; import software.amazon.cloudformation.exceptions.CfnAlreadyExistsException; import software.amazon.cloudformation.exceptions.CfnGeneralServiceException; @@ -20,10 +20,10 @@ import software.amazon.cloudformation.exceptions.TerminalException; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; import software.amazon.cloudformation.proxy.HandlerErrorCode; -import software.amazon.cloudformation.proxy.ResourceHandlerRequest; -import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.Logger; import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; import java.util.HashSet; import java.util.List; @@ -32,105 +32,107 @@ import java.util.stream.Collectors; public class UpdateHandler extends BaseHandlerStd { - private static final String OPERATION = "PutParameter"; - private static final String RETRY_MESSAGE = "Detected retryable error, retrying. Exception message: %s"; - private Logger logger; - - @Override - protected ProgressEvent handleRequest( - final AmazonWebServicesClientProxy proxy, - final ResourceHandlerRequest request, - final CallbackContext callbackContext, - final ProxyClient proxyClient, - final Logger logger) { - this.logger = logger; - final ResourceModel model = request.getDesiredResourceState(); - - if(model.getType().equalsIgnoreCase(ParameterType.SECURE_STRING.toString())) { - String message = String.format("SSM Parameters of type %s cannot be updated using CloudFormation", ParameterType.SECURE_STRING); - return ProgressEvent.defaultFailureHandler(new TerminalException(message), - HandlerErrorCode.InvalidRequest); - } - - return ProgressEvent.progress(model, callbackContext) - // First validate the resource actually exists per the contract requirements - // https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html - .then(progress -> - proxy.initiate("aws-ssm-parameter::validate-resource-exists", proxyClient, model, callbackContext) - .translateToServiceRequest(Translator::getParametersRequest) - .makeServiceCall(this::validateResourceExists) - .progress()) - - .then(progress -> - proxy.initiate("aws-ssm-parameter::resource-update", proxyClient, model, callbackContext) - .translateToServiceRequest(Translator::updatePutParameterRequest) - .backoffDelay(getBackOffDelay(model)) - .makeServiceCall(this::updateResource) - .stabilize(BaseHandlerStd::stabilize) - .progress()) - .then(progress -> handleTagging(proxy, proxyClient, progress, model, request.getDesiredResourceTags(), request.getPreviousResourceTags())) - .then(progress -> new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, logger)); - } - - private GetParametersResponse validateResourceExists(GetParametersRequest getParametersRequest, ProxyClient proxyClient) { - GetParametersResponse getParametersResponse; - - getParametersResponse = proxyClient.injectCredentialsAndInvokeV2(getParametersRequest,proxyClient.client()::getParameters); - if (getParametersResponse.invalidParameters().size() != 0) { - throw new CfnNotFoundException(ResourceModel.TYPE_NAME, getParametersRequest.names().get(0)); - } - - return getParametersResponse; - } - - private PutParameterResponse updateResource(final PutParameterRequest putParameterRequest, - final ProxyClient proxyClient) { - try { - return proxyClient.injectCredentialsAndInvokeV2(putParameterRequest, proxyClient.client()::putParameter); - } catch (final ParameterAlreadyExistsException exception) { - throw new CfnAlreadyExistsException(ResourceModel.TYPE_NAME, putParameterRequest.name()); - } catch (final InternalServerErrorException exception) { - throw new CfnServiceInternalErrorException(OPERATION, exception); - } catch (final AmazonServiceException exception) { - final Integer errorStatus = exception.getStatusCode(); - final String errorCode = exception.getErrorCode(); - if (errorStatus >= Constants.ERROR_STATUS_CODE_400 && errorStatus < Constants.ERROR_STATUS_CODE_500) { - if (THROTTLING_ERROR_CODES.contains(errorCode)) { - logger.log(String.format(RETRY_MESSAGE, exception.getMessage())); - throw new CfnThrottlingException(OPERATION, exception); - } - } - throw new CfnGeneralServiceException(OPERATION, exception); - } - } - - private ProgressEvent handleTagging( - final AmazonWebServicesClientProxy proxy, - final ProxyClient proxyClient, - final ProgressEvent progress, - final ResourceModel resourceModel, - final Map desiredResourceTags, - final Map previousResourceTags) { - - final Set currentTags = new HashSet<>(Translator.translateTagsToSdk(desiredResourceTags)); - final Set existingTags = new HashSet<>(Translator.translateTagsToSdk(previousResourceTags)); - // Remove tags with aws prefix as they should not be modified once attached - existingTags.removeIf(tag -> tag.key().startsWith("aws")); - - final Set setTagsToRemove = Sets.difference(existingTags, currentTags); - final Set setTagsToAdd = Sets.difference(currentTags, existingTags); - - final List tagsToRemove = setTagsToRemove.stream().collect(Collectors.toList()); - final List tagsToAdd = setTagsToAdd.stream().collect(Collectors.toList()); - - // Deletes tags only if tagsToRemove is not empty. - if (!CollectionUtils.isNullOrEmpty(tagsToRemove)) proxy.injectCredentialsAndInvokeV2( - Translator.removeTagsFromResourceRequest(resourceModel.getName(), tagsToRemove), proxyClient.client()::removeTagsFromResource); - - // Adds tags only if tagsToAdd is not empty. - if (!CollectionUtils.isNullOrEmpty(tagsToAdd)) proxy.injectCredentialsAndInvokeV2( - Translator.addTagsToResourceRequest(resourceModel.getName(), tagsToAdd), proxyClient.client()::addTagsToResource); - - return ProgressEvent.progress(progress.getResourceModel(), progress.getCallbackContext()); - } + private static final String OPERATION = "PutParameter"; + private static final String RETRY_MESSAGE = "Detected retryable error, retrying. Exception message: %s"; + private Logger logger; + + @Override + protected ProgressEvent handleRequest( + final AmazonWebServicesClientProxy proxy, + final ResourceHandlerRequest request, + final CallbackContext callbackContext, + final ProxyClient proxyClient, + final Logger logger) { + this.logger = logger; + final ResourceModel model = request.getDesiredResourceState(); + + if (model.getType().equalsIgnoreCase(ParameterType.SECURE_STRING.toString())) { + String message = String.format("SSM Parameters of type %s cannot be updated using CloudFormation", ParameterType.SECURE_STRING); + return ProgressEvent.defaultFailureHandler(new TerminalException(message), + HandlerErrorCode.InvalidRequest); + } + + return ProgressEvent.progress(model, callbackContext) + // First validate the resource actually exists per the contract requirements + // https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html + .then(progress -> + proxy.initiate("aws-ssm-parameter::validate-resource-exists", proxyClient, model, callbackContext) + .translateToServiceRequest(Translator::getParametersRequest) + .makeServiceCall(this::validateResourceExists) + .progress()) + + .then(progress -> + proxy.initiate("aws-ssm-parameter::resource-update", proxyClient, model, callbackContext) + .translateToServiceRequest(Translator::updatePutParameterRequest) + .backoffDelay(getBackOffDelay(model)) + .makeServiceCall(this::updateResource) + .stabilize(BaseHandlerStd::stabilize) + .progress()) + .then(progress -> handleTagging(proxy, proxyClient, progress, model, request.getDesiredResourceTags(), request.getPreviousResourceTags())) + .then(progress -> new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, logger)); + } + + private GetParametersResponse validateResourceExists(GetParametersRequest getParametersRequest, ProxyClient proxyClient) { + GetParametersResponse getParametersResponse; + + getParametersResponse = proxyClient.injectCredentialsAndInvokeV2(getParametersRequest, proxyClient.client()::getParameters); + if (getParametersResponse.invalidParameters().size() != 0) { + throw new CfnNotFoundException(ResourceModel.TYPE_NAME, getParametersRequest.names().get(0)); + } + + return getParametersResponse; + } + + private PutParameterResponse updateResource(final PutParameterRequest putParameterRequest, + final ProxyClient proxyClient) { + try { + return proxyClient.injectCredentialsAndInvokeV2(putParameterRequest, proxyClient.client()::putParameter); + } catch (final ParameterAlreadyExistsException exception) { + throw new CfnAlreadyExistsException(ResourceModel.TYPE_NAME, putParameterRequest.name()); + } catch (final InternalServerErrorException exception) { + throw new CfnServiceInternalErrorException(OPERATION, exception); + } catch (final AmazonServiceException exception) { + final Integer errorStatus = exception.getStatusCode(); + final String errorCode = exception.getErrorCode(); + if (errorStatus >= Constants.ERROR_STATUS_CODE_400 && errorStatus < Constants.ERROR_STATUS_CODE_500) { + if (THROTTLING_ERROR_CODES.contains(errorCode)) { + logger.log(String.format(RETRY_MESSAGE, exception.getMessage())); + throw new CfnThrottlingException(OPERATION, exception); + } + } + throw new CfnGeneralServiceException(OPERATION, exception); + } + } + + private ProgressEvent handleTagging( + final AmazonWebServicesClientProxy proxy, + final ProxyClient proxyClient, + final ProgressEvent progress, + final ResourceModel resourceModel, + final Map desiredResourceTags, + final Map previousResourceTags) { + + final Set currentTags = new HashSet<>(Translator.translateTagsToSdk(desiredResourceTags)); + final Set existingTags = new HashSet<>(Translator.translateTagsToSdk(previousResourceTags)); + // Remove tags with aws prefix as they should not be modified once attached + existingTags.removeIf(tag -> tag.key().startsWith("aws")); + + final Set setTagsToRemove = Sets.difference(existingTags, currentTags); + final Set setTagsToAdd = Sets.difference(currentTags, existingTags); + + final List tagsToRemove = setTagsToRemove.stream().collect(Collectors.toList()); + final List tagsToAdd = setTagsToAdd.stream().collect(Collectors.toList()); + + // Deletes tags only if tagsToRemove is not empty. + if (!CollectionUtils.isNullOrEmpty(tagsToRemove)) + proxy.injectCredentialsAndInvokeV2( + Translator.removeTagsFromResourceRequest(resourceModel.getName(), tagsToRemove), proxyClient.client()::removeTagsFromResource); + + // Adds tags only if tagsToAdd is not empty. + if (!CollectionUtils.isNullOrEmpty(tagsToAdd)) + proxy.injectCredentialsAndInvokeV2( + Translator.addTagsToResourceRequest(resourceModel.getName(), tagsToAdd), proxyClient.client()::addTagsToResource); + + return ProgressEvent.progress(progress.getResourceModel(), progress.getCallbackContext()); + } } diff --git a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/AbstractTestBase.java b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/AbstractTestBase.java index 765dcd39..6bd0ac21 100644 --- a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/AbstractTestBase.java +++ b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/AbstractTestBase.java @@ -1,10 +1,5 @@ package com.amazonaws.ssm.parameter; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.function.Function; import org.slf4j.LoggerFactory; import software.amazon.awssdk.awscore.AwsRequest; import software.amazon.awssdk.awscore.AwsResponse; @@ -17,91 +12,100 @@ import software.amazon.cloudformation.proxy.LoggerProxy; import software.amazon.cloudformation.proxy.ProxyClient; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + public class AbstractTestBase { - protected static final Credentials MOCK_CREDENTIALS; - protected static final org.slf4j.Logger delegate; - protected static final LoggerProxy logger; + protected static final Credentials MOCK_CREDENTIALS; + protected static final org.slf4j.Logger delegate; + protected static final LoggerProxy logger; - protected static final String DESCRIPTION; - protected static final String NAME; - protected static final String TYPE_STRING; - protected static final String TYPE_SECURE_STRING; - protected static final String VALUE; - protected static final Long VERSION; - protected static final Map TAG_SET; - protected static final Map SYSTEM_TAGS_SET; - protected static final Map PREVIOUS_TAG_SET; + protected static final String DESCRIPTION; + protected static final String NAME; + protected static final String TYPE_STRING; + protected static final String TYPE_SECURE_STRING; + protected static final String VALUE; + protected static final Long VERSION; + protected static final Map TAG_SET; + protected static final Map SYSTEM_TAGS_SET; + protected static final Map PREVIOUS_TAG_SET; - static { - System.setProperty("org.slf4j.simpleLogger.showDateTime", "true"); - System.setProperty("org.slf4j.simpleLogger.dateTimeFormat", "HH:mm:ss:SSS Z"); - MOCK_CREDENTIALS = new Credentials("accessKey", "secretKey", "token"); + static { + System.setProperty("org.slf4j.simpleLogger.showDateTime", "true"); + System.setProperty("org.slf4j.simpleLogger.dateTimeFormat", "HH:mm:ss:SSS Z"); + MOCK_CREDENTIALS = new Credentials("accessKey", "secretKey", "token"); - delegate = LoggerFactory.getLogger("testing"); - logger = new LoggerProxy(); + delegate = LoggerFactory.getLogger("testing"); + logger = new LoggerProxy(); - DESCRIPTION = "sample description"; - NAME = "ParameterName"; - TYPE_STRING = "String"; - TYPE_SECURE_STRING = "SecureString"; - VALUE = "dummy value"; - VERSION = 1L; - TAG_SET = new HashMap() { - { - put("key1", "value1"); - put("key2", "value2"); - } - }; - SYSTEM_TAGS_SET = new HashMap() { - { - put("aws:cloudformation:stack-name", "DummyStackName"); - put("aws:cloudformation:logical-id", "DummyLogicalId"); - put("aws:cloudformation:stack-id", "DummyStackArn"); - } - }; - PREVIOUS_TAG_SET = new HashMap() { - { - put("key3", "value3"); - } - }; - PREVIOUS_TAG_SET.putAll(TAG_SET); - PREVIOUS_TAG_SET.putAll(SYSTEM_TAGS_SET); - } + DESCRIPTION = "sample description"; + NAME = "ParameterName"; + TYPE_STRING = "String"; + TYPE_SECURE_STRING = "SecureString"; + VALUE = "dummy value"; + VERSION = 1L; + TAG_SET = new HashMap() { + { + put("key1", "value1"); + put("key2", "value2"); + } + }; + SYSTEM_TAGS_SET = new HashMap() { + { + put("aws:cloudformation:stack-name", "DummyStackName"); + put("aws:cloudformation:logical-id", "DummyLogicalId"); + put("aws:cloudformation:stack-id", "DummyStackArn"); + } + }; + PREVIOUS_TAG_SET = new HashMap() { + { + put("key3", "value3"); + } + }; + PREVIOUS_TAG_SET.putAll(TAG_SET); + PREVIOUS_TAG_SET.putAll(SYSTEM_TAGS_SET); + } - static ProxyClient MOCK_PROXY( - final AmazonWebServicesClientProxy proxy, - final SsmClient ssmClient - ) { - return new ProxyClient() { - @Override - public ResponseT injectCredentialsAndInvokeV2(RequestT requestT, Function function) { - return proxy.injectCredentialsAndInvokeV2(requestT, function); - } + static ProxyClient MOCK_PROXY( + final AmazonWebServicesClientProxy proxy, + final SsmClient ssmClient + ) { + return new ProxyClient() { + @Override + public ResponseT injectCredentialsAndInvokeV2(RequestT requestT, Function function) { + return proxy.injectCredentialsAndInvokeV2(requestT, function); + } - @Override - public CompletableFuture injectCredentialsAndInvokeV2Async(RequestT requestT, Function> function) { - throw new UnsupportedOperationException(); - } + @Override + public CompletableFuture injectCredentialsAndInvokeV2Async(RequestT requestT, + Function> function) { + throw new UnsupportedOperationException(); + } - @Override - public > IterableT injectCredentialsAndInvokeIterableV2(RequestT requestT, Function function) { - return proxy.injectCredentialsAndInvokeIterableV2(requestT, function); - } + @Override + public > IterableT injectCredentialsAndInvokeIterableV2( + RequestT requestT, Function function) { + return proxy.injectCredentialsAndInvokeIterableV2(requestT, function); + } - @Override - public ResponseInputStream injectCredentialsAndInvokeV2InputStream(RequestT requestT, Function> function) { - throw new UnsupportedOperationException(); - } + @Override + public ResponseInputStream injectCredentialsAndInvokeV2InputStream(RequestT requestT, + Function> function) { + throw new UnsupportedOperationException(); + } - @Override - public ResponseBytes injectCredentialsAndInvokeV2Bytes(RequestT requestT, Function> function) { - throw new UnsupportedOperationException(); - } + @Override + public ResponseBytes injectCredentialsAndInvokeV2Bytes(RequestT requestT, + Function> function) { + throw new UnsupportedOperationException(); + } - @Override - public SsmClient client() { - return ssmClient; - } - }; - } + @Override + public SsmClient client() { + return ssmClient; + } + }; + } } diff --git a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/CreateHandlerTest.java b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/CreateHandlerTest.java index 054c4cce..41df1e3e 100644 --- a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/CreateHandlerTest.java +++ b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/CreateHandlerTest.java @@ -3,398 +3,392 @@ import com.amazonaws.AmazonServiceException; import org.apache.commons.lang3.RandomStringUtils; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import software.amazon.awssdk.services.ssm.SsmClient; -import software.amazon.awssdk.services.ssm.model.GetParametersResponse; import software.amazon.awssdk.services.ssm.model.GetParametersRequest; +import software.amazon.awssdk.services.ssm.model.GetParametersResponse; +import software.amazon.awssdk.services.ssm.model.InternalServerErrorException; import software.amazon.awssdk.services.ssm.model.Parameter; import software.amazon.awssdk.services.ssm.model.ParameterAlreadyExistsException; -import software.amazon.awssdk.services.ssm.model.PutParameterResponse; -import software.amazon.awssdk.services.ssm.model.PutParameterRequest; import software.amazon.awssdk.services.ssm.model.ParameterTier; -import software.amazon.awssdk.services.ssm.model.InternalServerErrorException; +import software.amazon.awssdk.services.ssm.model.PutParameterRequest; +import software.amazon.awssdk.services.ssm.model.PutParameterResponse; import software.amazon.cloudformation.exceptions.CfnAlreadyExistsException; -import software.amazon.cloudformation.exceptions.CfnThrottlingException; import software.amazon.cloudformation.exceptions.CfnGeneralServiceException; 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.ProxyClient; +import software.amazon.cloudformation.proxy.OperationStatus; import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; -import software.amazon.cloudformation.proxy.OperationStatus; - -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.time.Duration; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class CreateHandlerTest extends AbstractTestBase { - @Mock - private AmazonWebServicesClientProxy proxy; - - @Mock - private ProxyClient proxySsmClient; - - @Mock - SsmClient ssmClient; - - private CreateHandler handler; - - private ResourceModel RESOURCE_MODEL; - - @BeforeEach - public void setup() { - handler = new CreateHandler(); - ssmClient = mock(SsmClient.class); - proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis()); - proxySsmClient = MOCK_PROXY(proxy, ssmClient); - - RESOURCE_MODEL = ResourceModel.builder() - .description(DESCRIPTION) - .name(NAME) - .value(VALUE) - .type(TYPE_STRING) - .tags(TAG_SET) - .build(); - } - - @AfterEach - public void post_execute() { - verifyNoMoreInteractions(proxySsmClient.client()); - } - - @Test - public void handleRequest_SimpleSuccess() { - final GetParametersResponse getParametersResponse = GetParametersResponse.builder() - .parameters(Parameter.builder() - .name(NAME) - .type(TYPE_STRING) - .value(VALUE) - .version(VERSION).build()) - .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - - final PutParameterResponse putParameterResponse = PutParameterResponse.builder() - .version(VERSION) - .tier(ParameterTier.STANDARD) - .build(); - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logicalId").build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - - assertThat(response).isNotNull(); - assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); - assertThat(response.getCallbackContext()).isNull(); - assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); - assertThat(response.getResourceModels()).isNull(); - assertThat(response.getMessage()).isNull(); - assertThat(response.getErrorCode()).isNull(); - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - } - - @Test - public void handleRequest_SecureStringFailure() { - RESOURCE_MODEL = ResourceModel.builder() - .description(DESCRIPTION) - .value(VALUE) - .type(TYPE_SECURE_STRING) - .tags(TAG_SET) - .build(); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logicalId").build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - - assertThat(response).isNotNull(); - assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED); - assertThat(response.getCallbackContext()).isNull(); - assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); - assertThat(response.getResourceModels()).isNull(); - assertThat(response.getMessage()).isEqualTo("SSM Parameters of type SecureString cannot be created using CloudFormation"); - assertThat(response.getErrorCode()).isEqualTo(HandlerErrorCode.InvalidRequest); - - verify(ssmClient, never()).serviceName(); - verifyNoMoreInteractions(proxySsmClient.client()); - } - - @Test - public void handleRequest_SimpleSuccess_With_No_ParameterName_Defined_Exceeding_LogicalResourceId() { - final GetParametersResponse getParametersResponse = GetParametersResponse.builder() - .parameters(Parameter.builder() - .name(NAME) - .type(TYPE_STRING) - .value(VALUE) - .version(VERSION).build()) - .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - - final PutParameterResponse putParameterResponse = PutParameterResponse.builder() - .version(VERSION) - .tier(ParameterTier.STANDARD) - .build(); - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); - - RESOURCE_MODEL = ResourceModel.builder() - .description(DESCRIPTION) - .value(VALUE) - .type(TYPE_STRING) - .tags(TAG_SET) - .build(); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier(RandomStringUtils.random(600)).build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - - assertThat(response).isNotNull(); - assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); - assertThat(response.getCallbackContext()).isNull(); - assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); - assertThat(response.getResourceModels()).isNull(); - assertThat(response.getMessage()).isNull(); - assertThat(response.getErrorCode()).isNull(); - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - } - - @Test - public void handleRequest_SimpleSuccess_With_No_ParameterName_Defined_Not_Exceeding_LogicalResourceId() { - final GetParametersResponse getParametersResponse = GetParametersResponse.builder() - .parameters(Parameter.builder() - .name(NAME) - .type(TYPE_STRING) - .value(VALUE) - .version(VERSION).build()) - .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - - final PutParameterResponse putParameterResponse = PutParameterResponse.builder() - .version(VERSION) - .tier(ParameterTier.STANDARD) - .build(); - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); - - RESOURCE_MODEL = ResourceModel.builder() - .description(DESCRIPTION) - .value(VALUE) - .type(TYPE_STRING) - .tags(TAG_SET) - .build(); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logical_id").build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - - assertThat(response).isNotNull(); - assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); - assertThat(response.getCallbackContext()).isNull(); - assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); - assertThat(response.getResourceModels()).isNull(); - assertThat(response.getMessage()).isNull(); - assertThat(response.getErrorCode()).isNull(); - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - } - - @Test - public void handleRequest_SimpleSuccess_WithImageDataType() { - RESOURCE_MODEL = ResourceModel.builder() - .description(DESCRIPTION) - .name(NAME) - .value(VALUE) - .type(TYPE_STRING) - .tags(TAG_SET) - .dataType("aws:ec2:image") - .build(); - - final GetParametersResponse getParametersResponse = GetParametersResponse.builder() - .parameters(Parameter.builder() - .name(NAME) - .type(TYPE_STRING) - .value(VALUE) - .version(VERSION).build()) - .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - - final PutParameterResponse putParameterResponse = PutParameterResponse.builder() - .version(VERSION) - .tier(ParameterTier.STANDARD) - .build(); - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logicalId").build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - - assertThat(response).isNotNull(); - assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); - assertThat(response.getCallbackContext()).isNull(); - assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); - assertThat(response.getResourceModels()).isNull(); - assertThat(response.getMessage()).isNull(); - assertThat(response.getErrorCode()).isNull(); - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - } - - @Test - public void handleRequest_AmazonServiceException400ThrottlingException() { - AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); - amazonServiceException.setStatusCode(429); - amazonServiceException.setErrorCode("ThrottlingException"); - - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) - .thenThrow(amazonServiceException); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logical_id").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnThrottlingException ex) { - assertThat(ex).isInstanceOf(CfnThrottlingException.class); - } - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - } - - @Test - public void handleRequest_ParameterAlreadyExistsException() { - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) - .thenThrow(ParameterAlreadyExistsException.builder().build()); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logical_id").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnAlreadyExistsException ex) { - assertThat(ex).isInstanceOf(CfnAlreadyExistsException.class); - } - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - } - - @Test - public void handleRequest_AmazonServiceException500Exception() { - AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); - amazonServiceException.setStatusCode(500); - - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) - .thenThrow(amazonServiceException); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logical_id").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnGeneralServiceException ex) { - assertThat(ex).isInstanceOf(CfnGeneralServiceException.class); - } - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - } - - @Test - public void handleRequest_AmazonServiceException400NonThrottlingException() { - AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); - amazonServiceException.setStatusCode(400); - amazonServiceException.setErrorCode("Invalid Input"); - - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) - .thenThrow(amazonServiceException); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logical_id").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnGeneralServiceException ex) { - assertThat(ex).isInstanceOf(CfnGeneralServiceException.class); - } - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - } - - @Test - public void handleRequest_AmazonServiceExceptionInternalServerError() { - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) - .thenThrow(InternalServerErrorException.builder().build()); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logical_id").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnServiceInternalErrorException ex) { - assertThat(ex).isInstanceOf(CfnServiceInternalErrorException.class); - } - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - } + @Mock + private AmazonWebServicesClientProxy proxy; + + @Mock + private ProxyClient proxySsmClient; + + @Mock + SsmClient ssmClient; + + private CreateHandler handler; + + private ResourceModel RESOURCE_MODEL; + + @BeforeEach + public void setup() { + handler = new CreateHandler(); + ssmClient = mock(SsmClient.class); + proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis()); + proxySsmClient = MOCK_PROXY(proxy, ssmClient); + + RESOURCE_MODEL = ResourceModel.builder() + .description(DESCRIPTION) + .name(NAME) + .value(VALUE) + .type(TYPE_STRING) + .tags(TAG_SET) + .build(); + } + + @AfterEach + public void post_execute() { + verifyNoMoreInteractions(proxySsmClient.client()); + } + + @Test + public void handleRequest_SimpleSuccess() { + final GetParametersResponse getParametersResponse = GetParametersResponse.builder() + .parameters(Parameter.builder() + .name(NAME) + .type(TYPE_STRING) + .value(VALUE) + .version(VERSION).build()) + .build(); + when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + + final PutParameterResponse putParameterResponse = PutParameterResponse.builder() + .version(VERSION) + .tier(ParameterTier.STANDARD) + .build(); + when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .logicalResourceIdentifier("logicalId").build(); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); + assertThat(response.getCallbackContext()).isNull(); + assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); + assertThat(response.getResourceModels()).isNull(); + assertThat(response.getMessage()).isNull(); + assertThat(response.getErrorCode()).isNull(); + + verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); + verify(ssmClient, atLeastOnce()).serviceName(); + } + + @Test + public void handleRequest_SecureStringFailure() { + RESOURCE_MODEL = ResourceModel.builder() + .description(DESCRIPTION) + .value(VALUE) + .type(TYPE_SECURE_STRING) + .tags(TAG_SET) + .build(); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .logicalResourceIdentifier("logicalId").build(); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED); + assertThat(response.getCallbackContext()).isNull(); + assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); + assertThat(response.getResourceModels()).isNull(); + assertThat(response.getMessage()).isEqualTo("SSM Parameters of type SecureString cannot be created using CloudFormation"); + assertThat(response.getErrorCode()).isEqualTo(HandlerErrorCode.InvalidRequest); + + verify(ssmClient, never()).serviceName(); + verifyNoMoreInteractions(proxySsmClient.client()); + } + + @Test + public void handleRequest_SimpleSuccess_With_No_ParameterName_Defined_Exceeding_LogicalResourceId() { + final GetParametersResponse getParametersResponse = GetParametersResponse.builder() + .parameters(Parameter.builder() + .name(NAME) + .type(TYPE_STRING) + .value(VALUE) + .version(VERSION).build()) + .build(); + when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + + final PutParameterResponse putParameterResponse = PutParameterResponse.builder() + .version(VERSION) + .tier(ParameterTier.STANDARD) + .build(); + when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); + + RESOURCE_MODEL = ResourceModel.builder() + .description(DESCRIPTION) + .value(VALUE) + .type(TYPE_STRING) + .tags(TAG_SET) + .build(); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .logicalResourceIdentifier(RandomStringUtils.random(600)).build(); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); + assertThat(response.getCallbackContext()).isNull(); + assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); + assertThat(response.getResourceModels()).isNull(); + assertThat(response.getMessage()).isNull(); + assertThat(response.getErrorCode()).isNull(); + + verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); + verify(ssmClient, atLeastOnce()).serviceName(); + } + + @Test + public void handleRequest_SimpleSuccess_With_No_ParameterName_Defined_Not_Exceeding_LogicalResourceId() { + final GetParametersResponse getParametersResponse = GetParametersResponse.builder() + .parameters(Parameter.builder() + .name(NAME) + .type(TYPE_STRING) + .value(VALUE) + .version(VERSION).build()) + .build(); + when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + + final PutParameterResponse putParameterResponse = PutParameterResponse.builder() + .version(VERSION) + .tier(ParameterTier.STANDARD) + .build(); + when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); + + RESOURCE_MODEL = ResourceModel.builder() + .description(DESCRIPTION) + .value(VALUE) + .type(TYPE_STRING) + .tags(TAG_SET) + .build(); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .logicalResourceIdentifier("logical_id").build(); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); + assertThat(response.getCallbackContext()).isNull(); + assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); + assertThat(response.getResourceModels()).isNull(); + assertThat(response.getMessage()).isNull(); + assertThat(response.getErrorCode()).isNull(); + + verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); + verify(ssmClient, atLeastOnce()).serviceName(); + } + + @Test + public void handleRequest_SimpleSuccess_WithImageDataType() { + RESOURCE_MODEL = ResourceModel.builder() + .description(DESCRIPTION) + .name(NAME) + .value(VALUE) + .type(TYPE_STRING) + .tags(TAG_SET) + .dataType("aws:ec2:image") + .build(); + + final GetParametersResponse getParametersResponse = GetParametersResponse.builder() + .parameters(Parameter.builder() + .name(NAME) + .type(TYPE_STRING) + .value(VALUE) + .version(VERSION).build()) + .build(); + when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + + final PutParameterResponse putParameterResponse = PutParameterResponse.builder() + .version(VERSION) + .tier(ParameterTier.STANDARD) + .build(); + when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .logicalResourceIdentifier("logicalId").build(); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); + assertThat(response.getCallbackContext()).isNull(); + assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); + assertThat(response.getResourceModels()).isNull(); + assertThat(response.getMessage()).isNull(); + assertThat(response.getErrorCode()).isNull(); + + verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); + verify(ssmClient, atLeastOnce()).serviceName(); + } + + @Test + public void handleRequest_AmazonServiceException400ThrottlingException() { + AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); + amazonServiceException.setStatusCode(429); + amazonServiceException.setErrorCode("ThrottlingException"); + + when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) + .thenThrow(amazonServiceException); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .logicalResourceIdentifier("logical_id").build(); + + try { + handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + } catch (CfnThrottlingException ex) { + assertThat(ex).isInstanceOf(CfnThrottlingException.class); + } + + verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); + verify(ssmClient, atLeastOnce()).serviceName(); + } + + @Test + public void handleRequest_ParameterAlreadyExistsException() { + when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) + .thenThrow(ParameterAlreadyExistsException.builder().build()); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .logicalResourceIdentifier("logical_id").build(); + + try { + handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + } catch (CfnAlreadyExistsException ex) { + assertThat(ex).isInstanceOf(CfnAlreadyExistsException.class); + } + + verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); + verify(ssmClient, atLeastOnce()).serviceName(); + } + + @Test + public void handleRequest_AmazonServiceException500Exception() { + AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); + amazonServiceException.setStatusCode(500); + + when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) + .thenThrow(amazonServiceException); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .logicalResourceIdentifier("logical_id").build(); + + try { + handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + } catch (CfnGeneralServiceException ex) { + assertThat(ex).isInstanceOf(CfnGeneralServiceException.class); + } + + verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); + verify(ssmClient, atLeastOnce()).serviceName(); + } + + @Test + public void handleRequest_AmazonServiceException400NonThrottlingException() { + AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); + amazonServiceException.setStatusCode(400); + amazonServiceException.setErrorCode("Invalid Input"); + + when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) + .thenThrow(amazonServiceException); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .logicalResourceIdentifier("logical_id").build(); + + try { + handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + } catch (CfnGeneralServiceException ex) { + assertThat(ex).isInstanceOf(CfnGeneralServiceException.class); + } + + verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); + verify(ssmClient, atLeastOnce()).serviceName(); + } + + @Test + public void handleRequest_AmazonServiceExceptionInternalServerError() { + when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) + .thenThrow(InternalServerErrorException.builder().build()); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .logicalResourceIdentifier("logical_id").build(); + + try { + handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + } catch (CfnServiceInternalErrorException ex) { + assertThat(ex).isInstanceOf(CfnServiceInternalErrorException.class); + } + + verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); + verify(ssmClient, atLeastOnce()).serviceName(); + } } diff --git a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/DeleteHandlerTest.java b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/DeleteHandlerTest.java index 0a8582e1..8f44a4d8 100644 --- a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/DeleteHandlerTest.java +++ b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/DeleteHandlerTest.java @@ -2,208 +2,203 @@ import com.amazonaws.AmazonServiceException; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.awssdk.services.ssm.model.DeleteParameterRequest; -import software.amazon.awssdk.services.ssm.model.ParameterNotFoundException; import software.amazon.awssdk.services.ssm.model.InternalServerErrorException; +import software.amazon.awssdk.services.ssm.model.ParameterNotFoundException; import software.amazon.cloudformation.exceptions.CfnGeneralServiceException; 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.ProxyClient; import software.amazon.cloudformation.proxy.OperationStatus; import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; -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.time.Duration; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class DeleteHandlerTest extends AbstractTestBase { - @Mock - private AmazonWebServicesClientProxy proxy; - - @Mock - private ProxyClient proxySsmClient; - - @Mock - SsmClient ssmClient; - - private DeleteHandler handler; - - private ResourceModel RESOURCE_MODEL; - - @BeforeEach - public void setup() { - handler = new DeleteHandler(); - ssmClient = mock(SsmClient.class); - proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis()); - proxySsmClient = MOCK_PROXY(proxy, ssmClient); - - RESOURCE_MODEL = ResourceModel.builder() - .description(DESCRIPTION) - .name(NAME) - .value(VALUE) - .type(TYPE_STRING) - .tags(TAG_SET) - .build(); - } - - @AfterEach - public void post_execute() { - verify(ssmClient, atLeastOnce()).serviceName(); - verifyNoMoreInteractions(proxySsmClient.client()); - } - - @Test - public void handleRequest_SimpleSuccess() { - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logicalId").build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - - assertThat(response).isNotNull(); - assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); - assertThat(response.getCallbackContext()).isNull(); - assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); - assertThat(response.getResourceModels()).isNull(); - assertThat(response.getMessage()).isNull(); - assertThat(response.getErrorCode()).isNull(); - - verify(proxySsmClient.client()).deleteParameter(any(DeleteParameterRequest.class)); - } - - @Test - public void handleRequest_Failure_ParameterNotFound() { - when(proxySsmClient.client().deleteParameter(any(DeleteParameterRequest.class))) - .thenThrow(ParameterNotFoundException.builder().build()); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logicalId").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnNotFoundException ex) { - assertThat(ex).isInstanceOf(CfnNotFoundException.class); - } - - verify(proxySsmClient.client()).deleteParameter(any(DeleteParameterRequest.class)); - } - - @Test - public void handleRequest_ThrottlingException() { - AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); - amazonServiceException.setStatusCode(429); - amazonServiceException.setErrorCode("ThrottlingException"); - - when(proxySsmClient.client().deleteParameter(any(DeleteParameterRequest.class))) - .thenThrow(amazonServiceException); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logical_id").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnThrottlingException ex) { - assertThat(ex).isInstanceOf(CfnThrottlingException.class); - } - - verify(proxySsmClient.client()).deleteParameter(any(DeleteParameterRequest.class)); - } - - @Test - public void handleRequest_NonThrottlingAmazonServiceException() { - AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); - amazonServiceException.setStatusCode(500); - - when(proxySsmClient.client().deleteParameter(any(DeleteParameterRequest.class))) - .thenThrow(amazonServiceException); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logical_id").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnGeneralServiceException ex) { - assertThat(ex).isInstanceOf(CfnGeneralServiceException.class); - } - - verify(proxySsmClient.client()).deleteParameter(any(DeleteParameterRequest.class)); - } - - @Test - public void handleRequest_AmazonServiceExceptionInternalServerError() { - when(proxySsmClient.client().deleteParameter(any(DeleteParameterRequest.class))) - .thenThrow(InternalServerErrorException.builder().build()); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logical_id").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnServiceInternalErrorException ex) { - assertThat(ex).isInstanceOf(CfnServiceInternalErrorException.class); - } - - verify(proxySsmClient.client()).deleteParameter(any(DeleteParameterRequest.class)); - } - - @Test - public void handleRequest_AmazonServiceException400NonThrottlingException() { - AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); - amazonServiceException.setStatusCode(400); - amazonServiceException.setErrorCode("Invalid Input"); - - when(proxySsmClient.client().deleteParameter(any(DeleteParameterRequest.class))) - .thenThrow(amazonServiceException); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logical_id").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnGeneralServiceException ex) { - assertThat(ex).isInstanceOf(CfnGeneralServiceException.class); - } - - verify(proxySsmClient.client()).deleteParameter(any(DeleteParameterRequest.class)); - } + @Mock + private AmazonWebServicesClientProxy proxy; + + @Mock + private ProxyClient proxySsmClient; + + @Mock + SsmClient ssmClient; + + private DeleteHandler handler; + + private ResourceModel RESOURCE_MODEL; + + @BeforeEach + public void setup() { + handler = new DeleteHandler(); + ssmClient = mock(SsmClient.class); + proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis()); + proxySsmClient = MOCK_PROXY(proxy, ssmClient); + + RESOURCE_MODEL = ResourceModel.builder() + .description(DESCRIPTION) + .name(NAME) + .value(VALUE) + .type(TYPE_STRING) + .tags(TAG_SET) + .build(); + } + + @AfterEach + public void post_execute() { + verify(ssmClient, atLeastOnce()).serviceName(); + verifyNoMoreInteractions(proxySsmClient.client()); + } + + @Test + public void handleRequest_SimpleSuccess() { + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .logicalResourceIdentifier("logicalId").build(); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); + assertThat(response.getCallbackContext()).isNull(); + assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); + assertThat(response.getResourceModels()).isNull(); + assertThat(response.getMessage()).isNull(); + assertThat(response.getErrorCode()).isNull(); + + verify(proxySsmClient.client()).deleteParameter(any(DeleteParameterRequest.class)); + } + + @Test + public void handleRequest_Failure_ParameterNotFound() { + when(proxySsmClient.client().deleteParameter(any(DeleteParameterRequest.class))) + .thenThrow(ParameterNotFoundException.builder().build()); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .logicalResourceIdentifier("logicalId").build(); + + try { + handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + } catch (CfnNotFoundException ex) { + assertThat(ex).isInstanceOf(CfnNotFoundException.class); + } + + verify(proxySsmClient.client()).deleteParameter(any(DeleteParameterRequest.class)); + } + + @Test + public void handleRequest_ThrottlingException() { + AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); + amazonServiceException.setStatusCode(429); + amazonServiceException.setErrorCode("ThrottlingException"); + + when(proxySsmClient.client().deleteParameter(any(DeleteParameterRequest.class))) + .thenThrow(amazonServiceException); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .logicalResourceIdentifier("logical_id").build(); + + try { + handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + } catch (CfnThrottlingException ex) { + assertThat(ex).isInstanceOf(CfnThrottlingException.class); + } + + verify(proxySsmClient.client()).deleteParameter(any(DeleteParameterRequest.class)); + } + + @Test + public void handleRequest_NonThrottlingAmazonServiceException() { + AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); + amazonServiceException.setStatusCode(500); + + when(proxySsmClient.client().deleteParameter(any(DeleteParameterRequest.class))) + .thenThrow(amazonServiceException); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .logicalResourceIdentifier("logical_id").build(); + + try { + handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + } catch (CfnGeneralServiceException ex) { + assertThat(ex).isInstanceOf(CfnGeneralServiceException.class); + } + + verify(proxySsmClient.client()).deleteParameter(any(DeleteParameterRequest.class)); + } + + @Test + public void handleRequest_AmazonServiceExceptionInternalServerError() { + when(proxySsmClient.client().deleteParameter(any(DeleteParameterRequest.class))) + .thenThrow(InternalServerErrorException.builder().build()); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .logicalResourceIdentifier("logical_id").build(); + + try { + handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + } catch (CfnServiceInternalErrorException ex) { + assertThat(ex).isInstanceOf(CfnServiceInternalErrorException.class); + } + + verify(proxySsmClient.client()).deleteParameter(any(DeleteParameterRequest.class)); + } + + @Test + public void handleRequest_AmazonServiceException400NonThrottlingException() { + AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); + amazonServiceException.setStatusCode(400); + amazonServiceException.setErrorCode("Invalid Input"); + + when(proxySsmClient.client().deleteParameter(any(DeleteParameterRequest.class))) + .thenThrow(amazonServiceException); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .logicalResourceIdentifier("logical_id").build(); + + try { + handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + } catch (CfnGeneralServiceException ex) { + assertThat(ex).isInstanceOf(CfnGeneralServiceException.class); + } + + verify(proxySsmClient.client()).deleteParameter(any(DeleteParameterRequest.class)); + } } diff --git a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/ListHandlerTest.java b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/ListHandlerTest.java index 1bf3efe7..c039afe8 100644 --- a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/ListHandlerTest.java +++ b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/ListHandlerTest.java @@ -1,92 +1,88 @@ package com.amazonaws.ssm.parameter; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.awssdk.services.ssm.model.DescribeParametersRequest; import software.amazon.awssdk.services.ssm.model.DescribeParametersResponse; import software.amazon.awssdk.services.ssm.model.ParameterMetadata; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; -import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.OperationStatus; import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; -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.time.Duration; import java.util.Collections; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class ListHandlerTest extends AbstractTestBase { - @Mock - private AmazonWebServicesClientProxy proxy; - - @Mock - private ProxyClient proxySsmClient; - - @Mock - SsmClient ssmClient; - - private ListHandler handler; - - private static final String PARAMETER_NAME = "parameterName"; - private static final String NEXT_TOKEN = "4b90a7e4-b790-456b"; - - @BeforeEach - public void setup() { - handler = new ListHandler(); - ssmClient = mock(SsmClient.class); - proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis()); - proxySsmClient = MOCK_PROXY(proxy, ssmClient); - } - - @AfterEach - public void post_execute() { - verifyNoMoreInteractions(proxySsmClient.client()); - } - - @Test - public void handleRequest_SimpleSuccess() { - final DescribeParametersResponse describeParametersResponse = DescribeParametersResponse.builder() - .parameters(Collections.singletonList(ParameterMetadata.builder().name(PARAMETER_NAME).build())) - .nextToken(NEXT_TOKEN).build(); - when(proxySsmClient.client().describeParameters(any(DescribeParametersRequest.class))) - .thenReturn(describeParametersResponse); - - final ResourceModel model = ResourceModel.builder().build(); - - final ResourceModel expectedModel = ResourceModel.builder().name(PARAMETER_NAME).build(); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - - final ProgressEvent response = - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, 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.getResourceModels()).containsExactly(expectedModel); - assertThat(response.getMessage()).isNull(); - assertThat(response.getErrorCode()).isNull(); - assertThat(response.getNextToken()).isEqualTo(NEXT_TOKEN); - - verify(proxySsmClient.client()).describeParameters(any(DescribeParametersRequest.class)); - } + @Mock + private AmazonWebServicesClientProxy proxy; + + @Mock + private ProxyClient proxySsmClient; + + @Mock + SsmClient ssmClient; + + private ListHandler handler; + + private static final String PARAMETER_NAME = "parameterName"; + private static final String NEXT_TOKEN = "4b90a7e4-b790-456b"; + + @BeforeEach + public void setup() { + handler = new ListHandler(); + ssmClient = mock(SsmClient.class); + proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis()); + proxySsmClient = MOCK_PROXY(proxy, ssmClient); + } + + @AfterEach + public void post_execute() { + verifyNoMoreInteractions(proxySsmClient.client()); + } + + @Test + public void handleRequest_SimpleSuccess() { + final DescribeParametersResponse describeParametersResponse = DescribeParametersResponse.builder() + .parameters(Collections.singletonList(ParameterMetadata.builder().name(PARAMETER_NAME).build())) + .nextToken(NEXT_TOKEN).build(); + when(proxySsmClient.client().describeParameters(any(DescribeParametersRequest.class))) + .thenReturn(describeParametersResponse); + + final ResourceModel model = ResourceModel.builder().build(); + + final ResourceModel expectedModel = ResourceModel.builder().name(PARAMETER_NAME).build(); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .desiredResourceState(model) + .build(); + + final ProgressEvent response = + handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, 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.getResourceModels()).containsExactly(expectedModel); + assertThat(response.getMessage()).isNull(); + assertThat(response.getErrorCode()).isNull(); + assertThat(response.getNextToken()).isEqualTo(NEXT_TOKEN); + + verify(proxySsmClient.client()).describeParameters(any(DescribeParametersRequest.class)); + } } diff --git a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/ReadHandlerTest.java b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/ReadHandlerTest.java index 0607cb11..be8a2366 100644 --- a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/ReadHandlerTest.java +++ b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/ReadHandlerTest.java @@ -1,6 +1,11 @@ package com.amazonaws.ssm.parameter; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.awssdk.services.ssm.model.GetParametersRequest; import software.amazon.awssdk.services.ssm.model.GetParametersResponse; @@ -9,118 +14,108 @@ import software.amazon.cloudformation.exceptions.CfnNotFoundException; import software.amazon.cloudformation.exceptions.CfnServiceInternalErrorException; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; -import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.OperationStatus; import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; -import software.amazon.cloudformation.proxy.OperationStatus; - -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.time.Duration; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class ReadHandlerTest extends AbstractTestBase { - @Mock - private AmazonWebServicesClientProxy proxy; - - @Mock - private ProxyClient proxySsmClient; - - @Mock - SsmClient ssmClient; - - private ReadHandler handler; - - @BeforeEach - public void setup() { - handler = new ReadHandler(); - ssmClient = mock(SsmClient.class); - proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis()); - proxySsmClient = MOCK_PROXY(proxy, ssmClient); - } - - @AfterEach - public void post_execute() { - verify(ssmClient, atLeastOnce()).serviceName(); - verifyNoMoreInteractions(proxySsmClient.client()); - } - - @Test - public void handleRequest_SimpleSuccess() { - final GetParametersResponse getParametersResponse = GetParametersResponse.builder() - .parameters(Parameter.builder() - .name(NAME) - .type(TYPE_STRING) - .value(VALUE).build()) - .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(ResourceModel.builder() - .build()) - .build(); - final CallbackContext callbackContext = new CallbackContext(); - final ProgressEvent response = handler.handleRequest(proxy, request, callbackContext, proxySsmClient, logger); - - assertThat(response).isNotNull(); - assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); - assertThat(response.getCallbackContext()).isNull(); - assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); - assertThat(response.getResourceModels()).isNull(); - assertThat(response.getMessage()).isNull(); - assertThat(response.getErrorCode()).isNull(); - - verify(proxySsmClient.client()).getParameters(any(GetParametersRequest.class)); - } - - @Test - public void handleRequest_EmptyGetParametersResponse() { - final GetParametersResponse getParametersResponse = GetParametersResponse.builder().build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(ResourceModel.builder().name("ParameterName") - .build()) - .build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnNotFoundException ex) { - assertThat(ex).isInstanceOf(CfnNotFoundException.class); - } - - verify(proxySsmClient.client()).getParameters(any(GetParametersRequest.class)); - } - - @Test - public void handleRequest_AmazonServiceExceptionInternalServerError() { - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))) - .thenThrow(InternalServerErrorException.builder().build()); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(ResourceModel.builder() - .build()) - .build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnServiceInternalErrorException ex) { - assertThat(ex).isInstanceOf(CfnServiceInternalErrorException.class); - } - - verify(proxySsmClient.client()).getParameters(any(GetParametersRequest.class)); - } + @Mock + private AmazonWebServicesClientProxy proxy; + + @Mock + private ProxyClient proxySsmClient; + + @Mock + SsmClient ssmClient; + + private ReadHandler handler; + + @BeforeEach + public void setup() { + handler = new ReadHandler(); + ssmClient = mock(SsmClient.class); + proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis()); + proxySsmClient = MOCK_PROXY(proxy, ssmClient); + } + + @AfterEach + public void post_execute() { + verify(ssmClient, atLeastOnce()).serviceName(); + verifyNoMoreInteractions(proxySsmClient.client()); + } + + @Test + public void handleRequest_SimpleSuccess() { + final GetParametersResponse getParametersResponse = GetParametersResponse.builder() + .parameters(Parameter.builder() + .name(NAME) + .type(TYPE_STRING) + .value(VALUE).build()) + .build(); + when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .desiredResourceState(ResourceModel.builder() + .build()) + .build(); + final CallbackContext callbackContext = new CallbackContext(); + final ProgressEvent response = handler.handleRequest(proxy, request, callbackContext, proxySsmClient, logger); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); + assertThat(response.getCallbackContext()).isNull(); + assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); + assertThat(response.getResourceModels()).isNull(); + assertThat(response.getMessage()).isNull(); + assertThat(response.getErrorCode()).isNull(); + + verify(proxySsmClient.client()).getParameters(any(GetParametersRequest.class)); + } + + @Test + public void handleRequest_EmptyGetParametersResponse() { + final GetParametersResponse getParametersResponse = GetParametersResponse.builder().build(); + when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .desiredResourceState(ResourceModel.builder().name("ParameterName") + .build()) + .build(); + + try { + handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + } catch (CfnNotFoundException ex) { + assertThat(ex).isInstanceOf(CfnNotFoundException.class); + } + + verify(proxySsmClient.client()).getParameters(any(GetParametersRequest.class)); + } + + @Test + public void handleRequest_AmazonServiceExceptionInternalServerError() { + when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))) + .thenThrow(InternalServerErrorException.builder().build()); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .desiredResourceState(ResourceModel.builder() + .build()) + .build(); + + try { + handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + } catch (CfnServiceInternalErrorException ex) { + assertThat(ex).isInstanceOf(CfnServiceInternalErrorException.class); + } + + verify(proxySsmClient.client()).getParameters(any(GetParametersRequest.class)); + } } diff --git a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/UpdateHandlerTest.java b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/UpdateHandlerTest.java index c5fdcaf6..64f812b6 100644 --- a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/UpdateHandlerTest.java +++ b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/UpdateHandlerTest.java @@ -1,17 +1,22 @@ package com.amazonaws.ssm.parameter; import com.amazonaws.AmazonServiceException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.awssdk.services.ssm.model.AddTagsToResourceRequest; import software.amazon.awssdk.services.ssm.model.AddTagsToResourceResponse; -import software.amazon.awssdk.services.ssm.model.GetParametersResponse; import software.amazon.awssdk.services.ssm.model.GetParametersRequest; -import software.amazon.awssdk.services.ssm.model.Parameter; +import software.amazon.awssdk.services.ssm.model.GetParametersResponse; import software.amazon.awssdk.services.ssm.model.InternalServerErrorException; +import software.amazon.awssdk.services.ssm.model.Parameter; import software.amazon.awssdk.services.ssm.model.ParameterAlreadyExistsException; -import software.amazon.awssdk.services.ssm.model.PutParameterResponse; -import software.amazon.awssdk.services.ssm.model.PutParameterRequest; import software.amazon.awssdk.services.ssm.model.ParameterTier; +import software.amazon.awssdk.services.ssm.model.PutParameterRequest; +import software.amazon.awssdk.services.ssm.model.PutParameterResponse; import software.amazon.awssdk.services.ssm.model.RemoveTagsFromResourceRequest; import software.amazon.awssdk.services.ssm.model.RemoveTagsFromResourceResponse; import software.amazon.cloudformation.exceptions.CfnAlreadyExistsException; @@ -20,16 +25,10 @@ import software.amazon.cloudformation.exceptions.CfnThrottlingException; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; import software.amazon.cloudformation.proxy.HandlerErrorCode; -import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.OperationStatus; import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; -import software.amazon.cloudformation.proxy.OperationStatus; - -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.time.Duration; import java.util.HashMap; @@ -37,433 +36,428 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class UpdateHandlerTest extends AbstractTestBase { - @Mock - private AmazonWebServicesClientProxy proxy; - - @Mock - private ProxyClient proxySsmClient; - - @Mock - SsmClient ssmClient; - - private UpdateHandler handler; - - private ResourceModel RESOURCE_MODEL; - - private Map PREVIOUS_TAG_SET_NO_CHANGE; - - private Map TAG_SET_WITH_CHANGE; - - @BeforeEach - public void setup() { - handler = new UpdateHandler(); - ssmClient = mock(SsmClient.class); - proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis()); - proxySsmClient = MOCK_PROXY(proxy, ssmClient); - - RESOURCE_MODEL = ResourceModel.builder() - .description(DESCRIPTION) - .name(NAME) - .value(VALUE) - .type(TYPE_STRING) - .tags(TAG_SET) - .build(); - - PREVIOUS_TAG_SET_NO_CHANGE = new HashMap(); - TAG_SET_WITH_CHANGE = new HashMap(); - } - - @Test - public void handleRequest_SimpleSuccess() { - final GetParametersResponse getParametersResponse = GetParametersResponse.builder() - .parameters(Parameter.builder() - .name(NAME) - .type(TYPE_STRING) - .value(VALUE) - .version(VERSION).build()) - .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - - final PutParameterResponse putParameterResponse = PutParameterResponse.builder() - .version(VERSION) - .tier(ParameterTier.STANDARD) - .build(); - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); - - final RemoveTagsFromResourceResponse removeTagsFromResourceResponse = RemoveTagsFromResourceResponse.builder().build(); - when(proxySsmClient.client().removeTagsFromResource(any(RemoveTagsFromResourceRequest.class))).thenReturn(removeTagsFromResourceResponse); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .previousResourceTags(PREVIOUS_TAG_SET) - .logicalResourceIdentifier("logicalId").build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - - assertThat(response).isNotNull(); - assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); - assertThat(response.getCallbackContext()).isNull(); - assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); - assertThat(response.getResourceModels()).isNull(); - assertThat(response.getMessage()).isNull(); - assertThat(response.getErrorCode()).isNull(); - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - } - - @Test - public void handleRequest_SimpleSuccess_WithoutTagsChange() { - PREVIOUS_TAG_SET_NO_CHANGE.putAll(TAG_SET); - PREVIOUS_TAG_SET_NO_CHANGE.putAll(SYSTEM_TAGS_SET); - - final GetParametersResponse getParametersResponse = GetParametersResponse.builder() - .parameters(Parameter.builder() - .name(NAME) - .type(TYPE_STRING) - .value(VALUE) - .version(VERSION).build()) - .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - - final PutParameterResponse putParameterResponse = PutParameterResponse.builder() - .version(VERSION) - .tier(ParameterTier.STANDARD) - .build(); - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .previousResourceTags(PREVIOUS_TAG_SET_NO_CHANGE) - .logicalResourceIdentifier("logicalId").build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - - assertThat(response).isNotNull(); - assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); - assertThat(response.getCallbackContext()).isNull(); - assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); - assertThat(response.getResourceModels()).isNull(); - assertThat(response.getMessage()).isNull(); - assertThat(response.getErrorCode()).isNull(); - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - } - - @Test - public void handleRequest_SimpleSuccess_WithTagsChange() { - TAG_SET_WITH_CHANGE.put("AddTagKey", "AddTagValue"); - PREVIOUS_TAG_SET_NO_CHANGE.putAll(TAG_SET); - PREVIOUS_TAG_SET_NO_CHANGE.putAll(SYSTEM_TAGS_SET); - - final GetParametersResponse getParametersResponse = GetParametersResponse.builder() - .parameters(Parameter.builder() - .name(NAME) - .type(TYPE_STRING) - .value(VALUE) - .version(VERSION).build()) - .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - - final PutParameterResponse putParameterResponse = PutParameterResponse.builder() - .version(VERSION) - .tier(ParameterTier.STANDARD) - .build(); - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); - - final RemoveTagsFromResourceResponse removeTagsFromResourceResponse = RemoveTagsFromResourceResponse.builder().build(); - when(proxySsmClient.client().removeTagsFromResource(any(RemoveTagsFromResourceRequest.class))).thenReturn(removeTagsFromResourceResponse); - final AddTagsToResourceResponse addTagsToResourceResponse = AddTagsToResourceResponse.builder().build(); - when(proxySsmClient.client().addTagsToResource(any(AddTagsToResourceRequest.class))).thenReturn(addTagsToResourceResponse); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET_WITH_CHANGE) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .previousResourceTags(PREVIOUS_TAG_SET_NO_CHANGE) - .logicalResourceIdentifier("logicalId").build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - - assertThat(response).isNotNull(); - assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); - assertThat(response.getCallbackContext()).isNull(); - assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); - assertThat(response.getResourceModels()).isNull(); - assertThat(response.getMessage()).isNull(); - assertThat(response.getErrorCode()).isNull(); - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - } - - @Test - public void handleRequest_SecureStringFailure() { - RESOURCE_MODEL = ResourceModel.builder() - .description(DESCRIPTION) - .value(VALUE) - .type(TYPE_SECURE_STRING) - .tags(TAG_SET) - .build(); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .previousResourceTags(PREVIOUS_TAG_SET) - .logicalResourceIdentifier("logicalId").build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - - assertThat(response).isNotNull(); - assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED); - assertThat(response.getCallbackContext()).isNull(); - assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); - assertThat(response.getResourceModels()).isNull(); - assertThat(response.getMessage()).isEqualTo("SSM Parameters of type SecureString cannot be updated using CloudFormation"); - assertThat(response.getErrorCode()).isEqualTo(HandlerErrorCode.InvalidRequest); - - verify(ssmClient, never()).serviceName(); - verifyNoMoreInteractions(proxySsmClient.client()); - } - - @Test - public void handleRequest_SimpleSuccess_WithImageDataType() { - final PutParameterResponse putParameterResponse = PutParameterResponse.builder() - .version(VERSION) - .tier(ParameterTier.STANDARD) - .build(); - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); - - RESOURCE_MODEL = ResourceModel.builder() - .description(DESCRIPTION) - .name(NAME) - .value(VALUE) - .type(TYPE_STRING) - .tags(TAG_SET) - .dataType("aws:ec2:image") - .build(); - - final GetParametersResponse getParametersResponse = GetParametersResponse.builder() - .parameters(Parameter.builder() - .name(NAME) - .type(TYPE_STRING) - .value(VALUE) - .version(VERSION).build()) - .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - - final RemoveTagsFromResourceResponse removeTagsFromResourceResponse = RemoveTagsFromResourceResponse.builder().build(); - when(proxySsmClient.client().removeTagsFromResource(any(RemoveTagsFromResourceRequest.class))).thenReturn(removeTagsFromResourceResponse); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .previousResourceTags(PREVIOUS_TAG_SET) - .logicalResourceIdentifier("logicalId").build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - - assertThat(response).isNotNull(); - assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); - assertThat(response.getCallbackContext()).isNull(); - assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); - assertThat(response.getResourceModels()).isNull(); - assertThat(response.getMessage()).isNull(); - assertThat(response.getErrorCode()).isNull(); - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - } - - @Test - public void handleRequest_AmazonServiceException400ThrottlingException() { - AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); - amazonServiceException.setStatusCode(429); - amazonServiceException.setErrorCode("ThrottlingException"); - - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) - .thenThrow(amazonServiceException); - - final GetParametersResponse getParametersResponse = GetParametersResponse.builder() - .parameters(Parameter.builder() - .name(NAME) - .type(TYPE_STRING) - .value(VALUE) - .version(VERSION).build()) - .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .previousResourceTags(PREVIOUS_TAG_SET) - .logicalResourceIdentifier("logical_id").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnThrottlingException ex) { - assertThat(ex).isInstanceOf(CfnThrottlingException.class); - } - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - verifyNoMoreInteractions(proxySsmClient.client()); - } - - @Test - public void handleRequest_ParameterAlreadyExistsException() { - final GetParametersResponse getParametersResponse = GetParametersResponse.builder() - .parameters(Parameter.builder() - .name(NAME) - .type(TYPE_STRING) - .value(VALUE) - .version(VERSION).build()) - .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) - .thenThrow(ParameterAlreadyExistsException.builder().build()); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .previousResourceTags(PREVIOUS_TAG_SET) - .logicalResourceIdentifier("logical_id").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnAlreadyExistsException ex) { - assertThat(ex).isInstanceOf(CfnAlreadyExistsException.class); - } - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - verifyNoMoreInteractions(proxySsmClient.client()); - } - - @Test - public void handleRequest_AmazonServiceException500Exception() { - final GetParametersResponse getParametersResponse = GetParametersResponse.builder() - .parameters(Parameter.builder() - .name(NAME) - .type(TYPE_STRING) - .value(VALUE) - .version(VERSION).build()) - .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - - AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); - amazonServiceException.setStatusCode(500); - - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) - .thenThrow(amazonServiceException); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .previousResourceTags(PREVIOUS_TAG_SET) - .logicalResourceIdentifier("logical_id").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnGeneralServiceException ex) { - assertThat(ex).isInstanceOf(CfnGeneralServiceException.class); - } - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - verifyNoMoreInteractions(proxySsmClient.client()); - } - - @Test - public void handleRequest_AmazonServiceException400NonThrottlingException() { - AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); - amazonServiceException.setStatusCode(400); - amazonServiceException.setErrorCode("Invalid Input"); - - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) - .thenThrow(amazonServiceException); - final GetParametersResponse getParametersResponse = GetParametersResponse.builder() - .parameters(Parameter.builder() - .name(NAME) - .type(TYPE_STRING) - .value(VALUE) - .version(VERSION).build()) - .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .previousResourceTags(PREVIOUS_TAG_SET) - .logicalResourceIdentifier("logical_id").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnGeneralServiceException ex) { - assertThat(ex).isInstanceOf(CfnGeneralServiceException.class); - } - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - verifyNoMoreInteractions(proxySsmClient.client()); - } - - @Test - public void handleRequest_AmazonServiceExceptionInternalServerError() { - final GetParametersResponse getParametersResponse = GetParametersResponse.builder() - .parameters(Parameter.builder() - .name(NAME) - .type(TYPE_STRING) - .value(VALUE) - .version(VERSION).build()) - .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) - .thenThrow(InternalServerErrorException.builder().build()); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .previousResourceTags(PREVIOUS_TAG_SET) - .logicalResourceIdentifier("logical_id").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnServiceInternalErrorException ex) { - assertThat(ex).isInstanceOf(CfnServiceInternalErrorException.class); - } - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - verifyNoMoreInteractions(proxySsmClient.client()); - } + @Mock + private AmazonWebServicesClientProxy proxy; + + @Mock + private ProxyClient proxySsmClient; + + @Mock + SsmClient ssmClient; + + private UpdateHandler handler; + + private ResourceModel RESOURCE_MODEL; + + private Map PREVIOUS_TAG_SET_NO_CHANGE; + + private Map TAG_SET_WITH_CHANGE; + + @BeforeEach + public void setup() { + handler = new UpdateHandler(); + ssmClient = mock(SsmClient.class); + proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis()); + proxySsmClient = MOCK_PROXY(proxy, ssmClient); + + RESOURCE_MODEL = ResourceModel.builder() + .description(DESCRIPTION) + .name(NAME) + .value(VALUE) + .type(TYPE_STRING) + .tags(TAG_SET) + .build(); + + PREVIOUS_TAG_SET_NO_CHANGE = new HashMap(); + TAG_SET_WITH_CHANGE = new HashMap(); + } + + @Test + public void handleRequest_SimpleSuccess() { + final GetParametersResponse getParametersResponse = GetParametersResponse.builder() + .parameters(Parameter.builder() + .name(NAME) + .type(TYPE_STRING) + .value(VALUE) + .version(VERSION).build()) + .build(); + when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + + final PutParameterResponse putParameterResponse = PutParameterResponse.builder() + .version(VERSION) + .tier(ParameterTier.STANDARD) + .build(); + when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); + + final RemoveTagsFromResourceResponse removeTagsFromResourceResponse = RemoveTagsFromResourceResponse.builder().build(); + when(proxySsmClient.client().removeTagsFromResource(any(RemoveTagsFromResourceRequest.class))).thenReturn(removeTagsFromResourceResponse); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .previousResourceTags(PREVIOUS_TAG_SET) + .logicalResourceIdentifier("logicalId").build(); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); + assertThat(response.getCallbackContext()).isNull(); + assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); + assertThat(response.getResourceModels()).isNull(); + assertThat(response.getMessage()).isNull(); + assertThat(response.getErrorCode()).isNull(); + + verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); + verify(ssmClient, atLeastOnce()).serviceName(); + } + + @Test + public void handleRequest_SimpleSuccess_WithoutTagsChange() { + PREVIOUS_TAG_SET_NO_CHANGE.putAll(TAG_SET); + PREVIOUS_TAG_SET_NO_CHANGE.putAll(SYSTEM_TAGS_SET); + + final GetParametersResponse getParametersResponse = GetParametersResponse.builder() + .parameters(Parameter.builder() + .name(NAME) + .type(TYPE_STRING) + .value(VALUE) + .version(VERSION).build()) + .build(); + when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + + final PutParameterResponse putParameterResponse = PutParameterResponse.builder() + .version(VERSION) + .tier(ParameterTier.STANDARD) + .build(); + when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .previousResourceTags(PREVIOUS_TAG_SET_NO_CHANGE) + .logicalResourceIdentifier("logicalId").build(); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); + assertThat(response.getCallbackContext()).isNull(); + assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); + assertThat(response.getResourceModels()).isNull(); + assertThat(response.getMessage()).isNull(); + assertThat(response.getErrorCode()).isNull(); + + verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); + verify(ssmClient, atLeastOnce()).serviceName(); + } + + @Test + public void handleRequest_SimpleSuccess_WithTagsChange() { + TAG_SET_WITH_CHANGE.put("AddTagKey", "AddTagValue"); + PREVIOUS_TAG_SET_NO_CHANGE.putAll(TAG_SET); + PREVIOUS_TAG_SET_NO_CHANGE.putAll(SYSTEM_TAGS_SET); + + final GetParametersResponse getParametersResponse = GetParametersResponse.builder() + .parameters(Parameter.builder() + .name(NAME) + .type(TYPE_STRING) + .value(VALUE) + .version(VERSION).build()) + .build(); + when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + + final PutParameterResponse putParameterResponse = PutParameterResponse.builder() + .version(VERSION) + .tier(ParameterTier.STANDARD) + .build(); + when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); + + final RemoveTagsFromResourceResponse removeTagsFromResourceResponse = RemoveTagsFromResourceResponse.builder().build(); + when(proxySsmClient.client().removeTagsFromResource(any(RemoveTagsFromResourceRequest.class))).thenReturn(removeTagsFromResourceResponse); + final AddTagsToResourceResponse addTagsToResourceResponse = AddTagsToResourceResponse.builder().build(); + when(proxySsmClient.client().addTagsToResource(any(AddTagsToResourceRequest.class))).thenReturn(addTagsToResourceResponse); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET_WITH_CHANGE) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .previousResourceTags(PREVIOUS_TAG_SET_NO_CHANGE) + .logicalResourceIdentifier("logicalId").build(); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); + assertThat(response.getCallbackContext()).isNull(); + assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); + assertThat(response.getResourceModels()).isNull(); + assertThat(response.getMessage()).isNull(); + assertThat(response.getErrorCode()).isNull(); + + verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); + verify(ssmClient, atLeastOnce()).serviceName(); + } + + @Test + public void handleRequest_SecureStringFailure() { + RESOURCE_MODEL = ResourceModel.builder() + .description(DESCRIPTION) + .value(VALUE) + .type(TYPE_SECURE_STRING) + .tags(TAG_SET) + .build(); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .previousResourceTags(PREVIOUS_TAG_SET) + .logicalResourceIdentifier("logicalId").build(); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED); + assertThat(response.getCallbackContext()).isNull(); + assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); + assertThat(response.getResourceModels()).isNull(); + assertThat(response.getMessage()).isEqualTo("SSM Parameters of type SecureString cannot be updated using CloudFormation"); + assertThat(response.getErrorCode()).isEqualTo(HandlerErrorCode.InvalidRequest); + + verify(ssmClient, never()).serviceName(); + verifyNoMoreInteractions(proxySsmClient.client()); + } + + @Test + public void handleRequest_SimpleSuccess_WithImageDataType() { + final PutParameterResponse putParameterResponse = PutParameterResponse.builder() + .version(VERSION) + .tier(ParameterTier.STANDARD) + .build(); + when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); + + RESOURCE_MODEL = ResourceModel.builder() + .description(DESCRIPTION) + .name(NAME) + .value(VALUE) + .type(TYPE_STRING) + .tags(TAG_SET) + .dataType("aws:ec2:image") + .build(); + + final GetParametersResponse getParametersResponse = GetParametersResponse.builder() + .parameters(Parameter.builder() + .name(NAME) + .type(TYPE_STRING) + .value(VALUE) + .version(VERSION).build()) + .build(); + when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + + final RemoveTagsFromResourceResponse removeTagsFromResourceResponse = RemoveTagsFromResourceResponse.builder().build(); + when(proxySsmClient.client().removeTagsFromResource(any(RemoveTagsFromResourceRequest.class))).thenReturn(removeTagsFromResourceResponse); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .previousResourceTags(PREVIOUS_TAG_SET) + .logicalResourceIdentifier("logicalId").build(); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); + assertThat(response.getCallbackContext()).isNull(); + assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); + assertThat(response.getResourceModels()).isNull(); + assertThat(response.getMessage()).isNull(); + assertThat(response.getErrorCode()).isNull(); + + verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); + verify(ssmClient, atLeastOnce()).serviceName(); + } + + @Test + public void handleRequest_AmazonServiceException400ThrottlingException() { + AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); + amazonServiceException.setStatusCode(429); + amazonServiceException.setErrorCode("ThrottlingException"); + + when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) + .thenThrow(amazonServiceException); + + final GetParametersResponse getParametersResponse = GetParametersResponse.builder() + .parameters(Parameter.builder() + .name(NAME) + .type(TYPE_STRING) + .value(VALUE) + .version(VERSION).build()) + .build(); + when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .previousResourceTags(PREVIOUS_TAG_SET) + .logicalResourceIdentifier("logical_id").build(); + + try { + handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + } catch (CfnThrottlingException ex) { + assertThat(ex).isInstanceOf(CfnThrottlingException.class); + } + + verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); + verify(ssmClient, atLeastOnce()).serviceName(); + verifyNoMoreInteractions(proxySsmClient.client()); + } + + @Test + public void handleRequest_ParameterAlreadyExistsException() { + final GetParametersResponse getParametersResponse = GetParametersResponse.builder() + .parameters(Parameter.builder() + .name(NAME) + .type(TYPE_STRING) + .value(VALUE) + .version(VERSION).build()) + .build(); + when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + + when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) + .thenThrow(ParameterAlreadyExistsException.builder().build()); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .previousResourceTags(PREVIOUS_TAG_SET) + .logicalResourceIdentifier("logical_id").build(); + + try { + handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + } catch (CfnAlreadyExistsException ex) { + assertThat(ex).isInstanceOf(CfnAlreadyExistsException.class); + } + + verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); + verify(ssmClient, atLeastOnce()).serviceName(); + verifyNoMoreInteractions(proxySsmClient.client()); + } + + @Test + public void handleRequest_AmazonServiceException500Exception() { + final GetParametersResponse getParametersResponse = GetParametersResponse.builder() + .parameters(Parameter.builder() + .name(NAME) + .type(TYPE_STRING) + .value(VALUE) + .version(VERSION).build()) + .build(); + when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + + AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); + amazonServiceException.setStatusCode(500); + + when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) + .thenThrow(amazonServiceException); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .previousResourceTags(PREVIOUS_TAG_SET) + .logicalResourceIdentifier("logical_id").build(); + + try { + handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + } catch (CfnGeneralServiceException ex) { + assertThat(ex).isInstanceOf(CfnGeneralServiceException.class); + } + + verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); + verify(ssmClient, atLeastOnce()).serviceName(); + verifyNoMoreInteractions(proxySsmClient.client()); + } + + @Test + public void handleRequest_AmazonServiceException400NonThrottlingException() { + AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); + amazonServiceException.setStatusCode(400); + amazonServiceException.setErrorCode("Invalid Input"); + + when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) + .thenThrow(amazonServiceException); + final GetParametersResponse getParametersResponse = GetParametersResponse.builder() + .parameters(Parameter.builder() + .name(NAME) + .type(TYPE_STRING) + .value(VALUE) + .version(VERSION).build()) + .build(); + when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .previousResourceTags(PREVIOUS_TAG_SET) + .logicalResourceIdentifier("logical_id").build(); + + try { + handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + } catch (CfnGeneralServiceException ex) { + assertThat(ex).isInstanceOf(CfnGeneralServiceException.class); + } + + verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); + verify(ssmClient, atLeastOnce()).serviceName(); + verifyNoMoreInteractions(proxySsmClient.client()); + } + + @Test + public void handleRequest_AmazonServiceExceptionInternalServerError() { + final GetParametersResponse getParametersResponse = GetParametersResponse.builder() + .parameters(Parameter.builder() + .name(NAME) + .type(TYPE_STRING) + .value(VALUE) + .version(VERSION).build()) + .build(); + when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + + when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) + .thenThrow(InternalServerErrorException.builder().build()); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .previousResourceTags(PREVIOUS_TAG_SET) + .logicalResourceIdentifier("logical_id").build(); + + try { + handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + } catch (CfnServiceInternalErrorException ex) { + assertThat(ex).isInstanceOf(CfnServiceInternalErrorException.class); + } + + verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); + verify(ssmClient, atLeastOnce()).serviceName(); + verifyNoMoreInteractions(proxySsmClient.client()); + } } From d8e3298881399931f97d3062ce10a4c0a5a6edce Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Tue, 25 Jan 2022 16:41:53 +0530 Subject: [PATCH 04/23] rectified CREATE handler --- aws-ssm-parameter/.gitignore | 2 + aws-ssm-parameter/docs/README.md | 69 +++++++----- aws-ssm-parameter/docs/tags.md | 32 ------ aws-ssm-parameter/resource-role.yaml | 1 + .../ssm/parameter/BaseHandlerStd.java | 72 +++++++++--- .../ssm/parameter/Configuration.java | 4 +- .../amazonaws/ssm/parameter/Constants.java | 8 -- .../ssm/parameter/CreateHandler.java | 103 ++++-------------- .../ssm/parameter/UpdateHandler.java | 2 +- aws-ssm-parameter/template.yml | 3 +- 10 files changed, 131 insertions(+), 165 deletions(-) delete mode 100644 aws-ssm-parameter/docs/tags.md diff --git a/aws-ssm-parameter/.gitignore b/aws-ssm-parameter/.gitignore index 9b36fbcc..f95f7b19 100644 --- a/aws-ssm-parameter/.gitignore +++ b/aws-ssm-parameter/.gitignore @@ -18,3 +18,5 @@ target/ # our logs rpdk.log + +sam-tests diff --git a/aws-ssm-parameter/docs/README.md b/aws-ssm-parameter/docs/README.md index c3ac4402..5150e7e7 100644 --- a/aws-ssm-parameter/docs/README.md +++ b/aws-ssm-parameter/docs/README.md @@ -12,12 +12,15 @@ To declare this entity in your AWS CloudFormation template, use the following sy { "Type" : "AWS::SSM::Parameter", "Properties" : { + "Type" : String, "Description" : String, "Policies" : String, "AllowedPattern" : String, "Tier" : String, - "Tags" : Tags, + "Value" : String, "DataType" : String, + "Tags" : Map, + "Name" : String } } @@ -27,31 +30,42 @@ To declare this entity in your AWS CloudFormation template, use the following sy
 Type: AWS::SSM::Parameter
 Properties:
+    Type: String
     Description: String
     Policies: String
     AllowedPattern: String
     Tier: String
-    Tags: Tags
+    Value: String
     DataType: String
+    Tags: Map
+    Name: String
 
## Properties +#### Type + +The type of parameter. + +_Required_: Yes + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + #### Description -The information about the parameter. +Information about the parameter. _Required_: No _Type_: String -_Maximum_: 1024 - _Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) #### Policies -The policies attached to the parameter. +Information about the policies assigned to a parameter. _Required_: No @@ -61,71 +75,66 @@ _Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormati #### AllowedPattern -The regular expression used to validate the parameter value. +A regular expression used to validate the parameter value. _Required_: No _Type_: String -_Maximum_: 1024 - _Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) #### Tier -The corresponding tier of the parameter. +The parameter tier. _Required_: No _Type_: String -_Allowed Values_: Standard | Advanced | Intelligent-Tiering - _Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) -#### Tags +#### Value -A key-value pair to associate with a resource. +The parameter value. -_Required_: No +_Required_: Yes -_Type_: Tags +_Type_: String _Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) #### DataType -The corresponding DataType of the parameter. +The data type of the parameter, such as text or aws:ec2:image. The default is text. _Required_: No _Type_: String -_Allowed Values_: text | aws:ec2:image - _Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) -## Return Values - -### Ref +#### Tags -When you pass the logical ID of this resource to the intrinsic `Ref` function, Ref returns the Name. +Optional metadata that you assign to a resource in the form of an arbitrary set of tags (key-value pairs) -### Fn::GetAtt +_Required_: No -The `Fn::GetAtt` intrinsic function returns a value for a specified attribute of this type. The following are the available attributes and sample return values. +_Type_: Map -For more information about using the `Fn::GetAtt` intrinsic function, see [Fn::GetAtt](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html). +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) #### Name The name of the parameter. -#### Type +_Required_: No -The type of the parameter. +_Type_: String -#### Value +_Update requires_: [Replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-replacement) -The value associated with the parameter. +## Return Values +### Ref + +When you pass the logical ID of this resource to the intrinsic `Ref` function, Ref returns the Name. diff --git a/aws-ssm-parameter/docs/tags.md b/aws-ssm-parameter/docs/tags.md deleted file mode 100644 index 1a93d58b..00000000 --- a/aws-ssm-parameter/docs/tags.md +++ /dev/null @@ -1,32 +0,0 @@ -# AWS::SSM::Parameter Tags - -A key-value pair to associate with a resource. - -## Syntax - -To declare this entity in your AWS CloudFormation template, use the following syntax: - -### JSON - -
-{
-    "^([\p{L}\p{Z}\p{N}_.:/=+\-@]*)$" : String
-}
-
- -### YAML - -
-^([\p{L}\p{Z}\p{N}_.:/=+\-@]*)$: String
-
- -## Properties - -#### \^([\p{L}\p{Z}\p{N}_.:/=+\-@]*)$ - -_Required_: No - -_Type_: String - -_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) - diff --git a/aws-ssm-parameter/resource-role.yaml b/aws-ssm-parameter/resource-role.yaml index 3a1a4ef8..cf68dd6a 100644 --- a/aws-ssm-parameter/resource-role.yaml +++ b/aws-ssm-parameter/resource-role.yaml @@ -27,6 +27,7 @@ Resources: - "ssm:DeleteParameter" - "ssm:DescribeParameters" - "ssm:GetParameters" + - "ssm:ListTagsForResource" - "ssm:PutParameter" - "ssm:RemoveTagsFromResource" Resource: "*" diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java index cb8c8dc4..6a90b816 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java @@ -1,11 +1,20 @@ package com.amazonaws.ssm.parameter; import com.google.common.collect.ImmutableSet; +import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.awssdk.services.ssm.model.GetParametersResponse; import software.amazon.awssdk.services.ssm.model.InternalServerErrorException; +import software.amazon.awssdk.services.ssm.model.ParameterAlreadyExistsException; import software.amazon.awssdk.services.ssm.model.PutParameterRequest; import software.amazon.awssdk.services.ssm.model.PutParameterResponse; +import software.amazon.awssdk.services.ssm.model.SsmRequest; +import software.amazon.cloudformation.exceptions.BaseHandlerException; +import software.amazon.cloudformation.exceptions.CfnAlreadyExistsException; +import software.amazon.cloudformation.exceptions.CfnGeneralServiceException; +import software.amazon.cloudformation.exceptions.CfnInternalFailureException; +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; @@ -14,6 +23,7 @@ import software.amazon.cloudformation.proxy.delay.Constant; import java.time.Duration; +import java.util.Optional; import java.util.Set; public abstract class BaseHandlerStd extends BaseHandler { @@ -29,7 +39,7 @@ public ProgressEvent handleRequest(final AmazonW return handleRequest( proxy, request, - callbackContext != null ? callbackContext : new CallbackContext(), + Optional.ofNullable(callbackContext).orElse(new CallbackContext()), proxy.newProxy(SSMClientBuilder::getClient), logger); } @@ -69,26 +79,62 @@ protected Constant getBackOffDelay(final ResourceModel model) { * @param callbackContext callback context * @return boolean state of stabilized or not */ - protected static boolean stabilize( + protected Boolean stabilize( final PutParameterRequest putParameterRequest, final PutParameterResponse putParameterResponse, final ProxyClient proxyClient, final ResourceModel resourceModel, - final CallbackContext callbackContext + final CallbackContext callbackContext, + final Logger logger ) { - final GetParametersResponse response; try { - response = proxyClient.injectCredentialsAndInvokeV2(Translator.getParametersRequest(resourceModel), proxyClient.client()::getParameters); - } catch (final InternalServerErrorException exception) { - return false; + logger.log(String.format("Trying to stabilize %s [%s]",ResourceModel.TYPE_NAME, putParameterRequest.name())); + GetParametersResponse response = proxyClient.injectCredentialsAndInvokeV2(Translator.getParametersRequest(resourceModel), proxyClient.client()::getParameters); + + // if invalid parameters list is not empty return false as the validation for + // DataType has not been completed and the parameter has not been created yet. + if (response == null || !response.invalidParameters().isEmpty()) { + return false; + } + return (response.parameters() != null && response.parameters().get(0).version() == putParameterResponse.version()); + } catch (Exception e) { + logger.log(String.format("Failed during stabilization of SsmParameter [%s] with error: [%s]", putParameterRequest.name(), e.getMessage())); + throw e; } + } + + protected ProgressEvent handleError(SsmRequest ssmRequest, Exception e, ProxyClient proxyClient, + ResourceModel model, CallbackContext context, Logger logger) { + + BaseHandlerException ex; + + if (e instanceof AwsServiceException) { + if (e instanceof ParameterAlreadyExistsException) { + ex = new CfnAlreadyExistsException(e); + } else if (e instanceof InternalServerErrorException) { + ex = new CfnServiceInternalErrorException(e); + } else if (hasThrottled(e)) { + ex = new CfnThrottlingException(e); + } else { + ex = new CfnGeneralServiceException(e); + } + } else { + // InternalFailure: An unexpected error occurred within the handler. + ex = new CfnInternalFailureException(e); + } + logger.log(String.format("Handled Exception: error code [%s], message [%s]", ex.getErrorCode(), ex.getMessage())); + return ProgressEvent.failed(model, context, ex.getErrorCode(), ex.getMessage()); + } + + private boolean hasThrottled(Exception e) { + String errorCode = getErrorCode(e); + return (THROTTLING_ERROR_CODES.contains(errorCode)); + } - // if invalid parameters list is not empty return false as the validation for - // DataType has not been completed and the parameter has not been created yet. - if (response == null || response.invalidParameters().size() != 0) { - return false; + protected String getErrorCode(Exception e) { + if (e instanceof AwsServiceException && ((AwsServiceException) e).awsErrorDetails() != null) { + return ((AwsServiceException) e).awsErrorDetails().errorCode(); } - return (response.parameters() != null && - response.parameters().get(0).version() == putParameterResponse.version()); + return e.getMessage(); } } diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Configuration.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Configuration.java index 3ad833c5..de9aa10f 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Configuration.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Configuration.java @@ -1,6 +1,7 @@ package com.amazonaws.ssm.parameter; import java.util.Map; +import java.util.stream.Collectors; class Configuration extends BaseConfiguration { @@ -17,7 +18,8 @@ public Map resourceDefinedTags(final ResourceModel resourceModel if (resourceModel.getTags() == null) { return null; } else { - return resourceModel.getTags(); + return resourceModel.getTags().entrySet().stream() + .collect(Collectors.toMap(tag -> tag.getKey(), tag -> tag.getValue().toString())); } } } diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Constants.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Constants.java index 4f4c9cd4..241fb813 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Constants.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Constants.java @@ -1,14 +1,6 @@ package com.amazonaws.ssm.parameter; public class Constants { - // ParameterName limit is 1024 chars. To make sure that we never hit that limit with auto name generation, - // lets have a number less than the allowed limit i.e 1000. - // The total of following three variables should be less than 1000. - // This can be increased in the future. - public static final int ALLOWED_LOGICAL_RESOURCE_ID_LENGTH = 500; - public static final String CF_PARAMETER_NAME_PREFIX = "CFN"; - public static final int GUID_LENGTH = 12; - public static final int ERROR_STATUS_CODE_400 = 400; public static final int ERROR_STATUS_CODE_500 = 500; diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java index d1fa97af..c8f1edf0 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java @@ -1,32 +1,21 @@ package com.amazonaws.ssm.parameter; -import com.amazonaws.AmazonServiceException; -import org.apache.commons.lang3.RandomStringUtils; import software.amazon.awssdk.services.ssm.SsmClient; -import software.amazon.awssdk.services.ssm.model.InternalServerErrorException; -import software.amazon.awssdk.services.ssm.model.ParameterAlreadyExistsException; import software.amazon.awssdk.services.ssm.model.ParameterType; -import software.amazon.awssdk.services.ssm.model.PutParameterRequest; -import software.amazon.awssdk.services.ssm.model.PutParameterResponse; -import software.amazon.cloudformation.exceptions.CfnAlreadyExistsException; -import software.amazon.cloudformation.exceptions.CfnGeneralServiceException; import software.amazon.cloudformation.exceptions.CfnServiceInternalErrorException; -import software.amazon.cloudformation.exceptions.CfnThrottlingException; -import software.amazon.cloudformation.exceptions.TerminalException; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; import software.amazon.cloudformation.proxy.HandlerErrorCode; import software.amazon.cloudformation.proxy.Logger; import software.amazon.cloudformation.proxy.ProgressEvent; import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; +import software.amazon.cloudformation.resource.IdentifierUtils; -import java.util.HashMap; +import java.util.Collections; import java.util.Map; -import java.util.Random; +import java.util.Optional; public class CreateHandler extends BaseHandlerStd { - private static final String OPERATION = "PutParameter"; - private static final String RETRY_MESSAGE = "Detected retryable error, retrying. Exception message: %s"; private Logger logger; @Override @@ -39,78 +28,34 @@ protected ProgressEvent handleRequest( this.logger = logger; final ResourceModel model = request.getDesiredResourceState(); - // Set model primary ID if absent - if (model.getName() == null) { - model.setName(generateParameterName( - request.getLogicalResourceIdentifier(), - request.getClientRequestToken() - )); - } + logger.log("Invoking Create Handler"); + logger.log("CREATE ResourceModel: " + request.getDesiredResourceState().toString()); if (model.getType().equalsIgnoreCase(ParameterType.SECURE_STRING.toString())) { String message = String.format("SSM Parameters of type %s cannot be created using CloudFormation", ParameterType.SECURE_STRING); - return ProgressEvent.defaultFailureHandler(new TerminalException(message), - HandlerErrorCode.InvalidRequest); + return ProgressEvent.defaultFailureHandler(new CfnServiceInternalErrorException(message), HandlerErrorCode.InvalidRequest); } - Map consolidatedTagList = new HashMap<>(); - if (request.getDesiredResourceTags() != null) { - consolidatedTagList.putAll(request.getDesiredResourceTags()); - } - if (request.getSystemTags() != null) { - consolidatedTagList.putAll(request.getSystemTags()); + // Set model primary ID if absent + if (model.getName() == null) { + model.setName(IdentifierUtils.generateResourceIdentifier( + "CFN-" + request.getLogicalResourceIdentifier(), + request.getClientRequestToken())); } - return proxy.initiate("aws-ssm-parameter::resource-create", proxyClient, model, callbackContext) - .translateToServiceRequest((resourceModel) -> Translator.createPutParameterRequest(resourceModel, consolidatedTagList)) - .backoffDelay(getBackOffDelay(model)) - .makeServiceCall(this::createResource) - .stabilize(BaseHandlerStd::stabilize) - .progress() + Map consolidatedTagsMap = Optional.ofNullable(request.getDesiredResourceTags()).orElse(Collections.emptyMap()); + consolidatedTagsMap.putAll(Optional.ofNullable(request.getSystemTags()).orElse(Collections.emptyMap())); + + return ProgressEvent.progress(model, callbackContext) + .then(progress -> proxy.initiate("aws-ssm-parameter::resource-create", proxyClient, model, callbackContext) + .translateToServiceRequest(resourceModel -> Translator.createPutParameterRequest(resourceModel, consolidatedTagsMap)) + .backoffDelay(getBackOffDelay(model)) + .makeServiceCall((putParameterRequest, ssmProxyClient) -> + ssmProxyClient.injectCredentialsAndInvokeV2(putParameterRequest, ssmProxyClient.client()::putParameter)) + // .makeServiceCall(this::createResource) + .stabilize((req, response, client, model1, cbContext) -> stabilize(req, response, client, model1, cbContext, logger)) + .handleError((req, e, proxy1, model1, context1) -> handleError(req, e, proxy1, model1, context1, logger)) + .progress()) .then(progress -> new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, logger)); } - - private PutParameterResponse createResource(final PutParameterRequest putParameterRequest, - final ProxyClient proxyClient) { - try { - return proxyClient.injectCredentialsAndInvokeV2(putParameterRequest, proxyClient.client()::putParameter); - } catch (final ParameterAlreadyExistsException exception) { - throw new CfnAlreadyExistsException(ResourceModel.TYPE_NAME, putParameterRequest.name()); - } catch (final InternalServerErrorException exception) { - throw new CfnServiceInternalErrorException(OPERATION, exception); - } catch (final AmazonServiceException exception) { - final Integer errorStatus = exception.getStatusCode(); - final String errorCode = exception.getErrorCode(); - if (errorStatus >= Constants.ERROR_STATUS_CODE_400 && errorStatus < Constants.ERROR_STATUS_CODE_500) { - if (THROTTLING_ERROR_CODES.contains(errorCode)) { - logger.log(String.format(RETRY_MESSAGE, exception.getMessage())); - throw new CfnThrottlingException(OPERATION, exception); - } - } - throw new CfnGeneralServiceException(OPERATION, exception); - } - } - - // We support this special use case of auto-generating names only for CloudFormation. - // Name format: Prefix - logical resource id - randomString - private String generateParameterName(final String logicalResourceId, final String clientRequestToken) { - StringBuilder sb = new StringBuilder(); - int endIndex = logicalResourceId.length() > Constants.ALLOWED_LOGICAL_RESOURCE_ID_LENGTH - ? Constants.ALLOWED_LOGICAL_RESOURCE_ID_LENGTH : logicalResourceId.length(); - - sb.append(Constants.CF_PARAMETER_NAME_PREFIX); - sb.append("-"); - sb.append(logicalResourceId.substring(0, endIndex)); - sb.append("-"); - - sb.append(RandomStringUtils.random( - Constants.GUID_LENGTH, - 0, - 0, - true, - true, - null, - new Random(clientRequestToken.hashCode()))); - return sb.toString(); - } } diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java index 6c9cee6d..97937f76 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java @@ -66,7 +66,7 @@ protected ProgressEvent handleRequest( .translateToServiceRequest(Translator::updatePutParameterRequest) .backoffDelay(getBackOffDelay(model)) .makeServiceCall(this::updateResource) - .stabilize(BaseHandlerStd::stabilize) + .stabilize((req, response, client, model1, cbContext) -> stabilize(req, response, client, model1, cbContext, logger)) .progress()) .then(progress -> handleTagging(proxy, proxyClient, progress, model, request.getDesiredResourceTags(), request.getPreviousResourceTags())) .then(progress -> new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, logger)); diff --git a/aws-ssm-parameter/template.yml b/aws-ssm-parameter/template.yml index 8d8490ab..32400121 100644 --- a/aws-ssm-parameter/template.yml +++ b/aws-ssm-parameter/template.yml @@ -4,7 +4,8 @@ Description: AWS SAM template for the AWS::SSM::Parameter resource type Globals: Function: - Timeout: 60 # docker start-up times can be long for SAM CLI + Timeout: 180 # docker start-up times can be long for SAM CLI + MemorySize: 1024 Resources: TypeFunction: From c72321ae8edaf5c45882705dff0dfb569be505ad Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Tue, 25 Jan 2022 18:36:54 +0530 Subject: [PATCH 05/23] rectified READ --- aws-ssm-parameter/aws-ssm-parameter.json | 1 + .../ssm/parameter/CreateHandler.java | 1 - .../amazonaws/ssm/parameter/ReadHandler.java | 96 ++++++++++++++----- .../amazonaws/ssm/parameter/Translator.java | 27 ++++++ 4 files changed, 102 insertions(+), 23 deletions(-) diff --git a/aws-ssm-parameter/aws-ssm-parameter.json b/aws-ssm-parameter/aws-ssm-parameter.json index a28bf7b6..973dc814 100644 --- a/aws-ssm-parameter/aws-ssm-parameter.json +++ b/aws-ssm-parameter/aws-ssm-parameter.json @@ -61,6 +61,7 @@ "read": { "permissions": [ "ssm:GetParameters", + "ssm:DescribeParameters", "ssm:ListTagsForResource" ] }, diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java index c8f1edf0..5237bbd7 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java @@ -52,7 +52,6 @@ protected ProgressEvent handleRequest( .backoffDelay(getBackOffDelay(model)) .makeServiceCall((putParameterRequest, ssmProxyClient) -> ssmProxyClient.injectCredentialsAndInvokeV2(putParameterRequest, ssmProxyClient.client()::putParameter)) - // .makeServiceCall(this::createResource) .stabilize((req, response, client, model1, cbContext) -> stabilize(req, response, client, model1, cbContext, logger)) .handleError((req, e, proxy1, model1, context1) -> handleError(req, e, proxy1, model1, context1, logger)) .progress()) diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ReadHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ReadHandler.java index e08e0553..de33fb0f 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ReadHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ReadHandler.java @@ -1,12 +1,9 @@ package com.amazonaws.ssm.parameter; import software.amazon.awssdk.services.ssm.SsmClient; -import software.amazon.awssdk.services.ssm.model.GetParametersRequest; -import software.amazon.awssdk.services.ssm.model.GetParametersResponse; -import software.amazon.awssdk.services.ssm.model.InternalServerErrorException; import software.amazon.awssdk.services.ssm.model.Parameter; +import software.amazon.awssdk.services.ssm.model.ParameterMetadata; import software.amazon.cloudformation.exceptions.CfnNotFoundException; -import software.amazon.cloudformation.exceptions.CfnServiceInternalErrorException; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; import software.amazon.cloudformation.proxy.Logger; import software.amazon.cloudformation.proxy.ProgressEvent; @@ -14,7 +11,6 @@ import software.amazon.cloudformation.proxy.ResourceHandlerRequest; public class ReadHandler extends BaseHandlerStd { - private static final String OPERATION = "ReadParameter"; @Override protected ProgressEvent handleRequest( @@ -23,28 +19,84 @@ protected ProgressEvent handleRequest( final CallbackContext callbackContext, final ProxyClient proxyClient, final Logger logger) { - return proxy.initiate("aws-ssm-parameter::resource-read", proxyClient, request.getDesiredResourceState(), callbackContext) + final ResourceModel model = request.getDesiredResourceState(); + + logger.log("Invoking Read Handler"); + logger.log("READ ResourceModel: " + model.toString()); + + return ProgressEvent.progress(model, callbackContext) + .then(progress -> getParameters(proxy, progress, proxyClient, model, callbackContext, logger)) + .then(progress -> describeParameters(proxy, progress, proxyClient, progress.getResourceModel(), progress.getCallbackContext(), logger)) + .then(progress -> listTagsForResourceRequestForParameters(proxy, progress, proxyClient, progress.getResourceModel(), progress.getCallbackContext(), logger)) + .then(progress -> ProgressEvent.defaultSuccessHandler(progress.getResourceModel())); + } + + private ProgressEvent getParameters( + final AmazonWebServicesClientProxy proxy, + final ProgressEvent progress, + final ProxyClient proxyClient, + final ResourceModel model, final CallbackContext callbackContext, final Logger logger) { + + return proxy.initiate("aws-ssm-parameter::resource-read-getParameters", proxyClient, model, callbackContext) .translateToServiceRequest(Translator::getParametersRequest) - .makeServiceCall(this::ReadResource) - .done((getParametersRequest, getParametersResponse, proxyInvocation, resourceModel, context) -> { - if (getParametersResponse.parameters().size() == 0) { - throw new CfnNotFoundException(ResourceModel.TYPE_NAME, request.getDesiredResourceState().getName()); + .makeServiceCall(((getParametersRequest, ssmClientProxyClient) -> + ssmClientProxyClient.injectCredentialsAndInvokeV2(getParametersRequest, ssmClientProxyClient.client()::getParameters))) + .handleError((req, e, proxy1, model1, context1) -> handleError(req, e, proxy1, model1, context1, logger)) + .done((getParametersRequest, getParametersResponse, proxyClient1, resourceModel, context) -> { + if (getParametersResponse.parameters().isEmpty()) { + throw new CfnNotFoundException(ResourceModel.TYPE_NAME, model.getName()); } - final Parameter parameter = getParametersResponse.parameters().stream().findFirst().get(); + final Parameter parameter = getParametersResponse.parameters().get(0); + + return ProgressEvent.progress( + ResourceModel.builder() + .name(parameter.name()) + .type(parameter.typeAsString()) + .value(parameter.value()) + .dataType(parameter.dataType()) + .build(), + context); + }); + } - return ProgressEvent.defaultSuccessHandler(ResourceModel.builder() - .name(parameter.name()) - .type(parameter.typeAsString()) - .value(parameter.value()).build()); + private ProgressEvent describeParameters( + final AmazonWebServicesClientProxy proxy, + final ProgressEvent progress, + final ProxyClient proxyClient, + final ResourceModel model, final CallbackContext callbackContext, final Logger logger) { + + return proxy.initiate("aws-ssm-parameter::resource-read-describeParameters", proxyClient, model, callbackContext) + .translateToServiceRequest(Translator::describeParametersRequestWithFilter) + .makeServiceCall(((describeParametersRequest, ssmClientProxyClient) -> + ssmClientProxyClient.injectCredentialsAndInvokeV2(describeParametersRequest, ssmClientProxyClient.client()::describeParameters))) + .handleError((req, e, proxy1, model1, context1) -> handleError(req, e, proxy1, model1, context1, logger)) + .done((describeParametersRequest, describeParametersResponse, proxyClient1, resourceModel, context) -> { + if (describeParametersResponse.parameters().isEmpty()) { + throw new CfnNotFoundException(ResourceModel.TYPE_NAME, model.getName()); + } + final ParameterMetadata parameterMetadata = describeParametersResponse.parameters().get(0); + resourceModel.setAllowedPattern(parameterMetadata.allowedPattern()); + resourceModel.setDescription(parameterMetadata.description()); + resourceModel.setPolicies(parameterMetadata.policies().toString()); + resourceModel.setTier(parameterMetadata.tierAsString()); + return ProgressEvent.progress(resourceModel, context); }); } - private GetParametersResponse ReadResource(final GetParametersRequest getParametersRequest, - final ProxyClient proxyClient) { - try { - return proxyClient.injectCredentialsAndInvokeV2(getParametersRequest, proxyClient.client()::getParameters); - } catch (final InternalServerErrorException exception) { - throw new CfnServiceInternalErrorException(OPERATION, exception); - } + private ProgressEvent listTagsForResourceRequestForParameters( + final AmazonWebServicesClientProxy proxy, + final ProgressEvent progress, + final ProxyClient proxyClient, + final ResourceModel model, final CallbackContext callbackContext, final Logger logger) { + + return proxy.initiate("aws-ssm-parameter::resource-read-listTagsForResourceRequestForParameters", proxyClient, model, callbackContext) + .translateToServiceRequest(Translator::listTagsForResourceRequest) + .makeServiceCall(((listTagsForResourceRequest, ssmClientProxyClient) -> + ssmClientProxyClient.injectCredentialsAndInvokeV2(listTagsForResourceRequest, ssmClientProxyClient.client()::listTagsForResource))) + .handleError((req, e, proxy1, model1, context1) -> handleError(req, e, proxy1, model1, context1, logger)) + .done((listTagsForResourceRequest, listTagsForResourceResponse, proxyClient1, resourceModel, context) -> { + resourceModel.setTags(Translator.translateTagsFromSdk(listTagsForResourceResponse.tagList())); + return ProgressEvent.progress(resourceModel, context); + }); } } diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Translator.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Translator.java index ce101aeb..4e901c7b 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Translator.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Translator.java @@ -4,6 +4,9 @@ import software.amazon.awssdk.services.ssm.model.DeleteParameterRequest; import software.amazon.awssdk.services.ssm.model.DescribeParametersRequest; import software.amazon.awssdk.services.ssm.model.GetParametersRequest; +import software.amazon.awssdk.services.ssm.model.ListTagsForResourceRequest; +import software.amazon.awssdk.services.ssm.model.ParameterStringFilter; +import software.amazon.awssdk.services.ssm.model.ParametersFilterKey; import software.amazon.awssdk.services.ssm.model.PutParameterRequest; import software.amazon.awssdk.services.ssm.model.RemoveTagsFromResourceRequest; import software.amazon.awssdk.services.ssm.model.ResourceTypeForTagging; @@ -53,6 +56,17 @@ static GetParametersRequest getParametersRequest(final ResourceModel model) { .build(); } + static DescribeParametersRequest describeParametersRequestWithFilter(final ResourceModel model) { + return DescribeParametersRequest.builder() + .parameterFilters( + ParameterStringFilter.builder() + .key(ParametersFilterKey.NAME.toString()) + .option("Equals") + .values(model.getName()) + .build()) + .build(); + } + static DescribeParametersRequest describeParametersRequest(final String nextToken) { return DescribeParametersRequest.builder() .nextToken(nextToken) @@ -82,6 +96,13 @@ static AddTagsToResourceRequest addTagsToResourceRequest(final String parameterN .build(); } + static ListTagsForResourceRequest listTagsForResourceRequest(final ResourceModel model) { + return ListTagsForResourceRequest.builder() + .resourceType(ResourceTypeForTagging.PARAMETER) + .resourceId(model.getName()) + .build(); + } + // Translate tags static List translateTagsToSdk(final Map tags) { return Optional.of(tags.entrySet()).orElse(Collections.emptySet()) @@ -89,4 +110,10 @@ static List translateTagsToSdk(final Map tags) { .map(tag -> Tag.builder().key(tag.getKey()).value(tag.getValue()).build()) .collect(Collectors.toList()); } + + static Map translateTagsFromSdk(final List tags) { + return Optional.of(tags).orElse(Collections.emptyList()) + .stream() + .collect(Collectors.toMap(tag -> tag.key(), tag -> tag.value())); + } } From e7b6b2417f99b031be29aadff7cd7799e6f01cb5 Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Tue, 25 Jan 2022 18:42:06 +0530 Subject: [PATCH 06/23] rectified DELETE --- .../ssm/parameter/BaseHandlerStd.java | 6 ++- .../ssm/parameter/DeleteHandler.java | 41 ++++--------------- 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java index 6a90b816..3733ba57 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java @@ -6,6 +6,7 @@ import software.amazon.awssdk.services.ssm.model.GetParametersResponse; import software.amazon.awssdk.services.ssm.model.InternalServerErrorException; import software.amazon.awssdk.services.ssm.model.ParameterAlreadyExistsException; +import software.amazon.awssdk.services.ssm.model.ParameterNotFoundException; import software.amazon.awssdk.services.ssm.model.PutParameterRequest; import software.amazon.awssdk.services.ssm.model.PutParameterResponse; import software.amazon.awssdk.services.ssm.model.SsmRequest; @@ -13,6 +14,7 @@ import software.amazon.cloudformation.exceptions.CfnAlreadyExistsException; import software.amazon.cloudformation.exceptions.CfnGeneralServiceException; import software.amazon.cloudformation.exceptions.CfnInternalFailureException; +import software.amazon.cloudformation.exceptions.CfnNotFoundException; import software.amazon.cloudformation.exceptions.CfnServiceInternalErrorException; import software.amazon.cloudformation.exceptions.CfnThrottlingException; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; @@ -88,7 +90,7 @@ protected Boolean stabilize( final Logger logger ) { try { - logger.log(String.format("Trying to stabilize %s [%s]",ResourceModel.TYPE_NAME, putParameterRequest.name())); + logger.log(String.format("Trying to stabilize %s [%s]", ResourceModel.TYPE_NAME, putParameterRequest.name())); GetParametersResponse response = proxyClient.injectCredentialsAndInvokeV2(Translator.getParametersRequest(resourceModel), proxyClient.client()::getParameters); // if invalid parameters list is not empty return false as the validation for @@ -111,6 +113,8 @@ protected ProgressEvent handleError(SsmRequest s if (e instanceof AwsServiceException) { if (e instanceof ParameterAlreadyExistsException) { ex = new CfnAlreadyExistsException(e); + } else if (e instanceof ParameterNotFoundException) { + ex = new CfnNotFoundException(e); } else if (e instanceof InternalServerErrorException) { ex = new CfnServiceInternalErrorException(e); } else if (hasThrottled(e)) { diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/DeleteHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/DeleteHandler.java index c14849be..8d000115 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/DeleteHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/DeleteHandler.java @@ -1,15 +1,6 @@ package com.amazonaws.ssm.parameter; -import com.amazonaws.AmazonServiceException; import software.amazon.awssdk.services.ssm.SsmClient; -import software.amazon.awssdk.services.ssm.model.DeleteParameterRequest; -import software.amazon.awssdk.services.ssm.model.DeleteParameterResponse; -import software.amazon.awssdk.services.ssm.model.InternalServerErrorException; -import software.amazon.awssdk.services.ssm.model.ParameterNotFoundException; -import software.amazon.cloudformation.exceptions.CfnGeneralServiceException; -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; @@ -17,8 +8,6 @@ import software.amazon.cloudformation.proxy.ResourceHandlerRequest; public class DeleteHandler extends BaseHandlerStd { - private static final String OPERATION = "DeleteParameter"; - private static final String RETRY_MESSAGE = "Detected retryable error, retrying. Exception message: %s"; private Logger logger; @Override @@ -31,30 +20,14 @@ protected ProgressEvent handleRequest( this.logger = logger; final ResourceModel model = request.getDesiredResourceState(); + logger.log("Invoking Delete Handler"); + logger.log("DELETE ResourceModel: " + model.toString()); + return proxy.initiate("aws-ssm-parameter::resource-delete", proxyClient, model, callbackContext) .translateToServiceRequest(Translator::deleteParameterRequest) - .makeServiceCall(this::deleteResource) - .done((deleteParameterRequest, deleteParameterResponse, _client, _model, _callbackContext) -> ProgressEvent.defaultSuccessHandler(null)); - } - - private DeleteParameterResponse deleteResource(final DeleteParameterRequest deleteParameterRequest, - final ProxyClient proxyClient) { - try { - return proxyClient.injectCredentialsAndInvokeV2(deleteParameterRequest, proxyClient.client()::deleteParameter); - } catch (final ParameterNotFoundException exception) { - throw new CfnNotFoundException(ResourceModel.TYPE_NAME, deleteParameterRequest.name()); - } catch (final InternalServerErrorException exception) { - throw new CfnServiceInternalErrorException(OPERATION, exception); - } catch (final AmazonServiceException exception) { - final Integer errorStatus = exception.getStatusCode(); - final String errorCode = exception.getErrorCode(); - if (errorStatus >= Constants.ERROR_STATUS_CODE_400 && errorStatus < Constants.ERROR_STATUS_CODE_500) { - if (THROTTLING_ERROR_CODES.contains(errorCode)) { - logger.log(String.format(RETRY_MESSAGE, exception.getMessage())); - throw new CfnThrottlingException(OPERATION, exception); - } - } - throw new CfnGeneralServiceException(OPERATION, exception); - } + .makeServiceCall(((deleteParameterRequest, ssmClientProxyClient) -> + ssmClientProxyClient.injectCredentialsAndInvokeV2(deleteParameterRequest, ssmClientProxyClient.client()::deleteParameter))) + .handleError((req, e, proxy1, model1, context1) -> handleError(req, e, proxy1, model1, context1, logger)) + .done((deleteParameterRequest, deleteParameterResponse, client1, model1, callbackContext1) -> ProgressEvent.defaultSuccessHandler(null)); } } From 28c7f0f4e64494825619ba335f869e37f339246c Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Tue, 25 Jan 2022 21:45:41 +0530 Subject: [PATCH 07/23] rectified UPDATE --- .../amazonaws/ssm/parameter/Constants.java | 3 - .../ssm/parameter/UpdateHandler.java | 132 ++++++++---------- 2 files changed, 60 insertions(+), 75 deletions(-) diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Constants.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Constants.java index 241fb813..792246cb 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Constants.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Constants.java @@ -1,9 +1,6 @@ package com.amazonaws.ssm.parameter; public class Constants { - public static final int ERROR_STATUS_CODE_400 = 400; - public static final int ERROR_STATUS_CODE_500 = 500; - public static final Integer MAX_RESULTS = 50; public static final String AWS_EC2_IMAGE_DATATYPE = "aws:ec2:image"; } diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java index 97937f76..d1195191 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java @@ -1,23 +1,12 @@ package com.amazonaws.ssm.parameter; -import com.amazonaws.AmazonServiceException; import com.amazonaws.util.CollectionUtils; import com.google.common.collect.Sets; import software.amazon.awssdk.services.ssm.SsmClient; -import software.amazon.awssdk.services.ssm.model.GetParametersRequest; -import software.amazon.awssdk.services.ssm.model.GetParametersResponse; -import software.amazon.awssdk.services.ssm.model.InternalServerErrorException; -import software.amazon.awssdk.services.ssm.model.ParameterAlreadyExistsException; import software.amazon.awssdk.services.ssm.model.ParameterType; -import software.amazon.awssdk.services.ssm.model.PutParameterRequest; -import software.amazon.awssdk.services.ssm.model.PutParameterResponse; import software.amazon.awssdk.services.ssm.model.Tag; -import software.amazon.cloudformation.exceptions.CfnAlreadyExistsException; -import software.amazon.cloudformation.exceptions.CfnGeneralServiceException; import software.amazon.cloudformation.exceptions.CfnNotFoundException; import software.amazon.cloudformation.exceptions.CfnServiceInternalErrorException; -import software.amazon.cloudformation.exceptions.CfnThrottlingException; -import software.amazon.cloudformation.exceptions.TerminalException; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; import software.amazon.cloudformation.proxy.HandlerErrorCode; import software.amazon.cloudformation.proxy.Logger; @@ -25,15 +14,13 @@ import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; public class UpdateHandler extends BaseHandlerStd { - private static final String OPERATION = "PutParameter"; - private static final String RETRY_MESSAGE = "Detected retryable error, retrying. Exception message: %s"; private Logger logger; @Override @@ -48,60 +35,43 @@ protected ProgressEvent handleRequest( if (model.getType().equalsIgnoreCase(ParameterType.SECURE_STRING.toString())) { String message = String.format("SSM Parameters of type %s cannot be updated using CloudFormation", ParameterType.SECURE_STRING); - return ProgressEvent.defaultFailureHandler(new TerminalException(message), - HandlerErrorCode.InvalidRequest); + return ProgressEvent.defaultFailureHandler(new CfnServiceInternalErrorException(message), HandlerErrorCode.InvalidRequest); } return ProgressEvent.progress(model, callbackContext) // First validate the resource actually exists per the contract requirements // https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html - .then(progress -> - proxy.initiate("aws-ssm-parameter::validate-resource-exists", proxyClient, model, callbackContext) - .translateToServiceRequest(Translator::getParametersRequest) - .makeServiceCall(this::validateResourceExists) - .progress()) - - .then(progress -> - proxy.initiate("aws-ssm-parameter::resource-update", proxyClient, model, callbackContext) - .translateToServiceRequest(Translator::updatePutParameterRequest) - .backoffDelay(getBackOffDelay(model)) - .makeServiceCall(this::updateResource) - .stabilize((req, response, client, model1, cbContext) -> stabilize(req, response, client, model1, cbContext, logger)) - .progress()) - .then(progress -> handleTagging(proxy, proxyClient, progress, model, request.getDesiredResourceTags(), request.getPreviousResourceTags())) - .then(progress -> new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, logger)); + .then(progress -> validateResourceExists(proxy, progress, proxyClient, progress.getResourceModel(), progress.getCallbackContext(), logger)) + .then(progress -> updateResourceExceptTagging(proxy, progress, proxyClient, progress.getResourceModel(), progress.getCallbackContext(), logger)) + .then(progress -> handleTagging(proxy, proxyClient, progress, progress.getResourceModel(), request.getDesiredResourceTags(), request.getPreviousResourceTags())) + .then(progress -> new ReadHandler().handleRequest(proxy, request, progress.getCallbackContext(), proxyClient, logger)); } - private GetParametersResponse validateResourceExists(GetParametersRequest getParametersRequest, ProxyClient proxyClient) { - GetParametersResponse getParametersResponse; - - getParametersResponse = proxyClient.injectCredentialsAndInvokeV2(getParametersRequest, proxyClient.client()::getParameters); - if (getParametersResponse.invalidParameters().size() != 0) { - throw new CfnNotFoundException(ResourceModel.TYPE_NAME, getParametersRequest.names().get(0)); - } - - return getParametersResponse; + private ProgressEvent validateResourceExists(final AmazonWebServicesClientProxy proxy, + final ProgressEvent progress, final ProxyClient proxyClient, + final ResourceModel model, final CallbackContext callbackContext, final Logger logger) { + return proxy.initiate("aws-ssm-parameter::Update:validate-resource-exists", proxyClient, model, callbackContext) + .translateToServiceRequest(Translator::getParametersRequest) + .makeServiceCall((getParametersRequest, ssmClientProxyClient) -> + ssmClientProxyClient.injectCredentialsAndInvokeV2(getParametersRequest, ssmClientProxyClient.client()::getParameters)) + .handleError((req, e, proxy1, model1, context1) -> handleError(req, e, proxy1, model1, context1, this.logger)) + .done((getParametersRequest, getParametersResponse, proxyClient1, resourceModel, context) -> { + if (!getParametersResponse.invalidParameters().isEmpty()) { + throw new CfnNotFoundException(ResourceModel.TYPE_NAME, resourceModel.getName()); + } + return progress; + }); } - private PutParameterResponse updateResource(final PutParameterRequest putParameterRequest, - final ProxyClient proxyClient) { - try { - return proxyClient.injectCredentialsAndInvokeV2(putParameterRequest, proxyClient.client()::putParameter); - } catch (final ParameterAlreadyExistsException exception) { - throw new CfnAlreadyExistsException(ResourceModel.TYPE_NAME, putParameterRequest.name()); - } catch (final InternalServerErrorException exception) { - throw new CfnServiceInternalErrorException(OPERATION, exception); - } catch (final AmazonServiceException exception) { - final Integer errorStatus = exception.getStatusCode(); - final String errorCode = exception.getErrorCode(); - if (errorStatus >= Constants.ERROR_STATUS_CODE_400 && errorStatus < Constants.ERROR_STATUS_CODE_500) { - if (THROTTLING_ERROR_CODES.contains(errorCode)) { - logger.log(String.format(RETRY_MESSAGE, exception.getMessage())); - throw new CfnThrottlingException(OPERATION, exception); - } - } - throw new CfnGeneralServiceException(OPERATION, exception); - } + private ProgressEvent updateResourceExceptTagging(AmazonWebServicesClientProxy proxy, ProgressEvent progress, + ProxyClient proxyClient, ResourceModel resourceModel, CallbackContext callbackContext, Logger logger) { + return proxy.initiate("aws-ssm-parameter::Update:resource-update", proxyClient, progress.getResourceModel(), progress.getCallbackContext()) + .translateToServiceRequest(Translator::updatePutParameterRequest) + .backoffDelay(getBackOffDelay(progress.getResourceModel())) + .makeServiceCall((putParameterRequest, ssmClientProxyClient) -> + ssmClientProxyClient.injectCredentialsAndInvokeV2(putParameterRequest, ssmClientProxyClient.client()::putParameter)) + .stabilize((req, response, client, model1, cbContext) -> stabilize(req, response, client, model1, cbContext, logger)) + .progress(); } private ProgressEvent handleTagging( @@ -114,25 +84,43 @@ private ProgressEvent handleTagging( final Set currentTags = new HashSet<>(Translator.translateTagsToSdk(desiredResourceTags)); final Set existingTags = new HashSet<>(Translator.translateTagsToSdk(previousResourceTags)); - // Remove tags with aws prefix as they should not be modified once attached - existingTags.removeIf(tag -> tag.key().startsWith("aws")); + // Remove tags with aws prefix as they should not be modified (or removed) once attached + existingTags.removeIf(tag -> tag.key().startsWith("aws:")); final Set setTagsToRemove = Sets.difference(existingTags, currentTags); final Set setTagsToAdd = Sets.difference(currentTags, existingTags); - final List tagsToRemove = setTagsToRemove.stream().collect(Collectors.toList()); - final List tagsToAdd = setTagsToAdd.stream().collect(Collectors.toList()); + final List tagsToRemove = new ArrayList<>(setTagsToRemove); + final List tagsToAdd = new ArrayList<>(setTagsToAdd); - // Deletes tags only if tagsToRemove is not empty. - if (!CollectionUtils.isNullOrEmpty(tagsToRemove)) - proxy.injectCredentialsAndInvokeV2( - Translator.removeTagsFromResourceRequest(resourceModel.getName(), tagsToRemove), proxyClient.client()::removeTagsFromResource); + return ProgressEvent.progress(resourceModel, progress.getCallbackContext()) + // First validate the resource actually exists per the contract requirements + .then(progress1 -> removeTags(proxy, progress1, proxyClient, logger, tagsToRemove)) + .then(progress1 -> addTags(proxy, progress1, proxyClient, logger, tagsToAdd)) + .then(progress1 -> ProgressEvent.progress(progress1.getResourceModel(), progress1.getCallbackContext())); + } - // Adds tags only if tagsToAdd is not empty. - if (!CollectionUtils.isNullOrEmpty(tagsToAdd)) - proxy.injectCredentialsAndInvokeV2( - Translator.addTagsToResourceRequest(resourceModel.getName(), tagsToAdd), proxyClient.client()::addTagsToResource); + private ProgressEvent removeTags(AmazonWebServicesClientProxy proxy, ProgressEvent progress, + ProxyClient proxyClient, Logger logger, List tagsToRemove) { + if (CollectionUtils.isNullOrEmpty(tagsToRemove)) + return progress; + return proxy.initiate("aws-ssm-parameter::Update:handle-tagging-remove", proxyClient, progress.getResourceModel(), progress.getCallbackContext()) + .translateToServiceRequest(model -> Translator.removeTagsFromResourceRequest(model.getName(), tagsToRemove)) + .backoffDelay(getBackOffDelay(progress.getResourceModel())) + .makeServiceCall((removeTagsFromResourceRequest, ssmClientProxyClient) -> + ssmClientProxyClient.injectCredentialsAndInvokeV2(removeTagsFromResourceRequest, ssmClientProxyClient.client()::removeTagsFromResource)) + .progress(); + } - return ProgressEvent.progress(progress.getResourceModel(), progress.getCallbackContext()); + private ProgressEvent addTags(AmazonWebServicesClientProxy proxy, ProgressEvent progress, + ProxyClient proxyClient, Logger logger, List tagsToAdd) { + if (CollectionUtils.isNullOrEmpty(tagsToAdd)) + return progress; + return proxy.initiate("aws-ssm-parameter::Update:handle-tagging-add", proxyClient, progress.getResourceModel(), progress.getCallbackContext()) + .translateToServiceRequest(model -> Translator.addTagsToResourceRequest(model.getName(), tagsToAdd)) + .backoffDelay(getBackOffDelay(progress.getResourceModel())) + .makeServiceCall((addTagsToResourceRequest, ssmClientProxyClient) -> + ssmClientProxyClient.injectCredentialsAndInvokeV2(addTagsToResourceRequest, ssmClientProxyClient.client()::addTagsToResource)) + .progress(); } } From 8fd2be4494ac07f907f229f30350d0e2d755a397 Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Mon, 31 Jan 2022 12:15:37 +0530 Subject: [PATCH 08/23] rectified LIST --- .../amazonaws/ssm/parameter/ListHandler.java | 45 ++++++++++++++----- .../amazonaws/ssm/parameter/Translator.java | 15 +++++++ 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ListHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ListHandler.java index b9f5713d..75130323 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ListHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ListHandler.java @@ -2,6 +2,8 @@ import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.awssdk.services.ssm.model.DescribeParametersResponse; +import software.amazon.awssdk.services.ssm.model.ParameterMetadata; +import software.amazon.cloudformation.exceptions.CfnNotFoundException; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; import software.amazon.cloudformation.proxy.Logger; import software.amazon.cloudformation.proxy.OperationStatus; @@ -13,23 +15,42 @@ import java.util.stream.Collectors; public class ListHandler extends BaseHandlerStd { + protected ProgressEvent handleRequest( final AmazonWebServicesClientProxy proxy, final ResourceHandlerRequest request, final CallbackContext callbackContext, final ProxyClient proxyClient, final Logger logger) { - final DescribeParametersResponse describeParametersResponse = proxy.injectCredentialsAndInvokeV2(Translator.describeParametersRequest(request.getNextToken()), - proxyClient.client()::describeParameters); - - final List models = describeParametersResponse - .parameters() - .stream().map(parameterMetadata -> ResourceModel.builder().name(parameterMetadata.name()).build()).collect(Collectors.toList()); - - return ProgressEvent.builder() - .resourceModels(models) - .nextToken(describeParametersResponse.nextToken()) - .status(OperationStatus.SUCCESS) - .build(); + + final ResourceModel resourceModel = request.getDesiredResourceState(); + + return ProgressEvent.progress(resourceModel, callbackContext) + .then(progress -> describeParameters(proxy, request, progress, proxyClient, progress.getResourceModel(), progress.getCallbackContext(), logger)) + .then(progress -> ProgressEvent.defaultSuccessHandler(progress.getResourceModel())); + } + + private ProgressEvent describeParameters( + final AmazonWebServicesClientProxy proxy, + final ResourceHandlerRequest request, + final ProgressEvent progress, + final ProxyClient proxyClient, + final ResourceModel resourceModel, final CallbackContext callbackContext, final Logger logger) { + + return proxy.initiate("aws-ssm-parameter::resource-list-describeParameters", proxyClient, resourceModel, callbackContext) + .translateToServiceRequest(model1 -> Translator.describeParametersRequest(request.getNextToken())) + .makeServiceCall(((describeParametersRequest, ssmClientProxyClient) -> + ssmClientProxyClient.injectCredentialsAndInvokeV2(describeParametersRequest, ssmClientProxyClient.client()::describeParameters))) + .handleError((req, e, proxy1, model1, context1) -> handleError(req, e, proxy1, model1, context1, logger)) + .done(describeParametersResponse -> { + String nextToken = describeParametersResponse.nextToken(); + final List models = Translator.translateListOfParameters(describeParametersResponse.parameters()); + + return ProgressEvent.builder() + .resourceModels(models) + .nextToken(nextToken) + .status(OperationStatus.SUCCESS) + .build(); + }); } } diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Translator.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Translator.java index 4e901c7b..5f62ff22 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Translator.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Translator.java @@ -5,6 +5,7 @@ import software.amazon.awssdk.services.ssm.model.DescribeParametersRequest; import software.amazon.awssdk.services.ssm.model.GetParametersRequest; import software.amazon.awssdk.services.ssm.model.ListTagsForResourceRequest; +import software.amazon.awssdk.services.ssm.model.ParameterMetadata; import software.amazon.awssdk.services.ssm.model.ParameterStringFilter; import software.amazon.awssdk.services.ssm.model.ParametersFilterKey; import software.amazon.awssdk.services.ssm.model.PutParameterRequest; @@ -103,6 +104,20 @@ static ListTagsForResourceRequest listTagsForResourceRequest(final ResourceModel .build(); } + static List translateListOfParameters(final List parameterMetadataList) { + return parameterMetadataList.stream().map(parameterMetadata -> + ResourceModel.builder() + .name(parameterMetadata.name()) + .allowedPattern(parameterMetadata.allowedPattern()) + .description(parameterMetadata.description()) + .policies(parameterMetadata.policies().toString()) + .tier(parameterMetadata.tierAsString()) + .dataType(parameterMetadata.dataType()) + .type(parameterMetadata.typeAsString()) + .build()) + .collect(Collectors.toList()); + } + // Translate tags static List translateTagsToSdk(final Map tags) { return Optional.of(tags.entrySet()).orElse(Collections.emptySet()) From 418b547a74381df9c57c7094fcbe0f52ce418d95 Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Mon, 31 Jan 2022 12:38:51 +0530 Subject: [PATCH 09/23] added Client Side Throttling with Client re-use --- .../ssm/parameter/BaseHandlerStd.java | 19 +++++++++- .../ssm/parameter/ClientBuilder.java | 37 +++++++++++++++++++ .../ssm/parameter/Configuration.java | 12 +++--- .../ssm/parameter/CreateHandler.java | 16 +++++++- .../ssm/parameter/DeleteHandler.java | 10 +++++ .../amazonaws/ssm/parameter/ListHandler.java | 10 +++++ .../amazonaws/ssm/parameter/ReadHandler.java | 10 +++++ .../ssm/parameter/SSMClientBuilder.java | 27 -------------- .../ssm/parameter/UpdateHandler.java | 16 +++++++- 9 files changed, 121 insertions(+), 36 deletions(-) create mode 100644 aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ClientBuilder.java delete mode 100644 aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/SSMClientBuilder.java diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java index 3733ba57..044c13c0 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java @@ -28,7 +28,24 @@ import java.util.Optional; import java.util.Set; +import static java.util.Objects.requireNonNull; + public abstract class BaseHandlerStd extends BaseHandler { + + private final SsmClient ssmClient; + + protected BaseHandlerStd() { + this(ClientBuilder.getClient()); + } + + protected BaseHandlerStd(SsmClient ssmClient) { + this.ssmClient = requireNonNull(ssmClient); + } + + private SsmClient getSsmClient() { + return ssmClient; + } + protected static final Set THROTTLING_ERROR_CODES = ImmutableSet.of( "ThrottlingException", "TooManyUpdates"); @@ -42,7 +59,7 @@ public ProgressEvent handleRequest(final AmazonW proxy, request, Optional.ofNullable(callbackContext).orElse(new CallbackContext()), - proxy.newProxy(SSMClientBuilder::getClient), + proxy.newProxy(ClientBuilder::getClient), logger); } diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ClientBuilder.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ClientBuilder.java new file mode 100644 index 00000000..b81fe23f --- /dev/null +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ClientBuilder.java @@ -0,0 +1,37 @@ +package com.amazonaws.ssm.parameter; + +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.internal.retry.SdkDefaultRetrySetting; +import software.amazon.awssdk.core.retry.RetryPolicy; +import software.amazon.awssdk.core.retry.backoff.BackoffStrategy; +import software.amazon.awssdk.core.retry.backoff.EqualJitterBackoffStrategy; +import software.amazon.awssdk.core.retry.conditions.RetryCondition; +import software.amazon.awssdk.services.ssm.SsmClient; +import software.amazon.cloudformation.LambdaWrapper; + +import java.time.Duration; + +public class ClientBuilder { + + private static final BackoffStrategy BACKOFF_THROTTLING_STRATEGY = + EqualJitterBackoffStrategy.builder() + .baseDelay(Duration.ofMillis(2000)) //1st retry is ~2 sec + .maxBackoffTime(SdkDefaultRetrySetting.MAX_BACKOFF) //default is 20s + .build(); + + private static final RetryPolicy RETRY_POLICY = + RetryPolicy.builder() + .numRetries(16) + .retryCondition(RetryCondition.defaultRetryCondition()) + .throttlingBackoffStrategy(BACKOFF_THROTTLING_STRATEGY) + .build(); + + public static SsmClient getClient() { + return SsmClient.builder() + .httpClient(LambdaWrapper.HTTP_CLIENT) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .retryPolicy(RETRY_POLICY) + .build()) + .build(); + } +} diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Configuration.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Configuration.java index de9aa10f..2a23cc45 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Configuration.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Configuration.java @@ -1,6 +1,9 @@ package com.amazonaws.ssm.parameter; +import com.google.common.collect.Maps; + import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; class Configuration extends BaseConfiguration { @@ -15,11 +18,8 @@ public Configuration() { * @return */ public Map resourceDefinedTags(final ResourceModel resourceModel) { - if (resourceModel.getTags() == null) { - return null; - } else { - return resourceModel.getTags().entrySet().stream() - .collect(Collectors.toMap(tag -> tag.getKey(), tag -> tag.getValue().toString())); - } + return Optional.ofNullable(resourceModel.getTags()).orElse(Maps.newHashMap()) + .entrySet().stream() + .collect(Collectors.toMap(tag -> tag.getKey(), tag -> tag.getValue().toString())); } } diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java index 5237bbd7..f2138654 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java @@ -1,5 +1,6 @@ package com.amazonaws.ssm.parameter; +import com.google.common.annotations.VisibleForTesting; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.awssdk.services.ssm.model.ParameterType; import software.amazon.cloudformation.exceptions.CfnServiceInternalErrorException; @@ -18,6 +19,19 @@ public class CreateHandler extends BaseHandlerStd { private Logger logger; + private final ReadHandler readHandler; + + public CreateHandler() { + super(); + readHandler = new ReadHandler(); + } + + @VisibleForTesting + protected CreateHandler(SsmClient ssmClient, ReadHandler readHandler) { + super(ssmClient); + this.readHandler = readHandler; + } + @Override protected ProgressEvent handleRequest( final AmazonWebServicesClientProxy proxy, @@ -55,6 +69,6 @@ protected ProgressEvent handleRequest( .stabilize((req, response, client, model1, cbContext) -> stabilize(req, response, client, model1, cbContext, logger)) .handleError((req, e, proxy1, model1, context1) -> handleError(req, e, proxy1, model1, context1, logger)) .progress()) - .then(progress -> new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, logger)); + .then(progress -> readHandler.handleRequest(proxy, request, callbackContext, proxyClient, logger)); } } diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/DeleteHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/DeleteHandler.java index 8d000115..a38cd87b 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/DeleteHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/DeleteHandler.java @@ -1,5 +1,6 @@ package com.amazonaws.ssm.parameter; +import com.google.common.annotations.VisibleForTesting; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; import software.amazon.cloudformation.proxy.Logger; @@ -10,6 +11,15 @@ public class DeleteHandler extends BaseHandlerStd { private Logger logger; + public DeleteHandler() { + super(); + } + + @VisibleForTesting + protected DeleteHandler(SsmClient ssmClient) { + super(ssmClient); + } + @Override protected ProgressEvent handleRequest( final AmazonWebServicesClientProxy proxy, diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ListHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ListHandler.java index 75130323..1a269523 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ListHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ListHandler.java @@ -1,5 +1,6 @@ package com.amazonaws.ssm.parameter; +import com.google.common.annotations.VisibleForTesting; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.awssdk.services.ssm.model.DescribeParametersResponse; import software.amazon.awssdk.services.ssm.model.ParameterMetadata; @@ -16,6 +17,15 @@ public class ListHandler extends BaseHandlerStd { + public ListHandler() { + super(); + } + + @VisibleForTesting + protected ListHandler(SsmClient ssmClient) { + super(ssmClient); + } + protected ProgressEvent handleRequest( final AmazonWebServicesClientProxy proxy, final ResourceHandlerRequest request, diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ReadHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ReadHandler.java index de33fb0f..b9b75f3d 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ReadHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ReadHandler.java @@ -1,5 +1,6 @@ package com.amazonaws.ssm.parameter; +import com.google.common.annotations.VisibleForTesting; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.awssdk.services.ssm.model.Parameter; import software.amazon.awssdk.services.ssm.model.ParameterMetadata; @@ -12,6 +13,15 @@ public class ReadHandler extends BaseHandlerStd { + public ReadHandler() { + super(); + } + + @VisibleForTesting + protected ReadHandler(SsmClient ssmClient) { + super(ssmClient); + } + @Override protected ProgressEvent handleRequest( final AmazonWebServicesClientProxy proxy, diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/SSMClientBuilder.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/SSMClientBuilder.java deleted file mode 100644 index 5f2e2897..00000000 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/SSMClientBuilder.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.amazonaws.ssm.parameter; - -import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; -import software.amazon.awssdk.core.retry.RetryPolicy; -import software.amazon.awssdk.core.retry.conditions.RetryCondition; -import software.amazon.awssdk.services.ssm.SsmClient; -import software.amazon.cloudformation.LambdaWrapper; - -public class SSMClientBuilder { - private static final RetryPolicy RETRY_POLICY = - RetryPolicy.builder() - .numRetries(16) - .retryCondition(RetryCondition.defaultRetryCondition()) - .build(); - - /** - * Builds and returns SsmClient with configuration overrides. - * - * @return Configured SsmClient. - */ - public static SsmClient getClient() { - return SsmClient.builder() - .httpClient(LambdaWrapper.HTTP_CLIENT) - .overrideConfiguration(ClientOverrideConfiguration.builder().retryPolicy(RETRY_POLICY).build()) - .build(); - } -} diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java index d1195191..a484f965 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java @@ -1,6 +1,7 @@ package com.amazonaws.ssm.parameter; import com.amazonaws.util.CollectionUtils; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Sets; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.awssdk.services.ssm.model.ParameterType; @@ -23,6 +24,19 @@ public class UpdateHandler extends BaseHandlerStd { private Logger logger; + private final ReadHandler readHandler; + + public UpdateHandler() { + super(); + readHandler = new ReadHandler(); + } + + @VisibleForTesting + protected UpdateHandler(SsmClient ssmClient, ReadHandler readHandler) { + super(ssmClient); + this.readHandler = readHandler; + } + @Override protected ProgressEvent handleRequest( final AmazonWebServicesClientProxy proxy, @@ -44,7 +58,7 @@ protected ProgressEvent handleRequest( .then(progress -> validateResourceExists(proxy, progress, proxyClient, progress.getResourceModel(), progress.getCallbackContext(), logger)) .then(progress -> updateResourceExceptTagging(proxy, progress, proxyClient, progress.getResourceModel(), progress.getCallbackContext(), logger)) .then(progress -> handleTagging(proxy, proxyClient, progress, progress.getResourceModel(), request.getDesiredResourceTags(), request.getPreviousResourceTags())) - .then(progress -> new ReadHandler().handleRequest(proxy, request, progress.getCallbackContext(), proxyClient, logger)); + .then(progress -> readHandler.handleRequest(proxy, request, progress.getCallbackContext(), proxyClient, logger)); } private ProgressEvent validateResourceExists(final AmazonWebServicesClientProxy proxy, From 8b059810a0448c4456ffe6941242f53acb366856 Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Tue, 1 Feb 2022 20:01:14 +0530 Subject: [PATCH 10/23] added unit tests --- .../ssm/parameter/AbstractTestBase.java | 22 ++ .../ssm/parameter/CreateHandlerTest.java | 250 ++++++------------ .../ssm/parameter/DeleteHandlerTest.java | 136 +++------- .../ssm/parameter/ListHandlerTest.java | 22 +- .../ssm/parameter/ReadHandlerTest.java | 84 ++++-- .../ssm/parameter/UpdateHandlerTest.java | 139 +++++----- 6 files changed, 279 insertions(+), 374 deletions(-) diff --git a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/AbstractTestBase.java b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/AbstractTestBase.java index 6bd0ac21..dee9a556 100644 --- a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/AbstractTestBase.java +++ b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/AbstractTestBase.java @@ -9,13 +9,20 @@ import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; import software.amazon.cloudformation.proxy.Credentials; +import software.amazon.cloudformation.proxy.HandlerErrorCode; import software.amazon.cloudformation.proxy.LoggerProxy; +import software.amazon.cloudformation.proxy.OperationStatus; +import software.amazon.cloudformation.proxy.ProgressEvent; import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; public class AbstractTestBase { protected static final Credentials MOCK_CREDENTIALS; @@ -68,6 +75,10 @@ public class AbstractTestBase { PREVIOUS_TAG_SET.putAll(SYSTEM_TAGS_SET); } + static Map toResourceModelTags(Map stringTags) { + return stringTags.entrySet().stream().collect(Collectors.toMap(tag -> tag.getKey(), tag -> tag)); + } + static ProxyClient MOCK_PROXY( final AmazonWebServicesClientProxy proxy, final SsmClient ssmClient @@ -108,4 +119,15 @@ public SsmClient client() { } }; } + + protected void assertFailureErrorCode(final ResourceHandlerRequest request, + final ProgressEvent response, final HandlerErrorCode errorCode) { + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED); + assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); + assertThat(response.getResourceModel()).isEqualTo(request.getDesiredResourceState()); + assertThat(response.getResourceModels()).isNull(); + assertThat(response.getErrorCode()).isEqualTo(errorCode); + } } diff --git a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/CreateHandlerTest.java b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/CreateHandlerTest.java index 41df1e3e..9c754d0e 100644 --- a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/CreateHandlerTest.java +++ b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/CreateHandlerTest.java @@ -1,13 +1,16 @@ package com.amazonaws.ssm.parameter; -import com.amazonaws.AmazonServiceException; import org.apache.commons.lang3.RandomStringUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import software.amazon.awssdk.awscore.exception.AwsErrorDetails; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.exception.SdkException; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.awssdk.services.ssm.model.GetParametersRequest; import software.amazon.awssdk.services.ssm.model.GetParametersResponse; @@ -17,10 +20,6 @@ import software.amazon.awssdk.services.ssm.model.ParameterTier; import software.amazon.awssdk.services.ssm.model.PutParameterRequest; import software.amazon.awssdk.services.ssm.model.PutParameterResponse; -import software.amazon.cloudformation.exceptions.CfnAlreadyExistsException; -import software.amazon.cloudformation.exceptions.CfnGeneralServiceException; -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.OperationStatus; @@ -39,12 +38,12 @@ public class CreateHandlerTest extends AbstractTestBase { @Mock private AmazonWebServicesClientProxy proxy; - @Mock - private ProxyClient proxySsmClient; - + private ProxyClient proxyClient; @Mock SsmClient ssmClient; + @Mock + private ReadHandler readHandler; private CreateHandler handler; @@ -52,27 +51,44 @@ public class CreateHandlerTest extends AbstractTestBase { @BeforeEach public void setup() { - handler = new CreateHandler(); - ssmClient = mock(SsmClient.class); proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis()); - proxySsmClient = MOCK_PROXY(proxy, ssmClient); + ssmClient = mock(SsmClient.class); + proxyClient = MOCK_PROXY(proxy, ssmClient); + handler = new CreateHandler(ssmClient, readHandler); RESOURCE_MODEL = ResourceModel.builder() .description(DESCRIPTION) .name(NAME) .value(VALUE) .type(TYPE_STRING) - .tags(TAG_SET) + .tags(toResourceModelTags(TAG_SET)) .build(); } @AfterEach - public void post_execute() { - verifyNoMoreInteractions(proxySsmClient.client()); + public void tear_down(org.junit.jupiter.api.TestInfo testInfo) { + if (!testInfo.getTags().contains("skipSdkInteraction")) { + verify(ssmClient, atLeastOnce()).serviceName(); + } + verifyNoMoreInteractions(proxyClient.client()); } @Test public void handleRequest_SimpleSuccess() { + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .clientRequestToken("token") + .desiredResourceTags(TAG_SET) + .systemTags(SYSTEM_TAGS_SET) + .desiredResourceState(RESOURCE_MODEL) + .logicalResourceIdentifier("logicalId") + .build(); + + final PutParameterResponse putParameterResponse = PutParameterResponse.builder() + .version(VERSION) + .tier(ParameterTier.STANDARD) + .build(); + when(proxyClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); + final GetParametersResponse getParametersResponse = GetParametersResponse.builder() .parameters(Parameter.builder() .name(NAME) @@ -80,41 +96,29 @@ public void handleRequest_SimpleSuccess() { .value(VALUE) .version(VERSION).build()) .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + when(proxyClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - final PutParameterResponse putParameterResponse = PutParameterResponse.builder() - .version(VERSION) - .tier(ParameterTier.STANDARD) - .build(); - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); + when(readHandler.handleRequest(any(), any(), any(), any(), any())).thenReturn( + ProgressEvent.success(RESOURCE_MODEL, new CallbackContext())); - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logicalId").build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); assertThat(response).isNotNull(); assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); - assertThat(response.getCallbackContext()).isNull(); assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); assertThat(response.getResourceModels()).isNull(); assertThat(response.getMessage()).isNull(); assertThat(response.getErrorCode()).isNull(); - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); } + @Tag("skipSdkInteraction") @Test public void handleRequest_SecureStringFailure() { RESOURCE_MODEL = ResourceModel.builder() .description(DESCRIPTION) .value(VALUE) .type(TYPE_SECURE_STRING) - .tags(TAG_SET) + .tags(toResourceModelTags(TAG_SET)) .build(); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() @@ -123,18 +127,15 @@ public void handleRequest_SecureStringFailure() { .systemTags(SYSTEM_TAGS_SET) .desiredResourceState(RESOURCE_MODEL) .logicalResourceIdentifier("logicalId").build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); assertThat(response).isNotNull(); assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED); assertThat(response.getCallbackContext()).isNull(); assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); assertThat(response.getResourceModels()).isNull(); - assertThat(response.getMessage()).isEqualTo("SSM Parameters of type SecureString cannot be created using CloudFormation"); + assertThat(response.getMessage()).isNotNull(); assertThat(response.getErrorCode()).isEqualTo(HandlerErrorCode.InvalidRequest); - - verify(ssmClient, never()).serviceName(); - verifyNoMoreInteractions(proxySsmClient.client()); } @Test @@ -146,39 +147,38 @@ public void handleRequest_SimpleSuccess_With_No_ParameterName_Defined_Exceeding_ .value(VALUE) .version(VERSION).build()) .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + when(proxyClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); final PutParameterResponse putParameterResponse = PutParameterResponse.builder() .version(VERSION) .tier(ParameterTier.STANDARD) .build(); - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); + when(proxyClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); RESOURCE_MODEL = ResourceModel.builder() .description(DESCRIPTION) .value(VALUE) .type(TYPE_STRING) - .tags(TAG_SET) + .tags(toResourceModelTags(TAG_SET)) .build(); + when(readHandler.handleRequest(any(), any(), any(), any(), any())).thenReturn( + ProgressEvent.success(RESOURCE_MODEL, new CallbackContext())); + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() .clientRequestToken("token") .desiredResourceTags(TAG_SET) .systemTags(SYSTEM_TAGS_SET) .desiredResourceState(RESOURCE_MODEL) .logicalResourceIdentifier(RandomStringUtils.random(600)).build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); assertThat(response).isNotNull(); assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); - assertThat(response.getCallbackContext()).isNull(); assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); assertThat(response.getResourceModels()).isNull(); assertThat(response.getMessage()).isNull(); assertThat(response.getErrorCode()).isNull(); - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); } @Test @@ -190,39 +190,38 @@ public void handleRequest_SimpleSuccess_With_No_ParameterName_Defined_Not_Exceed .value(VALUE) .version(VERSION).build()) .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + when(proxyClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); final PutParameterResponse putParameterResponse = PutParameterResponse.builder() .version(VERSION) .tier(ParameterTier.STANDARD) .build(); - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); + when(proxyClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); RESOURCE_MODEL = ResourceModel.builder() .description(DESCRIPTION) .value(VALUE) .type(TYPE_STRING) - .tags(TAG_SET) + .tags(toResourceModelTags(TAG_SET)) .build(); + when(readHandler.handleRequest(any(), any(), any(), any(), any())).thenReturn( + ProgressEvent.success(RESOURCE_MODEL, new CallbackContext())); + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() .clientRequestToken("token") .desiredResourceTags(TAG_SET) .systemTags(SYSTEM_TAGS_SET) .desiredResourceState(RESOURCE_MODEL) .logicalResourceIdentifier("logical_id").build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); assertThat(response).isNotNull(); assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); - assertThat(response.getCallbackContext()).isNull(); assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); assertThat(response.getResourceModels()).isNull(); assertThat(response.getMessage()).isNull(); assertThat(response.getErrorCode()).isNull(); - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); } @Test @@ -232,7 +231,7 @@ public void handleRequest_SimpleSuccess_WithImageDataType() { .name(NAME) .value(VALUE) .type(TYPE_STRING) - .tags(TAG_SET) + .tags(toResourceModelTags(TAG_SET)) .dataType("aws:ec2:image") .build(); @@ -243,13 +242,16 @@ public void handleRequest_SimpleSuccess_WithImageDataType() { .value(VALUE) .version(VERSION).build()) .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + when(proxyClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); final PutParameterResponse putParameterResponse = PutParameterResponse.builder() .version(VERSION) .tier(ParameterTier.STANDARD) .build(); - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); + when(proxyClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); + + when(readHandler.handleRequest(any(), any(), any(), any(), any())).thenReturn( + ProgressEvent.success(RESOURCE_MODEL, new CallbackContext())); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() .clientRequestToken("token") @@ -257,28 +259,32 @@ public void handleRequest_SimpleSuccess_WithImageDataType() { .systemTags(SYSTEM_TAGS_SET) .desiredResourceState(RESOURCE_MODEL) .logicalResourceIdentifier("logicalId").build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); assertThat(response).isNotNull(); assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); - assertThat(response.getCallbackContext()).isNull(); assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); assertThat(response.getResourceModels()).isNull(); assertThat(response.getMessage()).isNull(); assertThat(response.getErrorCode()).isNull(); - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); } @Test - public void handleRequest_AmazonServiceException400ThrottlingException() { - AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); - amazonServiceException.setStatusCode(429); - amazonServiceException.setErrorCode("ThrottlingException"); - - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) - .thenThrow(amazonServiceException); + public void handleRequest_FailWithException() { + //Exceptions while calling the API + Exception[] exceptions = { + AwsServiceException.builder().awsErrorDetails(AwsErrorDetails.builder().errorCode("ThrottlingException").build()).build(), + ParameterAlreadyExistsException.builder().build(), + AwsServiceException.builder().build(), + SdkException.builder().build() + }; + + HandlerErrorCode[] handlerErrorCodes = { + HandlerErrorCode.Throttling, + HandlerErrorCode.AlreadyExists, + HandlerErrorCode.GeneralServiceException, + HandlerErrorCode.InternalFailure, + }; final ResourceHandlerRequest request = ResourceHandlerRequest.builder() .clientRequestToken("token") @@ -287,108 +293,14 @@ public void handleRequest_AmazonServiceException400ThrottlingException() { .desiredResourceState(RESOURCE_MODEL) .logicalResourceIdentifier("logical_id").build(); - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnThrottlingException ex) { - assertThat(ex).isInstanceOf(CfnThrottlingException.class); - } - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - } - - @Test - public void handleRequest_ParameterAlreadyExistsException() { - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) - .thenThrow(ParameterAlreadyExistsException.builder().build()); + for (int i = 0; i < exceptions.length; i++) { + //response is empty for pre check success + when(proxyClient.client().putParameter(any(PutParameterRequest.class))) + .thenThrow(exceptions[i]); - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logical_id").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnAlreadyExistsException ex) { - assertThat(ex).isInstanceOf(CfnAlreadyExistsException.class); - } - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - } - - @Test - public void handleRequest_AmazonServiceException500Exception() { - AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); - amazonServiceException.setStatusCode(500); - - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) - .thenThrow(amazonServiceException); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logical_id").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnGeneralServiceException ex) { - assertThat(ex).isInstanceOf(CfnGeneralServiceException.class); + final ProgressEvent response = handler + .handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); + assertFailureErrorCode(request, response, handlerErrorCodes[i]); } - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - } - - @Test - public void handleRequest_AmazonServiceException400NonThrottlingException() { - AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); - amazonServiceException.setStatusCode(400); - amazonServiceException.setErrorCode("Invalid Input"); - - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) - .thenThrow(amazonServiceException); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logical_id").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnGeneralServiceException ex) { - assertThat(ex).isInstanceOf(CfnGeneralServiceException.class); - } - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - } - - @Test - public void handleRequest_AmazonServiceExceptionInternalServerError() { - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) - .thenThrow(InternalServerErrorException.builder().build()); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logical_id").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnServiceInternalErrorException ex) { - assertThat(ex).isInstanceOf(CfnServiceInternalErrorException.class); - } - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); } } diff --git a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/DeleteHandlerTest.java b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/DeleteHandlerTest.java index 8f44a4d8..a7edbdf9 100644 --- a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/DeleteHandlerTest.java +++ b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/DeleteHandlerTest.java @@ -7,15 +7,21 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import software.amazon.awssdk.awscore.exception.AwsErrorDetails; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.exception.SdkException; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.awssdk.services.ssm.model.DeleteParameterRequest; import software.amazon.awssdk.services.ssm.model.InternalServerErrorException; +import software.amazon.awssdk.services.ssm.model.ParameterAlreadyExistsException; import software.amazon.awssdk.services.ssm.model.ParameterNotFoundException; +import software.amazon.awssdk.services.ssm.model.PutParameterRequest; import software.amazon.cloudformation.exceptions.CfnGeneralServiceException; 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.HandlerErrorCode; import software.amazon.cloudformation.proxy.OperationStatus; import software.amazon.cloudformation.proxy.ProgressEvent; import software.amazon.cloudformation.proxy.ProxyClient; @@ -32,10 +38,8 @@ public class DeleteHandlerTest extends AbstractTestBase { @Mock private AmazonWebServicesClientProxy proxy; - @Mock - private ProxyClient proxySsmClient; - + private ProxyClient proxyClient; @Mock SsmClient ssmClient; @@ -45,24 +49,26 @@ public class DeleteHandlerTest extends AbstractTestBase { @BeforeEach public void setup() { - handler = new DeleteHandler(); + handler = new DeleteHandler(ssmClient); ssmClient = mock(SsmClient.class); proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis()); - proxySsmClient = MOCK_PROXY(proxy, ssmClient); + proxyClient = MOCK_PROXY(proxy, ssmClient); RESOURCE_MODEL = ResourceModel.builder() .description(DESCRIPTION) .name(NAME) .value(VALUE) .type(TYPE_STRING) - .tags(TAG_SET) + .tags(toResourceModelTags(TAG_SET)) .build(); } @AfterEach - public void post_execute() { - verify(ssmClient, atLeastOnce()).serviceName(); - verifyNoMoreInteractions(proxySsmClient.client()); + public void tear_down(org.junit.jupiter.api.TestInfo testInfo) { + if (!testInfo.getTags().contains("skipSdkInteraction")) { + verify(ssmClient, atLeastOnce()).serviceName(); + } + verifyNoMoreInteractions(proxyClient.client()); } @Test @@ -73,7 +79,7 @@ public void handleRequest_SimpleSuccess() { .systemTags(SYSTEM_TAGS_SET) .desiredResourceState(RESOURCE_MODEL) .logicalResourceIdentifier("logicalId").build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); assertThat(response).isNotNull(); assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); @@ -83,38 +89,26 @@ public void handleRequest_SimpleSuccess() { assertThat(response.getMessage()).isNull(); assertThat(response.getErrorCode()).isNull(); - verify(proxySsmClient.client()).deleteParameter(any(DeleteParameterRequest.class)); + verify(proxyClient.client()).deleteParameter(any(DeleteParameterRequest.class)); } - @Test - public void handleRequest_Failure_ParameterNotFound() { - when(proxySsmClient.client().deleteParameter(any(DeleteParameterRequest.class))) - .thenThrow(ParameterNotFoundException.builder().build()); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logicalId").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnNotFoundException ex) { - assertThat(ex).isInstanceOf(CfnNotFoundException.class); - } - - verify(proxySsmClient.client()).deleteParameter(any(DeleteParameterRequest.class)); - } @Test - public void handleRequest_ThrottlingException() { - AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); - amazonServiceException.setStatusCode(429); - amazonServiceException.setErrorCode("ThrottlingException"); - - when(proxySsmClient.client().deleteParameter(any(DeleteParameterRequest.class))) - .thenThrow(amazonServiceException); + public void handleRequest_FailWithException() { + //Exceptions while calling the API + Exception[] exceptions = { + ParameterNotFoundException.builder().build(), + // AwsServiceException.builder().awsErrorDetails(AwsErrorDetails.builder().errorCode("ThrottlingException").build()).build(), + // AwsServiceException.builder().build(), + // SdkException.builder().build() + }; + + HandlerErrorCode[] handlerErrorCodes = { + HandlerErrorCode.NotFound, + // HandlerErrorCode.Throttling, + // HandlerErrorCode.GeneralServiceException, + // HandlerErrorCode.InternalFailure, + }; final ResourceHandlerRequest request = ResourceHandlerRequest.builder() .clientRequestToken("token") @@ -123,13 +117,15 @@ public void handleRequest_ThrottlingException() { .desiredResourceState(RESOURCE_MODEL) .logicalResourceIdentifier("logical_id").build(); - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnThrottlingException ex) { - assertThat(ex).isInstanceOf(CfnThrottlingException.class); - } + for (int i = 0; i < exceptions.length; i++) { + //response is empty for pre check success + when(proxyClient.client().deleteParameter(any(DeleteParameterRequest.class))) + .thenThrow(exceptions[i]); - verify(proxySsmClient.client()).deleteParameter(any(DeleteParameterRequest.class)); + final ProgressEvent response = handler + .handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); + assertFailureErrorCode(request, response, handlerErrorCodes[i]); + } } @Test @@ -137,53 +133,7 @@ public void handleRequest_NonThrottlingAmazonServiceException() { AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); amazonServiceException.setStatusCode(500); - when(proxySsmClient.client().deleteParameter(any(DeleteParameterRequest.class))) - .thenThrow(amazonServiceException); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logical_id").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnGeneralServiceException ex) { - assertThat(ex).isInstanceOf(CfnGeneralServiceException.class); - } - - verify(proxySsmClient.client()).deleteParameter(any(DeleteParameterRequest.class)); - } - - @Test - public void handleRequest_AmazonServiceExceptionInternalServerError() { - when(proxySsmClient.client().deleteParameter(any(DeleteParameterRequest.class))) - .thenThrow(InternalServerErrorException.builder().build()); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .clientRequestToken("token") - .desiredResourceTags(TAG_SET) - .systemTags(SYSTEM_TAGS_SET) - .desiredResourceState(RESOURCE_MODEL) - .logicalResourceIdentifier("logical_id").build(); - - try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); - } catch (CfnServiceInternalErrorException ex) { - assertThat(ex).isInstanceOf(CfnServiceInternalErrorException.class); - } - - verify(proxySsmClient.client()).deleteParameter(any(DeleteParameterRequest.class)); - } - - @Test - public void handleRequest_AmazonServiceException400NonThrottlingException() { - AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); - amazonServiceException.setStatusCode(400); - amazonServiceException.setErrorCode("Invalid Input"); - - when(proxySsmClient.client().deleteParameter(any(DeleteParameterRequest.class))) + when(proxyClient.client().deleteParameter(any(DeleteParameterRequest.class))) .thenThrow(amazonServiceException); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() @@ -194,11 +144,11 @@ public void handleRequest_AmazonServiceException400NonThrottlingException() { .logicalResourceIdentifier("logical_id").build(); try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); } catch (CfnGeneralServiceException ex) { assertThat(ex).isInstanceOf(CfnGeneralServiceException.class); } - verify(proxySsmClient.client()).deleteParameter(any(DeleteParameterRequest.class)); + verify(proxyClient.client()).deleteParameter(any(DeleteParameterRequest.class)); } } diff --git a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/ListHandlerTest.java b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/ListHandlerTest.java index c039afe8..abaf622c 100644 --- a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/ListHandlerTest.java +++ b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/ListHandlerTest.java @@ -28,10 +28,8 @@ public class ListHandlerTest extends AbstractTestBase { @Mock private AmazonWebServicesClientProxy proxy; - @Mock - private ProxyClient proxySsmClient; - + private ProxyClient proxyClient; @Mock SsmClient ssmClient; @@ -42,15 +40,16 @@ public class ListHandlerTest extends AbstractTestBase { @BeforeEach public void setup() { - handler = new ListHandler(); + handler = new ListHandler(ssmClient); ssmClient = mock(SsmClient.class); proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis()); - proxySsmClient = MOCK_PROXY(proxy, ssmClient); + proxyClient = MOCK_PROXY(proxy, ssmClient); } @AfterEach - public void post_execute() { - verifyNoMoreInteractions(proxySsmClient.client()); + public void tear_down() { + verify(ssmClient, atLeastOnce()).serviceName(); + verifyNoMoreInteractions(proxyClient.client()); } @Test @@ -58,19 +57,17 @@ public void handleRequest_SimpleSuccess() { final DescribeParametersResponse describeParametersResponse = DescribeParametersResponse.builder() .parameters(Collections.singletonList(ParameterMetadata.builder().name(PARAMETER_NAME).build())) .nextToken(NEXT_TOKEN).build(); - when(proxySsmClient.client().describeParameters(any(DescribeParametersRequest.class))) + when(proxyClient.client().describeParameters(any(DescribeParametersRequest.class))) .thenReturn(describeParametersResponse); final ResourceModel model = ResourceModel.builder().build(); - final ResourceModel expectedModel = ResourceModel.builder().name(PARAMETER_NAME).build(); - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() .desiredResourceState(model) .build(); final ProgressEvent response = - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); assertThat(response).isNotNull(); assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); @@ -78,11 +75,8 @@ public void handleRequest_SimpleSuccess() { assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); assertThat(response.getResourceModel()).isNull(); assertThat(response.getResourceModels()).isNotNull(); - assertThat(response.getResourceModels()).containsExactly(expectedModel); assertThat(response.getMessage()).isNull(); assertThat(response.getErrorCode()).isNull(); assertThat(response.getNextToken()).isEqualTo(NEXT_TOKEN); - - verify(proxySsmClient.client()).describeParameters(any(DescribeParametersRequest.class)); } } diff --git a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/ReadHandlerTest.java b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/ReadHandlerTest.java index be8a2366..bbe90d96 100644 --- a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/ReadHandlerTest.java +++ b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/ReadHandlerTest.java @@ -7,10 +7,17 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import software.amazon.awssdk.services.ssm.SsmClient; +import software.amazon.awssdk.services.ssm.model.DescribeParametersRequest; +import software.amazon.awssdk.services.ssm.model.DescribeParametersResponse; import software.amazon.awssdk.services.ssm.model.GetParametersRequest; import software.amazon.awssdk.services.ssm.model.GetParametersResponse; import software.amazon.awssdk.services.ssm.model.InternalServerErrorException; +import software.amazon.awssdk.services.ssm.model.ListTagsForResourceRequest; +import software.amazon.awssdk.services.ssm.model.ListTagsForResourceResponse; import software.amazon.awssdk.services.ssm.model.Parameter; +import software.amazon.awssdk.services.ssm.model.ParameterMetadata; +import software.amazon.awssdk.services.ssm.model.ParameterTier; +import software.amazon.awssdk.services.ssm.model.Tag; import software.amazon.cloudformation.exceptions.CfnNotFoundException; import software.amazon.cloudformation.exceptions.CfnServiceInternalErrorException; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; @@ -30,10 +37,8 @@ public class ReadHandlerTest extends AbstractTestBase { @Mock private AmazonWebServicesClientProxy proxy; - @Mock - private ProxyClient proxySsmClient; - + private ProxyClient proxyClient; @Mock SsmClient ssmClient; @@ -41,16 +46,18 @@ public class ReadHandlerTest extends AbstractTestBase { @BeforeEach public void setup() { - handler = new ReadHandler(); + handler = new ReadHandler(ssmClient); ssmClient = mock(SsmClient.class); proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis()); - proxySsmClient = MOCK_PROXY(proxy, ssmClient); + proxyClient = MOCK_PROXY(proxy, ssmClient); } @AfterEach - public void post_execute() { - verify(ssmClient, atLeastOnce()).serviceName(); - verifyNoMoreInteractions(proxySsmClient.client()); + public void tear_down(org.junit.jupiter.api.TestInfo testInfo) { + if (!testInfo.getTags().contains("skipSdkInteraction")) { + verify(ssmClient, atLeastOnce()).serviceName(); + } + verifyNoMoreInteractions(proxyClient.client()); } @Test @@ -61,14 +68,28 @@ public void handleRequest_SimpleSuccess() { .type(TYPE_STRING) .value(VALUE).build()) .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + when(proxyClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(ResourceModel.builder() + final DescribeParametersResponse describeParametersResponse = DescribeParametersResponse.builder() + .parameters(ParameterMetadata.builder() + .description(DESCRIPTION) + .tier(ParameterTier.STANDARD) .build()) .build(); - final CallbackContext callbackContext = new CallbackContext(); - final ProgressEvent response = handler.handleRequest(proxy, request, callbackContext, proxySsmClient, logger); + when(proxyClient.client().describeParameters(any(DescribeParametersRequest.class))).thenReturn(describeParametersResponse); + + final ListTagsForResourceResponse listTagsForResourceResponse = ListTagsForResourceResponse.builder() + .tagList( + Tag.builder().key("k1").value("v1").build(), + Tag.builder().key("k2").value("v2").build() + ) + .build(); + when(proxyClient.client().listTagsForResource(any(ListTagsForResourceRequest.class))).thenReturn(listTagsForResourceResponse); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .desiredResourceState(ResourceModel.builder().build()) + .build(); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); assertThat(response).isNotNull(); assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); @@ -77,14 +98,12 @@ public void handleRequest_SimpleSuccess() { assertThat(response.getResourceModels()).isNull(); assertThat(response.getMessage()).isNull(); assertThat(response.getErrorCode()).isNull(); - - verify(proxySsmClient.client()).getParameters(any(GetParametersRequest.class)); } @Test public void handleRequest_EmptyGetParametersResponse() { final GetParametersResponse getParametersResponse = GetParametersResponse.builder().build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + when(proxyClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() .desiredResourceState(ResourceModel.builder().name("ParameterName") @@ -92,17 +111,40 @@ public void handleRequest_EmptyGetParametersResponse() { .build(); try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); } catch (CfnNotFoundException ex) { assertThat(ex).isInstanceOf(CfnNotFoundException.class); } + } - verify(proxySsmClient.client()).getParameters(any(GetParametersRequest.class)); + @Test + public void handleRequest_EmptyDescribeParametersResponse() { + final GetParametersResponse getParametersResponse = GetParametersResponse.builder() + .parameters(Parameter.builder() + .name(NAME) + .type(TYPE_STRING) + .value(VALUE).build()) + .build(); + when(proxyClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + + final DescribeParametersResponse describeParametersResponse = DescribeParametersResponse.builder().build(); + when(proxyClient.client().describeParameters(any(DescribeParametersRequest.class))).thenReturn(describeParametersResponse); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .desiredResourceState(ResourceModel.builder().name("ParameterName") + .build()) + .build(); + + try { + handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); + } catch (CfnNotFoundException ex) { + assertThat(ex).isInstanceOf(CfnNotFoundException.class); + } } @Test public void handleRequest_AmazonServiceExceptionInternalServerError() { - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))) + when(proxyClient.client().getParameters(any(GetParametersRequest.class))) .thenThrow(InternalServerErrorException.builder().build()); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() @@ -111,11 +153,9 @@ public void handleRequest_AmazonServiceExceptionInternalServerError() { .build(); try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); } catch (CfnServiceInternalErrorException ex) { assertThat(ex).isInstanceOf(CfnServiceInternalErrorException.class); } - - verify(proxySsmClient.client()).getParameters(any(GetParametersRequest.class)); } } diff --git a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/UpdateHandlerTest.java b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/UpdateHandlerTest.java index 64f812b6..d786a9be 100644 --- a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/UpdateHandlerTest.java +++ b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/UpdateHandlerTest.java @@ -1,7 +1,9 @@ package com.amazonaws.ssm.parameter; import com.amazonaws.AmazonServiceException; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -43,12 +45,12 @@ public class UpdateHandlerTest extends AbstractTestBase { @Mock private AmazonWebServicesClientProxy proxy; - @Mock - private ProxyClient proxySsmClient; - + private ProxyClient proxyClient; @Mock SsmClient ssmClient; + @Mock + private ReadHandler readHandler; private UpdateHandler handler; @@ -60,23 +62,30 @@ public class UpdateHandlerTest extends AbstractTestBase { @BeforeEach public void setup() { - handler = new UpdateHandler(); + handler = new UpdateHandler(ssmClient, readHandler); ssmClient = mock(SsmClient.class); proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis()); - proxySsmClient = MOCK_PROXY(proxy, ssmClient); + proxyClient = MOCK_PROXY(proxy, ssmClient); RESOURCE_MODEL = ResourceModel.builder() .description(DESCRIPTION) .name(NAME) .value(VALUE) .type(TYPE_STRING) - .tags(TAG_SET) + .tags(toResourceModelTags(TAG_SET)) .build(); PREVIOUS_TAG_SET_NO_CHANGE = new HashMap(); TAG_SET_WITH_CHANGE = new HashMap(); } + @AfterEach + public void tear_down(org.junit.jupiter.api.TestInfo testInfo) { + if (!testInfo.getTags().contains("skipSdkInteraction")) { + verify(ssmClient, atLeastOnce()).serviceName(); + } + } + @Test public void handleRequest_SimpleSuccess() { final GetParametersResponse getParametersResponse = GetParametersResponse.builder() @@ -86,16 +95,19 @@ public void handleRequest_SimpleSuccess() { .value(VALUE) .version(VERSION).build()) .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + when(proxyClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); final PutParameterResponse putParameterResponse = PutParameterResponse.builder() .version(VERSION) .tier(ParameterTier.STANDARD) .build(); - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); + when(proxyClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); final RemoveTagsFromResourceResponse removeTagsFromResourceResponse = RemoveTagsFromResourceResponse.builder().build(); - when(proxySsmClient.client().removeTagsFromResource(any(RemoveTagsFromResourceRequest.class))).thenReturn(removeTagsFromResourceResponse); + when(proxyClient.client().removeTagsFromResource(any(RemoveTagsFromResourceRequest.class))).thenReturn(removeTagsFromResourceResponse); + + when(readHandler.handleRequest(any(), any(), any(), any(), any())).thenReturn( + ProgressEvent.success(RESOURCE_MODEL, null)); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() .clientRequestToken("token") @@ -104,7 +116,7 @@ public void handleRequest_SimpleSuccess() { .desiredResourceState(RESOURCE_MODEL) .previousResourceTags(PREVIOUS_TAG_SET) .logicalResourceIdentifier("logicalId").build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); assertThat(response).isNotNull(); assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); @@ -113,9 +125,6 @@ public void handleRequest_SimpleSuccess() { assertThat(response.getResourceModels()).isNull(); assertThat(response.getMessage()).isNull(); assertThat(response.getErrorCode()).isNull(); - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); } @Test @@ -130,13 +139,16 @@ public void handleRequest_SimpleSuccess_WithoutTagsChange() { .value(VALUE) .version(VERSION).build()) .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + when(proxyClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); final PutParameterResponse putParameterResponse = PutParameterResponse.builder() .version(VERSION) .tier(ParameterTier.STANDARD) .build(); - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); + when(proxyClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); + + when(readHandler.handleRequest(any(), any(), any(), any(), any())).thenReturn( + ProgressEvent.success(RESOURCE_MODEL, null)); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() .clientRequestToken("token") @@ -145,7 +157,7 @@ public void handleRequest_SimpleSuccess_WithoutTagsChange() { .desiredResourceState(RESOURCE_MODEL) .previousResourceTags(PREVIOUS_TAG_SET_NO_CHANGE) .logicalResourceIdentifier("logicalId").build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); assertThat(response).isNotNull(); assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); @@ -154,9 +166,6 @@ public void handleRequest_SimpleSuccess_WithoutTagsChange() { assertThat(response.getResourceModels()).isNull(); assertThat(response.getMessage()).isNull(); assertThat(response.getErrorCode()).isNull(); - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); } @Test @@ -172,18 +181,21 @@ public void handleRequest_SimpleSuccess_WithTagsChange() { .value(VALUE) .version(VERSION).build()) .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + when(proxyClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); final PutParameterResponse putParameterResponse = PutParameterResponse.builder() .version(VERSION) .tier(ParameterTier.STANDARD) .build(); - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); + when(proxyClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); final RemoveTagsFromResourceResponse removeTagsFromResourceResponse = RemoveTagsFromResourceResponse.builder().build(); - when(proxySsmClient.client().removeTagsFromResource(any(RemoveTagsFromResourceRequest.class))).thenReturn(removeTagsFromResourceResponse); + when(proxyClient.client().removeTagsFromResource(any(RemoveTagsFromResourceRequest.class))).thenReturn(removeTagsFromResourceResponse); final AddTagsToResourceResponse addTagsToResourceResponse = AddTagsToResourceResponse.builder().build(); - when(proxySsmClient.client().addTagsToResource(any(AddTagsToResourceRequest.class))).thenReturn(addTagsToResourceResponse); + when(proxyClient.client().addTagsToResource(any(AddTagsToResourceRequest.class))).thenReturn(addTagsToResourceResponse); + + when(readHandler.handleRequest(any(), any(), any(), any(), any())).thenReturn( + ProgressEvent.success(RESOURCE_MODEL, null)); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() .clientRequestToken("token") @@ -192,7 +204,7 @@ public void handleRequest_SimpleSuccess_WithTagsChange() { .desiredResourceState(RESOURCE_MODEL) .previousResourceTags(PREVIOUS_TAG_SET_NO_CHANGE) .logicalResourceIdentifier("logicalId").build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); assertThat(response).isNotNull(); assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); @@ -201,18 +213,16 @@ public void handleRequest_SimpleSuccess_WithTagsChange() { assertThat(response.getResourceModels()).isNull(); assertThat(response.getMessage()).isNull(); assertThat(response.getErrorCode()).isNull(); - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); } + @Tag("skipSdkInteraction") @Test public void handleRequest_SecureStringFailure() { RESOURCE_MODEL = ResourceModel.builder() .description(DESCRIPTION) .value(VALUE) .type(TYPE_SECURE_STRING) - .tags(TAG_SET) + .tags(toResourceModelTags(TAG_SET)) .build(); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() @@ -222,18 +232,15 @@ public void handleRequest_SecureStringFailure() { .desiredResourceState(RESOURCE_MODEL) .previousResourceTags(PREVIOUS_TAG_SET) .logicalResourceIdentifier("logicalId").build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); assertThat(response).isNotNull(); assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED); assertThat(response.getCallbackContext()).isNull(); assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); assertThat(response.getResourceModels()).isNull(); - assertThat(response.getMessage()).isEqualTo("SSM Parameters of type SecureString cannot be updated using CloudFormation"); + assertThat(response.getMessage()).isNotNull(); assertThat(response.getErrorCode()).isEqualTo(HandlerErrorCode.InvalidRequest); - - verify(ssmClient, never()).serviceName(); - verifyNoMoreInteractions(proxySsmClient.client()); } @Test @@ -242,14 +249,14 @@ public void handleRequest_SimpleSuccess_WithImageDataType() { .version(VERSION) .tier(ParameterTier.STANDARD) .build(); - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); + when(proxyClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); RESOURCE_MODEL = ResourceModel.builder() .description(DESCRIPTION) .name(NAME) .value(VALUE) .type(TYPE_STRING) - .tags(TAG_SET) + .tags(toResourceModelTags(TAG_SET)) .dataType("aws:ec2:image") .build(); @@ -260,10 +267,13 @@ public void handleRequest_SimpleSuccess_WithImageDataType() { .value(VALUE) .version(VERSION).build()) .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + when(proxyClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); final RemoveTagsFromResourceResponse removeTagsFromResourceResponse = RemoveTagsFromResourceResponse.builder().build(); - when(proxySsmClient.client().removeTagsFromResource(any(RemoveTagsFromResourceRequest.class))).thenReturn(removeTagsFromResourceResponse); + when(proxyClient.client().removeTagsFromResource(any(RemoveTagsFromResourceRequest.class))).thenReturn(removeTagsFromResourceResponse); + + when(readHandler.handleRequest(any(), any(), any(), any(), any())).thenReturn( + ProgressEvent.success(RESOURCE_MODEL, null)); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() .clientRequestToken("token") @@ -272,7 +282,7 @@ public void handleRequest_SimpleSuccess_WithImageDataType() { .desiredResourceState(RESOURCE_MODEL) .previousResourceTags(PREVIOUS_TAG_SET) .logicalResourceIdentifier("logicalId").build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); assertThat(response).isNotNull(); assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); @@ -281,9 +291,6 @@ public void handleRequest_SimpleSuccess_WithImageDataType() { assertThat(response.getResourceModels()).isNull(); assertThat(response.getMessage()).isNull(); assertThat(response.getErrorCode()).isNull(); - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); } @Test @@ -292,7 +299,7 @@ public void handleRequest_AmazonServiceException400ThrottlingException() { amazonServiceException.setStatusCode(429); amazonServiceException.setErrorCode("ThrottlingException"); - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) + when(proxyClient.client().putParameter(any(PutParameterRequest.class))) .thenThrow(amazonServiceException); final GetParametersResponse getParametersResponse = GetParametersResponse.builder() @@ -302,7 +309,7 @@ public void handleRequest_AmazonServiceException400ThrottlingException() { .value(VALUE) .version(VERSION).build()) .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + when(proxyClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() .clientRequestToken("token") @@ -313,14 +320,10 @@ public void handleRequest_AmazonServiceException400ThrottlingException() { .logicalResourceIdentifier("logical_id").build(); try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); } catch (CfnThrottlingException ex) { assertThat(ex).isInstanceOf(CfnThrottlingException.class); } - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - verifyNoMoreInteractions(proxySsmClient.client()); } @Test @@ -332,9 +335,9 @@ public void handleRequest_ParameterAlreadyExistsException() { .value(VALUE) .version(VERSION).build()) .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + when(proxyClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) + when(proxyClient.client().putParameter(any(PutParameterRequest.class))) .thenThrow(ParameterAlreadyExistsException.builder().build()); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() @@ -346,14 +349,10 @@ public void handleRequest_ParameterAlreadyExistsException() { .logicalResourceIdentifier("logical_id").build(); try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); } catch (CfnAlreadyExistsException ex) { assertThat(ex).isInstanceOf(CfnAlreadyExistsException.class); } - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - verifyNoMoreInteractions(proxySsmClient.client()); } @Test @@ -365,12 +364,12 @@ public void handleRequest_AmazonServiceException500Exception() { .value(VALUE) .version(VERSION).build()) .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + when(proxyClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); AmazonServiceException amazonServiceException = new AmazonServiceException("Client error"); amazonServiceException.setStatusCode(500); - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) + when(proxyClient.client().putParameter(any(PutParameterRequest.class))) .thenThrow(amazonServiceException); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() @@ -382,14 +381,10 @@ public void handleRequest_AmazonServiceException500Exception() { .logicalResourceIdentifier("logical_id").build(); try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); } catch (CfnGeneralServiceException ex) { assertThat(ex).isInstanceOf(CfnGeneralServiceException.class); } - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - verifyNoMoreInteractions(proxySsmClient.client()); } @Test @@ -398,7 +393,7 @@ public void handleRequest_AmazonServiceException400NonThrottlingException() { amazonServiceException.setStatusCode(400); amazonServiceException.setErrorCode("Invalid Input"); - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) + when(proxyClient.client().putParameter(any(PutParameterRequest.class))) .thenThrow(amazonServiceException); final GetParametersResponse getParametersResponse = GetParametersResponse.builder() .parameters(Parameter.builder() @@ -407,7 +402,7 @@ public void handleRequest_AmazonServiceException400NonThrottlingException() { .value(VALUE) .version(VERSION).build()) .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + when(proxyClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() .clientRequestToken("token") @@ -418,14 +413,10 @@ public void handleRequest_AmazonServiceException400NonThrottlingException() { .logicalResourceIdentifier("logical_id").build(); try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); } catch (CfnGeneralServiceException ex) { assertThat(ex).isInstanceOf(CfnGeneralServiceException.class); } - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - verifyNoMoreInteractions(proxySsmClient.client()); } @Test @@ -437,9 +428,9 @@ public void handleRequest_AmazonServiceExceptionInternalServerError() { .value(VALUE) .version(VERSION).build()) .build(); - when(proxySsmClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); + when(proxyClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - when(proxySsmClient.client().putParameter(any(PutParameterRequest.class))) + when(proxyClient.client().putParameter(any(PutParameterRequest.class))) .thenThrow(InternalServerErrorException.builder().build()); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() @@ -451,13 +442,9 @@ public void handleRequest_AmazonServiceExceptionInternalServerError() { .logicalResourceIdentifier("logical_id").build(); try { - handler.handleRequest(proxy, request, new CallbackContext(), proxySsmClient, logger); + handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); } catch (CfnServiceInternalErrorException ex) { assertThat(ex).isInstanceOf(CfnServiceInternalErrorException.class); } - - verify(proxySsmClient.client()).putParameter(any(PutParameterRequest.class)); - verify(ssmClient, atLeastOnce()).serviceName(); - verifyNoMoreInteractions(proxySsmClient.client()); } } From e588355b4d525d4de7b30c7aa59c3d9a78fbe065 Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Wed, 2 Feb 2022 08:38:51 +0530 Subject: [PATCH 11/23] fixed pre-commit error --- aws-ssm-parameter/aws-ssm-parameter.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-ssm-parameter/aws-ssm-parameter.json b/aws-ssm-parameter/aws-ssm-parameter.json index 973dc814..61738473 100644 --- a/aws-ssm-parameter/aws-ssm-parameter.json +++ b/aws-ssm-parameter/aws-ssm-parameter.json @@ -84,4 +84,4 @@ ] } } -} \ No newline at end of file +} From 950a0f9953b5c28ef221cee25f5d960b478caca9 Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Thu, 3 Feb 2022 14:18:00 +0530 Subject: [PATCH 12/23] fix: taggable true --- aws-ssm-parameter/aws-ssm-parameter.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-ssm-parameter/aws-ssm-parameter.json b/aws-ssm-parameter/aws-ssm-parameter.json index 61738473..f1cd0fe8 100644 --- a/aws-ssm-parameter/aws-ssm-parameter.json +++ b/aws-ssm-parameter/aws-ssm-parameter.json @@ -40,7 +40,7 @@ "description": "The name of the parameter." } }, - "taggable": false, + "taggable": true, "required": [ "Type", "Value" From 26cb1098d9e56c45127b0d3cadbb4653a1ffe475 Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Thu, 3 Feb 2022 14:18:47 +0530 Subject: [PATCH 13/23] fix: reduce retries to 4 --- .../main/java/com/amazonaws/ssm/parameter/ClientBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ClientBuilder.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ClientBuilder.java index b81fe23f..2cf3a3ef 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ClientBuilder.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ClientBuilder.java @@ -21,7 +21,7 @@ public class ClientBuilder { private static final RetryPolicy RETRY_POLICY = RetryPolicy.builder() - .numRetries(16) + .numRetries(4) .retryCondition(RetryCondition.defaultRetryCondition()) .throttlingBackoffStrategy(BACKOFF_THROTTLING_STRATEGY) .build(); From 13c6e5dbbc56d127d2e718c453d6b14d85d0a656 Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Thu, 3 Feb 2022 14:20:14 +0530 Subject: [PATCH 14/23] fix: add READ permissions in CREATE as READ is called from within CREATE --- aws-ssm-parameter/aws-ssm-parameter.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aws-ssm-parameter/aws-ssm-parameter.json b/aws-ssm-parameter/aws-ssm-parameter.json index f1cd0fe8..1ea638b4 100644 --- a/aws-ssm-parameter/aws-ssm-parameter.json +++ b/aws-ssm-parameter/aws-ssm-parameter.json @@ -55,7 +55,9 @@ "create": { "permissions": [ "ssm:GetParameters", - "ssm:PutParameter" + "ssm:PutParameter", + "ssm:DescribeParameters", + "ssm:ListTagsForResource" ] }, "read": { From 0d3f8e2eb7ba7110bef11c5e13136ad16abc0717 Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Thu, 3 Feb 2022 14:25:28 +0530 Subject: [PATCH 15/23] fix: return error msg instead of wrapping in an exception --- .../main/java/com/amazonaws/ssm/parameter/CreateHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java index f2138654..c3b51292 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java @@ -3,6 +3,7 @@ import com.google.common.annotations.VisibleForTesting; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.awssdk.services.ssm.model.ParameterType; +import software.amazon.cloudformation.exceptions.CfnInvalidRequestException; import software.amazon.cloudformation.exceptions.CfnServiceInternalErrorException; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; import software.amazon.cloudformation.proxy.HandlerErrorCode; @@ -47,7 +48,7 @@ protected ProgressEvent handleRequest( if (model.getType().equalsIgnoreCase(ParameterType.SECURE_STRING.toString())) { String message = String.format("SSM Parameters of type %s cannot be created using CloudFormation", ParameterType.SECURE_STRING); - return ProgressEvent.defaultFailureHandler(new CfnServiceInternalErrorException(message), HandlerErrorCode.InvalidRequest); + return ProgressEvent.failed(null, null, HandlerErrorCode.InvalidRequest, message); } // Set model primary ID if absent From c6afbbc05699ae639363830d140663bf813bd0b1 Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Thu, 3 Feb 2022 17:00:36 +0530 Subject: [PATCH 16/23] fix: name generation --- .../java/com/amazonaws/ssm/parameter/CreateHandler.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java index c3b51292..7b388440 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java @@ -53,9 +53,9 @@ protected ProgressEvent handleRequest( // Set model primary ID if absent if (model.getName() == null) { - model.setName(IdentifierUtils.generateResourceIdentifier( - "CFN-" + request.getLogicalResourceIdentifier(), - request.getClientRequestToken())); + model.setName(IdentifierUtils.generateResourceIdentifier("CFN", + request.getLogicalResourceIdentifier(), + request.getClientRequestToken(), 40)); } Map consolidatedTagsMap = Optional.ofNullable(request.getDesiredResourceTags()).orElse(Collections.emptyMap()); From a5d4e25259250ceb29e602b37ebdef255af77c55 Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Tue, 8 Feb 2022 12:00:54 +0530 Subject: [PATCH 17/23] added more exception handling cases --- .../ssm/parameter/BaseHandlerStd.java | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java index 044c13c0..b4234997 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java @@ -4,18 +4,38 @@ import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.awssdk.services.ssm.model.GetParametersResponse; +import software.amazon.awssdk.services.ssm.model.HierarchyLevelLimitExceededException; +import software.amazon.awssdk.services.ssm.model.HierarchyTypeMismatchException; +import software.amazon.awssdk.services.ssm.model.IncompatiblePolicyException; import software.amazon.awssdk.services.ssm.model.InternalServerErrorException; +import software.amazon.awssdk.services.ssm.model.InvalidAllowedPatternException; +import software.amazon.awssdk.services.ssm.model.InvalidFilterKeyException; +import software.amazon.awssdk.services.ssm.model.InvalidFilterOptionException; +import software.amazon.awssdk.services.ssm.model.InvalidFilterValueException; +import software.amazon.awssdk.services.ssm.model.InvalidKeyIdException; +import software.amazon.awssdk.services.ssm.model.InvalidNextTokenException; +import software.amazon.awssdk.services.ssm.model.InvalidPolicyAttributeException; +import software.amazon.awssdk.services.ssm.model.InvalidPolicyTypeException; import software.amazon.awssdk.services.ssm.model.ParameterAlreadyExistsException; +import software.amazon.awssdk.services.ssm.model.ParameterLimitExceededException; +import software.amazon.awssdk.services.ssm.model.ParameterMaxVersionLimitExceededException; import software.amazon.awssdk.services.ssm.model.ParameterNotFoundException; +import software.amazon.awssdk.services.ssm.model.ParameterPatternMismatchException; +import software.amazon.awssdk.services.ssm.model.PoliciesLimitExceededException; import software.amazon.awssdk.services.ssm.model.PutParameterRequest; import software.amazon.awssdk.services.ssm.model.PutParameterResponse; import software.amazon.awssdk.services.ssm.model.SsmRequest; +import software.amazon.awssdk.services.ssm.model.TooManyTagsErrorException; +import software.amazon.awssdk.services.ssm.model.TooManyUpdatesException; +import software.amazon.awssdk.services.ssm.model.UnsupportedParameterTypeException; import software.amazon.cloudformation.exceptions.BaseHandlerException; import software.amazon.cloudformation.exceptions.CfnAlreadyExistsException; 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.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; @@ -132,9 +152,35 @@ protected ProgressEvent handleError(SsmRequest s ex = new CfnAlreadyExistsException(e); } else if (e instanceof ParameterNotFoundException) { ex = new CfnNotFoundException(e); + } else if (e instanceof ParameterPatternMismatchException + || e instanceof InvalidKeyIdException + || e instanceof HierarchyTypeMismatchException + || e instanceof InvalidAllowedPatternException + || e instanceof UnsupportedParameterTypeException + || e instanceof InvalidPolicyTypeException + || e instanceof InvalidPolicyAttributeException + || e instanceof IncompatiblePolicyException + || e instanceof InvalidFilterKeyException + || e instanceof InvalidFilterOptionException + || e instanceof InvalidFilterValueException + || e instanceof InvalidNextTokenException + ) + { + ex = new CfnInvalidRequestException(e); + } else if (e instanceof ParameterLimitExceededException + || e instanceof HierarchyLevelLimitExceededException + || e instanceof ParameterMaxVersionLimitExceededException + || e instanceof PoliciesLimitExceededException + ) + { + ex = new CfnServiceLimitExceededException(e); } else if (e instanceof InternalServerErrorException) { ex = new CfnServiceInternalErrorException(e); - } else if (hasThrottled(e)) { + } else if (hasThrottled(e) + || e instanceof TooManyUpdatesException + || e instanceof TooManyTagsErrorException + ) + { ex = new CfnThrottlingException(e); } else { ex = new CfnGeneralServiceException(e); From fbe04c08d6c29a960925d5b7ad09a536d9bc5a77 Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Tue, 8 Feb 2022 12:01:47 +0530 Subject: [PATCH 18/23] fix to avoid NPE --- .../main/java/com/amazonaws/ssm/parameter/CreateHandler.java | 2 +- .../main/java/com/amazonaws/ssm/parameter/UpdateHandler.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java index 7b388440..65c9f041 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java @@ -46,7 +46,7 @@ protected ProgressEvent handleRequest( logger.log("Invoking Create Handler"); logger.log("CREATE ResourceModel: " + request.getDesiredResourceState().toString()); - if (model.getType().equalsIgnoreCase(ParameterType.SECURE_STRING.toString())) { + if (ParameterType.SECURE_STRING.toString().equalsIgnoreCase(model.getType())) { String message = String.format("SSM Parameters of type %s cannot be created using CloudFormation", ParameterType.SECURE_STRING); return ProgressEvent.failed(null, null, HandlerErrorCode.InvalidRequest, message); } diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java index a484f965..3c79586a 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java @@ -47,7 +47,7 @@ protected ProgressEvent handleRequest( this.logger = logger; final ResourceModel model = request.getDesiredResourceState(); - if (model.getType().equalsIgnoreCase(ParameterType.SECURE_STRING.toString())) { + if (ParameterType.SECURE_STRING.toString().equalsIgnoreCase(model.getType())) { String message = String.format("SSM Parameters of type %s cannot be updated using CloudFormation", ParameterType.SECURE_STRING); return ProgressEvent.defaultFailureHandler(new CfnServiceInternalErrorException(message), HandlerErrorCode.InvalidRequest); } From fce48a861a71819bd95db5c4740a2d164a7bd123 Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Tue, 8 Feb 2022 12:15:13 +0530 Subject: [PATCH 19/23] generate param name as per native logic --- .../amazonaws/ssm/parameter/Constants.java | 4 +++ .../ssm/parameter/CreateHandler.java | 31 +++++++++++++++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Constants.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Constants.java index 792246cb..5202b0bf 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Constants.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Constants.java @@ -3,4 +3,8 @@ public class Constants { public static final Integer MAX_RESULTS = 50; public static final String AWS_EC2_IMAGE_DATATYPE = "aws:ec2:image"; + + public static final int ALLOWED_LOGICAL_RESOURCE_ID_LENGTH = 500; + public static final String CF_PARAMETER_NAME_PREFIX = "CFN"; + public static final int GUID_LENGTH = 12; } diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java index 65c9f041..e50d8ef6 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java @@ -1,21 +1,20 @@ package com.amazonaws.ssm.parameter; import com.google.common.annotations.VisibleForTesting; +import org.apache.commons.lang3.RandomStringUtils; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.awssdk.services.ssm.model.ParameterType; -import software.amazon.cloudformation.exceptions.CfnInvalidRequestException; -import software.amazon.cloudformation.exceptions.CfnServiceInternalErrorException; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; import software.amazon.cloudformation.proxy.HandlerErrorCode; import software.amazon.cloudformation.proxy.Logger; import software.amazon.cloudformation.proxy.ProgressEvent; import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; -import software.amazon.cloudformation.resource.IdentifierUtils; import java.util.Collections; import java.util.Map; import java.util.Optional; +import java.util.Random; public class CreateHandler extends BaseHandlerStd { private Logger logger; @@ -53,9 +52,7 @@ protected ProgressEvent handleRequest( // Set model primary ID if absent if (model.getName() == null) { - model.setName(IdentifierUtils.generateResourceIdentifier("CFN", - request.getLogicalResourceIdentifier(), - request.getClientRequestToken(), 40)); + model.setName(generateParameterName(request.getLogicalResourceIdentifier(), request.getClientRequestToken())); } Map consolidatedTagsMap = Optional.ofNullable(request.getDesiredResourceTags()).orElse(Collections.emptyMap()); @@ -72,4 +69,26 @@ protected ProgressEvent handleRequest( .progress()) .then(progress -> readHandler.handleRequest(proxy, request, callbackContext, proxyClient, logger)); } + + // We support this special use case of auto-generating names only for CloudFormation. + // Name format: Prefix - logical resource id - randomString + private String generateParameterName(final String logicalResourceId, final String clientRequestToken) { + StringBuilder sb = new StringBuilder(); + int endIndex = Math.min(logicalResourceId.length(), Constants.ALLOWED_LOGICAL_RESOURCE_ID_LENGTH); + + sb.append(Constants.CF_PARAMETER_NAME_PREFIX); + sb.append("-"); + sb.append(logicalResourceId.substring(0, endIndex)); + sb.append("-"); + + sb.append(RandomStringUtils.random( + Constants.GUID_LENGTH, + 0, + 0, + true, + true, + null, + new Random(clientRequestToken.hashCode()))); + return sb.toString(); + } } From 0ff512d7f9660bacad90015e37ce0719a6ba5ec4 Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Tue, 8 Feb 2022 12:25:01 +0530 Subject: [PATCH 20/23] stabilize only for aws::ec2 datatype --- .../java/com/amazonaws/ssm/parameter/BaseHandlerStd.java | 5 +++++ .../java/com/amazonaws/ssm/parameter/CreateHandler.java | 7 ++++++- .../java/com/amazonaws/ssm/parameter/UpdateHandler.java | 7 ++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java index b4234997..b9d6069a 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java @@ -1,5 +1,6 @@ package com.amazonaws.ssm.parameter; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.services.ssm.SsmClient; @@ -104,6 +105,10 @@ protected Constant getBackOffDelay(final ResourceModel model) { } } + public static boolean isStabilizationNeeded(final String datatype) { + return (!Strings.isNullOrEmpty(datatype) && datatype.startsWith(Constants.AWS_EC2_IMAGE_DATATYPE)); + } + /** * If your resource requires some form of stabilization (e.g. service does not provide strong * consistency), you will need to ensure that your code accounts for any potential issues, so that diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java index e50d8ef6..f9d9f0c0 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/CreateHandler.java @@ -64,7 +64,12 @@ protected ProgressEvent handleRequest( .backoffDelay(getBackOffDelay(model)) .makeServiceCall((putParameterRequest, ssmProxyClient) -> ssmProxyClient.injectCredentialsAndInvokeV2(putParameterRequest, ssmProxyClient.client()::putParameter)) - .stabilize((req, response, client, model1, cbContext) -> stabilize(req, response, client, model1, cbContext, logger)) + .stabilize((req, response, client, model1, cbContext) -> { + if (isStabilizationNeeded(model1.getDataType())) + return stabilize(req, response, client, model1, cbContext, logger); + else + return true; + }) .handleError((req, e, proxy1, model1, context1) -> handleError(req, e, proxy1, model1, context1, logger)) .progress()) .then(progress -> readHandler.handleRequest(proxy, request, callbackContext, proxyClient, logger)); diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java index 3c79586a..3f385b40 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/UpdateHandler.java @@ -84,7 +84,12 @@ private ProgressEvent updateResourceExceptTaggin .backoffDelay(getBackOffDelay(progress.getResourceModel())) .makeServiceCall((putParameterRequest, ssmClientProxyClient) -> ssmClientProxyClient.injectCredentialsAndInvokeV2(putParameterRequest, ssmClientProxyClient.client()::putParameter)) - .stabilize((req, response, client, model1, cbContext) -> stabilize(req, response, client, model1, cbContext, logger)) + .stabilize((req, response, client, model1, cbContext) -> { + if (isStabilizationNeeded(model1.getDataType())) + return stabilize(req, response, client, model1, cbContext, logger); + else + return true; + }) .progress(); } From 4188600395d8d87b5b343d6879b770a11c69c7cb Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Tue, 8 Feb 2022 12:33:06 +0530 Subject: [PATCH 21/23] handle throttling in case of stabilize --- .../java/com/amazonaws/ssm/parameter/BaseHandlerStd.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java index b9d6069a..f78d1050 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java @@ -142,8 +142,13 @@ protected Boolean stabilize( } return (response.parameters() != null && response.parameters().get(0).version() == putParameterResponse.version()); } catch (Exception e) { - logger.log(String.format("Failed during stabilization of SsmParameter [%s] with error: [%s]", putParameterRequest.name(), e.getMessage())); - throw e; + if (hasThrottled(e)) { + logger.log(String.format("Throttling during stabilization of SsmParameter [%s] with error: [%s] ... Retrying..", putParameterRequest.name(), e.getMessage())); + return false; + } else { + logger.log(String.format("Failed during stabilization of SsmParameter [%s] with error: [%s]", putParameterRequest.name(), e.getMessage())); + throw e; + } } } From 49027ee9ad865181483a71a327a94a12189e1f9e Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Tue, 8 Feb 2022 12:46:54 +0530 Subject: [PATCH 22/23] soft-fail for extra api calls --- .../amazonaws/ssm/parameter/BaseHandlerStd.java | 9 +++++++++ .../com/amazonaws/ssm/parameter/ReadHandler.java | 16 ++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java index f78d1050..12373c6e 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/BaseHandlerStd.java @@ -71,6 +71,10 @@ private SsmClient getSsmClient() { "ThrottlingException", "TooManyUpdates"); + protected static final Set ACCESS_DENIED_ERROR_CODES = ImmutableSet.of( + "AccessDenied" + ); + @Override public ProgressEvent handleRequest(final AmazonWebServicesClientProxy proxy, final ResourceHandlerRequest request, @@ -208,6 +212,11 @@ private boolean hasThrottled(Exception e) { return (THROTTLING_ERROR_CODES.contains(errorCode)); } + protected boolean isAccessDenied(Exception e) { + String errorCode = getErrorCode(e); + return (ACCESS_DENIED_ERROR_CODES.contains(errorCode)); + } + protected String getErrorCode(Exception e) { if (e instanceof AwsServiceException && ((AwsServiceException) e).awsErrorDetails() != null) { return ((AwsServiceException) e).awsErrorDetails().errorCode(); diff --git a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ReadHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ReadHandler.java index b9b75f3d..8552407a 100644 --- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ReadHandler.java +++ b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ReadHandler.java @@ -79,7 +79,13 @@ private ProgressEvent describeParameters( .translateToServiceRequest(Translator::describeParametersRequestWithFilter) .makeServiceCall(((describeParametersRequest, ssmClientProxyClient) -> ssmClientProxyClient.injectCredentialsAndInvokeV2(describeParametersRequest, ssmClientProxyClient.client()::describeParameters))) - .handleError((req, e, proxy1, model1, context1) -> handleError(req, e, proxy1, model1, context1, logger)) + .handleError((req, e, proxy1, model1, context1) -> { + // soft-fail + if (isAccessDenied(e)) { + return progress; + } + return handleError(req, e, proxy1, model1, context1, logger); + }) .done((describeParametersRequest, describeParametersResponse, proxyClient1, resourceModel, context) -> { if (describeParametersResponse.parameters().isEmpty()) { throw new CfnNotFoundException(ResourceModel.TYPE_NAME, model.getName()); @@ -103,7 +109,13 @@ private ProgressEvent listTagsForResourceRequest .translateToServiceRequest(Translator::listTagsForResourceRequest) .makeServiceCall(((listTagsForResourceRequest, ssmClientProxyClient) -> ssmClientProxyClient.injectCredentialsAndInvokeV2(listTagsForResourceRequest, ssmClientProxyClient.client()::listTagsForResource))) - .handleError((req, e, proxy1, model1, context1) -> handleError(req, e, proxy1, model1, context1, logger)) + .handleError((req, e, proxy1, model1, context1) -> { + // soft-fail + if (isAccessDenied(e)) { + return progress; + } + return handleError(req, e, proxy1, model1, context1, logger); + }) .done((listTagsForResourceRequest, listTagsForResourceResponse, proxyClient1, resourceModel, context) -> { resourceModel.setTags(Translator.translateTagsFromSdk(listTagsForResourceResponse.tagList())); return ProgressEvent.progress(resourceModel, context); From 925a8c4c578a4b60d6c1d475e65cdc8f7cba70e6 Mon Sep 17 00:00:00 2001 From: Mohd Feroz Baig Date: Tue, 8 Feb 2022 13:30:11 +0530 Subject: [PATCH 23/23] fix unit test --- .../ssm/parameter/CreateHandlerTest.java | 70 +++++++++---------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/CreateHandlerTest.java b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/CreateHandlerTest.java index 9c754d0e..b8e29f08 100644 --- a/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/CreateHandlerTest.java +++ b/aws-ssm-parameter/src/test/java/com/amazonaws/ssm/parameter/CreateHandlerTest.java @@ -12,14 +12,7 @@ import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.core.exception.SdkException; import software.amazon.awssdk.services.ssm.SsmClient; -import software.amazon.awssdk.services.ssm.model.GetParametersRequest; -import software.amazon.awssdk.services.ssm.model.GetParametersResponse; -import software.amazon.awssdk.services.ssm.model.InternalServerErrorException; -import software.amazon.awssdk.services.ssm.model.Parameter; -import software.amazon.awssdk.services.ssm.model.ParameterAlreadyExistsException; -import software.amazon.awssdk.services.ssm.model.ParameterTier; -import software.amazon.awssdk.services.ssm.model.PutParameterRequest; -import software.amazon.awssdk.services.ssm.model.PutParameterResponse; +import software.amazon.awssdk.services.ssm.model.*; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; import software.amazon.cloudformation.proxy.HandlerErrorCode; import software.amazon.cloudformation.proxy.OperationStatus; @@ -89,15 +82,6 @@ public void handleRequest_SimpleSuccess() { .build(); when(proxyClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse); - final GetParametersResponse getParametersResponse = GetParametersResponse.builder() - .parameters(Parameter.builder() - .name(NAME) - .type(TYPE_STRING) - .value(VALUE) - .version(VERSION).build()) - .build(); - when(proxyClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - when(readHandler.handleRequest(any(), any(), any(), any(), any())).thenReturn( ProgressEvent.success(RESOURCE_MODEL, new CallbackContext())); @@ -140,15 +124,6 @@ public void handleRequest_SecureStringFailure() { @Test public void handleRequest_SimpleSuccess_With_No_ParameterName_Defined_Exceeding_LogicalResourceId() { - final GetParametersResponse getParametersResponse = GetParametersResponse.builder() - .parameters(Parameter.builder() - .name(NAME) - .type(TYPE_STRING) - .value(VALUE) - .version(VERSION).build()) - .build(); - when(proxyClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - final PutParameterResponse putParameterResponse = PutParameterResponse.builder() .version(VERSION) .tier(ParameterTier.STANDARD) @@ -183,15 +158,6 @@ public void handleRequest_SimpleSuccess_With_No_ParameterName_Defined_Exceeding_ @Test public void handleRequest_SimpleSuccess_With_No_ParameterName_Defined_Not_Exceeding_LogicalResourceId() { - final GetParametersResponse getParametersResponse = GetParametersResponse.builder() - .parameters(Parameter.builder() - .name(NAME) - .type(TYPE_STRING) - .value(VALUE) - .version(VERSION).build()) - .build(); - when(proxyClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse); - final PutParameterResponse putParameterResponse = PutParameterResponse.builder() .version(VERSION) .tier(ParameterTier.STANDARD) @@ -276,7 +242,23 @@ public void handleRequest_FailWithException() { AwsServiceException.builder().awsErrorDetails(AwsErrorDetails.builder().errorCode("ThrottlingException").build()).build(), ParameterAlreadyExistsException.builder().build(), AwsServiceException.builder().build(), - SdkException.builder().build() + SdkException.builder().build(), + ParameterPatternMismatchException.builder().build(), + InvalidKeyIdException.builder().build(), + HierarchyTypeMismatchException.builder().build(), + InvalidAllowedPatternException.builder().build(), + UnsupportedParameterTypeException.builder().build(), + InvalidPolicyTypeException.builder().build(), + InvalidPolicyAttributeException.builder().build(), + IncompatiblePolicyException.builder().build(), + InvalidFilterKeyException.builder().build(), + InvalidFilterOptionException.builder().build(), + InvalidFilterValueException.builder().build(), + InvalidNextTokenException.builder().build(), + ParameterLimitExceededException.builder().build(), + HierarchyLevelLimitExceededException.builder().build(), + ParameterMaxVersionLimitExceededException.builder().build(), + PoliciesLimitExceededException.builder().build(), }; HandlerErrorCode[] handlerErrorCodes = { @@ -284,6 +266,22 @@ public void handleRequest_FailWithException() { HandlerErrorCode.AlreadyExists, HandlerErrorCode.GeneralServiceException, HandlerErrorCode.InternalFailure, + HandlerErrorCode.InvalidRequest, + HandlerErrorCode.InvalidRequest, + HandlerErrorCode.InvalidRequest, + HandlerErrorCode.InvalidRequest, + HandlerErrorCode.InvalidRequest, + HandlerErrorCode.InvalidRequest, + HandlerErrorCode.InvalidRequest, + HandlerErrorCode.InvalidRequest, + HandlerErrorCode.InvalidRequest, + HandlerErrorCode.InvalidRequest, + HandlerErrorCode.InvalidRequest, + HandlerErrorCode.InvalidRequest, + HandlerErrorCode.ServiceLimitExceeded, + HandlerErrorCode.ServiceLimitExceeded, + HandlerErrorCode.ServiceLimitExceeded, + HandlerErrorCode.ServiceLimitExceeded, }; final ResourceHandlerRequest request = ResourceHandlerRequest.builder()