package software.amazon.kendra.faq; import com.google.common.collect.Sets; import software.amazon.awssdk.services.kendra.KendraClient; import software.amazon.awssdk.services.kendra.model.DescribeFaqRequest; import software.amazon.awssdk.services.kendra.model.DescribeFaqResponse; import software.amazon.awssdk.services.kendra.model.ResourceNotFoundException; import software.amazon.awssdk.services.kendra.model.TagResourceRequest; import software.amazon.awssdk.services.kendra.model.UntagResourceRequest; import software.amazon.awssdk.services.kendra.model.ValidationException; import software.amazon.cloudformation.exceptions.CfnInvalidRequestException; import software.amazon.cloudformation.exceptions.CfnNotFoundException; import software.amazon.cloudformation.exceptions.CfnNotUpdatableException; 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; import java.util.HashSet; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; public class UpdateHandler extends BaseHandlerStd { private Logger logger; private FaqArnBuilder faqArnBuilder; public UpdateHandler() { super(); faqArnBuilder = new FaqArn(); } public UpdateHandler(FaqArnBuilder faqArnBuilder) { this.faqArnBuilder = faqArnBuilder; } protected ProgressEvent<ResourceModel, CallbackContext> handleRequest( final AmazonWebServicesClientProxy proxy, final ResourceHandlerRequest<ResourceModel> request, final CallbackContext callbackContext, final ProxyClient<KendraClient> proxyClient, final Logger logger) { this.logger = logger; final ResourceModel model = request.getDesiredResourceState(); verifyNonUpdatableFields(model, 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 return ProgressEvent.progress(model, callbackContext) .then(progress -> proxy.initiate("AWS-Kendra-Faq::ValidateResourceExists", proxyClient, model, callbackContext) .translateToServiceRequest(resourceModel -> Translator.translateToReadRequest(model)) .makeServiceCall(this::validateResourceExists) .progress()) // STEP 1 [first update/stabilize progress chain - required for resource update] .then(progress -> updateTags(proxyClient, progress, request)) .then(progress -> new ReadHandler(faqArnBuilder).handleRequest(proxy, request, callbackContext, proxyClient, logger)); } private DescribeFaqResponse validateResourceExists(DescribeFaqRequest describeFaqRequest, ProxyClient<KendraClient> proxyClient) { DescribeFaqResponse describeFaqResponse; try { describeFaqResponse = proxyClient.injectCredentialsAndInvokeV2(describeFaqRequest,proxyClient.client()::describeFaq); } catch (ResourceNotFoundException e) { throw new CfnNotFoundException(ResourceModel.TYPE_NAME, describeFaqRequest.id(), e); } return describeFaqResponse; } private ProgressEvent<ResourceModel, CallbackContext> updateTags( final ProxyClient<KendraClient> proxyClient, final ProgressEvent<ResourceModel, CallbackContext> progress, ResourceHandlerRequest<ResourceModel> request) { ResourceModel currResourceModel = request.getDesiredResourceState(); ResourceModel prevResourceModel = request.getPreviousResourceState(); CallbackContext callbackContext = progress.getCallbackContext(); Set<Tag> currentTags = new HashSet<>(); if (currResourceModel.getTags() != null) { currentTags = currResourceModel.getTags().stream().collect(Collectors.toSet()); } String arn = faqArnBuilder.build(request); Set<Tag> existingTags = new HashSet<>(); if (prevResourceModel != null && prevResourceModel.getTags() != null) { existingTags = prevResourceModel.getTags().stream().collect(Collectors.toSet()); } final Set<Tag> tagsToAdd = Sets.difference(currentTags, existingTags); if (!tagsToAdd.isEmpty()) { TagResourceRequest tagResourceRequest = Translator.translateToTagResourceRequest(tagsToAdd, arn); try { proxyClient.injectCredentialsAndInvokeV2(tagResourceRequest, proxyClient.client()::tagResource); } catch (ValidationException e) { throw new CfnInvalidRequestException(e.getMessage(), e); } } final Set<Tag> tagsToRemove = Sets.difference(existingTags, currentTags); if (!tagsToRemove.isEmpty()) { UntagResourceRequest untagResourceRequest = Translator.translateToUntagResourceRequest(tagsToRemove, arn); try { proxyClient.injectCredentialsAndInvokeV2(untagResourceRequest, proxyClient.client()::untagResource); } catch (ValidationException e) { throw new CfnInvalidRequestException(e.getMessage(), e); } } return ProgressEvent.progress(currResourceModel, callbackContext); } /** * Checks the if the create only fields have been updated and throws an exception if it is the case * @param currModel the current resource model * @param prevModel the previous resource model */ private void verifyNonUpdatableFields(ResourceModel currModel, ResourceModel prevModel) { if (prevModel != null) { if (!Optional.ofNullable(currModel.getIndexId()).equals(Optional.ofNullable(prevModel.getIndexId()))) { throw new CfnNotUpdatableException(ResourceModel.TYPE_NAME, "IndexId"); } if (!Optional.ofNullable(currModel.getName()).equals(Optional.ofNullable(prevModel.getName()))) { throw new CfnNotUpdatableException(ResourceModel.TYPE_NAME, "Name"); } if (!Optional.ofNullable(currModel.getS3Path()).equals(Optional.ofNullable(prevModel.getS3Path()))) { throw new CfnNotUpdatableException(ResourceModel.TYPE_NAME, "S3Path"); } if (!Optional.ofNullable(currModel.getRoleArn()).equals(Optional.ofNullable(prevModel.getRoleArn()))) { throw new CfnNotUpdatableException(ResourceModel.TYPE_NAME, "RoleArn"); } if (!Optional.ofNullable(currModel.getDescription()).equals(Optional.ofNullable(prevModel.getDescription()))) { throw new CfnNotUpdatableException(ResourceModel.TYPE_NAME, "Description"); } if (!Optional.ofNullable(currModel.getFileFormat()).equals(Optional.ofNullable(prevModel.getFileFormat()))) { throw new CfnNotUpdatableException(ResourceModel.TYPE_NAME, "FileFormat"); } } } }