Skip to content

Commit

Permalink
Support for Fifo Topics by adding FifoTopic and ContentBasedDeduplica… (
Browse files Browse the repository at this point in the history
#25)

* Support for Fifo Topics by adding FifoTopic and ContentBasedDeduplication attributes

* fix documentation
  • Loading branch information
yangmarc authored Dec 10, 2020
1 parent 2c15451 commit a53dca2
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 44 deletions.
22 changes: 14 additions & 8 deletions aws-sns-topic/aws-sns-topic.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"type": "string"
},
"KmsMasterKeyId": {
"description": "The ID of an AWS-managed customer master key (CMK) for Amazon SNS or a custom CMK.",
"description": "The ID of an AWS-managed customer master key (CMK) for Amazon SNS or a custom CMK. For more information, see Key Terms. For more examples, see KeyId in the AWS Key Management Service API Reference.\n\nThis property applies only to [server-side-encryption](https://docs.aws.amazon.com/sns/latest/dg/sns-server-side-encryption.html).",
"type": "string"
},
"Subscription": {
Expand All @@ -21,8 +21,15 @@
"$ref": "#/definitions/Subscription"
}
},
"FifoTopic": {
"description": "Set to true to create a FIFO topic.",
"type": "boolean"
},
"ContentBasedDeduplication": {
"description": "Enables content-based deduplication for FIFO topics. By default, ContentBasedDeduplication is set to false. If you create a FIFO topic and this attribute is false, you must specify a value for the MessageDeduplicationId parameter for the Publish action.\n\nWhen you set ContentBasedDeduplication to true, Amazon SNS uses a SHA-256 hash to generate the MessageDeduplicationId using the body of the message (but not the attributes of the message).\n\n(Optional) To override the generated value, you can specify a value for the the MessageDeduplicationId parameter for the Publish action.\n\n",
"type": "boolean"
},
"Tags": {
"description": "The list of tags to add to a new topic.",
"type": "array",
"uniqueItems": true,
"insertionOrder": false,
Expand All @@ -31,11 +38,10 @@
}
},
"TopicName": {
"description": "The name of the topic you want to create. Topic names must include only uppercase and lowercase ASCII letters, numbers, underscores, and hyphens, and must be between 1 and 256 characters long.",
"description": "The name of the topic you want to create. Topic names must include only uppercase and lowercase ASCII letters, numbers, underscores, and hyphens, and must be between 1 and 256 characters long. FIFO topic names must end with .fifo.\n\nIf you don't specify a name, AWS CloudFormation generates a unique physical ID and uses that ID for the topic name. For more information, see Name Type.",
"type": "string",
"minLength": 1,
"maxLength": 256,
"pattern": "^[a-zA-Z0-9_-]{1,256}$"
"maxLength": 256
},
"Id": {
"type": "string"
Expand Down Expand Up @@ -81,8 +87,7 @@
"sms",
"sqs",
"application",
"lambda",
"firehose"
"lambda"
]
}
},
Expand All @@ -93,7 +98,8 @@
}
},
"createOnlyProperties": [
"/properties/TopicName"
"/properties/TopicName",
"/properties/FifoTopic"
],
"primaryIdentifier": [
"/properties/Id"
Expand Down
44 changes: 37 additions & 7 deletions aws-sns-topic/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ To declare this entity in your AWS CloudFormation template, use the following sy
"Properties" : {
"<a href="#displayname" title="DisplayName">DisplayName</a>" : <i>String</i>,
"<a href="#kmsmasterkeyid" title="KmsMasterKeyId">KmsMasterKeyId</a>" : <i>String</i>,
"<a href="#subscription" title="Subscription">Subscription</a>" : <i>[ [ <a href="subscription.md">Subscription</a>, ... ], ... ]</i>,
"<a href="#subscription" title="Subscription">Subscription</a>" : <i>[ <a href="subscription.md">Subscription</a>, ... ]</i>,
"<a href="#fifotopic" title="FifoTopic">FifoTopic</a>" : <i>Boolean</i>,
"<a href="#contentbaseddeduplication" title="ContentBasedDeduplication">ContentBasedDeduplication</a>" : <i>Boolean</i>,
"<a href="#tags" title="Tags">Tags</a>" : <i>[ <a href="tag.md">Tag</a>, ... ]</i>,
"<a href="#topicname" title="TopicName">TopicName</a>" : <i>String</i>,
}
Expand All @@ -30,6 +32,8 @@ Properties:
<a href="#kmsmasterkeyid" title="KmsMasterKeyId">KmsMasterKeyId</a>: <i>String</i>
<a href="#subscription" title="Subscription">Subscription</a>: <i>
- <a href="subscription.md">Subscription</a></i>
<a href="#fifotopic" title="FifoTopic">FifoTopic</a>: <i>Boolean</i>
<a href="#contentbaseddeduplication" title="ContentBasedDeduplication">ContentBasedDeduplication</a>: <i>Boolean</i>
<a href="#tags" title="Tags">Tags</a>: <i>
- <a href="tag.md">Tag</a></i>
<a href="#topicname" title="TopicName">TopicName</a>: <i>String</i>
Expand All @@ -49,7 +53,9 @@ _Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormati

#### KmsMasterKeyId

The ID of an AWS-managed customer master key (CMK) for Amazon SNS or a custom CMK.
The ID of an AWS-managed customer master key (CMK) for Amazon SNS or a custom CMK. For more information, see Key Terms. For more examples, see KeyId in the AWS Key Management Service API Reference.

This property applies only to [server-side-encryption](https://docs.aws.amazon.com/sns/latest/dg/sns-server-side-encryption.html).

_Required_: No

Expand All @@ -67,9 +73,33 @@ _Type_: List of <a href="subscription.md">Subscription</a>

_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)

#### Tags
#### FifoTopic

Set to true to create a FIFO topic.

_Required_: No

_Type_: Boolean

_Update requires_: [Replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-replacement)

#### ContentBasedDeduplication

Enables content-based deduplication for FIFO topics. By default, ContentBasedDeduplication is set to false. If you create a FIFO topic and this attribute is false, you must specify a value for the MessageDeduplicationId parameter for the Publish action.

When you set ContentBasedDeduplication to true, Amazon SNS uses a SHA-256 hash to generate the MessageDeduplicationId using the body of the message (but not the attributes of the message).

(Optional) To override the generated value, you can specify a value for the the MessageDeduplicationId parameter for the Publish action.


The list of tags to add to a new topic.

_Required_: No

_Type_: Boolean

_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)

#### Tags

_Required_: No

Expand All @@ -79,7 +109,9 @@ _Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormati

#### TopicName

The name of the topic
The name of the topic you want to create. Topic names must include only uppercase and lowercase ASCII letters, numbers, underscores, and hyphens, and must be between 1 and 256 characters long. FIFO topic names must end with .fifo.

If you don't specify a name, AWS CloudFormation generates a unique physical ID and uses that ID for the topic name. For more information, see Name Type.

_Required_: No

Expand All @@ -89,8 +121,6 @@ _Minimum_: <code>1</code>

_Maximum_: <code>256</code>

_Pattern_: <code>^[a-zA-Z0-9_-]{1,256}$</code>

_Update requires_: [Replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-replacement)

## Return Values
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
public abstract class BaseHandlerStd extends BaseHandler<CallbackContext> {

public static final int TOPIC_NAME_MAX_LENGTH = 256;
public static final String FIFO_TOPIC_EXTENSION = ".fifo";

@Override
public final ProgressEvent<ResourceModel, CallbackContext> handleRequest(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(
}

if (StringUtils.isBlank(model.getTopicName())) {
model.setTopicName(IdentifierUtils.generateResourceIdentifier(request.getLogicalResourceIdentifier(), request.getClientRequestToken(), TOPIC_NAME_MAX_LENGTH).toLowerCase());
String randomTopicName = IdentifierUtils.generateResourceIdentifier(request.getLogicalResourceIdentifier(), request.getClientRequestToken(), TOPIC_NAME_MAX_LENGTH);
if (Boolean.TRUE.equals(model.getFifoTopic())) {
randomTopicName = IdentifierUtils.generateResourceIdentifier(request.getLogicalResourceIdentifier(), request.getClientRequestToken(), TOPIC_NAME_MAX_LENGTH - FIFO_TOPIC_EXTENSION.length());
randomTopicName += FIFO_TOPIC_EXTENSION;
}
model.setTopicName(randomTopicName.toLowerCase());
}

return ProgressEvent.progress(request.getDesiredResourceState(), callbackContext)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
public enum TopicAttributeName {
DISPLAY_NAME("DisplayName"),
TOPIC_ARN("TopicArn"),
KMS_MASTER_KEY_ID("KmsMasterKeyId");
KMS_MASTER_KEY_ID("KmsMasterKeyId"),
CONTENT_BASED_DEDUPLICATION("ContentBasedDeduplication"),
FIFO_TOPIC("FifoTopic");

private final String value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ public class Translator {
static CreateTopicRequest translateToCreateTopicRequest(final ResourceModel model, Map<String, String> desiredResourceTags) {
Map<String, String> attributes = new HashMap<>();

if(model.getDisplayName() != null)
attributes.put(TopicAttributeName.DISPLAY_NAME.toString(), model.getDisplayName());
if(model.getKmsMasterKeyId() != null)
attributes.put(TopicAttributeName.KMS_MASTER_KEY_ID.toString(), model.getKmsMasterKeyId());
putIfNotNull(attributes, TopicAttributeName.DISPLAY_NAME.toString(), model.getDisplayName());
putIfNotNull(attributes, TopicAttributeName.KMS_MASTER_KEY_ID.toString(), model.getKmsMasterKeyId());
putIfNotNull(attributes, TopicAttributeName.FIFO_TOPIC.toString(), model.getFifoTopic());
putIfNotNull(attributes, TopicAttributeName.CONTENT_BASED_DEDUPLICATION.toString(), model.getContentBasedDeduplication());

final Set<Tag> tags = convertResourceTagsToSet(desiredResourceTags);
return CreateTopicRequest.builder()
Expand Down Expand Up @@ -189,4 +189,10 @@ static Set<Tag> convertResourceTagsToSet(Map<String, String> resourceTags) {
}
return tags;
}

private static <V> void putIfNotNull(Map<String, String> attributeMap, String key, V value) {
if (value != null) {
attributeMap.put(key, value.toString());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,17 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(
}
return progress;
})
.then(progress -> {
String previousVal = previousModel.getContentBasedDeduplication() != null ? previousModel.getContentBasedDeduplication().toString() : null;
String desiredVal = model.getContentBasedDeduplication() != null ? model.getContentBasedDeduplication().toString() : null;
if (!StringUtils.equals(previousVal, desiredVal)) {
return proxy.initiate("AWS-SNS-Topic::Update::ContentBasedDeduplication", proxyClient, model, callbackContext)
.translateToServiceRequest(m -> Translator.translateToSetAttributesRequest(m.getId(), TopicAttributeName.CONTENT_BASED_DEDUPLICATION, desiredVal))
.makeServiceCall((setTopicAttributesRequest, client) -> proxy.injectCredentialsAndInvokeV2(setTopicAttributesRequest, client.client()::setTopicAttributes))
.progress();
}
return progress;
})
.then(progress ->
proxy.initiate("AWS-SNS-Topic::Update::ListSubscriptionArn", proxyClient, model, callbackContext)
.translateToServiceRequest(Translator::translateToListSubscriptionByTopic)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,60 @@ public void handleRequest_SimpleSuccess() {
verify(proxyClient.client()).listTagsForResource(any(ListTagsForResourceRequest.class));
}

@Test
public void handleRequest_SimpleSuccess_FifoTopic() {
String fifoTopicName = "sns-topic-name.fifo";
String fifoTopicArn = "arn:aws:sns:us-east-1:123456789012:" + fifoTopicName;

final ResourceModel model = ResourceModel.builder()
.fifoTopic(true)
.contentBasedDeduplication(true)
.build();

Map<String, String> attributes = new HashMap<>();
attributes.put(TopicAttributeName.TOPIC_ARN.toString(), fifoTopicArn);
final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder()
.attributes(attributes)
.build();

when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class)))
.thenThrow(NotFoundException.builder().message("no topic found").build())
.thenReturn(getTopicAttributesResponse);

final CreateTopicResponse createTopicResponse = CreateTopicResponse.builder()
.topicArn(fifoTopicArn)
.build();
when(proxyClient.client().createTopic(any(CreateTopicRequest.class))).thenReturn(createTopicResponse);

final ListSubscriptionsByTopicResponse listSubscriptionsByTopicResponse = ListSubscriptionsByTopicResponse.builder().build();
when(proxyClient.client().listSubscriptionsByTopic(any(ListSubscriptionsByTopicRequest.class))).thenReturn(listSubscriptionsByTopicResponse);
final ListTagsForResourceResponse listTagsForStreamResponse = ListTagsForResourceResponse.builder().build();
when(proxyClient.client().listTagsForResource(any(ListTagsForResourceRequest.class))).thenReturn(listTagsForStreamResponse);

final ResourceHandlerRequest<ResourceModel> request = ResourceHandlerRequest.<ResourceModel>builder()
.desiredResourceState(model)
.logicalResourceIdentifier("SnsTopic")
.clientRequestToken("dummy-token")
.region("us-east-1")
.awsAccountId("1234567890")
.build();
final ProgressEvent<ResourceModel, CallbackContext> 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();
assertThat(response.getResourceModel().getTopicName()).isEqualTo(fifoTopicName);
assertThat(response.getResourceModel().getId()).isEqualTo(fifoTopicArn);

verify(proxyClient.client()).createTopic(any(CreateTopicRequest.class));
verify(proxyClient.client(), times(2)).getTopicAttributes(any(GetTopicAttributesRequest.class));
verify(proxyClient.client()).listSubscriptionsByTopic(any(ListSubscriptionsByTopicRequest.class));
verify(proxyClient.client()).listTagsForResource(any(ListTagsForResourceRequest.class));
}

@Test
public void handleRequest_SimpleSuccess_WithAttributes() {
final ResourceModel model = ResourceModel.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@

import software.amazon.awssdk.services.sns.SnsClient;
import software.amazon.awssdk.services.sns.model.*;
import software.amazon.cloudformation.exceptions.CfnGeneralServiceException;
import software.amazon.cloudformation.exceptions.CfnNotFoundException;
import software.amazon.cloudformation.exceptions.CfnNotUpdatableException;
import software.amazon.cloudformation.exceptions.CfnServiceInternalErrorException;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
import software.amazon.cloudformation.proxy.OperationStatus;
import software.amazon.cloudformation.proxy.ProgressEvent;
Expand Down
Loading

0 comments on commit a53dca2

Please sign in to comment.