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/.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/aws-ssm-parameter.json b/aws-ssm-parameter/aws-ssm-parameter.json
index 61d9b4a2..1ea638b4 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": true,
"required": [
- "Value",
- "Type"
+ "Type",
+ "Value"
],
"createOnlyProperties": [
"/properties/Name"
@@ -78,32 +51,29 @@
"primaryIdentifier": [
"/properties/Name"
],
- "readOnlyProperties": [
- "/properties/Name",
- "/properties/Type",
- "/properties/Value"
- ],
"handlers": {
"create": {
"permissions": [
+ "ssm:GetParameters",
"ssm:PutParameter",
- "ssm:GetParameters"
- ],
- "timeoutInMinutes": 5
+ "ssm:DescribeParameters",
+ "ssm:ListTagsForResource"
+ ]
},
"read": {
"permissions": [
- "ssm:GetParameters"
+ "ssm:GetParameters",
+ "ssm:DescribeParameters",
+ "ssm:ListTagsForResource"
]
},
"update": {
"permissions": [
"ssm:PutParameter",
+ "ssm:GetParameters",
"ssm:AddTagsToResource",
- "ssm:RemoveTagsFromResource",
- "ssm:GetParameters"
- ],
- "timeoutInMinutes": 5
+ "ssm:RemoveTagsFromResource"
+ ]
},
"delete": {
"permissions": [
diff --git a/aws-ssm-parameter/docs/README.md b/aws-ssm-parameter/docs/README.md
new file mode 100644
index 00000000..5150e7e7
--- /dev/null
+++ b/aws-ssm-parameter/docs/README.md
@@ -0,0 +1,140 @@
+# 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" : {
+ "Type" : String,
+ "Description" : String,
+ "Policies" : String,
+ "AllowedPattern" : String,
+ "Tier" : String,
+ "Value" : String,
+ "DataType" : String,
+ "Tags" : Map,
+ "Name" : String
+ }
+}
+
+
+### YAML
+
+
+Type: AWS::SSM::Parameter
+Properties:
+ Type: String
+ Description: String
+ Policies: String
+ AllowedPattern: String
+ Tier: String
+ 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
+
+Information about 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)
+
+#### Policies
+
+Information about the policies assigned to a 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
+
+A regular expression used to validate the parameter value.
+
+_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)
+
+#### Tier
+
+The parameter tier.
+
+_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)
+
+#### Value
+
+The parameter value.
+
+_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)
+
+#### DataType
+
+The data type of the parameter, such as text or aws:ec2:image. The default is text.
+
+_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)
+
+#### Tags
+
+Optional metadata that you assign to a resource in the form of an arbitrary set of tags (key-value pairs)
+
+_Required_: No
+
+_Type_: Map
+
+_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.
+
+_Required_: No
+
+_Type_: String
+
+_Update requires_: [Replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-replacement)
+
+## 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/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
+
+
+
+
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 58653a80..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
@@ -1,94 +1,226 @@
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;
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.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.Optional;
import java.util.Set;
+import static java.util.Objects.requireNonNull;
+
public abstract class BaseHandlerStd extends BaseHandler {
- 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);
- }
-
- 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();
- }
- }
-
- /**
- * 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());
- }
+
+ 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");
+
+ protected static final Set ACCESS_DENIED_ERROR_CODES = ImmutableSet.of(
+ "AccessDenied"
+ );
+
+ @Override
+ public ProgressEvent handleRequest(final AmazonWebServicesClientProxy proxy,
+ final ResourceHandlerRequest request,
+ final CallbackContext callbackContext,
+ final Logger logger) {
+ return handleRequest(
+ proxy,
+ request,
+ Optional.ofNullable(callbackContext).orElse(new CallbackContext()),
+ proxy.newProxy(ClientBuilder::getClient),
+ 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();
+ }
+ }
+
+ 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
+ * 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 Boolean stabilize(
+ final PutParameterRequest putParameterRequest,
+ final PutParameterResponse putParameterResponse,
+ final ProxyClient proxyClient,
+ final ResourceModel resourceModel,
+ final CallbackContext callbackContext,
+ final Logger logger
+ ) {
+ try {
+ 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) {
+ 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;
+ }
+ }
+ }
+
+ 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 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)
+ || e instanceof TooManyUpdatesException
+ || e instanceof TooManyTagsErrorException
+ )
+ {
+ 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));
+ }
+
+ 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();
+ }
+ return e.getMessage();
+ }
}
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..2cf3a3ef
--- /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(4)
+ .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 919beb3c..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,22 +1,25 @@
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 {
- 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) {
+ 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/Constants.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Constants.java
index 948bee85..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
@@ -1,17 +1,10 @@
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 Integer MAX_RESULTS = 50;
+ public static final String AWS_EC2_IMAGE_DATATYPE = "aws:ec2:image";
- 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 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 a9005699..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
@@ -1,117 +1,99 @@
package com.amazonaws.ssm.parameter;
-import com.amazonaws.AmazonServiceException;
+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.ParameterAlreadyExistsException;
-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.awssdk.services.ssm.model.ParameterType;
-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.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.Collections;
import java.util.Map;
+import java.util.Optional;
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 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,
- 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()
- ));
- }
+ 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);
- }
+ 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);
+ }
- 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(generateParameterName(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()
- .then(progress -> new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, logger));
- }
+ Map consolidatedTagsMap = Optional.ofNullable(request.getDesiredResourceTags()).orElse(Collections.emptyMap());
+ consolidatedTagsMap.putAll(Optional.ofNullable(request.getSystemTags()).orElse(Collections.emptyMap()));
- 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);
- }
- }
+ 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))
+ .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));
+ }
- // 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 = 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(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..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,60 +1,43 @@
package com.amazonaws.ssm.parameter;
-import com.amazonaws.AmazonServiceException;
+import com.google.common.annotations.VisibleForTesting;
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.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 Logger logger;
+
+ public DeleteHandler() {
+ super();
+ }
+
+ @VisibleForTesting
+ protected DeleteHandler(SsmClient ssmClient) {
+ super(ssmClient);
+ }
- @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));
- }
+ logger.log("Invoking Delete Handler");
+ logger.log("DELETE ResourceModel: " + model.toString());
- 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);
- }
- }
+ return proxy.initiate("aws-ssm-parameter::resource-delete", proxyClient, model, callbackContext)
+ .translateToServiceRequest(Translator::deleteParameterRequest)
+ .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));
+ }
}
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..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,34 +1,66 @@
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;
+import software.amazon.cloudformation.exceptions.CfnNotFoundException;
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);
-
- 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();
- }
+
+ public ListHandler() {
+ super();
+ }
+
+ @VisibleForTesting
+ protected ListHandler(SsmClient ssmClient) {
+ super(ssmClient);
+ }
+
+ protected ProgressEvent handleRequest(
+ final AmazonWebServicesClientProxy proxy,
+ final ResourceHandlerRequest request,
+ final CallbackContext callbackContext,
+ final ProxyClient proxyClient,
+ final Logger logger) {
+
+ 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/ReadHandler.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/ReadHandler.java
index b662d2a1..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
@@ -1,50 +1,124 @@
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.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.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";
-
- @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());
- });
- }
-
- 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);
- }
- }
+
+ public ReadHandler() {
+ super();
+ }
+
+ @VisibleForTesting
+ protected ReadHandler(SsmClient ssmClient) {
+ super(ssmClient);
+ }
+
+ @Override
+ protected ProgressEvent handleRequest(
+ final AmazonWebServicesClientProxy proxy,
+ final ResourceHandlerRequest request,
+ final CallbackContext callbackContext,
+ final ProxyClient proxyClient,
+ final Logger logger) {
+ 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(((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().get(0);
+
+ return ProgressEvent.progress(
+ ResourceModel.builder()
+ .name(parameter.name())
+ .type(parameter.typeAsString())
+ .value(parameter.value())
+ .dataType(parameter.dataType())
+ .build(),
+ context);
+ });
+ }
+
+ 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) -> {
+ // 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());
+ }
+ 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 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) -> {
+ // 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);
+ });
+ }
}
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 cb46ec9b..00000000
--- a/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/SSMClientBuilder.java
+++ /dev/null
@@ -1,26 +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/Translator.java b/aws-ssm-parameter/src/main/java/com/amazonaws/ssm/parameter/Translator.java
index b1b64dac..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
@@ -4,89 +4,131 @@
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.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;
import software.amazon.awssdk.services.ssm.model.RemoveTagsFromResourceRequest;
import software.amazon.awssdk.services.ssm.model.ResourceTypeForTagging;
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 GetParametersRequest getParametersRequest(final ResourceModel model) {
+ return GetParametersRequest.builder()
+ .names(model.getName())
+ .withDecryption(Boolean.FALSE)
+ .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)
+ .maxResults(Constants.MAX_RESULTS)
+ .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 DeleteParameterRequest deleteParameterRequest(final ResourceModel model) {
+ return DeleteParameterRequest.builder()
+ .name(model.getName())
+ .build();
+ }
- static GetParametersRequest getParametersRequest(final ResourceModel model) {
- return GetParametersRequest.builder()
- .names(model.getName())
- .withDecryption(Boolean.FALSE)
- .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 DescribeParametersRequest describeParametersRequest(final String nextToken) {
- return DescribeParametersRequest.builder()
- .nextToken(nextToken)
- .maxResults(Constants.MAX_RESULTS)
- .build();
- }
+ static AddTagsToResourceRequest addTagsToResourceRequest(final String parameterName, List tagsToAdd) {
+ return AddTagsToResourceRequest.builder()
+ .resourceId(parameterName)
+ .resourceType(ResourceTypeForTagging.PARAMETER)
+ .tags(tagsToAdd)
+ .build();
+ }
- static DeleteParameterRequest deleteParameterRequest(final ResourceModel model) {
- return DeleteParameterRequest.builder()
- .name(model.getName())
- .build();
- }
+ static ListTagsForResourceRequest listTagsForResourceRequest(final ResourceModel model) {
+ return ListTagsForResourceRequest.builder()
+ .resourceType(ResourceTypeForTagging.PARAMETER)
+ .resourceId(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 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());
+ }
- 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());
- }
+ static Map translateTagsFromSdk(final List tags) {
+ return Optional.of(tags).orElse(Collections.emptyList())
+ .stream()
+ .collect(Collectors.toMap(tag -> tag.key(), tag -> tag.value()));
+ }
}
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..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
@@ -1,136 +1,145 @@
package com.amazonaws.ssm.parameter;
-import com.amazonaws.AmazonServiceException;
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.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.PutParameterResponse;
-import software.amazon.awssdk.services.ssm.model.PutParameterRequest;
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.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.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
- 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 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,
+ final ResourceHandlerRequest request,
+ final CallbackContext callbackContext,
+ final ProxyClient proxyClient,
+ final Logger logger) {
+ this.logger = logger;
+ final ResourceModel model = request.getDesiredResourceState();
+
+ 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);
+ }
+
+ 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 -> 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 -> readHandler.handleRequest(proxy, request, progress.getCallbackContext(), proxyClient, logger));
+ }
+
+ 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 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) -> {
+ if (isStabilizationNeeded(model1.getDataType()))
+ return stabilize(req, response, client, model1, cbContext, logger);
+ else
+ return true;
+ })
+ .progress();
+ }
+
+ 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 (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 = new ArrayList<>(setTagsToRemove);
+ final List tagsToAdd = new ArrayList<>(setTagsToAdd);
+
+ 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()));
+ }
+
+ 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();
+ }
+
+ 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();
+ }
}
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..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
@@ -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;
@@ -14,94 +9,125 @@
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;
- 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;
-
- 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();
-
- 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);
- }
-
- @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 ResponseInputStream injectCredentialsAndInvokeV2InputStream(RequestT requestT, Function> function) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public ResponseBytes injectCredentialsAndInvokeV2Bytes(RequestT requestT, Function> function) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public SsmClient client() {
- return ssmClient;
- }
- };
- }
+ 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;
+
+ 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();
+
+ 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 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
+ ) {
+ 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 > IterableT injectCredentialsAndInvokeIterableV2(
+ RequestT requestT, Function function) {
+ return proxy.injectCredentialsAndInvokeIterableV2(requestT, function);
+ }
+
+ @Override
+ public ResponseInputStream injectCredentialsAndInvokeV2InputStream(RequestT requestT,
+ Function> function) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ResponseBytes injectCredentialsAndInvokeV2Bytes(RequestT requestT,
+ Function> function) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SsmClient client() {
+ return ssmClient;
+ }
+ };
+ }
+
+ 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 054c4cce..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
@@ -1,400 +1,304 @@
package com.amazonaws.ssm.parameter;
-import com.amazonaws.AmazonServiceException;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.AfterEach;
-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.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.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.proxy.AmazonWebServicesClientProxy;
-import software.amazon.cloudformation.proxy.HandlerErrorCode;
-import software.amazon.cloudformation.proxy.ProxyClient;
-import software.amazon.cloudformation.proxy.ProgressEvent;
-import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
-import software.amazon.cloudformation.proxy.OperationStatus;
-
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.*;
+import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
+import software.amazon.cloudformation.proxy.HandlerErrorCode;
+import software.amazon.cloudformation.proxy.OperationStatus;
+import software.amazon.cloudformation.proxy.ProgressEvent;
+import software.amazon.cloudformation.proxy.ProxyClient;
+import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
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 proxyClient;
+ @Mock
+ SsmClient ssmClient;
+ @Mock
+ private ReadHandler readHandler;
+
+ private CreateHandler handler;
+
+ private ResourceModel RESOURCE_MODEL;
+
+ @BeforeEach
+ public void setup() {
+ proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis());
+ 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(toResourceModelTags(TAG_SET))
+ .build();
+ }
+
+ @AfterEach
+ 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);
+
+ when(readHandler.handleRequest(any(), any(), any(), any(), any())).thenReturn(
+ ProgressEvent.success(RESOURCE_MODEL, new CallbackContext()));
+
+ final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
+ assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
+ assertThat(response.getResourceModels()).isNull();
+ assertThat(response.getMessage()).isNull();
+ assertThat(response.getErrorCode()).isNull();
+ }
+
+ @Tag("skipSdkInteraction")
+ @Test
+ public void handleRequest_SecureStringFailure() {
+ RESOURCE_MODEL = ResourceModel.builder()
+ .description(DESCRIPTION)
+ .value(VALUE)
+ .type(TYPE_SECURE_STRING)
+ .tags(toResourceModelTags(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(), 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()).isNotNull();
+ assertThat(response.getErrorCode()).isEqualTo(HandlerErrorCode.InvalidRequest);
+ }
+
+ @Test
+ public void handleRequest_SimpleSuccess_With_No_ParameterName_Defined_Exceeding_LogicalResourceId() {
+ final PutParameterResponse putParameterResponse = PutParameterResponse.builder()
+ .version(VERSION)
+ .tier(ParameterTier.STANDARD)
+ .build();
+ when(proxyClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse);
+
+ RESOURCE_MODEL = ResourceModel.builder()
+ .description(DESCRIPTION)
+ .value(VALUE)
+ .type(TYPE_STRING)
+ .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(), proxyClient, logger);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
+ assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
+ assertThat(response.getResourceModels()).isNull();
+ assertThat(response.getMessage()).isNull();
+ assertThat(response.getErrorCode()).isNull();
+ }
+
+ @Test
+ public void handleRequest_SimpleSuccess_With_No_ParameterName_Defined_Not_Exceeding_LogicalResourceId() {
+ final PutParameterResponse putParameterResponse = PutParameterResponse.builder()
+ .version(VERSION)
+ .tier(ParameterTier.STANDARD)
+ .build();
+ when(proxyClient.client().putParameter(any(PutParameterRequest.class))).thenReturn(putParameterResponse);
+
+ RESOURCE_MODEL = ResourceModel.builder()
+ .description(DESCRIPTION)
+ .value(VALUE)
+ .type(TYPE_STRING)
+ .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(), proxyClient, logger);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
+ assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
+ assertThat(response.getResourceModels()).isNull();
+ assertThat(response.getMessage()).isNull();
+ assertThat(response.getErrorCode()).isNull();
+ }
+
+ @Test
+ public void handleRequest_SimpleSuccess_WithImageDataType() {
+ RESOURCE_MODEL = ResourceModel.builder()
+ .description(DESCRIPTION)
+ .name(NAME)
+ .value(VALUE)
+ .type(TYPE_STRING)
+ .tags(toResourceModelTags(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(proxyClient.client().getParameters(any(GetParametersRequest.class))).thenReturn(getParametersResponse);
+
+ final PutParameterResponse putParameterResponse = PutParameterResponse.builder()
+ .version(VERSION)
+ .tier(ParameterTier.STANDARD)
+ .build();
+ 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")
+ .desiredResourceTags(TAG_SET)
+ .systemTags(SYSTEM_TAGS_SET)
+ .desiredResourceState(RESOURCE_MODEL)
+ .logicalResourceIdentifier("logicalId").build();
+ final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
+ assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
+ assertThat(response.getResourceModels()).isNull();
+ assertThat(response.getMessage()).isNull();
+ assertThat(response.getErrorCode()).isNull();
+ }
+
+ @Test
+ 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(),
+ 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 = {
+ HandlerErrorCode.Throttling,
+ 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()
+ .clientRequestToken("token")
+ .desiredResourceTags(TAG_SET)
+ .systemTags(SYSTEM_TAGS_SET)
+ .desiredResourceState(RESOURCE_MODEL)
+ .logicalResourceIdentifier("logical_id").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 ProgressEvent response = handler
+ .handleRequest(proxy, request, new CallbackContext(), proxyClient, logger);
+ assertFailureErrorCode(request, response, handlerErrorCodes[i]);
+ }
+ }
}
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..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
@@ -2,208 +2,153 @@
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.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.ParameterNotFoundException;
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.ProxyClient;
+import software.amazon.cloudformation.proxy.HandlerErrorCode;
import software.amazon.cloudformation.proxy.OperationStatus;
import software.amazon.cloudformation.proxy.ProgressEvent;
+import software.amazon.cloudformation.proxy.ProxyClient;
import software.amazon.cloudformation.proxy.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 proxyClient;
+ @Mock
+ SsmClient ssmClient;
+
+ private DeleteHandler handler;
+
+ private ResourceModel RESOURCE_MODEL;
+
+ @BeforeEach
+ public void setup() {
+ handler = new DeleteHandler(ssmClient);
+ ssmClient = mock(SsmClient.class);
+ proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis());
+ proxyClient = MOCK_PROXY(proxy, ssmClient);
+
+ RESOURCE_MODEL = ResourceModel.builder()
+ .description(DESCRIPTION)
+ .name(NAME)
+ .value(VALUE)
+ .type(TYPE_STRING)
+ .tags(toResourceModelTags(TAG_SET))
+ .build();
+ }
+
+ @AfterEach
+ 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 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(proxyClient.client()).deleteParameter(any(DeleteParameterRequest.class));
+ }
+
+
+ @Test
+ 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")
+ .desiredResourceTags(TAG_SET)
+ .systemTags(SYSTEM_TAGS_SET)
+ .desiredResourceState(RESOURCE_MODEL)
+ .logicalResourceIdentifier("logical_id").build();
+
+ 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]);
+
+ final ProgressEvent response = handler
+ .handleRequest(proxy, request, new CallbackContext(), proxyClient, logger);
+ assertFailureErrorCode(request, response, handlerErrorCodes[i]);
+ }
+ }
+
+ @Test
+ public void handleRequest_NonThrottlingAmazonServiceException() {
+ AmazonServiceException amazonServiceException = new AmazonServiceException("Client error");
+ amazonServiceException.setStatusCode(500);
+
+ when(proxyClient.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(), proxyClient, logger);
+ } catch (CfnGeneralServiceException ex) {
+ assertThat(ex).isInstanceOf(CfnGeneralServiceException.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 1bf3efe7..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
@@ -1,92 +1,82 @@
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 proxyClient;
+ @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);
+ ssmClient = mock(SsmClient.class);
+ proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis());
+ proxyClient = MOCK_PROXY(proxy, ssmClient);
+ }
+
+ @AfterEach
+ public void tear_down() {
+ verify(ssmClient, atLeastOnce()).serviceName();
+ verifyNoMoreInteractions(proxyClient.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(proxyClient.client().describeParameters(any(DescribeParametersRequest.class)))
+ .thenReturn(describeParametersResponse);
+
+ final ResourceModel model = ResourceModel.builder().build();
+
+ final ResourceHandlerRequest request = ResourceHandlerRequest.builder()
+ .desiredResourceState(model)
+ .build();
+
+ 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.getResourceModel()).isNull();
+ assertThat(response.getResourceModels()).isNotNull();
+ assertThat(response.getMessage()).isNull();
+ assertThat(response.getErrorCode()).isNull();
+ assertThat(response.getNextToken()).isEqualTo(NEXT_TOKEN);
+ }
}
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..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
@@ -1,126 +1,161 @@
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.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;
-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