From 93cb30a47bb8d1886e9bc57c306281212a4ec6d4 Mon Sep 17 00:00:00 2001 From: Anton Mokhovikov Date: Thu, 5 Nov 2020 23:27:01 -0800 Subject: [PATCH] [codegen] adding best practice to update tags --- .../init/guided_aws/StubUpdateHandler.java | 83 ++++++++++++++++++- .../templates/init/guided_aws/Translator.java | 27 ++++++ 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/python/rpdk/java/templates/init/guided_aws/StubUpdateHandler.java b/python/rpdk/java/templates/init/guided_aws/StubUpdateHandler.java index ce00d765..6af6cfc3 100644 --- a/python/rpdk/java/templates/init/guided_aws/StubUpdateHandler.java +++ b/python/rpdk/java/templates/init/guided_aws/StubUpdateHandler.java @@ -12,9 +12,11 @@ import software.amazon.cloudformation.proxy.ProgressEvent; import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; +import software.amazon.cloudformation.proxy.HandlerErrorCode; public class UpdateHandler extends BaseHandlerStd { private Logger logger; + private static final String ACCESS_DENIED_EXCEPTION_MESSAGE = "not authorized"; protected ProgressEvent handleRequest( final AmazonWebServicesClientProxy proxy, @@ -25,6 +27,8 @@ protected ProgressEvent handleRequest( this.logger = logger; + final ResourceModel previousModel = request.getPreviousResourceState(); + // TODO: Adjust Progress Chain according to your implementation // https://github.com/aws-cloudformation/cloudformation-cli-java-plugin/blob/master/src/main/java/software/amazon/cloudformation/proxy/CallChain.java @@ -134,7 +138,84 @@ protected ProgressEvent handleRequest( }) .progress()) - // STEP 4 [TODO: describe call/chain to return the resource model] + // If your resource supports tags, then the following pattern is required to handle stack level tags via soft-failing pattern + // STEP 4 [update stack level tags progress chain] + .then(progress -> { + + // STEP 4.0 [initialize a proxy context] + // Stack level tag update should not force user but rather be optional, as it is possible that stack execution role will not have + // enough permissions to do so + // step in a discrete call/stabilize chain to ensure the entire resource is provisioned as intended. + ProgressEvent event = proxy.initiate("{{ call_graph }}::{{ operation }}::stack-level-tags", proxyClient, progress.getResourceModel(), progress.getCallbackContext()) + + // STEP 4.1 [TODO: construct a body of a request] + .translateToServiceRequest((model) -> Translator.translateToStackTagUpdateRequest(request.getPreviousResourceTags(), request.getDesiredResourceTags())) + + // STEP 4.2 [TODO: make an api call] + .makeServiceCall((awsRequest, client) -> { + AwsResponse awsResponse = null; + try { + + // TODO: put your post update resource code here + + } catch (final AwsServiceException e) { + /* + * While the handler contract states that the handler must always return a progress event, + * you may throw any instance of BaseHandlerException, as the wrapper map it to a progress event. + * Each BaseHandlerException maps to a specific error code, and you should map service exceptions as closely as possible + * to more specific error codes + */ + throw new CfnGeneralServiceException(ResourceModel.TYPE_NAME, e); + } + + logger.log(String.format("%s has successfully been updated.", ResourceModel.TYPE_NAME)); + return awsResponse; + }) + .progress(); + + // STEP 4.3 [TODO: check if event 1) failed 2) error code is access denied] + // if (event.isFailed() && ...) { + // return progress; + // } + return event; + }) + + // If your resource supports tags, then the following pattern is required to handle resource level tags + // STEP 5 [update resource level tags progress chain] + .then(progress -> + + // STEP 5.0 [initialize a proxy context] + // Resource level tag update should force user to use right set of permissions + // step in a discrete call/stabilize chain to ensure the entire resource is provisioned as intended. + proxy.initiate("{{ call_graph }}::{{ operation }}::resource-level-tags", proxyClient, progress.getResourceModel(), progress.getCallbackContext()) + + // STEP 5.1 [TODO: construct a body of a request] + .translateToServiceRequest((model) -> Translator.translateToResourceTagUpdateRequest(previousModel, model)) + + // STEP 5.2 [TODO: make an api call] + .makeServiceCall((awsRequest, client) -> { + AwsResponse awsResponse = null; + try { + + // TODO: put your post update resource code here + + } catch (final AwsServiceException e) { + /* + * While the handler contract states that the handler must always return a progress event, + * you may throw any instance of BaseHandlerException, as the wrapper map it to a progress event. + * Each BaseHandlerException maps to a specific error code, and you should map service exceptions as closely as possible + * to more specific error codes + */ + throw new CfnGeneralServiceException(ResourceModel.TYPE_NAME, e); + } + + logger.log(String.format("%s has successfully been updated.", ResourceModel.TYPE_NAME)); + return awsResponse; + }) + .progress()) + + + // STEP 6 [TODO: describe call/chain to return the resource model] .then(progress -> new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, logger)); } } diff --git a/python/rpdk/java/templates/init/guided_aws/Translator.java b/python/rpdk/java/templates/init/guided_aws/Translator.java index 16673519..722a5f70 100644 --- a/python/rpdk/java/templates/init/guided_aws/Translator.java +++ b/python/rpdk/java/templates/init/guided_aws/Translator.java @@ -6,6 +6,7 @@ import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -90,6 +91,32 @@ static AwsRequest translateToSecondUpdateRequest(final ResourceModel model) { return awsRequest; } + /** + * Request to update Stack level tags. Stack tags are defined as Maps by cloudformation + * @param previousStackTags previous stack tags + * @param stackTags currently provided stack tags + * @return awsRequest the aws service request to modify a resource + */ + static AwsRequest translateToStackTagUpdateRequest(final Map previousStackTags, final Map stackTags) { + final AwsRequest awsRequest = null; + // TODO: construct a request + return awsRequest; + } + + /** + * Request to update Resource level tags. Resource tags are defined by the service teams and could be represented by any collection + * @param previousModel previous resource model, which contains previously provided resource tags + * @param model current resource model, which contains currently provided resource tags + * @return awsRequest the aws service request to modify a resource + */ + static AwsRequest translateToResourceTagUpdateRequest(final ResourceModel previousModel, final ResourceModel model) { + final AwsRequest awsRequest = null; + // TODO: construct a request + return awsRequest; + } + + + /** * Request to list resources * @param nextToken token passed to the aws service list resources request