From 57a396e25e3f3cee40bbd931f53e3724bb729cb3 Mon Sep 17 00:00:00 2001 From: Samuel Suter Date: Thu, 22 Apr 2021 22:24:11 -0600 Subject: [PATCH] Add support for partner event bus and source --- .changelog/19072.txt | 7 ++ ...data_source_aws_cloudwatch_event_source.go | 73 +++++++++++++++++++ ...source_aws_cloudwatch_event_source_test.go | 51 +++++++++++++ aws/provider.go | 1 + aws/resource_aws_cloudwatch_event_bus.go | 20 +++++ aws/resource_aws_cloudwatch_event_bus_test.go | 37 ++++++++++ aws/validators.go | 7 +- aws/validators_test.go | 51 ++++++++++++- docs/MAINTAINING.md | 1 + .../d/cloudwatch_event_source.html.markdown | 40 ++++++++++ .../docs/r/cloudwatch_event_bus.html.markdown | 16 +++- 11 files changed, 298 insertions(+), 6 deletions(-) create mode 100644 .changelog/19072.txt create mode 100644 aws/data_source_aws_cloudwatch_event_source.go create mode 100644 aws/data_source_aws_cloudwatch_event_source_test.go create mode 100644 website/docs/d/cloudwatch_event_source.html.markdown diff --git a/.changelog/19072.txt b/.changelog/19072.txt new file mode 100644 index 00000000000..d9a398f0a9d --- /dev/null +++ b/.changelog/19072.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_cloudwatch_event_bus: Support partner event bus creation +``` + +```release-note:new-data-source +aws_cloudwatch_event_source +``` diff --git a/aws/data_source_aws_cloudwatch_event_source.go b/aws/data_source_aws_cloudwatch_event_source.go new file mode 100644 index 00000000000..4facf88f9b1 --- /dev/null +++ b/aws/data_source_aws_cloudwatch_event_source.go @@ -0,0 +1,73 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + events "github.com/aws/aws-sdk-go/service/cloudwatchevents" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceAwsCloudWatchEventSource() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsCloudWatchEventSourceRead, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "name_prefix": { + Type: schema.TypeString, + Optional: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "created_by": { + Type: schema.TypeString, + Computed: true, + }, + "state": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceAwsCloudWatchEventSourceRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloudwatcheventsconn + + input := &events.ListEventSourcesInput{} + if v, ok := d.GetOk("name_prefix"); ok { + input.NamePrefix = aws.String(v.(string)) + } + + log.Printf("[DEBUG] Listing cloudwatch Event sources: %s", input) + + resp, err := conn.ListEventSources(input) + if err != nil { + return fmt.Errorf("error listing cloudwatch event sources: %w", err) + } + + if resp == nil || len(resp.EventSources) == 0 { + return fmt.Errorf("no matching partner event source") + } + if len(resp.EventSources) > 1 { + return fmt.Errorf("multiple event sources matched; use additional constraints to reduce matches to a single event source") + } + + es := resp.EventSources[0] + + d.SetId(aws.StringValue(es.Name)) + d.Set("arn", es.Arn) + d.Set("created_by", es.CreatedBy) + d.Set("name", es.Name) + d.Set("state", es.State) + + return nil +} diff --git a/aws/data_source_aws_cloudwatch_event_source_test.go b/aws/data_source_aws_cloudwatch_event_source_test.go new file mode 100644 index 00000000000..4ea37acc63a --- /dev/null +++ b/aws/data_source_aws_cloudwatch_event_source_test.go @@ -0,0 +1,51 @@ +package aws + +import ( + "fmt" + "os" + "strings" + "testing" + + "github.com/aws/aws-sdk-go/service/cloudwatchevents" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataSourceAwsCloudWatchEventSource(t *testing.T) { + //resourceName := "aws_cloudwatch_event_bus.test" + dataSourceName := "data.aws_cloudwatch_event_source.test" + + key := "EVENT_BRIDGE_PARTNER_EVENT_BUS_NAME" + busName := os.Getenv(key) + if busName == "" { + t.Skipf("Environment variable %s is not set", key) + } + parts := strings.Split(busName, "/") + if len(parts) < 2 { + t.Errorf("unable to parse partner event bus name %s", busName) + } + namePrefix := parts[0] + "/" + parts[1] + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, cloudwatchevents.EndpointsID), + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAwsDataSourcePartnerEventSourceConfig(namePrefix), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, "name", busName), + resource.TestCheckResourceAttr(dataSourceName, "created_by", namePrefix), + resource.TestCheckResourceAttrSet(dataSourceName, "arn"), + ), + }, + }, + }) +} + +func testAccAwsDataSourcePartnerEventSourceConfig(namePrefix string) string { + return fmt.Sprintf(` +data "aws_cloudwatch_event_source" "test" { + name_prefix = "%s" +} +`, namePrefix) +} diff --git a/aws/provider.go b/aws/provider.go index 745c64e4937..495289c981e 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -217,6 +217,7 @@ func Provider() *schema.Provider { "aws_cloudfront_origin_request_policy": dataSourceAwsCloudFrontOriginRequestPolicy(), "aws_cloudhsm_v2_cluster": dataSourceCloudHsmV2Cluster(), "aws_cloudtrail_service_account": dataSourceAwsCloudTrailServiceAccount(), + "aws_cloudwatch_event_source": dataSourceAwsCloudWatchEventSource(), "aws_cloudwatch_log_group": dataSourceAwsCloudwatchLogGroup(), "aws_codeartifact_authorization_token": dataSourceAwsCodeArtifactAuthorizationToken(), "aws_codeartifact_repository_endpoint": dataSourceAwsCodeArtifactRepositoryEndpoint(), diff --git a/aws/resource_aws_cloudwatch_event_bus.go b/aws/resource_aws_cloudwatch_event_bus.go index ec6dce96eb8..234b7937645 100644 --- a/aws/resource_aws_cloudwatch_event_bus.go +++ b/aws/resource_aws_cloudwatch_event_bus.go @@ -3,6 +3,7 @@ package aws import ( "fmt" "log" + "regexp" "github.com/aws/aws-sdk-go/aws" events "github.com/aws/aws-sdk-go/service/cloudwatchevents" @@ -10,6 +11,10 @@ import ( "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" ) +var ( + partnerEventBusPattern = regexp.MustCompile(`^aws\.partner(/[\.\-_A-Za-z0-9]+){2,}$`) +) + func resourceAwsCloudWatchEventBus() *schema.Resource { return &schema.Resource{ Create: resourceAwsCloudWatchEventBusCreate, @@ -27,6 +32,13 @@ func resourceAwsCloudWatchEventBus() *schema.Resource { ForceNew: true, ValidateFunc: validateCloudWatchEventCustomEventBusName, }, + "event_source_name": { + Type: schema.TypeString, + Required: false, + Optional: true, + ForceNew: true, + ValidateFunc: validateCloudWatchEventCustomEventBusEventSourceName, + }, "arn": { Type: schema.TypeString, Computed: true, @@ -49,6 +61,10 @@ func resourceAwsCloudWatchEventBusCreate(d *schema.ResourceData, meta interface{ Name: aws.String(eventBusName), } + if v, ok := d.GetOk("event_source_name"); ok { + input.EventSourceName = aws.String(v.(string)) + } + if len(tags) > 0 { input.Tags = tags.IgnoreAws().CloudwatcheventsTags() } @@ -91,6 +107,10 @@ func resourceAwsCloudWatchEventBusRead(d *schema.ResourceData, meta interface{}) d.Set("arn", output.Arn) d.Set("name", output.Name) + // EventSourceName is an input field only, faking it on output if the event bus is a partner bus + if output.Name != nil && partnerEventBusPattern.MatchString(*output.Name) { + d.Set("event_source_name", output.Name) + } tags, err := keyvaluetags.CloudwatcheventsListTags(conn, aws.StringValue(output.Arn)) if err != nil { diff --git a/aws/resource_aws_cloudwatch_event_bus_test.go b/aws/resource_aws_cloudwatch_event_bus_test.go index 741833dce10..d0cdb3e9012 100644 --- a/aws/resource_aws_cloudwatch_event_bus_test.go +++ b/aws/resource_aws_cloudwatch_event_bus_test.go @@ -3,6 +3,7 @@ package aws import ( "fmt" "log" + "os" "regexp" "testing" @@ -218,6 +219,33 @@ func TestAccAWSCloudWatchEventBus_disappears(t *testing.T) { }) } +func TestAccAWSCloudWatchPartnerEventBus(t *testing.T) { + var busOutput events.DescribeEventBusOutput + resourceName := "aws_cloudwatch_event_bus.test" + key := "EVENT_BRIDGE_PARTNER_EVENT_BUS_NAME" + busName := os.Getenv(key) + if busName == "" { + t.Skipf("Environment variable %s is not set", key) + } + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, cloudwatchevents.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCloudWatchEventBusDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCloudWatchPartnerEventBusConfig(busName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudWatchEventBusExists(resourceName, &busOutput), + resource.TestCheckResourceAttr(resourceName, "name", busName), + testAccCheckResourceAttrRegionalARN(resourceName, "arn", "events", fmt.Sprintf("event-bus/%s", busName)), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + }, + }) +} + func testAccCheckAWSCloudWatchEventBusDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).cloudwatcheventsconn @@ -315,3 +343,12 @@ resource "aws_cloudwatch_event_bus" "test" { } `, name, key1, value1, key2, value2) } + +func testAccAWSCloudWatchPartnerEventBusConfig(name string) string { + return fmt.Sprintf(` +resource "aws_cloudwatch_event_bus" "test" { + name = %[1]q + event_source_name = %[1]q +} +`, name) +} diff --git a/aws/validators.go b/aws/validators.go index 2aa681a254f..d55b27835ee 100644 --- a/aws/validators.go +++ b/aws/validators.go @@ -2347,10 +2347,15 @@ func validateRoute53ResolverName(v interface{}, k string) (ws []string, errors [ var validateCloudWatchEventCustomEventBusName = validation.All( validation.StringLenBetween(1, 256), - validation.StringMatch(regexp.MustCompile(`^[a-zA-Z0-9._\-]+$`), ""), + validation.StringMatch(regexp.MustCompile(`^[/\.\-_A-Za-z0-9]+$`), ""), validation.StringDoesNotMatch(regexp.MustCompile(`^default$`), "cannot be 'default'"), ) +var validateCloudWatchEventCustomEventBusEventSourceName = validation.All( + validation.StringLenBetween(1, 256), + validation.StringMatch(regexp.MustCompile(`^aws\.partner(/[\.\-_A-Za-z0-9]+){2,}$`), ""), +) + var validateCloudWatchEventBusNameOrARN = validation.Any( validateArn, validation.All( diff --git a/aws/validators_test.go b/aws/validators_test.go index 996c7ea4e8b..47bd3ac0ea9 100644 --- a/aws/validators_test.go +++ b/aws/validators_test.go @@ -3136,12 +3136,12 @@ func TestCloudWatchEventCustomEventBusName(t *testing.T) { IsValid: false, }, { - Value: "aws.partner/test/test", - IsValid: false, + Value: "aws.partner/example.com/test/12345ab-cdef-1235", + IsValid: true, }, { Value: "/test0._1-", - IsValid: false, + IsValid: true, }, { Value: "test0._1-", @@ -3159,6 +3159,51 @@ func TestCloudWatchEventCustomEventBusName(t *testing.T) { } } +func TestCloudWatchEventCustomEventBusEventSourceName(t *testing.T) { + cases := []struct { + Value string + IsValid bool + }{ + { + Value: "", + IsValid: false, + }, + { + Value: "default", + IsValid: false, + }, + { + Value: "aws.partner/example.com/test/" + acctest.RandStringFromCharSet(227, acctest.CharSetAlpha), + IsValid: true, + }, + { + Value: "aws.partner/example.com/test/" + acctest.RandStringFromCharSet(228, acctest.CharSetAlpha), + IsValid: false, + }, + { + Value: "aws.partner/example.com/test/12345ab-cdef-1235", + IsValid: true, + }, + { + Value: "/test0._1-", + IsValid: false, + }, + { + Value: "test0._1-", + IsValid: false, + }, + } + for _, tc := range cases { + _, errors := validateCloudWatchEventCustomEventBusEventSourceName(tc.Value, "aws_cloudwatch_event_bus_event_source_name") + isValid := len(errors) == 0 + if tc.IsValid && !isValid { + t.Errorf("expected %q to return valid, but did not", tc.Value) + } else if !tc.IsValid && isValid { + t.Errorf("expected %q to not return valid, but did", tc.Value) + } + } +} + func TestValidateServiceDiscoveryNamespaceName(t *testing.T) { validNames := []string{ "ValidName", diff --git a/docs/MAINTAINING.md b/docs/MAINTAINING.md index 783189a5394..78d6f6c0c05 100644 --- a/docs/MAINTAINING.md +++ b/docs/MAINTAINING.md @@ -387,6 +387,7 @@ Environment variables (beyond standard AWS Go SDK ones) used by acceptance testi | `DX_CONNECTION_ID` | Identifier for Direct Connect Connection testing. | | `DX_VIRTUAL_INTERFACE_ID` | Identifier for Direct Connect Virtual Interface testing. | | `EC2_SECURITY_GROUP_RULES_PER_GROUP_LIMIT` | EC2 Quota for Rules per Security Group. Defaults to 50. **DEPRECATED:** Can be augmented or replaced with Service Quotas lookup. | +| `EVENT_BRIDGE_PARTNER_EVENT_BUS_NAME` | Amazon EventBridge partner event bus name. | | `GCM_API_KEY` | API Key for Google Cloud Messaging in Pinpoint and SNS Platform Application testing. | | `GITHUB_TOKEN` | GitHub token for CodePipeline testing. | | `MACIE_MEMBER_ACCOUNT_ID` | Identifier of AWS Account for Macie Member testing. **DEPRECATED:** Should be replaced with standard alternate account handling for tests. | diff --git a/website/docs/d/cloudwatch_event_source.html.markdown b/website/docs/d/cloudwatch_event_source.html.markdown new file mode 100644 index 00000000000..95c39bdab07 --- /dev/null +++ b/website/docs/d/cloudwatch_event_source.html.markdown @@ -0,0 +1,40 @@ +--- +subcategory: "CloudWatch" +layout: "aws" +page_title: "AWS: aws_cloudwatch_event_source" +description: |- + Get information on an EventBridge (Cloudwatch) Event Source. +--- + +# Data Source: aws_cloudwatch_event_source + +Use this data source to get information about an EventBridge Partner Event Source. This data source will only return one partner event source. An error will be returned if multiple sources match the same name prefix. + +~> **Note:** EventBridge was formerly known as CloudWatch Events. The functionality is identical. + +## Example Usage + +```terraform +data "aws_cloudwatch_event_source" "examplepartner" { + name_prefix = "aws.partner/examplepartner.com" +} + +resource "aws_cloudwatch_event_bus" "examplepartner" { + name = data.aws_cloudwatch_event_source.examplepartner.name + event_source_name = data.aws_cloudwatch_event_source.examplepartner.name +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name_prefix` - (Optional) A name prefix to filter results returned. Only API destinations with a name that starts with the prefix are returned + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - The ARN of the Partner event source +* `created_by` - The name of the SaaS partner that created the event source. +* `state` - The state of the event source (`ACTIVE` or `PENDING`) diff --git a/website/docs/r/cloudwatch_event_bus.html.markdown b/website/docs/r/cloudwatch_event_bus.html.markdown index cff9b15fc7f..1881f152014 100644 --- a/website/docs/r/cloudwatch_event_bus.html.markdown +++ b/website/docs/r/cloudwatch_event_bus.html.markdown @@ -21,11 +21,23 @@ resource "aws_cloudwatch_event_bus" "messenger" { } ``` +```terraform +data "aws_cloudwatch_event_source" "examplepartner" { + name_prefix = "aws.partner/examplepartner.com" +} + +resource "aws_cloudwatch_event_bus" "examplepartner" { + name = data.aws_cloudwatch_event_source.examplepartner.name + event_source_name = data.aws_cloudwatch_event_source.examplepartner.name +} +``` + ## Argument Reference The following arguments are supported: -* `name` - (Required) The name of the new event bus. The names of custom event buses can't contain the / character. Please note that a partner event bus is not supported at the moment. +* `name` - (Required) The name of the new event bus. The names of custom event buses can't contain the / character. To create a partner event bus, ensure the `name` matches the `event_source_name`. +* `event_source_name` (Optional) The partner event source that the new event bus will be matched with. Must match `name`. * `tags` - (Optional) A map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. ## Attributes Reference @@ -37,7 +49,7 @@ In addition to all arguments above, the following attributes are exported: ## Import -EventBridge event buses can be imported using the `name`, e.g. +EventBridge event buses can be imported using the `name` (which can also be a partner event source name), e.g. ```console $ terraform import aws_cloudwatch_event_bus.messenger chat-messages