diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bfd5e1bc..27cefdaa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -90,8 +90,14 @@ jobs: SUMOLOGIC_ACCESSKEY: ${{ secrets.SUMOLOGIC_ACCESSKEY }} SUMOLOGIC_ENVIRONMENT: ${{ secrets.SUMOLOGIC_ENVIRONMENT }} SUMOLOGIC_TEST_BUCKET_NAME: ${{ secrets.SUMOLOGIC_TEST_BUCKET_NAME }} - SUMOLOGIC_TEST_ROLE_ARN: ${{ secrets.SUMOLOGIC_TEST_ROLE_ARN }} + SUMOLOGIC_TEST_CONSUMER_GROUP: ${{ secrets.SUMOLOGIC_TEST_CONSUMER_GROUP }} + SUMOLOGIC_TEST_EVENT_HUB: ${{ secrets.SUMOLOGIC_TEST_EVENT_HUB }} SUMOLOGIC_TEST_GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.SUMOLOGIC_TEST_GOOGLE_APPLICATION_CREDENTIALS }} + SUMOLOGIC_TEST_NAMESPACE: ${{ secrets.SUMOLOGIC_TEST_NAMESPACE }} + SUMOLOGIC_TEST_REGION: ${{ secrets.SUMOLOGIC_TEST_REGION }} + SUMOLOGIC_TEST_ROLE_ARN: ${{ secrets.SUMOLOGIC_TEST_ROLE_ARN }} + SUMOLOGIC_TEST_SAS_KEY: ${{ secrets.SUMOLOGIC_TEST_SAS_KEY }} + SUMOLOGIC_TEST_SAS_KEY_NAME: ${{ secrets.SUMOLOGIC_TEST_SAS_KEY_NAME }} # disable go test timeout. We rely on GitHub action timeout. run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a11f190..cb1b997a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 2.29.1 (Unreleased) FEATURES: * resource/sumologic_muting_schedule: Added support for Muting Schedule for an alert group (GH-601) +* **New Resource:** sumologic_azure_event_hub_log_source (GH-626) ## 2.29.0 (April 9, 2024) diff --git a/sumologic/provider.go b/sumologic/provider.go index f4632b8d..c7d65409 100644 --- a/sumologic/provider.go +++ b/sumologic/provider.go @@ -117,6 +117,7 @@ func Provider() terraform.ResourceProvider { "sumologic_log_search": resourceSumologicLogSearch(), "sumologic_metrics_search": resourceSumologicMetricsSearch(), "sumologic_rum_source": resourceSumologicRumSource(), + "sumologic_azure_event_hub_log_source": resourceSumologicGenericPollingSource(), }, DataSourcesMap: map[string]*schema.Resource{ "sumologic_cse_log_mapping_vendor_product": dataSourceCSELogMappingVendorAndProduct(), diff --git a/sumologic/resource_sumologic_azure_event_hub_log_source_test.go b/sumologic/resource_sumologic_azure_event_hub_log_source_test.go new file mode 100644 index 00000000..348029ca --- /dev/null +++ b/sumologic/resource_sumologic_azure_event_hub_log_source_test.go @@ -0,0 +1,193 @@ +package sumologic + +import ( + "fmt" + "os" + "strconv" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccSumologicAzureEventHubLogSource_create(t *testing.T) { + var azureEventHubLogSource PollingSource + var collector Collector + cName, cDescription, cCategory := getRandomizedParams() + sName, sDescription, sCategory := getRandomizedParams() + azureEventHubLogResourceName := "sumologic_azure_event_hub_log_source.azure" + testNamespace := os.Getenv("SUMOLOGIC_TEST_NAMESPACE") + testEventHub := os.Getenv("SUMOLOGIC_TEST_EVENT_HUB") + testConsumerGroup := os.Getenv("SUMOLOGIC_TEST_CONSUMER_GROUP") + testRegion := os.Getenv("SUMOLOGIC_TEST_REGION") + testSASKeyName := os.Getenv("SUMOLOGIC_TEST_SAS_KEY_NAME") + testSASKey := os.Getenv("SUMOLOGIC_TEST_SAS_KEY") + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAzureEventHubLogSourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSumologicAzureEventHubLogSourceConfig(cName, cDescription, cCategory, sName, sDescription, sCategory, testSASKeyName, testSASKey, testNamespace, testEventHub, testConsumerGroup, testRegion), + Check: resource.ComposeTestCheckFunc( + testAccCheckAzureEventHubLogSourceExists(azureEventHubLogResourceName, &azureEventHubLogSource), + testAccCheckAzureEventHubLogSourceValues(&azureEventHubLogSource, sName, sDescription, sCategory), + testAccCheckCollectorExists("sumologic_collector.test", &collector), + testAccCheckCollectorValues(&collector, cName, cDescription, cCategory, "Etc/UTC", ""), + resource.TestCheckResourceAttrSet(azureEventHubLogResourceName, "id"), + resource.TestCheckResourceAttr(azureEventHubLogResourceName, "name", sName), + resource.TestCheckResourceAttr(azureEventHubLogResourceName, "description", sDescription), + resource.TestCheckResourceAttr(azureEventHubLogResourceName, "category", sCategory), + resource.TestCheckResourceAttr(azureEventHubLogResourceName, "content_type", "AzureEventHubLog"), + resource.TestCheckResourceAttr(azureEventHubLogResourceName, "path.0.type", "AzureEventHubPath"), + ), + }, + }, + }) +} +func TestAccSumologicAzureEventHubLogSource_update(t *testing.T) { + var azureEventHubLogSource PollingSource + cName, cDescription, cCategory := getRandomizedParams() + sName, sDescription, sCategory := getRandomizedParams() + sNameUpdated, sDescriptionUpdated, sCategoryUpdated := getRandomizedParams() + azureEventHubLogResourceName := "sumologic_azure_event_hub_log_source.azure" + testNamespace := os.Getenv("SUMOLOGIC_TEST_NAMESPACE") + testEventHub := os.Getenv("SUMOLOGIC_TEST_EVENT_HUB") + testConsumerGroup := os.Getenv("SUMOLOGIC_TEST_CONSUMER_GROUP") + testRegion := os.Getenv("SUMOLOGIC_TEST_REGION") + testSASKeyName := os.Getenv("SUMOLOGIC_TEST_SAS_KEY_NAME") + testSASKey := os.Getenv("SUMOLOGIC_TEST_SAS_KEY") + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAzureEventHubLogSourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSumologicAzureEventHubLogSourceConfig(cName, cDescription, cCategory, sName, sDescription, sCategory, testSASKeyName, testSASKey, testNamespace, testEventHub, testConsumerGroup, testRegion), + Check: resource.ComposeTestCheckFunc( + testAccCheckAzureEventHubLogSourceExists(azureEventHubLogResourceName, &azureEventHubLogSource), + testAccCheckAzureEventHubLogSourceValues(&azureEventHubLogSource, sName, sDescription, sCategory), + resource.TestCheckResourceAttrSet(azureEventHubLogResourceName, "id"), + resource.TestCheckResourceAttr(azureEventHubLogResourceName, "name", sName), + resource.TestCheckResourceAttr(azureEventHubLogResourceName, "description", sDescription), + resource.TestCheckResourceAttr(azureEventHubLogResourceName, "category", sCategory), + resource.TestCheckResourceAttr(azureEventHubLogResourceName, "content_type", "AzureEventHubLog"), + resource.TestCheckResourceAttr(azureEventHubLogResourceName, "path.0.type", "AzureEventHubPath"), + ), + }, + { + Config: testAccSumologicAzureEventHubLogSourceConfig(cName, cDescription, cCategory, sNameUpdated, sDescriptionUpdated, sCategoryUpdated, testSASKeyName, testSASKey, testNamespace, testEventHub, testConsumerGroup, testRegion), + Check: resource.ComposeTestCheckFunc( + testAccCheckAzureEventHubLogSourceExists(azureEventHubLogResourceName, &azureEventHubLogSource), + testAccCheckAzureEventHubLogSourceValues(&azureEventHubLogSource, sNameUpdated, sDescriptionUpdated, sCategoryUpdated), + resource.TestCheckResourceAttrSet(azureEventHubLogResourceName, "id"), + resource.TestCheckResourceAttr(azureEventHubLogResourceName, "name", sNameUpdated), + resource.TestCheckResourceAttr(azureEventHubLogResourceName, "description", sDescriptionUpdated), + resource.TestCheckResourceAttr(azureEventHubLogResourceName, "category", sCategoryUpdated), + resource.TestCheckResourceAttr(azureEventHubLogResourceName, "content_type", "AzureEventHubLog"), + resource.TestCheckResourceAttr(azureEventHubLogResourceName, "path.0.type", "AzureEventHubPath"), + ), + }, + }, + }) +} +func testAccCheckAzureEventHubLogSourceDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*Client) + for _, rs := range s.RootModule().Resources { + if rs.Type != "sumologic_azure_event_hub_log_source" { + continue + } + if rs.Primary.ID == "" { + return fmt.Errorf("Azure Event Hub Log Source destruction check: Azure Event Hub Log Source ID is not set") + } + id, err := strconv.Atoi(rs.Primary.ID) + if err != nil { + return fmt.Errorf("Encountered an error: " + err.Error()) + } + collectorID, err := strconv.Atoi(rs.Primary.Attributes["collector_id"]) + if err != nil { + return fmt.Errorf("Encountered an error: " + err.Error()) + } + s, err := client.GetPollingSource(collectorID, id) + if err != nil { + return fmt.Errorf("Encountered an error: " + err.Error()) + } + if s != nil { + return fmt.Errorf("Polling Source still exists") + } + } + return nil +} +func testAccCheckAzureEventHubLogSourceExists(n string, pollingSource *PollingSource) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("not found: %s", n) + } + if rs.Primary.ID == "" { + return fmt.Errorf("Polling Source ID is not set") + } + id, err := strconv.Atoi(rs.Primary.ID) + if err != nil { + return fmt.Errorf("Polling Source id should be int; got %s", rs.Primary.ID) + } + collectorID, err := strconv.Atoi(rs.Primary.Attributes["collector_id"]) + if err != nil { + return fmt.Errorf("Encountered an error: " + err.Error()) + } + c := testAccProvider.Meta().(*Client) + pollingSourceResp, err := c.GetPollingSource(collectorID, id) + if err != nil { + return err + } + *pollingSource = *pollingSourceResp + return nil + } +} +func testAccCheckAzureEventHubLogSourceValues(pollingSource *PollingSource, name, description, category string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if pollingSource.Name != name { + return fmt.Errorf("bad name, expected \"%s\", got: %#v", name, pollingSource.Name) + } + if pollingSource.Description != description { + return fmt.Errorf("bad description, expected \"%s\", got: %#v", description, pollingSource.Description) + } + if pollingSource.Category != category { + return fmt.Errorf("bad category, expected \"%s\", got: %#v", category, pollingSource.Category) + } + return nil + } +} +func testAccSumologicAzureEventHubLogSourceConfig(cName, cDescription, cCategory, sName, sDescription, sCategory, testSASKeyName, testSASKey, testNamespace, testEventHub, testConsumerGroup, testRegion string) string { + return fmt.Sprintf(` +resource "sumologic_collector" "test" { + name = "%s" + description = "%s" + category = "%s" +} +resource "sumologic_azure_event_hub_log_source" "azure" { + name = "%s" + description = "%s" + category = "%s" + content_type = "AzureEventHubLog" + collector_id = "${sumologic_collector.test.id}" + + authentication { + type = "AzureEventHubAuthentication" + shared_access_policy_name = "%s" + shared_access_policy_key = "%s" + } + + path { + type = "AzureEventHubPath" + namespace = "%s" + event_hub_name = "%s" + consumer_group = "%s" + region = "%s" + } + + lifecycle { + ignore_changes = [authentication.0.shared_access_policy_key] + } +}`, cName, cDescription, cCategory, sName, sDescription, sCategory, testSASKeyName, testSASKey, testNamespace, testEventHub, testConsumerGroup, testRegion) +} diff --git a/sumologic/resource_sumologic_generic_polling_source.go b/sumologic/resource_sumologic_generic_polling_source.go index ad7b4463..829fad4b 100644 --- a/sumologic/resource_sumologic_generic_polling_source.go +++ b/sumologic/resource_sumologic_generic_polling_source.go @@ -25,15 +25,21 @@ func resourceSumologicGenericPollingSource() *schema.Resource { Required: true, ForceNew: true, ValidateFunc: validation.StringInSlice([]string{"AwsS3Bucket", "AwsElbBucket", "AwsCloudFrontBucket", - "AwsCloudTrailBucket", "AwsS3AuditBucket", "AwsCloudWatch", "AwsInventory", "AwsXRay", "GcpMetrics", "AwsS3ArchiveBucket"}, false), + "AwsCloudTrailBucket", "AwsS3AuditBucket", "AwsCloudWatch", "AwsInventory", "AwsXRay", "GcpMetrics", "AwsS3ArchiveBucket", "AzureEventHubLog"}, false), } pollingSource.Schema["scan_interval"] = &schema.Schema{ Type: schema.TypeInt, - Required: true, + Optional: true, + Default: 300000, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + contentType := d.Get("content_type").(string) + return contentType == "AzureEventHubLog" + }, } pollingSource.Schema["paused"] = &schema.Schema{ Type: schema.TypeBool, - Required: true, + Optional: true, + Default: false, } pollingSource.Schema["url"] = &schema.Schema{ Type: schema.TypeString, @@ -50,7 +56,7 @@ func resourceSumologicGenericPollingSource() *schema.Resource { "type": { Type: schema.TypeString, Required: true, - ValidateFunc: validation.StringInSlice([]string{"S3BucketAuthentication", "AWSRoleBasedAuthentication", "service_account"}, false), + ValidateFunc: validation.StringInSlice([]string{"S3BucketAuthentication", "AWSRoleBasedAuthentication", "service_account", "AzureEventHubAuthentication"}, false), }, "access_key": { Type: schema.TypeString, @@ -104,6 +110,14 @@ func resourceSumologicGenericPollingSource() *schema.Resource { Type: schema.TypeString, Optional: true, }, + "shared_access_policy_name": { + Type: schema.TypeString, + Optional: true, + }, + "shared_access_policy_key": { + Type: schema.TypeString, + Optional: true, + }, }, }, } @@ -119,7 +133,7 @@ func resourceSumologicGenericPollingSource() *schema.Resource { Type: schema.TypeString, Required: true, ValidateFunc: validation.StringInSlice([]string{"S3BucketPathExpression", "CloudWatchPath", - "AwsInventoryPath", "AwsXRayPath", "GcpMetricsPath"}, false), + "AwsInventoryPath", "AwsXRayPath", "GcpMetricsPath", "AzureEventHubPath"}, false), }, "bucket_name": { Type: schema.TypeString, @@ -220,6 +234,22 @@ func resourceSumologicGenericPollingSource() *schema.Resource { }, }, }, + "namespace": { + Type: schema.TypeString, + Optional: true, + }, + "event_hub_name": { + Type: schema.TypeString, + Optional: true, + }, + "consumer_group": { + Type: schema.TypeString, + Optional: true, + }, + "region": { + Type: schema.TypeString, + Optional: true, + }, }, }, } @@ -315,6 +345,10 @@ func resourceToGenericPollingSource(d *schema.ResourceData) (PollingSource, erro URL: d.Get("url").(string), } + if source.ContentType == "AzureEventHubLog" { + pollingSource.ScanInterval = -1 + } + authSettings, errAuthSettings := getPollingAuthentication(d) if errAuthSettings != nil { return pollingSource, errAuthSettings @@ -352,7 +386,12 @@ func getPollingThirdPartyPathAttributes(pollingResource []PollingResource) []map "custom_services": flattenCustomServices(t.Path.CustomServices), "tag_filters": flattenPollingTagFilters(t.Path.TagFilters), "sns_topic_or_subscription_arn": flattenPollingSnsTopicOrSubscriptionArn(t.Path.SnsTopicOrSubscriptionArn), + "namespace": t.Path.Namespace, + "event_hub_name": t.Path.EventHubName, + "consumer_group": t.Path.ConsumerGroup, + "region": t.Path.Region, } + s = append(s, mapping) } return s @@ -378,6 +417,8 @@ func getPollingThirdPartyAuthenticationAttributes(pollingResource []PollingResou "token_uri": t.Authentication.TokenUrl, "auth_provider_x509_cert_url": t.Authentication.AuthProviderX509CertUrl, "client_x509_cert_url": t.Authentication.ClientX509CertUrl, + "shared_access_policy_name": t.Authentication.SharedAccessPolicyName, + "shared_access_policy_key": t.Authentication.SharedAccessPolicyKey, } s = append(s, mapping) } @@ -539,6 +580,10 @@ func getPollingAuthentication(d *schema.ResourceData) (PollingAuthentication, er if err != nil { return authSettings, err } + case "AzureEventHubAuthentication": + authSettings.Type = "AzureEventHubAuthentication" + authSettings.SharedAccessPolicyName = auth["shared_access_policy_name"].(string) + authSettings.SharedAccessPolicyKey = auth["shared_access_policy_key"].(string) default: errorMessage := fmt.Sprintf("[ERROR] Unknown authType: %v", authType) @@ -625,6 +670,14 @@ func getPollingPathSettings(d *schema.ResourceData) (PollingPath, error) { case "GcpMetricsPath": pathSettings.Type = pathType addGcpMetricsPathSettings(&pathSettings, path) + case "AzureEventHubPath": + pathSettings.Type = "AzureEventHubPath" + pathSettings.Namespace = path["namespace"].(string) + pathSettings.EventHubName = path["event_hub_name"].(string) + pathSettings.ConsumerGroup = path["consumer_group"].(string) + if path["region"] != nil { + pathSettings.Region = path["region"].(string) + } default: errorMessage := fmt.Sprintf("[ERROR] Unknown resourceType in path: %v", pathType) log.Print(errorMessage) diff --git a/sumologic/sumologic_polling_source.go b/sumologic/sumologic_polling_source.go index 4765574b..cf367d4e 100644 --- a/sumologic/sumologic_polling_source.go +++ b/sumologic/sumologic_polling_source.go @@ -39,6 +39,8 @@ type PollingAuthentication struct { TokenUrl string `json:"token_uri"` AuthProviderX509CertUrl string `json:"auth_provider_x509_cert_url"` ClientX509CertUrl string `json:"client_x509_cert_url"` + SharedAccessPolicyName string `json:"sharedAccessPolicyName"` + SharedAccessPolicyKey string `json:"sharedAccessPolicyKey"` } type PollingPath struct { @@ -52,6 +54,10 @@ type PollingPath struct { TagFilters []TagFilter `json:"tagFilters,omitempty"` SnsTopicOrSubscriptionArn PollingSnsTopicOrSubscriptionArn `json:"snsTopicOrSubscriptionArn,omitempty"` UseVersionedApi *bool `json:"useVersionedApi,omitempty"` + Namespace string `json:"namespace,omitempty"` + EventHubName string `json:"eventHubName,omitempty"` + ConsumerGroup string `json:"consumerGroup,omitempty"` + Region string `json:"region,omitempty"` } type TagFilter struct { diff --git a/website/docs/r/azure_event_hub_log_source.html.markdown b/website/docs/r/azure_event_hub_log_source.html.markdown new file mode 100644 index 00000000..ca46b97c --- /dev/null +++ b/website/docs/r/azure_event_hub_log_source.html.markdown @@ -0,0 +1,55 @@ +--- +layout: "sumologic" +page_title: "SumoLogic: sumologic_azure_event_hub_log_source" +description: |- + Provides a Sumologic Azure Event Hub Log Source. +--- + +# sumologic_azure_event_hub_log_source +Provides a [Sumologic Azure Event Hub Log Source](https://help.sumologic.com/docs/send-data/collect-from-other-data-sources/azure-monitoring/ms-azure-event-hubs-source/). + +__IMPORTANT:__ The Azure Event Hub credentials are stored in plain-text in the state. This is a potential security issue. + +## Example Usage +```hcl + resource "sumologic_azure_event_hub_log_source" "terraform_azure_event_hub_log_source" { + name = "Azure Event Hub Log Source" + description = "My description" + category = "azure/eventhub" + content_type = "AzureEventHubLog" + collector_id = "${sumologic_collector.collector.id}" + authentication { + type = "AzureEventHubAuthentication" + shared_access_policy_name = "%s" + shared_access_policy_key = "%s" + } + path { + type = "AzureEventHubPath" + namespace = "%s" + event_hub_name = "%s" + consumer_group = "%s" + region = "%s" + } + } + + resource "sumologic_collector" "collector" { + name = "my-collector" + description = "Just testing this" + } +``` + +## Argument reference + +In addition to the [Common Source Properties](https://registry.terraform.io/providers/SumoLogic/sumologic/latest/docs#common-source-properties), the following arguments are supported: + + - `content_type` - (Required) Must be `AzureEventHubLog`. + - `authentication` - (Required) Authentication details for connecting to Azure Event Hub. + + `type` - (Required) Must be `AzureEventHubAuthentication`. + + `shared_access_policy_name` - (Required) Your shared access policy name. + + `shared_access_policy_key` - (Required) Your shared access policy key. + - `path` - (Required) The location to scan for new data. + + `type` - (Required) Must be `AzureEventHubPath`. + + `namespace` - (Required) The namespace of the event hub. + + `event_hub_name` - (Required) The name of the event hub. + + `consumer_group` - (Required) The consumer group of the event hub. + + `region` - (Optional) The region of the event hub. The value can be either `Commercial` for Azure, or `US Gov` for Azure Government. Defaults to `Commercial`.