Skip to content

Commit 850d993

Browse files
authored
Add Tag helper class into code-gen package (#372)
* Add Tag helper class into code-gen package * Fix TagHelper class name and package info * Fix compiling issue for generated packages * Fix tag update checking logic and rename related method * Fix typo for generateTagsForCreate method * Rename currentTags with previousTags and revise based on comments * Fix null value issues for stack level tags
1 parent c454174 commit 850d993

File tree

4 files changed

+268
-4
lines changed

4 files changed

+268
-4
lines changed

.pre-commit-config.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
repos:
22
- repo: https://github.com/pre-commit/mirrors-isort
3-
rev: v4.3.17
3+
rev: v5.9.3
44
hooks:
55
- id: isort
66
# language_version: python3.6
77
- repo: https://github.com/ambv/black
8-
rev: stable
8+
rev: 21.7b0
99
hooks:
1010
- id: black
1111
# language_version: python3.6
@@ -35,13 +35,13 @@ repos:
3535
- id: check-merge-conflict
3636
- id: check-yaml
3737
- repo: https://github.com/pre-commit/pygrep-hooks
38-
rev: v1.3.0
38+
rev: v1.9.0
3939
hooks:
4040
- id: python-check-blanket-noqa
4141
- id: python-check-mock-methods
4242
- id: python-no-log-warn
4343
- repo: https://github.com/PyCQA/bandit
44-
rev: f5a6f0ca62 # TODO: update once a release > 1.5.1 hits with this change in
44+
rev: 1.7.0 # TODO: update once a release > 1.5.1 hits with this change in
4545
hooks:
4646
- id: bandit
4747
files: "^python/"

python/rpdk/java/codegen.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,13 @@ def init_guided_aws(self, project, src, tst):
278278
self._writing_component(project, src, entity="Translator.java")
279279
self._writing_component(project, src, entity="ClientBuilder.java")
280280
self._writing_component(project, src, entity="BaseHandlerStd.java")
281+
self._writing_component(
282+
project,
283+
src,
284+
entity="TagHelper.java",
285+
operation="TagOps",
286+
call_graph=project.type_name.replace("::", "-"),
287+
)
281288
self._writing_component(project, tst, entity="AbstractTestBase.java")
282289

283290
@logdebug
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
package {{ package_name }};
2+
3+
import java.util.Collection;
4+
import java.util.Collections;
5+
import java.util.HashMap;
6+
import java.util.HashSet;
7+
import java.util.Map;
8+
import java.util.Objects;
9+
import java.util.Set;
10+
import java.util.stream.Collectors;
11+
12+
import com.google.common.collect.Sets;
13+
import org.apache.commons.collections.CollectionUtils;
14+
import org.apache.commons.collections.MapUtils;
15+
import org.apache.commons.lang3.ObjectUtils;
16+
import software.amazon.awssdk.awscore.AwsResponse;
17+
import software.amazon.awssdk.core.SdkClient;
18+
// 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;
20+
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
21+
import software.amazon.cloudformation.proxy.Logger;
22+
import software.amazon.cloudformation.proxy.ProgressEvent;
23+
import software.amazon.cloudformation.proxy.ProxyClient;
24+
import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
25+
26+
public class TagHelper {
27+
/**
28+
* convertToMap
29+
*
30+
* Converts a collection of Tag objects to a tag-name -> tag-value map.
31+
*
32+
* Note: Tag objects with null tag values will not be included in the output
33+
* map.
34+
*
35+
* @param tags Collection of tags to convert
36+
* @return Converted Map of tags
37+
*/
38+
public static Map<String, String> convertToMap(final Collection<Tag> tags) {
39+
if (CollectionUtils.isEmpty(tags)) {
40+
return Collections.emptyMap();
41+
}
42+
return tags.stream()
43+
.filter(tag -> tag.value() != null)
44+
.collect(Collectors.toMap(
45+
Tag::key,
46+
Tag::value,
47+
(oldValue, newValue) -> newValue));
48+
}
49+
50+
/**
51+
* convertToSet
52+
*
53+
* Converts a tag map to a set of Tag objects.
54+
*
55+
* Note: Like convertToMap, convertToSet filters out value-less tag entries.
56+
*
57+
* @param tagMap Map of tags to convert
58+
* @return Set of Tag objects
59+
*/
60+
public static Set<Tag> convertToSet(final Map<String, String> tagMap) {
61+
if (MapUtils.isEmpty(tagMap)) {
62+
return Collections.emptySet();
63+
}
64+
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());
71+
}
72+
73+
/**
74+
* generateTagsForCreate
75+
*
76+
* Generate tags to put into resource creation request.
77+
* This includes user defined tags and system tags as well.
78+
*/
79+
public final Map<String, String> generateTagsForCreate(final ResourceModel resourceModel, final ResourceHandlerRequest<ResourceModel> handlerRequest) {
80+
final Map<String, String> tagMap = new HashMap<>();
81+
82+
// merge system tags with desired resource tags if your service supports CloudFormation system tags
83+
tagMap.putAll(handlerRequest.getSystemTags());
84+
85+
if (handlerRequest.getDesiredResourceTags() != null) {
86+
tagMap.putAll(handlerRequest.getDesiredResourceTags());
87+
}
88+
89+
// TODO: get tags from resource model based on your tag property name
90+
// TODO: tagMap.putAll(convertToMap(resourceModel.getTags()));
91+
return Collections.unmodifiableMap(tagMap);
92+
}
93+
94+
/**
95+
* shouldUpdateTags
96+
*
97+
* Determines whether user defined tags have been changed during update.
98+
*/
99+
public final boolean shouldUpdateTags(final ResourceModel resourceModel, final ResourceHandlerRequest<ResourceModel> handlerRequest) {
100+
final Map<String, String> previousTags = getPreviouslyAttachedTags(handlerRequest);
101+
final Map<String, String> desiredTags = getNewDesiredTags(resourceModel, handlerRequest);
102+
return ObjectUtils.notEqual(previousTags, desiredTags);
103+
}
104+
105+
/**
106+
* getPreviouslyAttachedTags
107+
*
108+
* If stack tags and resource tags are not merged together in Configuration class,
109+
* we will get previous attached user defined tags from both handlerRequest.getPreviousResourceTags (stack tags)
110+
* and handlerRequest.getPreviousResourceState (resource tags).
111+
*/
112+
public Map<String, String> getPreviouslyAttachedTags(final ResourceHandlerRequest<ResourceModel> handlerRequest) {
113+
// get previous stack level tags from handlerRequest
114+
final Map<String, String> previousTags = handlerRequest.getPreviousResourceTags() != null ?
115+
handlerRequest.getPreviousResourceTags() : Collections.emptyMap();
116+
117+
// TODO: get resource level tags from previous resource state based on your tag property name
118+
// TODO: previousTags.putAll(handlerRequest.getPreviousResourceState().getTags());
119+
return previousTags;
120+
}
121+
122+
/**
123+
* getNewDesiredTags
124+
*
125+
* If stack tags and resource tags are not merged together in Configuration class,
126+
* we will get new user defined tags from both resource model and previous stack tags.
127+
*/
128+
public Map<String, String> getNewDesiredTags(final ResourceModel resourceModel, final ResourceHandlerRequest<ResourceModel> handlerRequest) {
129+
// get new stack level tags from handlerRequest
130+
final Map<String, String> desiredTags = handlerRequest.getDesiredResourceTags() != null ?
131+
handlerRequest.getDesiredResourceTags() : Collections.emptyMap();
132+
133+
// TODO: get resource level tags from resource model based on your tag property name
134+
// TODO: desiredTags.putAll(convertToMap(resourceModel.getTags()));
135+
return desiredTags;
136+
}
137+
138+
/**
139+
* generateTagsToAdd
140+
*
141+
* Determines the tags the customer desired to define or redefine.
142+
*/
143+
public Map<String, String> generateTagsToAdd(final Map<String, String> previousTags, final Map<String, String> desiredTags) {
144+
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));
149+
}
150+
151+
/**
152+
* getTagsToRemove
153+
*
154+
* Determines the tags the customer desired to remove from the function.
155+
*/
156+
public Set<String> generateTagsToRemove(final Map<String, String> previousTags, final Map<String, String> desiredTags) {
157+
final Set<String> desiredTagNames = desiredTags.keySet();
158+
159+
return previousTags.keySet().stream()
160+
.filter(tagName -> !desiredTagNames.contains(tagName))
161+
.collect(Collectors.toSet());
162+
}
163+
164+
/**
165+
* generateTagsToAdd
166+
*
167+
* Determines the tags the customer desired to define or redefine.
168+
*/
169+
public Set<Tag> generateTagsToAdd(final Set<Tag> previousTags, final Set<Tag> desiredTags) {
170+
return Sets.difference(new HashSet<>(desiredTags), new HashSet<>(previousTags));
171+
}
172+
173+
/**
174+
* getTagsToRemove
175+
*
176+
* Determines the tags the customer desired to remove from the function.
177+
*/
178+
public Set<Tag> generateTagsToRemove(final Set<Tag> previousTags, final Set<Tag> desiredTags) {
179+
return Sets.difference(new HashSet<>(previousTags), new HashSet<>(desiredTags));
180+
}
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("{{ call_graph }}::{{ operation }}", 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("{{ call_graph }}::{{ operation }}", 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+
231+
}

python/rpdk/java/templates/init/guided_aws/Translator.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
import java.util.Collection;
88
import java.util.List;
9+
import java.util.Map;
910
import java.util.Optional;
11+
import java.util.Set;
1012
import java.util.stream.Collectors;
1113
import java.util.stream.Stream;
1214

@@ -121,4 +123,28 @@ private static <T> Stream<T> streamOfOrEmpty(final Collection<T> collection) {
121123
.map(Collection::stream)
122124
.orElseGet(Stream::empty);
123125
}
126+
127+
/**
128+
* Request to add tags to a resource
129+
* @param model resource model
130+
* @return awsRequest the aws service request to create a resource
131+
*/
132+
static AwsRequest tagResourceRequest(final ResourceModel model, final Map<String, String> addedTags) {
133+
final AwsRequest awsRequest = null;
134+
// TODO: construct a request
135+
// e.g. https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-logs/blob/2077c92299aeb9a68ae8f4418b5e932b12a8b186/aws-logs-loggroup/src/main/java/com/aws/logs/loggroup/Translator.java#L39-L43
136+
return awsRequest;
137+
}
138+
139+
/**
140+
* Request to add tags to a resource
141+
* @param model resource model
142+
* @return awsRequest the aws service request to create a resource
143+
*/
144+
static AwsRequest untagResourceRequest(final ResourceModel model, final Set<String> removedTags) {
145+
final AwsRequest awsRequest = null;
146+
// TODO: construct a request
147+
// e.g. https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-logs/blob/2077c92299aeb9a68ae8f4418b5e932b12a8b186/aws-logs-loggroup/src/main/java/com/aws/logs/loggroup/Translator.java#L39-L43
148+
return awsRequest;
149+
}
124150
}

0 commit comments

Comments
 (0)