Skip to content

Commit 93651ea

Browse files
authored
Fixing the tagging logic for namespace and workgroup continued by merging all stack, resource and system tags together. (#78)
* Fixing the tagging logic for namespace and workgroup continued by merging all stack, resource and system tags together. * Updating List to Set implementation for tags method in namespace and workgroup * Fixing the remaining tagging test_create_with_tag_denied CV2 test for workgroup
1 parent af543cc commit 93651ea

File tree

12 files changed

+368
-223
lines changed

12 files changed

+368
-223
lines changed

aws-redshiftserverless-namespace/src/main/java/software/amazon/redshiftserverless/namespace/CreateHandler.java

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
import java.util.Collections;
2626
import java.util.Optional;
27+
import java.util.List;
28+
import java.util.Set;
2729

2830

2931
public class CreateHandler extends BaseHandlerStd {
@@ -40,15 +42,22 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(
4042

4143
return ProgressEvent.progress(request.getDesiredResourceState(), callbackContext)
4244
.then(progress -> {
45+
Set<Tag>mergedTags = TagHelper.convertToTagSet(
46+
TagHelper.mergeTags(
47+
request,
48+
TagHelper.convertToMap(request.getDesiredResourceState().getTags()),
49+
request.getSystemTags(),
50+
request.getDesiredResourceTags()
51+
));
4352
return proxy.initiate("AWS-RedshiftServerless-Namespace::Create", proxyClient, progress.getResourceModel(), callbackContext)
44-
.translateToServiceRequest(Translator::translateToCreateRequest)
45-
.makeServiceCall(this::createNamespace)
46-
.stabilize(this::isNamespaceActive)
47-
.handleError(this::defaultErrorHandler)
48-
.done((_request, _response, _client, _model, _context) -> {
49-
callbackContext.setNamespaceArn(_response.namespace().namespaceArn());
50-
return ProgressEvent.progress(_model, callbackContext);
51-
});
53+
.translateToServiceRequest((model) -> Translator.translateToCreateRequest(model, mergedTags))
54+
.makeServiceCall(this::createNamespace)
55+
.stabilize(this::isNamespaceActive)
56+
.handleError(this::defaultErrorHandler)
57+
.done((_request, _response, _client, _model, _context) -> {
58+
callbackContext.setNamespaceArn(_response.namespace().namespaceArn());
59+
return ProgressEvent.progress(_model, callbackContext);
60+
});
5261
})
5362
.then(progress -> {
5463
if (progress.getResourceModel().getNamespaceResourcePolicy() != null) {

aws-redshiftserverless-namespace/src/main/java/software/amazon/redshiftserverless/namespace/TagHelper.java

Lines changed: 49 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import software.amazon.awssdk.awscore.AwsResponse;
1717
import software.amazon.awssdk.core.SdkClient;
1818
// TODO: Critical! Please replace the CloudFormation Tag model below with your service's own SDK Tag model
19-
import software.amazon.awssdk.services.cloudformation.model.Tag;
2019
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
2120
import software.amazon.cloudformation.proxy.Logger;
2221
import software.amazon.cloudformation.proxy.ProgressEvent;
@@ -40,11 +39,11 @@ public static Map<String, String> convertToMap(final Collection<Tag> tags) {
4039
return Collections.emptyMap();
4140
}
4241
return tags.stream()
43-
.filter(tag -> tag.value() != null)
44-
.collect(Collectors.toMap(
45-
Tag::key,
46-
Tag::value,
47-
(oldValue, newValue) -> newValue));
42+
.filter(tag -> tag.getValue() != null)
43+
.collect(Collectors.toMap(
44+
Tag::getKey,
45+
Tag::getValue,
46+
(oldValue, newValue) -> newValue));
4847
}
4948

5049
/**
@@ -57,17 +56,17 @@ public static Map<String, String> convertToMap(final Collection<Tag> tags) {
5756
* @param tagMap Map of tags to convert
5857
* @return Set of Tag objects
5958
*/
60-
public static Set<Tag> convertToSet(final Map<String, String> tagMap) {
59+
public static Set<Tag> convertToTagSet(final Map<String, String> tagMap) {
6160
if (MapUtils.isEmpty(tagMap)) {
6261
return Collections.emptySet();
6362
}
6463
return tagMap.entrySet().stream()
65-
.filter(tag -> tag.getValue() != null)
66-
.map(tag -> Tag.builder()
67-
.key(tag.getKey())
68-
.value(tag.getValue())
69-
.build())
70-
.collect(Collectors.toSet());
64+
.filter(tag -> tag.getValue() != null)
65+
.map(tag -> Tag.builder()
66+
.key(tag.getKey())
67+
.value(tag.getValue())
68+
.build())
69+
.collect(Collectors.toSet());
7170
}
7271

7372
/**
@@ -96,23 +95,45 @@ public final Map<String, String> generateTagsForCreate(final ResourceModel resou
9695
*
9796
* Determines whether user defined tags have been changed during update.
9897
*/
99-
public final boolean shouldUpdateTags(final ResourceModel resourceModel, final ResourceHandlerRequest<ResourceModel> handlerRequest) {
98+
public static boolean shouldUpdateTags(final ResourceHandlerRequest<ResourceModel> handlerRequest) {
10099
final Map<String, String> previousTags = getPreviouslyAttachedTags(handlerRequest);
101-
final Map<String, String> desiredTags = getNewDesiredTags(resourceModel, handlerRequest);
100+
final Map<String, String> desiredTags = getNewDesiredTags(handlerRequest);
102101
return ObjectUtils.notEqual(previousTags, desiredTags);
103102
}
104103

104+
/**
105+
* If stack tags and resource tags are not merged together in Configuration class,
106+
* we will get new user defined tags from both resource model and previous stack tags.
107+
*/
108+
public static Map<String, String> mergeTags(
109+
ResourceHandlerRequest<ResourceModel> request,
110+
Map<String, String> resourceTags,
111+
Map<String, String> systemTags,
112+
Map<String, String> stackTags) {
113+
final Map<String, String> tagMap = new HashMap<>();
114+
if (resourceTags != null) {
115+
tagMap.putAll(resourceTags);
116+
}
117+
if (systemTags != null) {
118+
tagMap.putAll(systemTags);
119+
}
120+
if (stackTags != null) {
121+
tagMap.putAll(stackTags);
122+
}
123+
return tagMap;
124+
}
125+
105126
/**
106127
* getPreviouslyAttachedTags
107128
*
108129
* If stack tags and resource tags are not merged together in Configuration class,
109130
* we will get previous attached user defined tags from both handlerRequest.getPreviousResourceTags (stack tags)
110131
* and handlerRequest.getPreviousResourceState (resource tags).
111132
*/
112-
public Map<String, String> getPreviouslyAttachedTags(final ResourceHandlerRequest<ResourceModel> handlerRequest) {
133+
public static Map<String, String> getPreviouslyAttachedTags(final ResourceHandlerRequest<ResourceModel> handlerRequest) {
113134
// get previous stack level tags from handlerRequest
114135
final Map<String, String> previousTags = handlerRequest.getPreviousResourceTags() != null ?
115-
handlerRequest.getPreviousResourceTags() : Collections.emptyMap();
136+
handlerRequest.getPreviousResourceTags() : Collections.emptyMap();
116137

117138
// TODO: get resource level tags from previous resource state based on your tag property name
118139
// TODO: previousTags.putAll(handlerRequest.getPreviousResourceState().getTags());
@@ -125,10 +146,10 @@ public Map<String, String> getPreviouslyAttachedTags(final ResourceHandlerReques
125146
* If stack tags and resource tags are not merged together in Configuration class,
126147
* we will get new user defined tags from both resource model and previous stack tags.
127148
*/
128-
public Map<String, String> getNewDesiredTags(final ResourceModel resourceModel, final ResourceHandlerRequest<ResourceModel> handlerRequest) {
149+
public static Map<String, String> getNewDesiredTags(final ResourceHandlerRequest<ResourceModel> handlerRequest) {
129150
// get new stack level tags from handlerRequest
130151
final Map<String, String> desiredTags = handlerRequest.getDesiredResourceTags() != null ?
131-
handlerRequest.getDesiredResourceTags() : Collections.emptyMap();
152+
handlerRequest.getDesiredResourceTags() : Collections.emptyMap();
132153

133154
// TODO: get resource level tags from resource model based on your tag property name
134155
// TODO: desiredTags.putAll(convertToMap(resourceModel.getTags()));
@@ -140,25 +161,25 @@ public Map<String, String> getNewDesiredTags(final ResourceModel resourceModel,
140161
*
141162
* Determines the tags the customer desired to define or redefine.
142163
*/
143-
public Map<String, String> generateTagsToAdd(final Map<String, String> previousTags, final Map<String, String> desiredTags) {
164+
public static Map<String, String> generateTagsToAdd(final Map<String, String> previousTags, final Map<String, String> desiredTags) {
144165
return desiredTags.entrySet().stream()
145-
.filter(e -> !previousTags.containsKey(e.getKey()) || !Objects.equals(previousTags.get(e.getKey()), e.getValue()))
146-
.collect(Collectors.toMap(
147-
Map.Entry::getKey,
148-
Map.Entry::getValue));
166+
.filter(e -> !previousTags.containsKey(e.getKey()) || !Objects.equals(previousTags.get(e.getKey()), e.getValue()))
167+
.collect(Collectors.toMap(
168+
Map.Entry::getKey,
169+
Map.Entry::getValue));
149170
}
150171

151172
/**
152173
* getTagsToRemove
153174
*
154175
* Determines the tags the customer desired to remove from the function.
155176
*/
156-
public Set<String> generateTagsToRemove(final Map<String, String> previousTags, final Map<String, String> desiredTags) {
177+
public static Set<String> generateTagsToRemove(final Map<String, String> previousTags, final Map<String, String> desiredTags) {
157178
final Set<String> desiredTagNames = desiredTags.keySet();
158179

159180
return previousTags.keySet().stream()
160-
.filter(tagName -> !desiredTagNames.contains(tagName))
161-
.collect(Collectors.toSet());
181+
.filter(tagName -> !desiredTagNames.contains(tagName))
182+
.collect(Collectors.toSet());
162183
}
163184

164185
/**
@@ -178,54 +199,5 @@ public Set<Tag> generateTagsToAdd(final Set<Tag> previousTags, final Set<Tag> de
178199
public Set<Tag> generateTagsToRemove(final Set<Tag> previousTags, final Set<Tag> desiredTags) {
179200
return Sets.difference(new HashSet<>(previousTags), new HashSet<>(desiredTags));
180201
}
181-
182-
183-
/**
184-
* tagResource during update
185-
*
186-
* Calls the service:TagResource API.
187-
*/
188-
private ProgressEvent<ResourceModel, CallbackContext>
189-
tagResource(final AmazonWebServicesClientProxy proxy, final ProxyClient<SdkClient> serviceClient, final ResourceModel resourceModel,
190-
final ResourceHandlerRequest<ResourceModel> handlerRequest, final CallbackContext callbackContext, final Map<String, String> addedTags, final Logger logger) {
191-
// TODO: add log for adding tags to resources during update
192-
// e.g. logger.log(String.format("[UPDATE][IN PROGRESS] Going to add tags for ... resource: %s with AccountId: %s",
193-
// resourceModel.getResourceName(), handlerRequest.getAwsAccountId()));
194-
195-
// TODO: change untagResource in the method to your service API according to your SDK
196-
return proxy.initiate("AWS-RedshiftServerless-Namespace::TagOps", serviceClient, resourceModel, callbackContext)
197-
.translateToServiceRequest(model ->
198-
Translator.tagResourceRequest(model, addedTags))
199-
.makeServiceCall((request, client) -> {
200-
return (AwsResponse) null;
201-
// TODO: replace the return null with your invoke log to call tagResource API to add tags
202-
// e.g. proxy.injectCredentialsAndInvokeV2(request, client.client()::tagResource))
203-
})
204-
.progress();
205-
}
206-
207-
/**
208-
* untagResource during update
209-
*
210-
* Calls the service:UntagResource API.
211-
*/
212-
private ProgressEvent<ResourceModel, CallbackContext>
213-
untagResource(final AmazonWebServicesClientProxy proxy, final ProxyClient<SdkClient> serviceClient, final ResourceModel resourceModel,
214-
final ResourceHandlerRequest<ResourceModel> handlerRequest, final CallbackContext callbackContext, final Set<String> removedTags, final Logger logger) {
215-
// TODO: add log for removing tags from resources during update
216-
// e.g. logger.log(String.format("[UPDATE][IN PROGRESS] Going to remove tags for ... resource: %s with AccountId: %s",
217-
// resourceModel.getResourceName(), handlerRequest.getAwsAccountId()));
218-
219-
// TODO: change untagResource in the method to your service API according to your SDK
220-
return proxy.initiate("AWS-RedshiftServerless-Namespace::TagOps", serviceClient, resourceModel, callbackContext)
221-
.translateToServiceRequest(model ->
222-
Translator.untagResourceRequest(model, removedTags))
223-
.makeServiceCall((request, client) -> {
224-
return (AwsResponse) null;
225-
// TODO: replace the return null with your invoke log to call untag API to remove tags
226-
// e.g. proxy.injectCredentialsAndInvokeV2(request, client.client()::untagResource)
227-
})
228-
.progress();
229-
}
230-
231202
}
203+

aws-redshiftserverless-namespace/src/main/java/software/amazon/redshiftserverless/namespace/Translator.java

Lines changed: 34 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import software.amazon.awssdk.services.redshiftserverless.model.TagResourceRequest;
2727
import software.amazon.awssdk.services.redshiftserverless.model.UntagResourceRequest;
2828
import software.amazon.cloudformation.proxy.Logger;
29+
import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
2930

3031
import java.io.IOException;
3132
import java.net.URLDecoder;
@@ -36,6 +37,10 @@
3637
import java.util.stream.Collectors;
3738
import java.util.stream.Stream;
3839

40+
import static software.amazon.redshiftserverless.namespace.TagHelper.convertToTagSet;
41+
import static software.amazon.redshiftserverless.namespace.TagHelper.generateTagsToAdd;
42+
import static software.amazon.redshiftserverless.namespace.TagHelper.generateTagsToRemove;
43+
3944
/**
4045
* This class is a centralized placeholder for
4146
* - api request construction
@@ -49,9 +54,9 @@ public class Translator {
4954
/**
5055
* Request to create a resource
5156
* @param model resource model
52-
* @return awsRequest the aws service request to create a resource
57+
* @return mergedTags - tags merging resource, stack and system tags.
5358
*/
54-
static CreateNamespaceRequest translateToCreateRequest(final ResourceModel model) {
59+
static CreateNamespaceRequest translateToCreateRequest(final ResourceModel model, final Set<Tag>mergedTags) {
5560
return CreateNamespaceRequest.builder()
5661
.namespaceName(model.getNamespaceName())
5762
.adminUsername(model.getAdminUsername())
@@ -61,7 +66,7 @@ static CreateNamespaceRequest translateToCreateRequest(final ResourceModel model
6166
.defaultIamRoleArn(model.getDefaultIamRoleArn())
6267
.iamRoles(model.getIamRoles())
6368
.logExportsWithStrings(model.getLogExports())
64-
.tags(translateToSdkTags(model.getTags()))
69+
.tags(translateToSdkTags(mergedTags))
6570
.manageAdminPassword(model.getManageAdminPassword())
6671
.adminPasswordSecretKmsKeyId(model.getAdminPasswordSecretKmsKeyId())
6772
.redshiftIdcApplicationArn(model.getRedshiftIdcApplicationArn())
@@ -207,6 +212,30 @@ static UpdateNamespaceRequest translateToUpdateRequest(final ResourceModel model
207212
.build();
208213
}
209214

215+
/**
216+
* Request to update properties of a previously created resource
217+
* @param previousTags Tags associated before Update operation
218+
* @param desiredTags Tags to be associated after Update operation
219+
* @param resourceArn ResourceArn to associate the tags
220+
* @return UpdateTagsRequest
221+
*/
222+
static UpdateTagsRequest translateToUpdateTagsRequest(Map<String, String>previousTags, Map<String, String>desiredTags, String resourceArn) {
223+
Set<Tag> toBeCreatedTags = convertToTagSet(generateTagsToAdd(previousTags, desiredTags));
224+
225+
Set<String> tagKeysToBeDeleted = generateTagsToRemove(previousTags, desiredTags);
226+
227+
return UpdateTagsRequest.builder()
228+
.createNewTagsRequest(TagResourceRequest.builder()
229+
.tags(translateToSdkTags(toBeCreatedTags))
230+
.resourceArn(resourceArn)
231+
.build())
232+
.deleteOldTagsRequest(UntagResourceRequest.builder()
233+
.tagKeys(tagKeysToBeDeleted)
234+
.resourceArn(resourceArn)
235+
.build())
236+
.build();
237+
}
238+
210239
/**
211240
* Request to list resources
212241
* @param nextToken token passed to the aws service list resources request
@@ -288,62 +317,15 @@ static ResourceModel translateFromReadTagsResponse(final ListTagsForResourceResp
288317
.build();
289318
}
290319

291-
/**
292-
* Request to update tags for a resource
293-
*
294-
* @param desiredResourceState the resource model request to update tags
295-
* @param currentResourceState the resource model request to delete tags
296-
* @return awsRequest the aws service request to update tags of a resource
297-
*/
298-
static UpdateTagsRequest translateToUpdateTagsRequest(final ResourceModel desiredResourceState,
299-
final ResourceModel currentResourceState) {
300-
String resourceArn = currentResourceState.getNamespace().getNamespaceArn();
301-
302-
// If desiredResourceState.getTags() is null, we should preserve existing tags
303-
// This ensures that when CloudFormation doesn't specify tags in the update,
304-
// we don't remove existing tags
305-
final List<Tag> effectiveDesiredTags;
306-
if (desiredResourceState.getTags() == null && currentResourceState.getTags() != null) {
307-
// Preserve existing tags by using them as the desired tags
308-
effectiveDesiredTags = currentResourceState.getTags();
309-
} else {
310-
effectiveDesiredTags = desiredResourceState.getTags();
311-
}
312-
313-
List<Tag> toBeCreatedTags = effectiveDesiredTags == null ? Collections.emptyList() : effectiveDesiredTags
314-
.stream()
315-
.filter(tag -> currentResourceState.getTags() == null || !currentResourceState.getTags().contains(tag))
316-
.collect(Collectors.toList());
317-
318-
List<Tag> toBeDeletedTags = currentResourceState.getTags() == null ? Collections.emptyList() : currentResourceState.getTags()
319-
.stream()
320-
.filter(tag -> effectiveDesiredTags == null || !effectiveDesiredTags.contains(tag))
321-
.collect(Collectors.toList());
322-
323-
return UpdateTagsRequest.builder()
324-
.createNewTagsRequest(TagResourceRequest.builder()
325-
.tags(translateToSdkTags(toBeCreatedTags))
326-
.resourceArn(resourceArn)
327-
.build())
328-
.deleteOldTagsRequest(UntagResourceRequest.builder()
329-
.tagKeys(toBeDeletedTags
330-
.stream()
331-
.map(Tag::getKey)
332-
.collect(Collectors.toList()))
333-
.resourceArn(resourceArn)
334-
.build())
335-
.build();
336-
}
337-
338320
private static software.amazon.awssdk.services.redshiftserverless.model.Tag translateToSdkTag(Tag tag) {
339321
return GSON.fromJson(GSON.toJson(tag), software.amazon.awssdk.services.redshiftserverless.model.Tag.class);
340322
}
341323

342-
private static List<software.amazon.awssdk.services.redshiftserverless.model.Tag> translateToSdkTags(final List<Tag> tags) {
324+
private static Set<software.amazon.awssdk.services.redshiftserverless.model.Tag> translateToSdkTags(final Set<Tag> tags) {
343325
return tags == null ? null : tags
344326
.stream()
345327
.map(Translator::translateToSdkTag)
346-
.collect(Collectors.toList());
328+
.collect(Collectors.toSet());
347329
}
348330

349331
private static Tag translateToModelTag(software.amazon.awssdk.services.redshiftserverless.model.Tag tag) {

0 commit comments

Comments
 (0)