diff --git a/aws-ssm-association/pom.xml b/aws-ssm-association/pom.xml
index ce7300a7..ad368b64 100644
--- a/aws-ssm-association/pom.xml
+++ b/aws-ssm-association/pom.xml
@@ -30,7 +30,7 @@
software.amazon.cloudformation
aws-cloudformation-rpdk-java-plugin
- [2.0.0, 3.0.0)
+ [2.0.0, 2.1.0)
diff --git a/aws-ssm-document/.rpdk-config b/aws-ssm-document/.rpdk-config
index 3a3d145e..b42c9e60 100644
--- a/aws-ssm-document/.rpdk-config
+++ b/aws-ssm-document/.rpdk-config
@@ -1,7 +1,7 @@
{
"typeName": "AWS::SSM::Document",
"language": "java",
- "runtime": "java8",
+ "runtime": "java11",
"entrypoint": "com.amazonaws.ssm.document.HandlerWrapper::handleRequest",
"testEntrypoint": "com.amazonaws.ssm.document.HandlerWrapper::testEntrypoint",
"settings": {
diff --git a/aws-ssm-document/aws-ssm-document.json b/aws-ssm-document/aws-ssm-document.json
index 091798c2..51e25299 100644
--- a/aws-ssm-document/aws-ssm-document.json
+++ b/aws-ssm-document/aws-ssm-document.json
@@ -190,19 +190,33 @@
"/properties/Name"
],
"tagging": {
- "taggable": true
+ "taggable": true,
+ "tagOnCreate": true,
+ "tagUpdatable": true,
+ "cloudFormationSystemTags": true,
+ "tagProperty": "/properties/Tags",
+ "permissions": [
+ "ssm:AddTagsToResource",
+ "ssm:ListTagsForResource",
+ "ssm:RemoveTagsFromResource"
+ ]
},
"handlers": {
"create": {
"permissions": [
"ssm:CreateDocument",
+ "ssm:GetDocument",
+ "ssm:AddTagsToResource",
+ "ssm:ListTagsForResource",
"s3:GetObject",
"iam:PassRole"
]
},
"read": {
"permissions": [
- "ssm:GetDocument"
+ "ssm:DescribeDocument",
+ "ssm:GetDocument",
+ "ssm:ListTagsForResource"
]
},
"update": {
@@ -219,7 +233,8 @@
},
"delete": {
"permissions": [
- "ssm:DeleteDocument"
+ "ssm:DeleteDocument",
+ "ssm:GetDocument"
]
},
"list": {
diff --git a/aws-ssm-document/contract-tests-artifacts/inputs_1.json b/aws-ssm-document/contract-tests-artifacts/inputs_1.json
index b284126a..5b27afef 100644
--- a/aws-ssm-document/contract-tests-artifacts/inputs_1.json
+++ b/aws-ssm-document/contract-tests-artifacts/inputs_1.json
@@ -1,6 +1,6 @@
{
"CreateInputs": {
- "Name": "commandTest1",
+ "Name": "commandTest1-{{ uuid }}",
"Content": "{\"schemaVersion\":\"2.2\",\"description\":\"Command Document Example Create JSON Template\",\"mainSteps\":[{\"action\":\"aws:runPowerShellScript\",\"name\":\"example\",\"inputs\":{\"runCommand\":[\"Write-Output\"]}}]}",
"DocumentType": "Command",
"DocumentFormat": "JSON",
diff --git a/aws-ssm-document/contract-tests-artifacts/inputs_2.json b/aws-ssm-document/contract-tests-artifacts/inputs_2.json
index a998da95..4a92d628 100644
--- a/aws-ssm-document/contract-tests-artifacts/inputs_2.json
+++ b/aws-ssm-document/contract-tests-artifacts/inputs_2.json
@@ -1,6 +1,6 @@
{
"CreateInputs": {
- "Name": "automationTest1",
+ "Name": "automationTest1-{{ uuid }}",
"Content": "{\"description\": \"Automation Document Example JSON Template\",\"schemaVersion\": \"0.3\",\"assumeRole\":\"{{AutomationAssumeRole }}\",\"parameters\":{\"AutomationAssumeRole\":{\"type\":\"String\",\"description\":\"(Optional) The ARN of the role that allows Automation to perform the actions on your behalf.\",\"default\": \"\"}},\"mainSteps\":[{\"name\": \"slp\",\"action\":\"aws:sleep\",\"inputs\":{\"Duration\":\"PT3S\"}}]}",
"DocumentType": "Automation",
"DocumentFormat": "JSON",
diff --git a/aws-ssm-document/contract-tests-artifacts/inputs_3.json b/aws-ssm-document/contract-tests-artifacts/inputs_3.json
index cbba65ba..3d460365 100644
--- a/aws-ssm-document/contract-tests-artifacts/inputs_3.json
+++ b/aws-ssm-document/contract-tests-artifacts/inputs_3.json
@@ -1,6 +1,6 @@
{
"CreateInputs": {
- "Name": "appConfigTest1",
+ "Name": "appConfigTest1-{{ uuid }}",
"Content": "{\"whitelist\":[{\"name\":\"user1\",\"email\":\"user1@email.com\",\"location\":\"virginia\"},{\"name\":\"user2\",\"email\":\"{VersionName}@email.com\",\"location\":\"washington\"}]}",
"DocumentType": "ApplicationConfiguration",
"DocumentFormat": "JSON",
diff --git a/aws-ssm-document/pom.xml b/aws-ssm-document/pom.xml
index f79eb72c..a921ee06 100644
--- a/aws-ssm-document/pom.xml
+++ b/aws-ssm-document/pom.xml
@@ -37,7 +37,7 @@
software.amazon.cloudformation
aws-cloudformation-rpdk-java-plugin
- [2.0.0, 3.0.0)
+ [2.0.0, 2.1.0)
@@ -99,7 +99,6 @@
-Xlint:all,-options,-processing
- -Werror
diff --git a/aws-ssm-document/resource-role.yaml b/aws-ssm-document/resource-role.yaml
index 1661b3bc..5511c036 100644
--- a/aws-ssm-document/resource-role.yaml
+++ b/aws-ssm-document/resource-role.yaml
@@ -15,6 +15,13 @@ Resources:
Principal:
Service: resources.cloudformation.amazonaws.com
Action: sts:AssumeRole
+ Condition:
+ StringEquals:
+ aws:SourceAccount:
+ Ref: AWS::AccountId
+ StringLike:
+ aws:SourceArn:
+ Fn::Sub: arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:type/resource/AWS-SSM-Document/*
Path: "/"
Policies:
- PolicyName: ResourceTypePolicy
diff --git a/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/CallbackContext.java b/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/CallbackContext.java
index 5aab4c18..ce879554 100644
--- a/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/CallbackContext.java
+++ b/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/CallbackContext.java
@@ -3,6 +3,7 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import software.amazon.cloudformation.proxy.StdCallbackContext;
import lombok.Builder;
import lombok.Data;
@@ -11,8 +12,8 @@
@Data
@Builder
@JsonDeserialize(builder = CallbackContext.CallbackContextBuilder.class)
-@EqualsAndHashCode
-public class CallbackContext {
+@EqualsAndHashCode(callSuper = true)
+public class CallbackContext extends StdCallbackContext {
/**
* Boolean that tells whether the create operation on resource started.
diff --git a/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/CreateHandler.java b/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/CreateHandler.java
index ced28d78..4246c4cb 100644
--- a/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/CreateHandler.java
+++ b/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/CreateHandler.java
@@ -9,7 +9,6 @@
import software.amazon.awssdk.services.ssm.model.CreateDocumentResponse;
import software.amazon.awssdk.services.ssm.model.SsmException;
import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
-import software.amazon.cloudformation.exceptions.CfnUnauthorizedTaggingOperationException;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
import software.amazon.cloudformation.proxy.HandlerErrorCode;
import software.amazon.cloudformation.proxy.Logger;
diff --git a/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/DocumentExceptionTranslator.java b/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/DocumentExceptionTranslator.java
index 5dc9a7bb..3add0329 100644
--- a/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/DocumentExceptionTranslator.java
+++ b/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/DocumentExceptionTranslator.java
@@ -18,6 +18,7 @@
import software.amazon.awssdk.services.ssm.model.InvalidResourceIdException;
import software.amazon.awssdk.services.ssm.model.MaxDocumentSizeExceededException;
import software.amazon.awssdk.services.ssm.model.SsmException;
+import software.amazon.cloudformation.exceptions.CfnAccessDeniedException;
import software.amazon.cloudformation.exceptions.CfnAlreadyExistsException;
import software.amazon.cloudformation.exceptions.CfnGeneralServiceException;
import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
@@ -32,6 +33,8 @@
import lombok.NonNull;
import java.io.IOException;
+import static com.amazonaws.ssm.document.tags.TagUtil.LIST_TAGS_OPERATION_NAME;
+
class DocumentExceptionTranslator {
private static final int GENERIC_USER_ERROR_STATUS_CODE = 400;
@@ -82,6 +85,13 @@ RuntimeException getCfnException(@NonNull final SsmException e, String documentN
return new CfnServiceInternalErrorException(operationName, e);
} else if (e.getCause() instanceof IOException) {
return new CfnNetworkFailureException(operationName, e);
+ } else if (e.awsErrorDetails() != null
+ && e.awsErrorDetails().errorCode() != null
+ && e.awsErrorDetails().errorCode().equalsIgnoreCase("AccessDeniedException")) {
+ if (operationName.equalsIgnoreCase(LIST_TAGS_OPERATION_NAME)) {
+ return new CfnUnauthorizedTaggingOperationException(e);
+ }
+ return new CfnAccessDeniedException(operationName, e);
} else if (e.statusCode() == GENERIC_USER_ERROR_STATUS_CODE) {
return new CfnInvalidRequestException(e.getMessage(), e);
}
diff --git a/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/DocumentModelTranslator.java b/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/DocumentModelTranslator.java
index aa68cbdc..a51f3809 100644
--- a/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/DocumentModelTranslator.java
+++ b/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/DocumentModelTranslator.java
@@ -132,12 +132,35 @@ DeleteDocumentRequest generateDeleteDocumentRequest(@NonNull final ResourceModel
.build();
}
- ListDocumentsRequest generateListDocumentsRequest() {
- List keyValuesFilters =
- ImmutableList.of(DocumentKeyValuesFilter.builder().key("owner").values("self").build());
+ static ListDocumentsRequest generateListDocumentsRequest(@Nullable final String nextToken, @Nullable final ResourceModel model) {
+ final ImmutableList.Builder filterListBuilder =
+ ImmutableList.builder();
+
+ if (model == null) {
+ return ListDocumentsRequest.builder()
+ .maxResults(50)
+ .nextToken(nextToken)
+ .build();
+ }
+
+ if (model.getName() != null) {
+ filterListBuilder.add(DocumentKeyValuesFilter.builder().key("Name").values(model.getName()).build());
+ }
+
+ if (model.getDocumentType() != null) {
+ filterListBuilder.add(DocumentKeyValuesFilter.builder().key("DocumentType").values(model.getDocumentType()).build());
+ }
+
+ if (model.getTargetType() != null) {
+ filterListBuilder.add(DocumentKeyValuesFilter.builder().key("TargetType").values(model.getTargetType()).build());
+ }
+
+ final List filters = filterListBuilder.build();
return ListDocumentsRequest.builder()
- .filters(keyValuesFilters)
+ .filters(filters.isEmpty() ? null : filters)
+ .maxResults(50)
+ .nextToken(nextToken)
.build();
}
diff --git a/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/DocumentResponseModelTranslator.java b/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/DocumentResponseModelTranslator.java
index 09bbb61c..28da3bc8 100644
--- a/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/DocumentResponseModelTranslator.java
+++ b/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/DocumentResponseModelTranslator.java
@@ -48,7 +48,7 @@ ResourceInformation generateResourceInformation(@NonNull final DescribeDocumentR
.versionName(response.document().versionName())
.documentFormat(response.document().documentFormatAsString())
.documentType(response.document().documentTypeAsString())
- .tags(translateDocumentTagsToResourceModelTags(response.document().tags()))
+ .tags(translateTags(response.document().tags()))
.requires(translateRequires(response))
.targetType(response.document().targetType())
.build();
@@ -90,15 +90,6 @@ private List translateToResourceModelTags(final Map tagMap)
.collect(Collectors.toList());
}
- private List translateDocumentTagsToResourceModelTags(final List tags) {
- return tags.stream().map(
- tag -> Tag.builder()
- .key(tag.key())
- .value(tag.value())
- .build())
- .collect(Collectors.toList());
- }
-
private List translateRequires(final GetDocumentResponse response) {
if (!response.hasRequires()) {
return null;
@@ -128,4 +119,13 @@ private List translateRequires(final DescribeDocumentResponse
.build())
.collect(Collectors.toList());
}
+
+ private List translateTags(final List tags) {
+ return tags.stream().map(
+ tag -> Tag.builder()
+ .key(tag.key())
+ .value(tag.value())
+ .build())
+ .collect(Collectors.toList());
+ }
}
diff --git a/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/ListHandler.java b/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/ListHandler.java
index c66bf3a8..10a74815 100644
--- a/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/ListHandler.java
+++ b/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/ListHandler.java
@@ -5,14 +5,16 @@
import lombok.RequiredArgsConstructor;
import software.amazon.awssdk.services.ssm.SsmClient;
import software.amazon.awssdk.services.ssm.model.ListDocumentsRequest;
+import software.amazon.awssdk.services.ssm.model.ListDocumentsResponse;
+import software.amazon.awssdk.services.ssm.model.SsmException;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
import software.amazon.cloudformation.proxy.Logger;
import software.amazon.cloudformation.proxy.ProgressEvent;
import software.amazon.cloudformation.proxy.OperationStatus;
import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
-import java.util.ArrayList;
import java.util.List;
+import java.util.stream.Collectors;
@RequiredArgsConstructor
public class ListHandler extends BaseHandler {
@@ -26,25 +28,47 @@ public class ListHandler extends BaseHandler {
@NonNull
private final SsmClient ssmClient;
+ @NonNull
+ private final DocumentExceptionTranslator exceptionTranslator;
+
+ @NonNull
+ private final SafeLogger safeLogger;
+
@VisibleForTesting
public ListHandler() {
- this(new DocumentModelTranslator(), new DocumentResponseModelTranslator(), SsmClient.create());
+ this(DocumentModelTranslator.getInstance(), DocumentResponseModelTranslator.getInstance(), ClientBuilder.getClient(),
+ DocumentExceptionTranslator.getInstance(), SafeLogger.getInstance());
}
+
@Override
public ProgressEvent handleRequest(
- final AmazonWebServicesClientProxy proxy,
- final ResourceHandlerRequest request,
- final CallbackContext callbackContext,
- final Logger logger) {
+ final AmazonWebServicesClientProxy proxy,
+ final ResourceHandlerRequest request,
+ final CallbackContext callbackContext,
+ final Logger logger) {
+ final ResourceModel model = request.getDesiredResourceState();
+
+ safeLogger.safeLogDocumentInformation(model, callbackContext, request.getAwsAccountId(), request.getSystemTags(), logger);
- final List models = new ArrayList<>();
+ try {
+ final ListDocumentsRequest listDocumentsRequest = DocumentModelTranslator.generateListDocumentsRequest(request.getNextToken(), model);
+ final ListDocumentsResponse listDocumentsResponse = proxy.injectCredentialsAndInvokeV2(
+ listDocumentsRequest, ClientBuilder.getClient()::listDocuments);
- final ListDocumentsRequest listDocumentsRequest = documentModelTranslator.generateListDocumentsRequest();
+ final List models = listDocumentsResponse
+ .documentIdentifiers()
+ .stream().map(documentIdentifier -> ResourceModel.builder().name(documentIdentifier.name()).build()).collect(Collectors.toList());
- return ProgressEvent.builder()
- .resourceModels(models)
- .status(OperationStatus.SUCCESS)
- .build();
+ return ProgressEvent.builder()
+ .resourceModels(models)
+ .status(OperationStatus.SUCCESS)
+ .nextToken(listDocumentsResponse.nextToken())
+ .build();
+ } catch (final SsmException e) {
+ throw exceptionTranslator.getCfnException(e, null, "AWS::SSM::ListDocuments", logger);
+ }
}
+
+
}
diff --git a/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/ReadHandler.java b/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/ReadHandler.java
index 69dde01d..4df19af1 100644
--- a/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/ReadHandler.java
+++ b/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/ReadHandler.java
@@ -17,6 +17,8 @@
import software.amazon.cloudformation.proxy.OperationStatus;
import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
+import static com.amazonaws.ssm.document.tags.TagUtil.LIST_TAGS_OPERATION_NAME;
+
/**
* Get AWS::SSM::Document resource.
*/
@@ -25,6 +27,7 @@ public class ReadHandler extends BaseHandler {
private static final String OPERATION_NAME = "AWS::SSM::GetDocument";
private static final String ACCESS_DENIED_ERROR_CODE = "AccessDeniedException";
+ private static final String INVALID_DOCUMENT_FORMAT_JSON = "Invalid document format JSON";
@NonNull
private final DocumentModelTranslator documentModelTranslator;
@@ -61,21 +64,13 @@ public ProgressEvent handleRequest(
safeLogger.safeLogDocumentInformation(model, callbackContext, request.getAwsAccountId(), request.getSystemTags(), logger);
- final GetDocumentRequest getDocumentRequest = documentModelTranslator.generateGetDocumentRequest(model);
-
final DescribeDocumentRequest describeDocumentRequest = documentModelTranslator.generateDescribeDocumentRequest(model);
+ DescribeDocumentResponse describeDocumentResponse = null;
+ GetDocumentResponse getDocumentResponse;
try {
- final GetDocumentResponse getDocumentResponse = proxy.injectCredentialsAndInvokeV2(getDocumentRequest, ssmClient::getDocument);
-
- final Map documentTags = tagReader.getDocumentTags(model.getName(), ssmClient, proxy);
- final ResourceInformation resourceInformation =
- documentResponseModelTranslator.generateResourceInformation(getDocumentResponse, documentTags);
-
try {
- final DescribeDocumentResponse describeDocumentResponse = proxy.injectCredentialsAndInvokeV2(describeDocumentRequest, ssmClient::describeDocument);
-
- resourceInformation.getResourceModel().setTargetType(describeDocumentResponse.document().targetType());
+ describeDocumentResponse = proxy.injectCredentialsAndInvokeV2(describeDocumentRequest, ssmClient::describeDocument);
} catch(SsmException e) {
if (!ACCESS_DENIED_ERROR_CODE.equalsIgnoreCase(e.awsErrorDetails().errorCode())) {
throw e;
@@ -84,12 +79,44 @@ public ProgressEvent handleRequest(
describeDocumentRequest.name()));
}
- return ProgressEvent.builder()
- .resourceModel(resourceInformation.getResourceModel())
- .status(OperationStatus.SUCCESS)
- .build();
+ GetDocumentRequest getDocumentRequest = documentModelTranslator.generateGetDocumentRequest(model);
+
+ if (describeDocumentResponse != null &&
+ describeDocumentResponse.document().documentTypeAsString().equalsIgnoreCase("ChangeCalendar")) {
+ getDocumentRequest = getDocumentRequest.toBuilder().documentFormat("TEXT").build();
+ }
+
+ try {
+ getDocumentResponse = proxy.injectCredentialsAndInvokeV2(getDocumentRequest, ssmClient::getDocument);
+ } catch (SsmException e) {
+ if (INVALID_DOCUMENT_FORMAT_JSON.equalsIgnoreCase(e.awsErrorDetails().errorMessage())) {
+ getDocumentRequest = getDocumentRequest.toBuilder().documentFormat("TEXT").build();
+ getDocumentResponse = proxy.injectCredentialsAndInvokeV2(getDocumentRequest, ssmClient::getDocument);
+ } else {
+ throw e;
+ }
+ }
} catch (final SsmException e) {
throw exceptionTranslator.getCfnException(e, model.getName(), OPERATION_NAME, logger);
}
+
+ final Map documentTags;
+
+ try {
+ documentTags = tagReader.getDocumentTags(model.getName(), ssmClient, proxy);
+ } catch (final SsmException e) {
+ throw exceptionTranslator.getCfnException(e, model.getName(), LIST_TAGS_OPERATION_NAME, logger);
+ }
+
+ final ResourceInformation resourceInformation =
+ documentResponseModelTranslator.generateResourceInformation(getDocumentResponse, documentTags);
+
+ if (describeDocumentResponse != null) {
+ resourceInformation.getResourceModel().setTargetType(describeDocumentResponse.document().targetType());
+ }
+ return ProgressEvent.builder()
+ .resourceModel(resourceInformation.getResourceModel())
+ .status(OperationStatus.SUCCESS)
+ .build();
}
}
diff --git a/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/SafeLogger.java b/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/SafeLogger.java
index d1244602..5453945e 100644
--- a/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/SafeLogger.java
+++ b/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/SafeLogger.java
@@ -23,13 +23,13 @@ public static SafeLogger getInstance() {
return INSTANCE;
}
- public void safeLogDocumentInformation(@NonNull final ResourceModel resourceModel,
+ public void safeLogDocumentInformation(@Nullable final ResourceModel resourceModel,
@Nullable final CallbackContext callbackContext,
@NonNull final String customerAccountId,
@Nullable final Map systemTags,
@NonNull final Logger logger) {
- final String documentDetails = String.format(DOCUMENT_INFO_SAFE_LOG_FORMAT, resourceModel.getName(),
- resourceModel.getDocumentType(), resourceModel.getDocumentFormat());
+ final String documentDetails = resourceModel != null ? String.format(DOCUMENT_INFO_SAFE_LOG_FORMAT, resourceModel.getName(),
+ resourceModel.getDocumentType(), resourceModel.getDocumentFormat()) : null;
final String stackId = systemTags != null ? systemTags.get(STACK_ID_KEY) : null;
diff --git a/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/tags/TagUpdater.java b/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/tags/TagUpdater.java
index d4d92815..5a20e570 100644
--- a/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/tags/TagUpdater.java
+++ b/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/tags/TagUpdater.java
@@ -8,7 +8,6 @@
import software.amazon.awssdk.services.ssm.SsmClient;
import software.amazon.awssdk.services.ssm.model.SsmException;
import software.amazon.awssdk.services.ssm.model.Tag;
-import software.amazon.cloudformation.exceptions.CfnUnauthorizedTaggingOperationException;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
import software.amazon.cloudformation.proxy.Logger;
diff --git a/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/tags/TagUtil.java b/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/tags/TagUtil.java
index 496ce5b1..09a340e7 100644
--- a/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/tags/TagUtil.java
+++ b/aws-ssm-document/src/main/java/com/amazonaws/ssm/document/tags/TagUtil.java
@@ -20,6 +20,8 @@ public class TagUtil {
public static final String TAGGING_PERMISSION_MESSAGE_FORMAT = "Did not have IAM permissions to process tags on" +
" AWS::SSM::Document resource.";
+ public static final String LIST_TAGS_OPERATION_NAME = "ListTagsForResource";
+
private static final String ACCESS_DENIED_ERROR_CODE = "AccessDeniedException";
private static final String ADD_TAGS_ACTION = "ssm:AddTagsToResource";
diff --git a/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/CallbackContextTest.java b/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/CallbackContextTest.java
index 923a3b1a..366ccac5 100644
--- a/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/CallbackContextTest.java
+++ b/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/CallbackContextTest.java
@@ -1,6 +1,7 @@
package com.amazonaws.ssm.document;
import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -16,6 +17,7 @@ public void testSerializeAndDeserialize() throws JsonProcessingException {
.eventStarted(true)
.build();
+ OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
final String serializedText = OBJECT_MAPPER.writeValueAsString(context);
Assertions.assertEquals(context, OBJECT_MAPPER.readValue(serializedText, CallbackContext.class));
diff --git a/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/CreateHandlerTest.java b/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/CreateHandlerTest.java
index d1c66ee1..0c76e2ed 100644
--- a/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/CreateHandlerTest.java
+++ b/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/CreateHandlerTest.java
@@ -381,6 +381,7 @@ public void handleRequest_NewDocumentCreation_ssmServiceThrowsException_doesNotS
when(documentModelTranslator.generateCreateDocumentRequest(resourceModel, SAMPLE_LOGICAL_RESOURCE_ID, SAMPLE_SYSTEM_TAGS, SAMPLE_RESOURCE_TAGS, SAMPLE_REQUEST_TOKEN)).thenReturn(SAMPLE_CREATE_DOCUMENT_REQUEST);
when(ssmException.awsErrorDetails()).thenReturn(ACCESS_DENIED_ERROR_DETAILS);
when(proxy.injectCredentialsAndInvokeV2(eq(SAMPLE_CREATE_DOCUMENT_REQUEST), any())).thenThrow(ssmException);
+
when(exceptionTranslator.getCfnException(ssmException, SAMPLE_DOCUMENT_NAME, OPERATION_NAME, logger)).thenReturn(cfnException);
Assertions.assertThrows(CfnGeneralServiceException.class, () -> unitUnderTest.handleRequest(proxy, resourceModelHandlerRequest, null, logger));
diff --git a/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/DocumentExceptionTranslatorTest.java b/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/DocumentExceptionTranslatorTest.java
index 91d4726f..78b74957 100644
--- a/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/DocumentExceptionTranslatorTest.java
+++ b/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/DocumentExceptionTranslatorTest.java
@@ -7,6 +7,7 @@
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
+import software.amazon.awssdk.awscore.exception.AwsErrorDetails;
import software.amazon.awssdk.services.ssm.model.AutomationDefinitionNotFoundException;
import software.amazon.awssdk.services.ssm.model.DocumentAlreadyExistsException;
import software.amazon.awssdk.services.ssm.model.DocumentLimitExceededException;
@@ -18,6 +19,7 @@
import software.amazon.awssdk.services.ssm.model.InvalidResourceIdException;
import software.amazon.awssdk.services.ssm.model.MaxDocumentSizeExceededException;
import software.amazon.awssdk.services.ssm.model.SsmException;
+import software.amazon.cloudformation.exceptions.CfnAccessDeniedException;
import software.amazon.cloudformation.exceptions.CfnAlreadyExistsException;
import software.amazon.cloudformation.exceptions.CfnGeneralServiceException;
import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
@@ -29,6 +31,8 @@
import software.amazon.cloudformation.exceptions.CfnUnauthorizedTaggingOperationException;
import software.amazon.cloudformation.proxy.Logger;
+import static com.amazonaws.ssm.document.tags.TagUtil.LIST_TAGS_OPERATION_NAME;
+
@ExtendWith(MockitoExtension.class)
public class DocumentExceptionTranslatorTest {
@@ -103,4 +107,18 @@ public void testGetCfnException_400StatusCode_verifyExceptionsReturned() {
Assertions.assertTrue(unitUnderTest.getCfnException(ssmException, SAMPLE_DOCUMENT_NAME, SAMPLE_OPERATION_NAME, logger) instanceof CfnInvalidRequestException);
}
+
+ @Test
+ public void testGetCfnException_AccessDeniedException_verifyExceptionsReturned() {
+ Mockito.when(ssmException.awsErrorDetails()).thenReturn(AwsErrorDetails.builder().errorCode("AccessDeniedException").build());
+
+ Assertions.assertTrue(unitUnderTest.getCfnException(ssmException, SAMPLE_DOCUMENT_NAME, SAMPLE_OPERATION_NAME, logger) instanceof CfnAccessDeniedException);
+ }
+
+ @Test
+ public void testGetCfnException_ListTagsForResourceAccessDeniedException_verifyExceptionsReturned() {
+ Mockito.when(ssmException.awsErrorDetails()).thenReturn(AwsErrorDetails.builder().errorCode("AccessDeniedException").build());
+
+ Assertions.assertTrue(unitUnderTest.getCfnException(ssmException, SAMPLE_DOCUMENT_NAME, LIST_TAGS_OPERATION_NAME, logger) instanceof CfnUnauthorizedTaggingOperationException);
+ }
}
diff --git a/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/DocumentModelTranslatorTest.java b/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/DocumentModelTranslatorTest.java
index de376e84..2786f84b 100644
--- a/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/DocumentModelTranslatorTest.java
+++ b/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/DocumentModelTranslatorTest.java
@@ -7,9 +7,10 @@
import software.amazon.awssdk.services.ssm.model.CreateDocumentRequest;
import software.amazon.awssdk.services.ssm.model.DeleteDocumentRequest;
import software.amazon.awssdk.services.ssm.model.DescribeDocumentRequest;
+import software.amazon.awssdk.services.ssm.model.DocumentKeyValuesFilter;
import software.amazon.awssdk.services.ssm.model.DocumentFormat;
import software.amazon.awssdk.services.ssm.model.GetDocumentRequest;
-import software.amazon.awssdk.services.ssm.model.GetDocumentResponse;
+import software.amazon.awssdk.services.ssm.model.ListDocumentsRequest;
import software.amazon.awssdk.services.ssm.model.UpdateDocumentRequest;
import java.util.List;
@@ -48,6 +49,7 @@ public class DocumentModelTranslatorTest {
private static final String SAMPLE_DOCUMENT_TYPE = "type";
private static final String LATEST_DOCUMENT_VERSION = "$LATEST";
private static final String SAMPLE_TARGET_TYPE = "targetType";
+ private static final Integer MAX_RESULTS = 50;
private static final List SAMPLE_RESOURCE_MODEL_TAGS = ImmutableList.of(
Tag.builder().key("tagKey1").value("tagValue1").build(),
Tag.builder().key("tagKey2").value("tagValue2").build()
@@ -488,6 +490,97 @@ public void testGenerateDescribeDocumentRequest_verifyResult() {
Assertions.assertEquals(expectedRequest, request);
}
+ //ListDocumentRequest tests
+ @Test
+ public void testGenerateListDocumentsRequest_NoFilters_verifyResult() {
+ final ResourceModel model = ResourceModel.builder().build();
+
+ final ListDocumentsRequest expectedRequest = ListDocumentsRequest.builder()
+ .maxResults(MAX_RESULTS)
+ .nextToken(SAMPLE_REQUEST_TOKEN)
+ .build();
+
+ final ListDocumentsRequest request = unitUnderTest.generateListDocumentsRequest(SAMPLE_REQUEST_TOKEN,model);
+
+ Assertions.assertEquals(expectedRequest, request);
+ }
+
+ @Test
+ public void testGenerateListDocumentsRequest_NameFilter_verifyResult() {
+ final ResourceModel model = ResourceModel.builder()
+ .name(SAMPLE_DOCUMENT_NAME)
+ .build();
+
+ List keyValuesFilters =
+ ImmutableList.of(DocumentKeyValuesFilter.builder().key("Name").values(SAMPLE_DOCUMENT_NAME).build());
+ final ListDocumentsRequest expectedRequest = ListDocumentsRequest.builder()
+ .filters(keyValuesFilters)
+ .maxResults(MAX_RESULTS)
+ .nextToken(SAMPLE_REQUEST_TOKEN)
+ .build();
+
+ final ListDocumentsRequest request = unitUnderTest.generateListDocumentsRequest(SAMPLE_REQUEST_TOKEN,model);
+
+ Assertions.assertEquals(expectedRequest, request);
+ }
+ @Test
+ public void testGenerateListDocumentsRequest_DocumentTypeFilter_verifyResult() {
+ final ResourceModel model = ResourceModel.builder()
+ .documentType(SAMPLE_DOCUMENT_TYPE)
+ .build();
+
+ List keyValuesFilters =
+ ImmutableList.of(DocumentKeyValuesFilter.builder().key("DocumentType").values(SAMPLE_DOCUMENT_TYPE).build());
+ final ListDocumentsRequest expectedRequest = ListDocumentsRequest.builder()
+ .filters(keyValuesFilters)
+ .maxResults(MAX_RESULTS)
+ .nextToken(SAMPLE_REQUEST_TOKEN)
+ .build();
+
+ final ListDocumentsRequest request = unitUnderTest.generateListDocumentsRequest(SAMPLE_REQUEST_TOKEN,model);
+
+ Assertions.assertEquals(expectedRequest, request);
+ }
+ @Test
+ public void testGenerateListDocumentsRequest_TargetTypeFilter_verifyResult() {
+ final ResourceModel model = ResourceModel.builder()
+ .targetType(SAMPLE_TARGET_TYPE)
+ .build();
+
+ List keyValuesFilters =
+ ImmutableList.of(DocumentKeyValuesFilter.builder().key("TargetType").values(SAMPLE_TARGET_TYPE).build());
+ final ListDocumentsRequest expectedRequest = ListDocumentsRequest.builder()
+ .filters(keyValuesFilters)
+ .maxResults(MAX_RESULTS)
+ .nextToken(SAMPLE_REQUEST_TOKEN)
+ .build();
+
+ final ListDocumentsRequest request = unitUnderTest.generateListDocumentsRequest(SAMPLE_REQUEST_TOKEN,model);
+
+ Assertions.assertEquals(expectedRequest, request);
+ }
+ @Test
+ public void testGenerateListDocumentsRequest_AllThreeFilter_verifyResult() {
+ final ResourceModel model = ResourceModel.builder()
+ .name(SAMPLE_DOCUMENT_NAME)
+ .documentType(SAMPLE_DOCUMENT_TYPE)
+ .targetType(SAMPLE_TARGET_TYPE)
+ .build();
+
+ List keyValuesFilters =
+ ImmutableList.of(DocumentKeyValuesFilter.builder().key("Name").values(SAMPLE_DOCUMENT_NAME).build(),DocumentKeyValuesFilter.builder().key("DocumentType").values(SAMPLE_DOCUMENT_TYPE).build(),DocumentKeyValuesFilter.builder().key("TargetType").values(SAMPLE_TARGET_TYPE).build());
+ final ListDocumentsRequest expectedRequest = ListDocumentsRequest.builder()
+ .filters(keyValuesFilters)
+ .maxResults(MAX_RESULTS)
+ .nextToken(SAMPLE_REQUEST_TOKEN)
+ .build();
+
+ final ListDocumentsRequest request = unitUnderTest.generateListDocumentsRequest(SAMPLE_REQUEST_TOKEN,model);
+
+ Assertions.assertEquals(expectedRequest, request);
+ }
+
+
private ResourceModel createResourceModel() {
return ResourceModel.builder()
.name(SAMPLE_DOCUMENT_NAME)
diff --git a/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/ListHandlerTest.java b/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/ListHandlerTest.java
index 84eee957..0a7194c2 100644
--- a/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/ListHandlerTest.java
+++ b/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/ListHandlerTest.java
@@ -1,42 +1,93 @@
package com.amazonaws.ssm.document;
+import com.google.common.collect.ImmutableList;
+import software.amazon.awssdk.services.ssm.SsmClient;
+import software.amazon.awssdk.services.ssm.model.DocumentIdentifier;
+import software.amazon.awssdk.services.ssm.model.ListDocumentsRequest;
+import software.amazon.awssdk.services.ssm.model.ListDocumentsResponse;
+import software.amazon.awssdk.services.ssm.model.SsmException;
+import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
import software.amazon.cloudformation.proxy.Logger;
import software.amazon.cloudformation.proxy.OperationStatus;
import software.amazon.cloudformation.proxy.ProgressEvent;
import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+import java.util.Arrays;
+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.when;
@ExtendWith(MockitoExtension.class)
public class ListHandlerTest {
-
@Mock
private AmazonWebServicesClientProxy proxy;
@Mock
private Logger logger;
+ @Mock
+ private DocumentModelTranslator documentModelTranslator;
+
+ @Mock
+ private DocumentResponseModelTranslator documentResponseModelTranslator;
+
+ @Mock
+ private DocumentExceptionTranslator exceptionTranslator;
+
+ @Mock
+ private SsmException ssmException;
+
+ @Mock
+ private SafeLogger safeLogger;
+
+ @Mock
+ private SsmClient ssmClient;
+
+ private ListHandler handler;
+
+ private static final String Document_Identifier = "doc:aws::ssm:test";
+ private static final String Document_Identifier2 = "doc:aws::ssm:test2";
+ private static final String Document_Identifier3 = "doc:aws::ssm:test3";
+ private static final String OPERATION_NAME = "AWS::SSM::ListDocuments";
+
@BeforeEach
public void setup() {
- proxy = mock(AmazonWebServicesClientProxy.class);
- logger = mock(Logger.class);
+ documentModelTranslator = DocumentModelTranslator.getInstance();
+ handler = new ListHandler(documentModelTranslator, documentResponseModelTranslator, ssmClient,
+ exceptionTranslator, safeLogger);
}
- //@Test
+ @Test
public void handleRequest_SimpleSuccess() {
- final ListHandler handler = new ListHandler();
+ final ListDocumentsResponse listDocumentsResponse = ListDocumentsResponse.builder()
+ .documentIdentifiers(Arrays.asList(
+ DocumentIdentifier.builder().name(Document_Identifier).build(),
+ DocumentIdentifier.builder().name(Document_Identifier2).build()))
+ .nextToken("token2")
+ .build();
+
+ when(proxy.injectCredentialsAndInvokeV2(any(ListDocumentsRequest.class), any())).thenReturn(listDocumentsResponse);
- final ResourceModel model = ResourceModel.builder().build();
+ final ResourceModel model1 = ResourceModel.builder()
+ .name(Document_Identifier)
+ .build();
+ final ResourceModel model2 = ResourceModel.builder()
+ .name(Document_Identifier2)
+ .build();
final ResourceHandlerRequest request = ResourceHandlerRequest.builder()
- .desiredResourceState(model)
+ .nextToken("token")
.build();
final ProgressEvent response =
@@ -47,8 +98,57 @@ public void handleRequest_SimpleSuccess() {
assertThat(response.getCallbackContext()).isNull();
assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
assertThat(response.getResourceModel()).isNull();
- assertThat(response.getResourceModels()).isNotNull();
+ assertThat(response.getResourceModels()).containsAll(Arrays.asList(model1, model2));
+ assertThat(response.getMessage()).isNull();
+ assertThat(response.getErrorCode()).isNull();
+ assertThat(response.getNextToken()).isEqualTo("token2");
+ }
+
+ @Test
+ public void handleRequest_EmptyToken_throwsException() {
+ final ResourceModel model3 = ResourceModel.builder()
+ .name(Document_Identifier3)
+ .build();
+
+ final ResourceHandlerRequest request = ResourceHandlerRequest.builder()
+ .nextToken("") //empty string as nextToken should mean empty page
+ .build();
+
+ when(proxy.injectCredentialsAndInvokeV2(any(ListDocumentsRequest.class), any())).thenThrow(ssmException);
+ when(exceptionTranslator.getCfnException(ssmException, null, OPERATION_NAME, logger)).thenThrow(CfnInvalidRequestException.class);
+
+ Assertions.assertThrows(CfnInvalidRequestException.class, () -> handler.handleRequest(proxy, request, null, logger));
+ }
+
+ @Test
+ public void handleRequest_NullToken() {
+ final ListDocumentsResponse listDocumentsResponse = ListDocumentsResponse.builder()
+ .documentIdentifiers(Arrays.asList(
+ DocumentIdentifier.builder().name(Document_Identifier3).build()))
+ .nextToken("token_next")
+ .build();
+
+ when(proxy.injectCredentialsAndInvokeV2(any(ListDocumentsRequest.class), any())).thenReturn(listDocumentsResponse);
+
+ final ResourceModel model3 = ResourceModel.builder()
+ .name(Document_Identifier3)
+ .build();
+
+ final ResourceHandlerRequest request = ResourceHandlerRequest.builder()
+ //No nextToken in Request should mean first page
+ .build();
+
+ final ProgressEvent response =
+ handler.handleRequest(proxy, request, null, logger);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
+ assertThat(response.getCallbackContext()).isNull();
+ assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
+ assertThat(response.getResourceModel()).isNull();
+ assertThat(response.getResourceModels()).contains(model3);
assertThat(response.getMessage()).isNull();
assertThat(response.getErrorCode()).isNull();
+ assertThat(response.getNextToken()).isEqualTo("token_next");
}
}
diff --git a/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/ReadHandlerTest.java b/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/ReadHandlerTest.java
index 91519d74..d5402d85 100644
--- a/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/ReadHandlerTest.java
+++ b/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/ReadHandlerTest.java
@@ -38,6 +38,8 @@ public class ReadHandlerTest {
private static final String SAMPLE_ACCOUNT_ID = "123456";
private static final String SAMPLE_DOCUMENT_NAME = "sampleDocument";
private static final String SAMPLE_TARGET_TYPE = "/AWS::EC2::Volume";
+ private static final String SAMPLE_DOCUMENT_TYPE = "Command";
+ private static final String CHANGE_CALENDAR_DOCUMENT_TYPE = "ChangeCalendar";
private static final Map SAMPLE_DOCUMENT_CONTENT = ImmutableMap.of(
"schemaVersion", "1.2",
"description", "Join instances to an AWS Directory Service domain."
@@ -55,15 +57,28 @@ public class ReadHandlerTest {
private static final GetDocumentRequest SAMPLE_GET_DOCUMENT_REQUEST = GetDocumentRequest.builder()
.name(SAMPLE_DOCUMENT_NAME)
.build();
- final GetDocumentResponse SAMPLE_GET_DOCUMENT_RESPONSE = GetDocumentResponse.builder()
+ private static final GetDocumentRequest TEXT_GET_DOCUMENT_REQUEST = GetDocumentRequest.builder()
+ .name(SAMPLE_DOCUMENT_NAME)
+ .documentFormat("TEXT")
+ .build();
+ private final GetDocumentResponse SAMPLE_GET_DOCUMENT_RESPONSE = GetDocumentResponse.builder()
.name(SAMPLE_DOCUMENT_NAME).status(DocumentStatus.ACTIVE)
.build();
private static final DescribeDocumentRequest SAMPLE_DESCRIBE_DOCUMENT_REQUEST = DescribeDocumentRequest.builder()
.name(SAMPLE_DOCUMENT_NAME)
.build();
- final DescribeDocumentResponse SAMPLE_DESCRIBE_DOCUMENT_RESPONSE = DescribeDocumentResponse.builder()
+ private final DescribeDocumentResponse SAMPLE_DESCRIBE_DOCUMENT_RESPONSE = DescribeDocumentResponse.builder()
.document(DocumentDescription.builder()
.name(SAMPLE_DOCUMENT_NAME)
+ .documentType(SAMPLE_DOCUMENT_TYPE)
+ .targetType(SAMPLE_TARGET_TYPE)
+ .status(DocumentStatus.ACTIVE)
+ .build())
+ .build();
+ final DescribeDocumentResponse CHANGE_CALENDAR_DESCRIBE_DOCUMENT_RESPONSE = DescribeDocumentResponse.builder()
+ .document(DocumentDescription.builder()
+ .name(SAMPLE_DOCUMENT_NAME)
+ .documentType(CHANGE_CALENDAR_DOCUMENT_TYPE)
.targetType(SAMPLE_TARGET_TYPE)
.status(DocumentStatus.ACTIVE)
.build())
@@ -74,6 +89,13 @@ public class ReadHandlerTest {
private static final SsmException ACCESS_DENIED_EXCEPTION = (SsmException) SsmException.builder()
.awsErrorDetails(AwsErrorDetails.builder()
.errorCode("AccessDeniedException")
+ .errorMessage("AccessDeniedMessage")
+ .build())
+ .build();
+ private static final SsmException INVALID_DOCUMENT_FORMAT_JSON_EXCEPTION = (SsmException) SsmException.builder()
+ .awsErrorDetails(AwsErrorDetails.builder()
+ .errorCode("ValidationException")
+ .errorMessage("Invalid document format JSON")
.build())
.build();
@@ -182,7 +204,12 @@ public void testHandleRequest_DescribeDocumentAccessDeniedSoftFail_verifyResult(
@Test
public void testHandleRequest_GetDocumentThrowsSsmException_verifyExceptionReturned() {
when(documentModelTranslator.generateGetDocumentRequest(SAMPLE_RESOURCE_MODEL)).thenReturn(SAMPLE_GET_DOCUMENT_REQUEST);
+ when(documentModelTranslator.generateDescribeDocumentRequest(SAMPLE_RESOURCE_MODEL)).thenReturn(SAMPLE_DESCRIBE_DOCUMENT_REQUEST);
+ when(proxy.injectCredentialsAndInvokeV2(eq(SAMPLE_DESCRIBE_DOCUMENT_REQUEST), any())).thenReturn(SAMPLE_DESCRIBE_DOCUMENT_RESPONSE);
when(proxy.injectCredentialsAndInvokeV2(eq(SAMPLE_GET_DOCUMENT_REQUEST), any())).thenThrow(ssmException);
+ when(ssmException.awsErrorDetails()).thenReturn(awsErrorDetails);
+ when(awsErrorDetails.errorMessage()).thenReturn("Some SsmException");
+
when(exceptionTranslator.getCfnException(ssmException, SAMPLE_DOCUMENT_NAME, OPERATION_NAME, logger)).thenReturn(cfnException);
Assertions.assertThrows(CfnGeneralServiceException.class, () -> unitUnderTest.handleRequest(proxy, SAMPLE_RESOURCE_HANDLER_REQUEST, null, logger));
@@ -191,9 +218,7 @@ public void testHandleRequest_GetDocumentThrowsSsmException_verifyExceptionRetur
@Test
public void testHandleRequest_DescribeThrowsSsmException_verifyExceptionReturned() {
- when(documentModelTranslator.generateGetDocumentRequest(SAMPLE_RESOURCE_MODEL)).thenReturn(SAMPLE_GET_DOCUMENT_REQUEST);
when(documentModelTranslator.generateDescribeDocumentRequest(SAMPLE_RESOURCE_MODEL)).thenReturn(SAMPLE_DESCRIBE_DOCUMENT_REQUEST);
- when(proxy.injectCredentialsAndInvokeV2(eq(SAMPLE_GET_DOCUMENT_REQUEST), any())).thenReturn(SAMPLE_GET_DOCUMENT_RESPONSE);
when(proxy.injectCredentialsAndInvokeV2(eq(SAMPLE_DESCRIBE_DOCUMENT_REQUEST), any())).thenThrow(ssmException);
when(ssmException.awsErrorDetails()).thenReturn(awsErrorDetails);
when(exceptionTranslator.getCfnException(ssmException, SAMPLE_DOCUMENT_NAME, OPERATION_NAME, logger)).thenReturn(cfnException);
@@ -201,4 +226,66 @@ public void testHandleRequest_DescribeThrowsSsmException_verifyExceptionReturned
Assertions.assertThrows(CfnGeneralServiceException.class, () -> unitUnderTest.handleRequest(proxy, SAMPLE_RESOURCE_HANDLER_REQUEST, null, logger));
verify(safeLogger).safeLogDocumentInformation(SAMPLE_RESOURCE_MODEL, null, SAMPLE_ACCOUNT_ID, SAMPLE_SYSTEM_TAGS, logger);
}
+
+ @Test
+ public void testHandleRequest_DescribeReturnsChangeCalenarDocument_verifyResult() {
+ final ResourceModel expectedModel = ResourceModel.builder()
+ .name(SAMPLE_DOCUMENT_NAME)
+ .content(SAMPLE_DOCUMENT_CONTENT)
+ .targetType(SAMPLE_TARGET_TYPE).build();
+ final ResourceInformation expectedResourceInformation = ResourceInformation.builder().resourceModel(expectedModel)
+ .status(SAMPLE_RESOURCE_STATE)
+ .statusInformation(SAMPLE_STATUS_INFO)
+ .build();
+ final ProgressEvent expectedResponse = ProgressEvent.builder()
+ .resourceModel(expectedModel)
+ .status(OperationStatus.SUCCESS)
+ .build();
+
+ when(documentModelTranslator.generateGetDocumentRequest(SAMPLE_RESOURCE_MODEL)).thenReturn(SAMPLE_GET_DOCUMENT_REQUEST);
+ when(documentModelTranslator.generateDescribeDocumentRequest(SAMPLE_RESOURCE_MODEL)).thenReturn(SAMPLE_DESCRIBE_DOCUMENT_REQUEST);
+ when(proxy.injectCredentialsAndInvokeV2(eq(TEXT_GET_DOCUMENT_REQUEST), any())).thenReturn(SAMPLE_GET_DOCUMENT_RESPONSE);
+ when(proxy.injectCredentialsAndInvokeV2(eq(SAMPLE_DESCRIBE_DOCUMENT_REQUEST), any())).thenReturn(CHANGE_CALENDAR_DESCRIBE_DOCUMENT_RESPONSE);
+ when(tagReader.getDocumentTags(SAMPLE_DOCUMENT_NAME, ssmClient, proxy)).thenReturn(SAMPLE_TAG_MAP);
+ when(documentResponseModelTranslator.generateResourceInformation(SAMPLE_GET_DOCUMENT_RESPONSE, SAMPLE_TAG_MAP))
+ .thenReturn(expectedResourceInformation);
+
+ final ProgressEvent response
+ = unitUnderTest.handleRequest(proxy, SAMPLE_RESOURCE_HANDLER_REQUEST, null, logger);
+
+ Assertions.assertEquals(expectedResponse, response);
+ verify(safeLogger).safeLogDocumentInformation(SAMPLE_RESOURCE_MODEL, null, SAMPLE_ACCOUNT_ID, SAMPLE_SYSTEM_TAGS, logger);
+ }
+
+ @Test
+ public void testHandleRequest_DescribeDocumentAccessDeniedForChangeCalendarDocumentType_verifyResult() {
+ final ResourceModel expectedModel = ResourceModel.builder()
+ .name(SAMPLE_DOCUMENT_NAME)
+ .content(SAMPLE_DOCUMENT_CONTENT)
+ .targetType(SAMPLE_TARGET_TYPE).build();
+ final ResourceInformation expectedResourceInformation = ResourceInformation.builder().resourceModel(expectedModel)
+ .status(SAMPLE_RESOURCE_STATE)
+ .statusInformation(SAMPLE_STATUS_INFO)
+ .build();
+ final ProgressEvent expectedResponse = ProgressEvent.builder()
+ .resourceModel(expectedModel)
+ .status(OperationStatus.SUCCESS)
+ .build();
+
+ when(documentModelTranslator.generateGetDocumentRequest(SAMPLE_RESOURCE_MODEL)).thenReturn(SAMPLE_GET_DOCUMENT_REQUEST);
+ when(documentModelTranslator.generateDescribeDocumentRequest(SAMPLE_RESOURCE_MODEL)).thenReturn(SAMPLE_DESCRIBE_DOCUMENT_REQUEST);
+ when(proxy.injectCredentialsAndInvokeV2(eq(SAMPLE_DESCRIBE_DOCUMENT_REQUEST), any())).thenThrow(ACCESS_DENIED_EXCEPTION);
+ when(proxy.injectCredentialsAndInvokeV2(eq(SAMPLE_GET_DOCUMENT_REQUEST), any())).thenThrow(INVALID_DOCUMENT_FORMAT_JSON_EXCEPTION);
+ when(proxy.injectCredentialsAndInvokeV2(eq(TEXT_GET_DOCUMENT_REQUEST), any())).thenReturn(SAMPLE_GET_DOCUMENT_RESPONSE);
+
+ when(tagReader.getDocumentTags(SAMPLE_DOCUMENT_NAME, ssmClient, proxy)).thenReturn(SAMPLE_TAG_MAP);
+ when(documentResponseModelTranslator.generateResourceInformation(SAMPLE_GET_DOCUMENT_RESPONSE, SAMPLE_TAG_MAP))
+ .thenReturn(expectedResourceInformation);
+
+ final ProgressEvent response
+ = unitUnderTest.handleRequest(proxy, SAMPLE_RESOURCE_HANDLER_REQUEST, null, logger);
+
+ Assertions.assertEquals(expectedResponse, response);
+ verify(safeLogger).safeLogDocumentInformation(SAMPLE_RESOURCE_MODEL, null, SAMPLE_ACCOUNT_ID, SAMPLE_SYSTEM_TAGS, logger);
+ }
}
diff --git a/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/UpdateHandlerTest.java b/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/UpdateHandlerTest.java
index 78bdd617..7aad690e 100644
--- a/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/UpdateHandlerTest.java
+++ b/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/UpdateHandlerTest.java
@@ -686,8 +686,8 @@ public void testHandleRequest_withTrueUpdate_StabilizationRetrieverThrowsExcepti
.build();
when(progressUpdater.getEventProgress(expectedModel, inProgressCallbackContext, ssmClient, proxy, logger))
- .thenThrow(ssmException);
- when(exceptionTranslator.getCfnException(ssmException, SAMPLE_DOCUMENT_NAME, OPERATION_NAME, logger)).thenThrow(CfnGeneralServiceException.class);
+ .thenThrow(SsmException.class);
+ when(exceptionTranslator.getCfnException(any(SsmException.class), eq(SAMPLE_DOCUMENT_NAME), eq(OPERATION_NAME), eq(logger))).thenThrow(CfnGeneralServiceException.class);
final ResourceHandlerRequest request = buildRequest(previousModel, expectedModel);
Assertions.assertThrows(CfnGeneralServiceException.class, () -> unitUnderTest.handleRequest(proxy, request, inProgressCallbackContext, logger));
diff --git a/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/tags/TagUpdaterTest.java b/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/tags/TagUpdaterTest.java
index ed6500e0..3fc0982a 100644
--- a/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/tags/TagUpdaterTest.java
+++ b/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/tags/TagUpdaterTest.java
@@ -20,6 +20,8 @@
import java.util.List;
import java.util.Map;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static com.amazonaws.ssm.document.tags.TagUtil.TAGGING_PERMISSION_MESSAGE_FORMAT;
@ExtendWith(MockitoExtension.class)
@@ -186,8 +188,9 @@ public void testUpdateTags_addTagsAPIAccessDenied_shouldHardFail_VerifyException
Mockito.doThrow(ssmException).when(tagClient).addTags(expectedTagsToAdd, SAMPLE_DOCUMENT_NAME, ssmClient, proxy);
Mockito.when(tagUtil.isResourceTagModified(SAMPLE_PREVIOUS_MODEL_TAGS, SAMPLE_MODEL_TAGS)).thenReturn(true);
+ Mockito.doNothing().when(logger).log(any());
- Assertions.assertThrows(CfnUnauthorizedTaggingOperationException.class, () -> unitUnderTest.updateTags(SAMPLE_DOCUMENT_NAME, SAMPLE_EXISTING_RESOURCE_REQUEST_TAGS, SAMPLE_RESOURCE_REQUEST_TAGS,
+ Assertions.assertThrows(SsmException.class, () -> unitUnderTest.updateTags(SAMPLE_DOCUMENT_NAME, SAMPLE_EXISTING_RESOURCE_REQUEST_TAGS, SAMPLE_RESOURCE_REQUEST_TAGS,
SAMPLE_PREVIOUS_MODEL_TAGS, SAMPLE_MODEL_TAGS, ssmClient, proxy, logger));
verifyTagClientCalls(expectedTagsToAdd, expectedTagsToRemove);
@@ -211,8 +214,9 @@ public void testUpdateTags_removeTagsAPIAccessDenied_shouldHardFail_VerifyExcept
Mockito.doThrow(ssmException).when(tagClient).removeTags(expectedTagsToRemove, SAMPLE_DOCUMENT_NAME, ssmClient, proxy);
Mockito.when(tagUtil.isResourceTagModified(SAMPLE_PREVIOUS_MODEL_TAGS, SAMPLE_MODEL_TAGS)).thenReturn(true);
+ Mockito.doNothing().when(logger).log(any());
- Assertions.assertThrows(CfnUnauthorizedTaggingOperationException.class, () -> unitUnderTest.updateTags(SAMPLE_DOCUMENT_NAME, SAMPLE_EXISTING_RESOURCE_REQUEST_TAGS, SAMPLE_RESOURCE_REQUEST_TAGS,
+ Assertions.assertThrows(SsmException.class, () -> unitUnderTest.updateTags(SAMPLE_DOCUMENT_NAME, SAMPLE_EXISTING_RESOURCE_REQUEST_TAGS, SAMPLE_RESOURCE_REQUEST_TAGS,
SAMPLE_PREVIOUS_MODEL_TAGS, SAMPLE_MODEL_TAGS, ssmClient, proxy, logger));
Mockito.verify(tagClient, Mockito.times(1)).removeTags(expectedTagsToRemove, SAMPLE_DOCUMENT_NAME, ssmClient, proxy);
diff --git a/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/tags/TagUtilTest.java b/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/tags/TagUtilTest.java
index 87138d1e..3bf2422e 100644
--- a/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/tags/TagUtilTest.java
+++ b/aws-ssm-document/src/test/java/com/amazonaws/ssm/document/tags/TagUtilTest.java
@@ -111,7 +111,6 @@ public void testIsTaggingPermissionFailure_AccessDeniedException_CauseNotFromTag
.build());
Assertions.assertFalse(unitUnderTest.isTaggingPermissionFailure(ssmException));
-
}
@Test
diff --git a/aws-ssm-document/template.yml b/aws-ssm-document/template.yml
index 9777e4a5..79575735 100644
--- a/aws-ssm-document/template.yml
+++ b/aws-ssm-document/template.yml
@@ -11,12 +11,14 @@ Resources:
Type: AWS::Serverless::Function
Properties:
Handler: com.amazonaws.ssm.document.HandlerWrapper::handleRequest
- Runtime: java8
- CodeUri: ./target/aws-ssm-document-handler-1.0-SNAPSHOT.jar
+ Runtime: java11
+ CodeUri: ./target/aws-ssm-document-1.0.jar
+ MemorySize: 256 # to prevent OutOfMemory exception when testing
TestEntrypoint:
Type: AWS::Serverless::Function
Properties:
Handler: com.amazonaws.ssm.document.HandlerWrapper::testEntrypoint
- Runtime: java8
- CodeUri: ./target/aws-ssm-document-handler-1.0-SNAPSHOT.jar
+ Runtime: java11
+ CodeUri: ./target/aws-ssm-document-1.0.jar
+ MemorySize: 256 # to prevent OutOfMemory exception when testing
diff --git a/aws-ssm-maintenancewindow/pom.xml b/aws-ssm-maintenancewindow/pom.xml
index 80ae2d63..eb81df03 100644
--- a/aws-ssm-maintenancewindow/pom.xml
+++ b/aws-ssm-maintenancewindow/pom.xml
@@ -28,7 +28,7 @@
software.amazon.cloudformation
aws-cloudformation-rpdk-java-plugin
- [2.0.0, 3.0.0)
+ [2.0.0, 2.1.0)
diff --git a/aws-ssm-maintenancewindowtarget/pom.xml b/aws-ssm-maintenancewindowtarget/pom.xml
index da2ebc23..5dbcf9d7 100644
--- a/aws-ssm-maintenancewindowtarget/pom.xml
+++ b/aws-ssm-maintenancewindowtarget/pom.xml
@@ -29,7 +29,7 @@
software.amazon.cloudformation
aws-cloudformation-rpdk-java-plugin
- [2.0.0, 3.0.0)
+ [2.0.0, 2.1.0)
diff --git a/aws-ssm-maintenancewindowtask/pom.xml b/aws-ssm-maintenancewindowtask/pom.xml
index 32bc2cdd..1085385a 100644
--- a/aws-ssm-maintenancewindowtask/pom.xml
+++ b/aws-ssm-maintenancewindowtask/pom.xml
@@ -29,7 +29,7 @@
software.amazon.cloudformation
aws-cloudformation-rpdk-java-plugin
- [2.0.0, 3.0.0)
+ [2.0.0, 2.1.0)
diff --git a/aws-ssm-opsmetadata/pom.xml b/aws-ssm-opsmetadata/pom.xml
index 84db1d7a..240008c3 100644
--- a/aws-ssm-opsmetadata/pom.xml
+++ b/aws-ssm-opsmetadata/pom.xml
@@ -41,7 +41,7 @@
software.amazon.cloudformation
aws-cloudformation-rpdk-java-plugin
- [2.0.0,3.0.0)
+ [2.0.0, 2.1.0)
diff --git a/aws-ssm-parameter/pom.xml b/aws-ssm-parameter/pom.xml
index d3f072e4..8ec3adc6 100644
--- a/aws-ssm-parameter/pom.xml
+++ b/aws-ssm-parameter/pom.xml
@@ -41,7 +41,7 @@
software.amazon.cloudformation
aws-cloudformation-rpdk-java-plugin
- [2.0.0, 3.0.0)
+ [2.0.0, 2.1.0)
diff --git a/aws-ssm-patchbaseline/pom.xml b/aws-ssm-patchbaseline/pom.xml
index 267e3d17..33e066a4 100644
--- a/aws-ssm-patchbaseline/pom.xml
+++ b/aws-ssm-patchbaseline/pom.xml
@@ -29,7 +29,7 @@
software.amazon.cloudformation
aws-cloudformation-rpdk-java-plugin
- [2.0.0, 3.0.0)
+ [2.0.0, 2.1.0)
diff --git a/aws-ssm-resourcedatasync/pom.xml b/aws-ssm-resourcedatasync/pom.xml
index 58875cf0..237904e0 100644
--- a/aws-ssm-resourcedatasync/pom.xml
+++ b/aws-ssm-resourcedatasync/pom.xml
@@ -29,7 +29,7 @@
software.amazon.cloudformation
aws-cloudformation-rpdk-java-plugin
- [2.0.0, 3.0.0)
+ [2.0.0, 2.1.0)