Skip to content

Commit

Permalink
Add Tag helper class into code-gen package (#372)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Yunhao-Jiao committed Aug 25, 2021
1 parent c454174 commit 850d993
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 4 deletions.
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
repos:
- repo: https://github.com/pre-commit/mirrors-isort
rev: v4.3.17
rev: v5.9.3
hooks:
- id: isort
# language_version: python3.6
- repo: https://github.com/ambv/black
rev: stable
rev: 21.7b0
hooks:
- id: black
# language_version: python3.6
Expand Down Expand Up @@ -35,13 +35,13 @@ repos:
- id: check-merge-conflict
- id: check-yaml
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.3.0
rev: v1.9.0
hooks:
- id: python-check-blanket-noqa
- id: python-check-mock-methods
- id: python-no-log-warn
- repo: https://github.com/PyCQA/bandit
rev: f5a6f0ca62 # TODO: update once a release > 1.5.1 hits with this change in
rev: 1.7.0 # TODO: update once a release > 1.5.1 hits with this change in
hooks:
- id: bandit
files: "^python/"
Expand Down
7 changes: 7 additions & 0 deletions python/rpdk/java/codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,13 @@ def init_guided_aws(self, project, src, tst):
self._writing_component(project, src, entity="Translator.java")
self._writing_component(project, src, entity="ClientBuilder.java")
self._writing_component(project, src, entity="BaseHandlerStd.java")
self._writing_component(
project,
src,
entity="TagHelper.java",
operation="TagOps",
call_graph=project.type_name.replace("::", "-"),
)
self._writing_component(project, tst, entity="AbstractTestBase.java")

@logdebug
Expand Down
231 changes: 231 additions & 0 deletions python/rpdk/java/templates/init/guided_aws/TagHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
package {{ package_name }};

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import com.google.common.collect.Sets;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import software.amazon.awssdk.awscore.AwsResponse;
import software.amazon.awssdk.core.SdkClient;
// TODO: Critical! Please replace the CloudFormation Tag model below with your service's own SDK Tag model
import software.amazon.awssdk.services.cloudformation.model.Tag;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
import software.amazon.cloudformation.proxy.Logger;
import software.amazon.cloudformation.proxy.ProgressEvent;
import software.amazon.cloudformation.proxy.ProxyClient;
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<String, String> convertToMap(final Collection<Tag> tags) {
if (CollectionUtils.isEmpty(tags)) {
return Collections.emptyMap();
}
return tags.stream()
.filter(tag -> tag.value() != null)
.collect(Collectors.toMap(
Tag::key,
Tag::value,
(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<Tag> convertToSet(final Map<String, String> 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());
}

/**
* generateTagsForCreate
*
* Generate tags to put into resource creation request.
* This includes user defined tags and system tags as well.
*/
public final Map<String, String> generateTagsForCreate(final ResourceModel resourceModel, final ResourceHandlerRequest<ResourceModel> handlerRequest) {
final Map<String, String> tagMap = new HashMap<>();

// merge system tags with desired resource tags if your service supports CloudFormation system tags
tagMap.putAll(handlerRequest.getSystemTags());

if (handlerRequest.getDesiredResourceTags() != null) {
tagMap.putAll(handlerRequest.getDesiredResourceTags());
}

// TODO: get tags from resource model based on your tag property name
// TODO: tagMap.putAll(convertToMap(resourceModel.getTags()));
return Collections.unmodifiableMap(tagMap);
}

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

/**
* getPreviouslyAttachedTags
*
* If stack tags and resource tags are not merged together in Configuration class,
* we will get previous attached user defined tags from both handlerRequest.getPreviousResourceTags (stack tags)
* and handlerRequest.getPreviousResourceState (resource tags).
*/
public Map<String, String> getPreviouslyAttachedTags(final ResourceHandlerRequest<ResourceModel> handlerRequest) {
// get previous stack level tags from handlerRequest
final Map<String, String> previousTags = handlerRequest.getPreviousResourceTags() != null ?
handlerRequest.getPreviousResourceTags() : Collections.emptyMap();

// TODO: get resource level tags from previous resource state based on your tag property name
// TODO: previousTags.putAll(handlerRequest.getPreviousResourceState().getTags());
return previousTags;
}

/**
* getNewDesiredTags
*
* If stack tags and resource tags are not merged together in Configuration class,
* we will get new user defined tags from both resource model and previous stack tags.
*/
public Map<String, String> getNewDesiredTags(final ResourceModel resourceModel, final ResourceHandlerRequest<ResourceModel> handlerRequest) {
// get new stack level tags from handlerRequest
final Map<String, String> desiredTags = handlerRequest.getDesiredResourceTags() != null ?
handlerRequest.getDesiredResourceTags() : Collections.emptyMap();

// TODO: get resource level tags from resource model based on your tag property name
// TODO: desiredTags.putAll(convertToMap(resourceModel.getTags()));
return desiredTags;
}

/**
* generateTagsToAdd
*
* Determines the tags the customer desired to define or redefine.
*/
public Map<String, String> generateTagsToAdd(final Map<String, String> previousTags, final Map<String, String> 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<String> generateTagsToRemove(final Map<String, String> previousTags, final Map<String, String> desiredTags) {
final Set<String> desiredTagNames = desiredTags.keySet();

return previousTags.keySet().stream()
.filter(tagName -> !desiredTagNames.contains(tagName))
.collect(Collectors.toSet());
}

/**
* generateTagsToAdd
*
* Determines the tags the customer desired to define or redefine.
*/
public Set<Tag> generateTagsToAdd(final Set<Tag> previousTags, final Set<Tag> desiredTags) {
return Sets.difference(new HashSet<>(desiredTags), new HashSet<>(previousTags));
}

/**
* getTagsToRemove
*
* Determines the tags the customer desired to remove from the function.
*/
public Set<Tag> generateTagsToRemove(final Set<Tag> previousTags, final Set<Tag> desiredTags) {
return Sets.difference(new HashSet<>(previousTags), new HashSet<>(desiredTags));
}


/**
* tagResource during update
*
* Calls the service:TagResource API.
*/
private ProgressEvent<ResourceModel, CallbackContext>
tagResource(final AmazonWebServicesClientProxy proxy, final ProxyClient<SdkClient> serviceClient, final ResourceModel resourceModel,
final ResourceHandlerRequest<ResourceModel> handlerRequest, final CallbackContext callbackContext, final Map<String, String> addedTags, final Logger logger) {
// TODO: add log for adding tags to resources during update
// e.g. logger.log(String.format("[UPDATE][IN PROGRESS] Going to add tags for ... resource: %s with AccountId: %s",
// resourceModel.getResourceName(), handlerRequest.getAwsAccountId()));

// TODO: change untagResource in the method to your service API according to your SDK
return proxy.initiate("{{ call_graph }}::{{ operation }}", serviceClient, resourceModel, callbackContext)
.translateToServiceRequest(model ->
Translator.tagResourceRequest(model, addedTags))
.makeServiceCall((request, client) -> {
return (AwsResponse) null;
// TODO: replace the return null with your invoke log to call tagResource API to add tags
// e.g. proxy.injectCredentialsAndInvokeV2(request, client.client()::tagResource))
})
.progress();
}

/**
* untagResource during update
*
* Calls the service:UntagResource API.
*/
private ProgressEvent<ResourceModel, CallbackContext>
untagResource(final AmazonWebServicesClientProxy proxy, final ProxyClient<SdkClient> serviceClient, final ResourceModel resourceModel,
final ResourceHandlerRequest<ResourceModel> handlerRequest, final CallbackContext callbackContext, final Set<String> removedTags, final Logger logger) {
// TODO: add log for removing tags from resources during update
// e.g. logger.log(String.format("[UPDATE][IN PROGRESS] Going to remove tags for ... resource: %s with AccountId: %s",
// resourceModel.getResourceName(), handlerRequest.getAwsAccountId()));

// TODO: change untagResource in the method to your service API according to your SDK
return proxy.initiate("{{ call_graph }}::{{ operation }}", serviceClient, resourceModel, callbackContext)
.translateToServiceRequest(model ->
Translator.untagResourceRequest(model, removedTags))
.makeServiceCall((request, client) -> {
return (AwsResponse) null;
// TODO: replace the return null with your invoke log to call untag API to remove tags
// e.g. proxy.injectCredentialsAndInvokeV2(request, client.client()::untagResource)
})
.progress();
}

}
26 changes: 26 additions & 0 deletions python/rpdk/java/templates/init/guided_aws/Translator.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

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 java.util.stream.Stream;

Expand Down Expand Up @@ -121,4 +123,28 @@ private static <T> Stream<T> streamOfOrEmpty(final Collection<T> collection) {
.map(Collection::stream)
.orElseGet(Stream::empty);
}

/**
* Request to add tags to a resource
* @param model resource model
* @return awsRequest the aws service request to create a resource
*/
static AwsRequest tagResourceRequest(final ResourceModel model, final Map<String, String> addedTags) {
final AwsRequest awsRequest = null;
// TODO: construct a request
// 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
return awsRequest;
}

/**
* Request to add tags to a resource
* @param model resource model
* @return awsRequest the aws service request to create a resource
*/
static AwsRequest untagResourceRequest(final ResourceModel model, final Set<String> removedTags) {
final AwsRequest awsRequest = null;
// TODO: construct a request
// 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
return awsRequest;
}
}

0 comments on commit 850d993

Please sign in to comment.