package software.amazon.iotfleethub.application; import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang3.StringUtils; import software.amazon.awssdk.services.iotfleethub.IoTFleetHubClient; import software.amazon.awssdk.services.iotfleethub.model.DescribeApplicationRequest; import software.amazon.awssdk.services.iotfleethub.model.DescribeApplicationResponse; import software.amazon.awssdk.services.iotfleethub.model.ResourceNotFoundException; import software.amazon.awssdk.services.iotfleethub.model.TagResourceRequest; import software.amazon.awssdk.services.iotfleethub.model.UntagResourceRequest; import software.amazon.awssdk.services.iotfleethub.model.UpdateApplicationRequest; import software.amazon.awssdk.services.iotfleethub.model.UpdateApplicationResponse; import software.amazon.cloudformation.exceptions.CfnGeneralServiceException; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; import software.amazon.cloudformation.proxy.HandlerErrorCode; import software.amazon.cloudformation.proxy.Logger; import software.amazon.cloudformation.proxy.ProgressEvent; import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Objects; public class UpdateHandler extends BaseHandler { private final IoTFleetHubClient iotFleetHubClient; public UpdateHandler() { iotFleetHubClient = IoTFleetHubClient.builder().build(); } @Override public ProgressEvent handleRequest( final AmazonWebServicesClientProxy proxy, final ResourceHandlerRequest request, final CallbackContext callbackContext, final Logger logger) { ResourceModel prevModel = request.getPreviousResourceState(); ResourceModel model = request.getDesiredResourceState(); // UpdateHandler must return a NotFound error if the ApplicationId is not provided if (model.getApplicationId() == null) { logger.log(String.format("ApplicationId was not provided.")); return ProgressEvent.failed(model, callbackContext, HandlerErrorCode.NotFound, "ApplicationId was not provided."); } if (StringUtils.isEmpty(request.getClientRequestToken())) { logger.log(String.format("ClientToken is Required, but a client request token was not provided.")); return ProgressEvent.failed(model, callbackContext, HandlerErrorCode.InvalidRequest, "ClientToken was not provided."); } if (Objects.isNull(prevModel)) { logger.log(String.format("Previous Resource State not found.")); } else if (Translator.isReadOnlyFieldChanged(logger, "ApplicationArn", prevModel.getApplicationArn(), model.getApplicationArn()) || Translator.isReadOnlyFieldChanged(logger, "ApplicationUrl", prevModel.getApplicationUrl(), model.getApplicationUrl()) || Translator.isReadOnlyFieldChanged(logger, "ApplicationState", prevModel.getApplicationState(), model.getApplicationState()) || Translator.isReadOnlyFieldChanged(logger, "SsoClientId", prevModel.getSsoClientId(), model.getSsoClientId()) || Translator.isReadOnlyFieldChanged(logger, "ErrorMessage", prevModel.getErrorMessage(), model.getErrorMessage())) { return ProgressEvent.failed(model, callbackContext, HandlerErrorCode.InvalidRequest, "Can only update ApplicationName, ApplicationDescription, or Tags."); } UpdateApplicationRequest updateRequest = Translator.translateToUpdateRequest(request, model); try { proxy.injectCredentialsAndInvokeV2(updateRequest, iotFleetHubClient::updateApplication); } catch (ResourceNotFoundException e) { logger.log(String.format("Application with Id %s was not found", model.getApplicationId())); } catch (RuntimeException e) { HandlerErrorCode err = Translator.translateExceptionToErrorCode(e, logger); return ProgressEvent.failed(model, callbackContext, err, e.getMessage()); } // Retrieving applicationArn to update tags DescribeApplicationRequest describeRequest = DescribeApplicationRequest.builder() .applicationId(model.getApplicationId()) .build(); DescribeApplicationResponse describeResponse; try { describeResponse = proxy.injectCredentialsAndInvokeV2(describeRequest, iotFleetHubClient::describeApplication); } catch (ResourceNotFoundException e) { logger.log(String.format("Application with Id %s was not found", model.getApplicationId())); return ProgressEvent.failed(model, callbackContext, HandlerErrorCode.NotFound, e.getMessage()); } catch (RuntimeException e) { HandlerErrorCode err = Translator.translateExceptionToErrorCode(e, logger); return ProgressEvent.failed(model, callbackContext, err, e.getMessage()); } String applicationArn = describeResponse.applicationArn(); Map currentTags = describeResponse.tags(); try { updateTags(proxy, request, applicationArn, currentTags, logger); } catch (RuntimeException e) { HandlerErrorCode err = Translator.translateExceptionToErrorCode(e, logger); return ProgressEvent.failed(model, callbackContext, err, e.getMessage()); } logger.log(String.format("Updated Application with Id %s.", model.getApplicationId())); return ProgressEvent.defaultSuccessHandler(request.getDesiredResourceState()); } void updateTags(AmazonWebServicesClientProxy proxy, ResourceHandlerRequest request, String applicationArn, Map currentTags, Logger logger) { Map desiredTags = new HashMap<>(); ResourceModel model = request.getDesiredResourceState(); if (model.getTags() != null) { for (Tag t : model.getTags()) { desiredTags.put(t.getKey(), t.getValue()); } } if (request.getDesiredResourceTags() != null) { desiredTags.putAll(request.getDesiredResourceTags()); } // Add Tags Map tagsToAdd = new HashMap<>(); for (Map.Entry tagEntry : desiredTags.entrySet()) { String currentTagValue = currentTags.get(tagEntry.getKey()); if (currentTagValue == null || !currentTagValue.equals(tagEntry.getValue())) { tagsToAdd.put(tagEntry.getKey(), tagEntry.getValue()); } } if (!tagsToAdd.isEmpty()) { TagResourceRequest tagRequest = TagResourceRequest.builder() .resourceArn(applicationArn) .tags(tagsToAdd) .build(); proxy.injectCredentialsAndInvokeV2(tagRequest, iotFleetHubClient::tagResource); logger.log(String.format("Called TagResource for %s.", applicationArn)); } // Remove Tags Collection tagKeysToRemove = new HashSet<>(); for (Map.Entry tagEntry : currentTags.entrySet()) { String currentKey = tagEntry.getKey(); if (desiredTags.get(currentKey) == null) { tagKeysToRemove.add(currentKey); } } if (!tagKeysToRemove.isEmpty()) { UntagResourceRequest untagRequest = UntagResourceRequest.builder() .resourceArn(applicationArn) .tagKeys(tagKeysToRemove) .build(); proxy.injectCredentialsAndInvokeV2(untagRequest, iotFleetHubClient::untagResource); logger.log(String.format("Called UntagResource for %s.", applicationArn)); } } }