From 4a42521f7edbb47c10ef6daf80b7d09be5bbf579 Mon Sep 17 00:00:00 2001 From: Jeff Registre <8570494+jregistr@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:32:35 -0500 Subject: [PATCH] Use common project constant and Tag Utils (#63) --- README.md | 9 + aws-qbusiness-application/pom.xml | 17 +- .../qbusiness/application/ClientBuilder.java | 4 +- .../qbusiness/application/CreateHandler.java | 3 +- .../qbusiness/application/TagHelper.java | 181 ------------------ .../qbusiness/application/Translator.java | 57 ++++-- .../qbusiness/application/UpdateHandler.java | 26 +-- .../amazon/qbusiness/application/Utils.java | 1 + .../application/UpdateHandlerTest.java | 6 +- aws-qbusiness-common/pom.xml | 34 ++++ .../qbusiness/common/SharedConstants.java | 11 ++ .../amazon/qbusiness/common/TagUtils.java | 106 ++++++++++ .../amazon/qbusiness/common/TagUtilsTest.java | 67 +++++++ aws-qbusiness-datasource/pom.xml | 6 + aws-qbusiness-index/pom.xml | 6 + aws-qbusiness-plugin/pom.xml | 6 + aws-qbusiness-retriever/pom.xml | 6 + aws-qbusiness-webexperience/pom.xml | 6 + pom.xml | 41 +++- 19 files changed, 365 insertions(+), 228 deletions(-) create mode 100644 aws-qbusiness-common/pom.xml create mode 100644 aws-qbusiness-common/src/main/java/software/amazon/qbusiness/common/SharedConstants.java create mode 100644 aws-qbusiness-common/src/main/java/software/amazon/qbusiness/common/TagUtils.java create mode 100644 aws-qbusiness-common/src/test/java/software/amazon/qbusiness/common/TagUtilsTest.java diff --git a/README.md b/README.md index 6baf154..408ca42 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,15 @@ This repository contains CloudFormation resource providers for the `AWS::QBusiness::*` namespace. +## Build and Test your changes +#### Compile and run the tests for all resources +```shell +mvn verify +``` +#### Compile and run tests for a specific resource +```shell +mvn verify -pl aws-qbusiness-application -am +``` ## Security diff --git a/aws-qbusiness-application/pom.xml b/aws-qbusiness-application/pom.xml index 524ff73..5d555fd 100644 --- a/aws-qbusiness-application/pom.xml +++ b/aws-qbusiness-application/pom.xml @@ -6,9 +6,9 @@ 4.0.0 software.amazon.qbusiness.application - aws-qbusiness-application-handler - aws-qbusiness-application-handler - 1.0-SNAPSHOT + aws-qbusiness-application + aws-qbusiness-application + 1.0 jar @@ -26,6 +26,17 @@ + + software.amazon.qbusiness.common + aws-qbusiness-handler-common + ${project.version} + + + + software.amazon.cloudformation + aws-cloudformation-rpdk-java-plugin + [2.0.0,3.0.0) + diff --git a/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/ClientBuilder.java b/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/ClientBuilder.java index 72b8b29..dfc7714 100644 --- a/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/ClientBuilder.java +++ b/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/ClientBuilder.java @@ -1,7 +1,7 @@ package software.amazon.qbusiness.application; -import static software.amazon.qbusiness.application.Constants.ENV_AWS_REGION; -import static software.amazon.qbusiness.application.Constants.SERVICE_NAME_LOWER; +import static software.amazon.qbusiness.common.SharedConstants.ENV_AWS_REGION; +import static software.amazon.qbusiness.common.SharedConstants.SERVICE_NAME_LOWER; import java.net.URI; diff --git a/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/CreateHandler.java b/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/CreateHandler.java index 5706cc1..d49dd60 100644 --- a/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/CreateHandler.java +++ b/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/CreateHandler.java @@ -14,7 +14,6 @@ import software.amazon.awssdk.services.qbusiness.model.CreateApplicationResponse; import software.amazon.awssdk.services.qbusiness.model.GetApplicationResponse; import software.amazon.awssdk.services.qbusiness.model.IdentityType; -import software.amazon.awssdk.services.qbusiness.model.SubscriptionType; import software.amazon.awssdk.services.qbusiness.model.UpdateApplicationRequest; import software.amazon.awssdk.services.qbusiness.model.UpdateApplicationResponse; import software.amazon.awssdk.utils.StringUtils; @@ -59,7 +58,7 @@ protected ProgressEvent handleRequest( return ProgressEvent.progress(request.getDesiredResourceState(), callbackContext) .then(progress -> proxy.initiate("AWS-QBusiness-Application::Create", proxyClient, progress.getResourceModel(), progress.getCallbackContext()) - .translateToServiceRequest(model -> Translator.translateToCreateRequest(request.getClientRequestToken(), model, request.getSystemTags())) + .translateToServiceRequest(model -> Translator.translateToCreateRequest(request, model)) .backoffDelay(backOffStrategy) .makeServiceCall((awsRequest, clientProxyClient) -> callCreateApplication(awsRequest, clientProxyClient, progress.getResourceModel())) .stabilize((awsReq, response, clientProxyClient, model, context) -> isStabilized(clientProxyClient, model, logger)) diff --git a/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/TagHelper.java b/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/TagHelper.java index 14d7378..b3b7fd3 100644 --- a/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/TagHelper.java +++ b/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/TagHelper.java @@ -18,187 +18,6 @@ import software.amazon.cloudformation.proxy.ResourceHandlerRequest; public class TagHelper { - /** - * convertToMap - *

- * Converts a collection of Tag objects to a tag-name -> tag-value map. - *

- * Note: Tag objects with null tag values will not be included in the output - * map. - * - * @param tags Collection of tags to convert - * @return Converted Map of tags - */ - public static Map convertToMap(final Collection tags) { - if (CollectionUtils.isEmpty(tags)) { - return Collections.emptyMap(); - } - return tags.stream() - .filter(tag -> tag.getValue() != null) - .collect(Collectors.toMap( - software.amazon.qbusiness.application.Tag::getKey, - software.amazon.qbusiness.application.Tag::getValue, - (oldValue, newValue) -> newValue) - ); - } - - /** - * convertToSet - *

- * Converts a tag map to a set of Tag objects. - *

- * Note: Like convertToMap, convertToSet filters out value-less tag entries. - * - * @param tagMap Map of tags to convert - * @return Set of Tag objects - */ - public static Set convertToSet(final Map tagMap) { - if (MapUtils.isEmpty(tagMap)) { - return Collections.emptySet(); - } - return tagMap.entrySet().stream() - .filter(tag -> tag.getValue() != null) - .map(tag -> Tag.builder() - .key(tag.getKey()) - .value(tag.getValue()) - .build()) - .collect(Collectors.toSet()); - } - - public static List cfnTagsFromServiceTags( - List serviceTags - ) { - return serviceTags.stream() - .map(serviceTag -> new software.amazon.qbusiness.application.Tag(serviceTag.key(), serviceTag.value())) - .toList(); - } - - public static List serviceTagsFromCfnTags( - Collection modelTags, - Map systemTags - ) { - if (modelTags == null && systemTags == null) { - return null; - } - - var tags = new ArrayList(); - if (modelTags != null) { - for (software.amazon.qbusiness.application.Tag modelTag : modelTags) { - tags.add( - Tag.builder() - .key(modelTag.getKey()) - .value(modelTag.getValue()) - .build() - ); - } - } - - if (systemTags != null) { - for (Map.Entry systemTag: systemTags.entrySet()) { - tags.add(Tag.builder() - .key(systemTag.getKey()) - .value(systemTag.getValue()).build() - ); - } - } - - return tags; - } - - /** - * shouldUpdateTags - *

- * Determines whether user defined tags have been changed during update. - */ - public final boolean shouldUpdateTags(final ResourceHandlerRequest handlerRequest) { - final Map previousTags = getPreviouslyAttachedTags(handlerRequest); - final Map desiredTags = getNewDesiredTags(handlerRequest); - return ObjectUtils.notEqual(previousTags, desiredTags); - } - - /** - * getPreviouslyAttachedTags - *

- * If stack tags and resource tags are not merged together in Configuration class, - * we will get previously attached system (with `aws:cloudformation` prefix) and user defined tags from - * handlerRequest.getPreviousSystemTags() (system tags), - * handlerRequest.getPreviousResourceTags() (stack tags), - * handlerRequest.getPreviousResourceState().getTags() (resource tags). - *

- * System tags are an optional feature. Merge them to your tags if you have enabled them for your resource. - * System tags can change on resource update if the resource is imported to the stack. - */ - public Map getPreviouslyAttachedTags(final ResourceHandlerRequest handlerRequest) { - final Map previousTags = new HashMap<>(); - - if (handlerRequest.getPreviousSystemTags() != null) { - previousTags.putAll(handlerRequest.getPreviousSystemTags()); - } - - // get previous stack level tags from handlerRequest - if (handlerRequest.getPreviousResourceTags() != null) { - previousTags.putAll(handlerRequest.getPreviousResourceTags()); - } - - if (handlerRequest.getPreviousResourceState() != null && handlerRequest.getPreviousResourceState().getTags() != null) { - previousTags.putAll(convertToMap(handlerRequest.getPreviousResourceState().getTags())); - } - return previousTags; - } - - /** - * getNewDesiredTags - *

- * If stack tags and resource tags are not merged together in Configuration class, - * we will get new desired system (with `aws:cloudformation` prefix) and user defined tags from - * handlerRequest.getSystemTags() (system tags), - * handlerRequest.getDesiredResourceTags() (stack tags), - * handlerRequest.getDesiredResourceState().getTags() (resource tags). - *

- * System tags are an optional feature. Merge them to your tags if you have enabled them for your resource. - * System tags can change on resource update if the resource is imported to the stack. - */ - public Map getNewDesiredTags(final ResourceHandlerRequest handlerRequest) { - final Map desiredTags = new HashMap<>(); - - if (handlerRequest.getSystemTags() != null) { - desiredTags.putAll(handlerRequest.getSystemTags()); - } - - // get desired stack level tags from handlerRequest - if (handlerRequest.getDesiredResourceTags() != null) { - desiredTags.putAll(handlerRequest.getDesiredResourceTags()); - } - - desiredTags.putAll(convertToMap(handlerRequest.getDesiredResourceState().getTags())); // if tags are not null - return desiredTags; - } - - /** - * generateTagsToAdd - *

- * Determines the tags the customer desired to define or redefine. - */ - public Map generateTagsToAdd(final Map previousTags, final Map desiredTags) { - return desiredTags.entrySet().stream() - .filter(e -> !previousTags.containsKey(e.getKey()) || !Objects.equals(previousTags.get(e.getKey()), e.getValue())) - .collect(Collectors.toMap( - Map.Entry::getKey, - Map.Entry::getValue)); - } - - /** - * getTagsToRemove - *

- * Determines the tags the customer desired to remove from the function. - */ - public Set generateTagsToRemove(final Map previousTags, final Map desiredTags) { - final Set desiredTagNames = desiredTags.keySet(); - - return previousTags.keySet().stream() - .filter(tagName -> !desiredTagNames.contains(tagName)) - .collect(Collectors.toSet()); - } } diff --git a/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/Translator.java b/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/Translator.java index 6a1ba3b..b583059 100644 --- a/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/Translator.java +++ b/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/Translator.java @@ -1,11 +1,14 @@ package software.amazon.qbusiness.application; import java.time.Instant; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import software.amazon.awssdk.services.qbusiness.model.CreateApplicationRequest; @@ -20,8 +23,8 @@ import software.amazon.awssdk.services.qbusiness.model.TagResourceRequest; import software.amazon.awssdk.services.qbusiness.model.UntagResourceRequest; import software.amazon.awssdk.services.qbusiness.model.UpdateApplicationRequest; -import software.amazon.cloudformation.exceptions.CfnInvalidRequestException; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; +import software.amazon.qbusiness.common.TagUtils; /** * This class is a centralized placeholder for @@ -35,15 +38,17 @@ public class Translator { /** * Request to create a resource * - * @param model resource model + * @param request The CFN request + * @param model resource model * @return awsRequest the aws service request to create a resource */ static CreateApplicationRequest translateToCreateRequest( - final String idempotentToken, - final ResourceModel model, - final Map systemTags) { + final ResourceHandlerRequest request, + final ResourceModel model + ) { + var merged = TagUtils.mergeCreateHandlerTagsToSdkTags(cfnTagsToGenericMap(model.getTags()), request); return CreateApplicationRequest.builder() - .clientToken(idempotentToken) + .clientToken(request.getClientRequestToken()) .displayName(model.getDisplayName()) .roleArn(model.getRoleArn()) .identityType(model.getIdentityType()) @@ -53,7 +58,7 @@ static CreateApplicationRequest translateToCreateRequest( .description(model.getDescription()) .encryptionConfiguration(toServiceEncryptionConfig(model.getEncryptionConfiguration())) .attachmentsConfiguration(toServiceAttachmentConfiguration(model.getAttachmentsConfiguration())) - .tags(TagHelper.serviceTagsFromCfnTags(model.getTags(), systemTags)) + .tags(merged) .qAppsConfiguration(toServiceQAppsConfiguration(model.getQAppsConfiguration())) .personalizationConfiguration(toServicePersonalizationConfiguration(model.getPersonalizationConfiguration())) .build(); @@ -217,7 +222,7 @@ static software.amazon.awssdk.services.qbusiness.model.PersonalizationConfigurat } static AutoSubscriptionConfiguration fromServiceAutoSubscriptionConfiguration( - software.amazon.awssdk.services.qbusiness.model.AutoSubscriptionConfiguration serviceConfig + software.amazon.awssdk.services.qbusiness.model.AutoSubscriptionConfiguration serviceConfig ) { if (serviceConfig == null) { return null; @@ -228,9 +233,9 @@ static AutoSubscriptionConfiguration fromServiceAutoSubscriptionConfiguration( } return AutoSubscriptionConfiguration.builder() - .autoSubscribe(serviceConfig.autoSubscribeAsString()) - .defaultSubscriptionType(serviceConfig.defaultSubscriptionTypeAsString()) - .build(); + .autoSubscribe(serviceConfig.autoSubscribeAsString()) + .defaultSubscriptionType(serviceConfig.defaultSubscriptionTypeAsString()) + .build(); } static software.amazon.awssdk.services.qbusiness.model.AutoSubscriptionConfiguration toServiceAutoSubscriptionConfiguration( @@ -252,10 +257,32 @@ static ResourceModel translateFromReadResponseWithTags(final ListTagsForResource } return model.toBuilder() - .tags(TagHelper.cfnTagsFromServiceTags(listTagsResponse.tags())) + .tags(cfnTagsFromServiceTags(listTagsResponse.tags())) .build(); } + static List cfnTagsFromServiceTags( + List serviceTags + ) { + return serviceTags.stream() + .map(serviceTag -> new software.amazon.qbusiness.application.Tag(serviceTag.key(), serviceTag.value())) + .toList(); + } + + public static Map cfnTagsToGenericMap(final Collection tags) { + if (CollectionUtils.isEmpty(tags)) { + return Map.of(); + } + + return tags.stream() + .filter(tag -> tag.getValue() != null) + .collect(Collectors.toMap( + software.amazon.qbusiness.application.Tag::getKey, + software.amazon.qbusiness.application.Tag::getValue, + (oldValue, newValue) -> newValue) + ); + } + /** * Request to delete a resource * @@ -290,9 +317,9 @@ static UpdateApplicationRequest translateToUpdateRequest(final ResourceModel mod static UpdateApplicationRequest translateToPostCreateUpdateRequest(final ResourceModel model) { return UpdateApplicationRequest.builder() - .applicationId(model.getApplicationId()) - .autoSubscriptionConfiguration(toServiceAutoSubscriptionConfiguration(model.getAutoSubscriptionConfiguration())) - .build(); + .applicationId(model.getApplicationId()) + .autoSubscriptionConfiguration(toServiceAutoSubscriptionConfiguration(model.getAutoSubscriptionConfiguration())) + .build(); } /** diff --git a/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/UpdateHandler.java b/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/UpdateHandler.java index d10012f..f71548f 100644 --- a/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/UpdateHandler.java +++ b/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/UpdateHandler.java @@ -23,6 +23,7 @@ import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; import software.amazon.cloudformation.proxy.delay.Constant; +import software.amazon.qbusiness.common.TagUtils; public class UpdateHandler extends BaseHandlerStd { @@ -32,16 +33,14 @@ public class UpdateHandler extends BaseHandlerStd { .build(); private final Constant backOffStrategy; - private final TagHelper tagHelper; private Logger logger; public UpdateHandler() { - this(DEFAULT_BACK_OFF_STRATEGY, new TagHelper()); + this(DEFAULT_BACK_OFF_STRATEGY); } - public UpdateHandler(Constant backOffStrategy, TagHelper tagHelper) { + public UpdateHandler(Constant backOffStrategy) { this.backOffStrategy = backOffStrategy; - this.tagHelper = tagHelper; } protected ProgressEvent handleRequest( @@ -72,14 +71,19 @@ protected ProgressEvent handleRequest( .progress() ) .then(progress -> { - if (!tagHelper.shouldUpdateTags(request)) { + var previousTags = TagUtils.getPreviouslyAttachedTags( + Translator.cfnTagsToGenericMap(request.getPreviousResourceState().getTags()), request); + var desiredTags = TagUtils.getNewDesiredTags( + Translator.cfnTagsToGenericMap(request.getDesiredResourceState().getTags()), request); + + if (!TagUtils.shouldUpdateTags(previousTags, desiredTags)) { // No updates to tags needed, return early with get application. Since ReadHandler will return Done, this will be the last step return readHandler(proxy, request, callbackContext, proxyClient, logger); } - Map tagsToAdd = tagHelper.generateTagsToAdd( - tagHelper.getPreviouslyAttachedTags(request), - tagHelper.getNewDesiredTags(request) + Map tagsToAdd = TagUtils.generateTagsToAdd( + previousTags, + desiredTags ); if (tagsToAdd == null || tagsToAdd.isEmpty()) { @@ -92,9 +96,9 @@ protected ProgressEvent handleRequest( .progress(); }) .then(progress -> { - Set tagsToRemove = tagHelper.generateTagsToRemove( - tagHelper.getPreviouslyAttachedTags(request), - tagHelper.getNewDesiredTags(request) + Set tagsToRemove = TagUtils.generateTagsToRemove( + TagUtils.getPreviouslyAttachedTags(Translator.cfnTagsToGenericMap(request.getPreviousResourceState().getTags()), request), + TagUtils.getNewDesiredTags(Translator.cfnTagsToGenericMap(request.getDesiredResourceState().getTags()), request) ); if (CollectionUtils.isEmpty(tagsToRemove)) { diff --git a/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/Utils.java b/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/Utils.java index a7249c4..3e1a503 100644 --- a/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/Utils.java +++ b/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/Utils.java @@ -22,6 +22,7 @@ public static String buildApplicationArn(final ResourceHandlerRequest) t -> t.applicationId().equals(APP_ID)) ); verify(sdkClient).listTagsForResource(any(ListTagsForResourceRequest.class)); - verify(tagHelper).shouldUpdateTags(any()); } private static Stream tagAndUntagArguments() { @@ -299,7 +296,6 @@ proxy, testRequest, new CallbackContext(), proxyClient, logger argThat((ArgumentMatcher) t -> t.applicationId().equals(APP_ID)) ); verify(sdkClient).listTagsForResource(any(ListTagsForResourceRequest.class)); - verify(tagHelper).shouldUpdateTags(any()); } @Test diff --git a/aws-qbusiness-common/pom.xml b/aws-qbusiness-common/pom.xml new file mode 100644 index 0000000..8425195 --- /dev/null +++ b/aws-qbusiness-common/pom.xml @@ -0,0 +1,34 @@ + + + 4.0.0 + + software.amazon.qbusiness.common + aws-qbusiness-handler-common + aws-qbusiness-common + 1.0 + jar + + + 17 + 17 + + + + + software.amazon.qbusiness + aws-qbusiness-cloudformation-handlers + 1.0-SNAPSHOT + + + + + + software.amazon.cloudformation + aws-cloudformation-rpdk-java-plugin + [2.0.0,3.0.0) + + + diff --git a/aws-qbusiness-common/src/main/java/software/amazon/qbusiness/common/SharedConstants.java b/aws-qbusiness-common/src/main/java/software/amazon/qbusiness/common/SharedConstants.java new file mode 100644 index 0000000..1025825 --- /dev/null +++ b/aws-qbusiness-common/src/main/java/software/amazon/qbusiness/common/SharedConstants.java @@ -0,0 +1,11 @@ +package software.amazon.qbusiness.common; + +import java.util.Locale; + +public final class SharedConstants { + public static final String SERVICE_NAME = "QBusiness"; + public static final String SERVICE_NAME_LOWER = SERVICE_NAME.toLowerCase(Locale.ENGLISH); + public static final String ENV_AWS_REGION = "AWS_REGION"; + + private SharedConstants(){} +} diff --git a/aws-qbusiness-common/src/main/java/software/amazon/qbusiness/common/TagUtils.java b/aws-qbusiness-common/src/main/java/software/amazon/qbusiness/common/TagUtils.java new file mode 100644 index 0000000..08fc69c --- /dev/null +++ b/aws-qbusiness-common/src/main/java/software/amazon/qbusiness/common/TagUtils.java @@ -0,0 +1,106 @@ +package software.amazon.qbusiness.common; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.commons.lang3.ObjectUtils; + +import software.amazon.awssdk.services.qbusiness.model.Tag; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; + +/** + * Class containing common handler tag operations. + */ +public final class TagUtils { + + private TagUtils() { + } + + public static List mergeCreateHandlerTagsToSdkTags( + final Map modelTags, + final ResourceHandlerRequest handlerRequest + ) { + return Stream.of(handlerRequest.getDesiredResourceTags(), modelTags, handlerRequest.getSystemTags()) + .filter(Objects::nonNull) + .flatMap(map -> map.entrySet().stream()) + .map(entry -> Tag.builder() + .key(entry.getKey()) + .value(entry.getValue()) + .build() + ) + .collect(Collectors.toList()); + } + + public static boolean shouldUpdateTags( + final Map allPreviousTags, + final Map allDesiredTags + ) { + return ObjectUtils.notEqual(allPreviousTags, allDesiredTags); + } + + public static Map getPreviouslyAttachedTags( + final Map previousModelTags, + final ResourceHandlerRequest handlerRequest + ) { + return mergedTags( + previousModelTags, + handlerRequest.getPreviousSystemTags(), + handlerRequest.getPreviousResourceTags() + ); + } + + public static Map getNewDesiredTags( + final Map modelTags, + final ResourceHandlerRequest handlerRequest + ) { + return mergedTags( + modelTags, + handlerRequest.getSystemTags(), + handlerRequest.getDesiredResourceTags() + ); + } + + public static Map generateTagsToAdd( + final Map previousTags, + final Map desiredTags + ) { + return desiredTags.entrySet().stream() + .filter(e -> !previousTags.containsKey(e.getKey()) || !Objects.equals(previousTags.get(e.getKey()), e.getValue())) + .collect(Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue)); + } + + public static Set generateTagsToRemove( + final Map previousTags, + final Map desiredTags + ) { + final Set desiredTagNames = desiredTags.keySet(); + + return previousTags.keySet().stream() + .filter(tagName -> !desiredTagNames.contains(tagName)) + .collect(Collectors.toSet()); + } + + private static Map mergedTags( + Map modelTags, + Map systemTags, + Map resourceTags + ) { + var combined = new HashMap(); + Stream.of( + Optional.ofNullable(modelTags), + Optional.ofNullable(systemTags), + Optional.ofNullable(resourceTags) + ).flatMap(Optional::stream) + .forEach(combined::putAll); + return combined; + } + +} diff --git a/aws-qbusiness-common/src/test/java/software/amazon/qbusiness/common/TagUtilsTest.java b/aws-qbusiness-common/src/test/java/software/amazon/qbusiness/common/TagUtilsTest.java new file mode 100644 index 0000000..8b1618c --- /dev/null +++ b/aws-qbusiness-common/src/test/java/software/amazon/qbusiness/common/TagUtilsTest.java @@ -0,0 +1,67 @@ +package software.amazon.qbusiness.common; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; + +import software.amazon.awssdk.services.qbusiness.model.Tag; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; + +class TagUtilsTest { + + private ResourceHandlerRequest testRequest; + private Map resourceModelTags; + + private AutoCloseable testAutoCloseable; + + @BeforeEach + void setUp() { + testAutoCloseable = MockitoAnnotations.openMocks(this); + resourceModelTags = Map.of( + "tagA", "ValueValue" + ); + + testRequest = ResourceHandlerRequest.builder() + .desiredResourceTags(Map.of( + "stackTagA", "valueA" + )) + .systemTags(Map.of( + "aws:cloudformation:stack-id", "superstack" + )) + .build(); + } + + @AfterEach + void tearDown() throws Exception { + testAutoCloseable.close(); + } + + @Test + void mergeCreateHandlerTagsToSdkTags() { + List merged = TagUtils.mergeCreateHandlerTagsToSdkTags(resourceModelTags, testRequest); + assertThat(merged).containsExactlyInAnyOrder( + Tag.builder().key("tagA").value("ValueValue").build(), + Tag.builder().key("stackTagA").value("valueA").build(), + Tag.builder().key("aws:cloudformation:stack-id").value("superstack").build() + ); + } + + @Test + void testItSkipsStackTags() { + testRequest = testRequest.toBuilder() + .desiredResourceTags(null) + .build(); + List merged = TagUtils.mergeCreateHandlerTagsToSdkTags(resourceModelTags, testRequest); + + assertThat(merged).containsExactlyInAnyOrder( + Tag.builder().key("tagA").value("ValueValue").build(), + Tag.builder().key("aws:cloudformation:stack-id").value("superstack").build() + ); + } +} diff --git a/aws-qbusiness-datasource/pom.xml b/aws-qbusiness-datasource/pom.xml index d5e1281..78f97e2 100644 --- a/aws-qbusiness-datasource/pom.xml +++ b/aws-qbusiness-datasource/pom.xml @@ -26,6 +26,12 @@ + + + software.amazon.cloudformation + aws-cloudformation-rpdk-java-plugin + [2.0.0,3.0.0) + diff --git a/aws-qbusiness-index/pom.xml b/aws-qbusiness-index/pom.xml index 48d9761..f6005e8 100644 --- a/aws-qbusiness-index/pom.xml +++ b/aws-qbusiness-index/pom.xml @@ -26,6 +26,12 @@ + + + software.amazon.cloudformation + aws-cloudformation-rpdk-java-plugin + [2.0.0,3.0.0) + diff --git a/aws-qbusiness-plugin/pom.xml b/aws-qbusiness-plugin/pom.xml index 91d37e4..930de7d 100644 --- a/aws-qbusiness-plugin/pom.xml +++ b/aws-qbusiness-plugin/pom.xml @@ -26,6 +26,12 @@ + + + software.amazon.cloudformation + aws-cloudformation-rpdk-java-plugin + [2.0.0,3.0.0) + diff --git a/aws-qbusiness-retriever/pom.xml b/aws-qbusiness-retriever/pom.xml index 9fec619..4d545a7 100644 --- a/aws-qbusiness-retriever/pom.xml +++ b/aws-qbusiness-retriever/pom.xml @@ -26,6 +26,12 @@ + + + software.amazon.cloudformation + aws-cloudformation-rpdk-java-plugin + [2.0.0,3.0.0) + diff --git a/aws-qbusiness-webexperience/pom.xml b/aws-qbusiness-webexperience/pom.xml index ab3f675..2f192b6 100644 --- a/aws-qbusiness-webexperience/pom.xml +++ b/aws-qbusiness-webexperience/pom.xml @@ -26,6 +26,12 @@ + + + software.amazon.cloudformation + aws-cloudformation-rpdk-java-plugin + [2.0.0,3.0.0) + diff --git a/pom.xml b/pom.xml index a4fb00f..59b12da 100644 --- a/pom.xml +++ b/pom.xml @@ -10,6 +10,7 @@ pom + aws-qbusiness-common aws-qbusiness-application aws-qbusiness-datasource aws-qbusiness-index @@ -18,13 +19,19 @@ aws-qbusiness-webexperience + + + + software.amazon.awssdk + bom + 2.29.11 + pom + import + + + + - - - software.amazon.cloudformation - aws-cloudformation-rpdk-java-plugin - [2.0.0,3.0.0) - org.projectlombok @@ -50,24 +57,40 @@ log4j-slf4j-impl 2.17.1 + software.amazon.awssdk qbusiness - 2.28.24 software.amazon.awssdk aws-core - 2.25.42 software.amazon.awssdk sdk-core - 2.25.42 + + + software.amazon.awssdk + core + 2.29.11 + pom + + + + software.amazon.awssdk + auth + + + + software.amazon.awssdk + utils + + org.assertj