package software.amazon.events.rule; import java.util.regex.Matcher; import java.util.regex.Pattern; import lombok.Getter; import static java.util.Objects.isNull; import static java.util.Objects.nonNull; /** * AWS::Events::Rule is a peculiar resource because it supports cross-account resource provisioning. * To handle that scenario in Uluru, the handler should be able to calculate the event rule name and event bus (name or arn) * from the Event Rule ARN (PrimaryIdentifier) OR from native physicalResourceId */ public class CompositePID { private static final String COMPOSITE_PID_FORMAT = "%s|%s"; private static final String DEFAULT_EVENT_BUS_NAME = "default"; private static final String EVENT_BUS_ARN_FORMAT = "arn:%s:events:%s:%s:event-bus/%s"; private static final String EVENT_BUS_ARN_PATTERN = "^arn:(?<Partition>[aws[\\w-]*]*):events:(?<Region>[^:\n]*):(?<AccountId>[0-9]{12}):(?<ResourceType>event-bus)/(?<EventBusName>[^:\n]*)$"; private static final String EVENT_RULE_ARN_PATTERN = "^arn:(?<Partition>[aws[\\w-]*]*):events:(?<Region>[^:\n]*):(?<AccountId>[0-9]{12}):(?<ResourceType>rule)/(?<EventRuleName>[^:\n]*)$"; private static final String EVENT_RULE_ARN_PATTERN_WITH_EVENT_BUS = "^arn:(?<Partition>[aws[\\w-]*]*):events:(?<Region>[^:\n]*):(?<AccountId>[0-9]{12}):(?<ResourceType>rule)/(?<EventBusName>[^:\n]*)/(?<EventRuleName>[^:\n]*)$"; @Getter public String pid = null; @Getter public String eventRuleName = null; @Getter public String eventBusName = null; public CompositePID(final ResourceModel model, final String stackOwnerAccountId) { if (nonNull(model.getName())) { eventRuleName = model.getName(); } if (nonNull(model.getEventBusName())) { eventBusName = model.getEventBusName(); } final String arnOrPhysicalResourceId = model.getArn(); if (isNull(eventBusName)) { if (nonNull(arnOrPhysicalResourceId) && hasEventRuleArn(arnOrPhysicalResourceId)) { eventBusName = getEventBusNameFromRuleArn(arnOrPhysicalResourceId, stackOwnerAccountId); } else { eventBusName = inferBusNameFromPhysicalResourceId(arnOrPhysicalResourceId); } } if (isNull(eventRuleName)) { if (nonNull(arnOrPhysicalResourceId) && hasEventRuleArn(arnOrPhysicalResourceId)) { eventRuleName = getEventRuleNameFromArn(arnOrPhysicalResourceId); } else { eventRuleName = inferRuleNameFromPhysicalResourceId(arnOrPhysicalResourceId); } } pid = calculateEventRulePID(stackOwnerAccountId); } /** * Returns true if string contains Events Rule Arn pattern, false otherwise */ private boolean hasEventRuleArn(final String eventRuleArnOrIdentifier) { return (Pattern.compile(EVENT_RULE_ARN_PATTERN).matcher(eventRuleArnOrIdentifier).find() || Pattern.compile(EVENT_RULE_ARN_PATTERN_WITH_EVENT_BUS).matcher(eventRuleArnOrIdentifier).find()); } /** * For existent Event Rule resources, CFN won't have Event Rule Arn available * PrimaryIdentifier value is the current PhysicalResourceId value: * ${EventRuleName} - if rule resides in the same account and uses default event bus * ${EventBusName|EventRuleName} - if rule resides in the same account and uses a custom event bus * ${EventBusArn|EventRuleName} - If rule is created in another account * @return PhysicalResourceId */ private String inferRuleNameFromPhysicalResourceId(final String pid) { final int identifierSize = pid.split("\\|").length; if (identifierSize > 1) { return pid.split("\\|")[1]; } else { return pid; } } private String inferBusNameFromPhysicalResourceId(final String pid) { if (isNull(pid)) { return DEFAULT_EVENT_BUS_NAME; } final int identifierSize = pid.split("\\|").length; if (identifierSize > 1) { return pid.split("\\|")[0]; } else { return DEFAULT_EVENT_BUS_NAME; } } /** * Return event rule name from event rule ARN * @param arn - Event Rule ARN * @return Event rule name */ private String getEventRuleNameFromArn(final String arn) { final Matcher eventRuleArnMatcherWithBus = Pattern.compile(EVENT_RULE_ARN_PATTERN_WITH_EVENT_BUS).matcher(arn); if (eventRuleArnMatcherWithBus.matches()) { return eventRuleArnMatcherWithBus.group("EventRuleName"); } final Matcher eventRuleArnMatcher = Pattern.compile(EVENT_RULE_ARN_PATTERN).matcher(arn); eventRuleArnMatcher.matches(); return eventRuleArnMatcher.group("EventRuleName"); } /** * Get Event Bus Name from Event Rule ARN * NOTE: Event Bus Name should be in ARN format in case Rule was created in another account * @param arn - Event Rule ARN * @return - Event Bus Name or ARN */ private String getEventBusNameFromRuleArn(final String arn, final String stackOwnerAccountId) { String partition; String region; String accountId; String eventBusName = DEFAULT_EVENT_BUS_NAME; boolean crossAccountEventRule = false; final Matcher eventRuleArnDefaultBus = Pattern.compile(EVENT_RULE_ARN_PATTERN).matcher(arn); final Matcher eventRuleArnContainsCustomEventBus = Pattern.compile(EVENT_RULE_ARN_PATTERN_WITH_EVENT_BUS).matcher(arn); if (eventRuleArnDefaultBus.matches() || eventRuleArnContainsCustomEventBus.matches()) { partition = eventRuleArnDefaultBus.group("Partition"); region = eventRuleArnDefaultBus.group("Region"); accountId = eventRuleArnDefaultBus.group("AccountId"); if (eventRuleArnContainsCustomEventBus.matches()) { eventBusName = eventRuleArnContainsCustomEventBus.group("EventBusName"); } crossAccountEventRule = !stackOwnerAccountId.equals(accountId); if (crossAccountEventRule) { return String.format(EVENT_BUS_ARN_FORMAT, partition, region, accountId, eventBusName); } } return eventBusName; } /** * Because Event rule is a resource migrated from native, the PhysicalResourceId value must not change * Event Rule's PID format: * ${EventRuleName} - if rule resides in the same account and uses default event bus * ${EventBusName|EventRuleName} - if rule resides in the same account and uses a custom event bus * ${EventBusArn|EventRuleName} - If rule is created in another account * @return PhysicalResourceId */ private String calculateEventRulePID(final String stackOwnerAccountId) { if (eventBusName.equals(DEFAULT_EVENT_BUS_NAME)) { return eventRuleName; } final Matcher eventBusNameContainsArn = Pattern.compile(EVENT_BUS_ARN_PATTERN).matcher(eventBusName); if (eventBusNameContainsArn.matches()) { final boolean eventBusFromSourceAccount = stackOwnerAccountId.equals(eventBusNameContainsArn.group("AccountId")); if (eventBusFromSourceAccount) { if (eventBusNameContainsArn.group("EventBusName").equals(DEFAULT_EVENT_BUS_NAME)) { return eventRuleName; } return String.format(COMPOSITE_PID_FORMAT, eventBusNameContainsArn.group("EventBusName"), eventRuleName); } } return String.format(COMPOSITE_PID_FORMAT, eventBusName, eventRuleName); } }