|
| 1 | +package software.amazon.datasync.agent; |
| 2 | + |
| 3 | +import com.google.common.collect.MapDifference; |
| 4 | +import com.google.common.collect.Maps; |
| 5 | +import com.google.common.collect.Sets; |
| 6 | +import software.amazon.awssdk.services.datasync.DataSyncClient; |
| 7 | +import software.amazon.awssdk.services.datasync.model.DataSyncException; |
| 8 | +import software.amazon.awssdk.services.datasync.model.InternalException; |
| 9 | +import software.amazon.awssdk.services.datasync.model.InvalidRequestException; |
| 10 | +import software.amazon.awssdk.services.datasync.model.ListTagsForResourceRequest; |
| 11 | +import software.amazon.awssdk.services.datasync.model.ListTagsForResourceResponse; |
| 12 | +import software.amazon.awssdk.services.datasync.model.TagResourceRequest; |
| 13 | +import software.amazon.awssdk.services.datasync.model.UntagResourceRequest; |
| 14 | +import software.amazon.cloudformation.exceptions.CfnInvalidRequestException; |
| 15 | +import software.amazon.cloudformation.exceptions.CfnNotFoundException; |
| 16 | +import software.amazon.cloudformation.exceptions.CfnServiceInternalErrorException; |
| 17 | +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; |
| 18 | +import software.amazon.cloudformation.proxy.Logger; |
| 19 | +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; |
| 20 | + |
| 21 | +import java.util.HashMap; |
| 22 | +import java.util.HashSet; |
| 23 | +import java.util.Map; |
| 24 | +import java.util.Set; |
| 25 | +import java.util.stream.Collectors; |
| 26 | + |
| 27 | +/** |
| 28 | + * Since tags cannot be retrieved or updated through the DataSync Describe and Update |
| 29 | + * API calls, these methods make the appropriate tag-specific API requests. |
| 30 | + */ |
| 31 | +public class TagRequestMaker { |
| 32 | + private static final String AWS_TAG_PREFIX = "aws:"; |
| 33 | + |
| 34 | + /** |
| 35 | + * Retrieve the tags associated with the given resource. |
| 36 | + * |
| 37 | + * @param proxy |
| 38 | + * @param client |
| 39 | + * @param resourceArn |
| 40 | + * @return the set of tags currently attached to the resource |
| 41 | + */ |
| 42 | + public static Set<Tag> listTagsForResource( |
| 43 | + final AmazonWebServicesClientProxy proxy, |
| 44 | + final DataSyncClient client, |
| 45 | + final String resourceArn) { |
| 46 | + final ListTagsForResourceRequest listTagsForResourceRequest = TagTranslator.translateToListTagsRequest(resourceArn); |
| 47 | + |
| 48 | + ListTagsForResourceResponse tagsResponse; |
| 49 | + try { |
| 50 | + tagsResponse = proxy.injectCredentialsAndInvokeV2(listTagsForResourceRequest, client::listTagsForResource); |
| 51 | + } catch (InvalidRequestException e) { |
| 52 | + throw new CfnNotFoundException(ResourceModel.TYPE_NAME, resourceArn); |
| 53 | + } catch (InternalException e) { |
| 54 | + throw new CfnServiceInternalErrorException(e.getMessage(), e.getCause()); |
| 55 | + } catch (DataSyncException e) { |
| 56 | + throw Translator.translateDataSyncExceptionToCfnException(e); |
| 57 | + } |
| 58 | + |
| 59 | + if (tagsResponse.tags() != null) { |
| 60 | + return TagTranslator.translateTagListEntries(tagsResponse.tags()); |
| 61 | + } |
| 62 | + return new HashSet<Tag>(); |
| 63 | + } |
| 64 | + |
| 65 | + /** |
| 66 | + * Calculate and perform a delta update (additions and removals as needed) to |
| 67 | + * resource tags based on the current and previous tags supplied by the CloudFormation request. |
| 68 | + * |
| 69 | + * @param proxy |
| 70 | + * @param client |
| 71 | + * @param resourceArn |
| 72 | + * @param request |
| 73 | + * @param logger |
| 74 | + */ |
| 75 | + public static void updateTagsForResource( |
| 76 | + final AmazonWebServicesClientProxy proxy, |
| 77 | + final DataSyncClient client, |
| 78 | + final String resourceArn, |
| 79 | + final ResourceHandlerRequest<ResourceModel> request, |
| 80 | + final Logger logger) { |
| 81 | + |
| 82 | + Map<String, String> tagList = request.getDesiredResourceTags(); |
| 83 | + if (tagList == null) { |
| 84 | + tagList = new HashMap<String, String>(); |
| 85 | + } |
| 86 | + |
| 87 | + Map<String, String> prevTagList = new HashMap<String, String>(); |
| 88 | + if (request.getPreviousResourceTags() != null) { |
| 89 | + prevTagList = request.getPreviousResourceTags(); |
| 90 | + } |
| 91 | + |
| 92 | + final Set<String> keysToRemove = Sets.difference( |
| 93 | + prevTagList.keySet(), |
| 94 | + tagList.keySet() |
| 95 | + ); |
| 96 | + |
| 97 | + if (!keysToRemove.isEmpty()) { |
| 98 | + UntagResourceRequest untagResourceRequest = TagTranslator.translateToUntagResourceRequest( |
| 99 | + keysToRemove, resourceArn); |
| 100 | + try { |
| 101 | + proxy.injectCredentialsAndInvokeV2(untagResourceRequest, client::untagResource); |
| 102 | + logger.log(String.format("%s %s old tags removed successfully", ResourceModel.TYPE_NAME, |
| 103 | + resourceArn)); |
| 104 | + } catch (InvalidRequestException e) { |
| 105 | + throw new CfnNotFoundException(ResourceModel.TYPE_NAME, resourceArn); |
| 106 | + } catch (InternalException e) { |
| 107 | + throw new CfnServiceInternalErrorException(e.getMessage(), e.getCause()); |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + MapDifference<String, String> mapDifference = Maps.difference(tagList, prevTagList); |
| 112 | + final Set<Tag> tagsToAdd = mapDifference.entriesDiffering().entrySet().stream().map(entry -> { |
| 113 | + return Tag.builder().key(entry.getKey()).value(entry.getValue().leftValue()).build(); |
| 114 | + }).collect(Collectors.toSet()); |
| 115 | + tagsToAdd.addAll(TagTranslator.translateMapToTags(mapDifference.entriesOnlyOnLeft())); |
| 116 | + |
| 117 | + for (Tag tag : tagsToAdd) { |
| 118 | + if (tag.getKey().trim().toLowerCase().startsWith(AWS_TAG_PREFIX)) { |
| 119 | + throw new CfnInvalidRequestException(tag.getKey() + " is an invalid key. aws: prefixed tag key names cannot be requested."); |
| 120 | + } |
| 121 | + } |
| 122 | + |
| 123 | + if (request.getPreviousSystemTags() == null && request.getSystemTags() != null) { |
| 124 | + tagsToAdd.addAll(TagTranslator.translateMapToTags(request.getSystemTags())); |
| 125 | + } |
| 126 | + |
| 127 | + if (!tagsToAdd.isEmpty()) { |
| 128 | + TagResourceRequest tagResourceRequest = TagTranslator.translateToTagResourceRequest( |
| 129 | + tagsToAdd, resourceArn); |
| 130 | + try { |
| 131 | + proxy.injectCredentialsAndInvokeV2(tagResourceRequest, client::tagResource); |
| 132 | + logger.log(String.format("%s %s tags updated successfully", ResourceModel.TYPE_NAME, |
| 133 | + resourceArn)); |
| 134 | + } catch (InvalidRequestException e) { |
| 135 | + throw new CfnNotFoundException(ResourceModel.TYPE_NAME, resourceArn); |
| 136 | + } catch (InternalException e) { |
| 137 | + throw new CfnServiceInternalErrorException(e.getMessage(), e.getCause()); |
| 138 | + } |
| 139 | + } |
| 140 | + } |
| 141 | +} |
0 commit comments