diff --git a/.changelog/18491.txt b/.changelog/18491.txt new file mode 100644 index 00000000000..225dc3f2efe --- /dev/null +++ b/.changelog/18491.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_cloudwatch_event_target: Support partner event bus names +``` + +```release-note:enhancement +resource/aws_cloudwatch_event_rule: Support partner event bus names +``` diff --git a/aws/internal/service/cloudwatchevents/id.go b/aws/internal/service/cloudwatchevents/id.go index 0da79c7054c..7d383458c5a 100644 --- a/aws/internal/service/cloudwatchevents/id.go +++ b/aws/internal/service/cloudwatchevents/id.go @@ -2,9 +2,14 @@ package cloudwatchevents import ( "fmt" + "regexp" "strings" ) +var ( + partnerEventBusPattern = regexp.MustCompile(`^aws\.partner(/[\.\-_A-Za-z0-9]+){2,}$`) +) + const DefaultEventBusName = "default" const PermissionIDSeparator = "/" @@ -45,6 +50,14 @@ func RuleParseID(id string) (string, string, error) { if len(parts) == 2 && parts[0] != "" && parts[1] != "" { return parts[0], parts[1], nil } + if len(parts) > 2 { + i := strings.LastIndex(id, ruleIDSeparator) + busName := id[:i] + statementID := id[i+1:] + if partnerEventBusPattern.MatchString(busName) && statementID != "" { + return busName, statementID, nil + } + } return "", "", fmt.Errorf("unexpected format for ID (%q), expected "+ruleIDSeparator+" or ", id) } @@ -71,6 +84,16 @@ func TargetParseImportID(id string) (string, string, string, error) { if len(parts) == 3 && parts[0] != "" && parts[1] != "" && parts[2] != "" { return parts[0], parts[1], parts[2], nil } + if len(parts) > 3 { + iTarget := strings.LastIndex(id, targetImportIDSeparator) + targetID := id[iTarget+1:] + iRule := strings.LastIndex(id[:iTarget], targetImportIDSeparator) + busName := id[:iRule] + ruleName := id[iRule+1 : iTarget] + if partnerEventBusPattern.MatchString(busName) && ruleName != "" && targetID != "" { + return busName, ruleName, targetID, nil + } + } return "", "", "", fmt.Errorf("unexpected format for ID (%q), expected "+targetImportIDSeparator+""+targetImportIDSeparator+" or "+targetImportIDSeparator+"", id) } diff --git a/aws/internal/service/cloudwatchevents/id_test.go b/aws/internal/service/cloudwatchevents/id_test.go new file mode 100644 index 00000000000..97ae7823a90 --- /dev/null +++ b/aws/internal/service/cloudwatchevents/id_test.go @@ -0,0 +1,331 @@ +package cloudwatchevents_test + +import ( + "testing" + + tfevents "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/cloudwatchevents" +) + +func TestPermissionParseID(t *testing.T) { + testCases := []struct { + TestName string + InputID string + ExpectedError bool + ExpectedPart0 string + ExpectedPart1 string + }{ + { + TestName: "empty ID", + InputID: "", + ExpectedError: true, + }, + { + TestName: "single part", + InputID: "TestStatement", + ExpectedPart0: tfevents.DefaultEventBusName, + ExpectedPart1: "TestStatement", + }, + { + TestName: "two parts", + InputID: tfevents.PermissionCreateID("TestEventBus", "TestStatement"), + ExpectedPart0: "TestEventBus", + ExpectedPart1: "TestStatement", + }, + { + TestName: "two parts with default event bus", + InputID: tfevents.PermissionCreateID(tfevents.DefaultEventBusName, "TestStatement"), + ExpectedPart0: tfevents.DefaultEventBusName, + ExpectedPart1: "TestStatement", + }, + { + TestName: "partner event bus", + InputID: "aws.partner/example.com/Test/TestStatement", + ExpectedError: true, + }, + { + TestName: "empty both parts", + InputID: "/", + ExpectedError: true, + }, + { + TestName: "empty first part", + InputID: "/TestStatement", + ExpectedError: true, + }, + { + TestName: "empty second part", + InputID: "TestEventBus/", + ExpectedError: true, + }, + { + TestName: "three parts", + InputID: "TestEventBus/TestStatement/Suffix", + ExpectedError: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.TestName, func(t *testing.T) { + gotPart0, gotPart1, err := tfevents.PermissionParseID(testCase.InputID) + + if err == nil && testCase.ExpectedError { + t.Fatalf("expected error, got no error") + } + + if err != nil && !testCase.ExpectedError { + t.Fatalf("got unexpected error: %s", err) + } + + if gotPart0 != testCase.ExpectedPart0 { + t.Errorf("got part 0 %s, expected %s", gotPart0, testCase.ExpectedPart0) + } + + if gotPart1 != testCase.ExpectedPart1 { + t.Errorf("got part 1 %s, expected %s", gotPart1, testCase.ExpectedPart1) + } + }) + } +} + +func TestRuleParseID(t *testing.T) { + testCases := []struct { + TestName string + InputID string + ExpectedError bool + ExpectedPart0 string + ExpectedPart1 string + }{ + { + TestName: "empty ID", + InputID: "", + ExpectedError: true, + }, + { + TestName: "single part", + InputID: "TestRule", + ExpectedPart0: tfevents.DefaultEventBusName, + ExpectedPart1: "TestRule", + }, + { + TestName: "two parts", + InputID: tfevents.RuleCreateID("TestEventBus", "TestRule"), + ExpectedPart0: "TestEventBus", + ExpectedPart1: "TestRule", + }, + { + TestName: "two parts with default event bus", + InputID: tfevents.RuleCreateID(tfevents.DefaultEventBusName, "TestRule"), + ExpectedPart0: tfevents.DefaultEventBusName, + ExpectedPart1: "TestRule", + }, + { + TestName: "partner event bus", + InputID: "aws.partner/example.com/Test/TestRule", + ExpectedPart0: "aws.partner/example.com/Test", + ExpectedPart1: "TestRule", + }, + { + TestName: "empty both parts", + InputID: "/", + ExpectedError: true, + }, + { + TestName: "empty first part", + InputID: "/TestRule", + ExpectedError: true, + }, + { + TestName: "empty second part", + InputID: "TestEventBus/", + ExpectedError: true, + }, + { + TestName: "empty partner event rule", + InputID: "aws.partner/example.com/Test/", + ExpectedError: true, + }, + { + TestName: "three parts", + InputID: "TestEventBus/TestRule/Suffix", + ExpectedError: true, + }, + { + TestName: "four parts", + InputID: "abc.partner/TestEventBus/TestRule/Suffix", + ExpectedError: true, + }, + { + TestName: "five parts", + InputID: "test/aws.partner/example.com/Test/TestRule", + ExpectedError: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.TestName, func(t *testing.T) { + gotPart0, gotPart1, err := tfevents.RuleParseID(testCase.InputID) + + if err == nil && testCase.ExpectedError { + t.Fatalf("expected error, got no error") + } + + if err != nil && !testCase.ExpectedError { + t.Fatalf("got unexpected error: %s", err) + } + + if gotPart0 != testCase.ExpectedPart0 { + t.Errorf("got part 0 %s, expected %s", gotPart0, testCase.ExpectedPart0) + } + + if gotPart1 != testCase.ExpectedPart1 { + t.Errorf("got part 1 %s, expected %s", gotPart1, testCase.ExpectedPart1) + } + }) + } +} + +func TestTargetParseImportID(t *testing.T) { + testCases := []struct { + TestName string + InputID string + ExpectedError bool + ExpectedPart0 string + ExpectedPart1 string + ExpectedPart2 string + }{ + { + TestName: "empty ID", + InputID: "", + ExpectedError: true, + }, + { + TestName: "single part", + InputID: "TestRule", + ExpectedError: true, + }, + { + TestName: "two parts", + InputID: "TestTarget/TestRule", + ExpectedPart0: tfevents.DefaultEventBusName, + ExpectedPart1: "TestTarget", + ExpectedPart2: "TestRule", + }, + { + TestName: "three parts", + InputID: "TestEventBus/TestRule/TestTarget", + ExpectedPart0: "TestEventBus", + ExpectedPart1: "TestRule", + ExpectedPart2: "TestTarget", + }, + { + TestName: "three parts with default event bus", + InputID: tfevents.DefaultEventBusName + "/TestRule/TestTarget", + ExpectedPart0: tfevents.DefaultEventBusName, + ExpectedPart1: "TestRule", + ExpectedPart2: "TestTarget", + }, + { + TestName: "empty two parts", + InputID: "/", + ExpectedError: true, + }, + { + TestName: "empty three parts", + InputID: "//", + ExpectedError: true, + }, + { + TestName: "empty first part of two", + InputID: "/TestTarget", + ExpectedError: true, + }, + { + TestName: "empty second part of two", + InputID: "TestRule/", + ExpectedError: true, + }, + { + TestName: "empty first part of three", + InputID: "/TestRule/TestTarget", + ExpectedError: true, + }, + { + TestName: "empty second part of three", + InputID: "TestEventBus//TestTarget", + ExpectedError: true, + }, + { + TestName: "empty third part of three", + InputID: "TestEventBus/TestRule/", + ExpectedError: true, + }, + { + TestName: "empty first two of three parts", + InputID: "//TestTarget", + ExpectedError: true, + }, + { + TestName: "empty first and third of three parts", + InputID: "/TestRule/", + ExpectedError: true, + }, + { + TestName: "empty final two of three parts", + InputID: "TestEventBus//", + ExpectedError: true, + }, + { + TestName: "partner event bus", + InputID: "aws.partner/example.com/Test/TestRule/TestTarget", + ExpectedPart0: "aws.partner/example.com/Test", + ExpectedPart1: "TestRule", + ExpectedPart2: "TestTarget", + }, + { + TestName: "empty partner event rule and target", + InputID: "aws.partner/example.com/Test//", + ExpectedError: true, + }, + { + TestName: "four parts", + InputID: "aws.partner/example.com/Test/TestRule", + ExpectedError: true, + }, + { + TestName: "five parts", + InputID: "abc.partner/example.com/Test/TestRule/TestTarget", + ExpectedError: true, + }, + { + TestName: "six parts", + InputID: "test/aws.partner/example.com/Test/TestRule/TestTarget", + ExpectedError: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.TestName, func(t *testing.T) { + gotPart0, gotPart1, gotPart2, err := tfevents.TargetParseImportID(testCase.InputID) + + if err == nil && testCase.ExpectedError { + t.Fatalf("expected error, got no error") + } + + if err != nil && !testCase.ExpectedError { + t.Fatalf("got unexpected error: %s", err) + } + + if gotPart0 != testCase.ExpectedPart0 { + t.Errorf("got part 0 %s, expected %s", gotPart0, testCase.ExpectedPart0) + } + + if gotPart1 != testCase.ExpectedPart1 { + t.Errorf("got part 1 %s, expected %s", gotPart1, testCase.ExpectedPart1) + } + + if gotPart2 != testCase.ExpectedPart2 { + t.Errorf("got part 2 %s, expected %s", gotPart2, testCase.ExpectedPart2) + } + }) + } +} diff --git a/aws/resource_aws_cloudwatch_event_rule_test.go b/aws/resource_aws_cloudwatch_event_rule_test.go index 2a9944073be..69ed9b6e712 100644 --- a/aws/resource_aws_cloudwatch_event_rule_test.go +++ b/aws/resource_aws_cloudwatch_event_rule_test.go @@ -3,6 +3,7 @@ package aws import ( "fmt" "log" + "os" "regexp" "testing" @@ -480,6 +481,47 @@ func TestAccAWSCloudWatchEventRule_IsEnabled(t *testing.T) { }) } +func TestAccAWSCloudWatchEventRule_PartnerEventBus(t *testing.T) { + key := "EVENT_BRIDGE_PARTNER_EVENT_BUS_NAME" + busName := os.Getenv(key) + if busName == "" { + t.Skipf("Environment variable %s is not set", key) + } + + var v events.DescribeRuleOutput + rName := acctest.RandomWithPrefix("tf-acc-test-rule") + resourceName := "aws_cloudwatch_event_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, cloudwatchevents.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCloudWatchEventRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCloudWatchEventRulePartnerEventBusConfig(rName, busName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudWatchEventRuleExists(resourceName, &v), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "events", regexp.MustCompile(fmt.Sprintf(`rule/%s/%s$`, busName, rName))), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "event_bus_name", busName), + testAccCheckResourceAttrEquivalentJSON(resourceName, "event_pattern", "{\"source\":[\"aws.ec2\"]}"), + resource.TestCheckResourceAttr(resourceName, "is_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "role_arn", ""), + resource.TestCheckResourceAttr(resourceName, "schedule_expression", ""), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccCheckCloudWatchEventRuleExists(n string, rule *events.DescribeRuleOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -733,3 +775,18 @@ resource "aws_cloudwatch_event_rule" "test" { } `, name) } + +func testAccAWSCloudWatchEventRulePartnerEventBusConfig(rName, eventBusName string) string { + return fmt.Sprintf(` +resource "aws_cloudwatch_event_rule" "test" { + name = %[1]q + event_bus_name = %[2]q + + event_pattern = <