diff --git a/CHANGELOG.md b/CHANGELOG.md index aa12f68a..2039d82d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,18 @@ -## 2.24.1 (Unreleased) +## 2.25.1 (Unreleased) + +## 2.25.0 (August 8, 2023) FEATURES: * **New Resource:** sumologic_metrics_search (GH-528) * resource/sumologic_monitor: Added support for associating tags with a Monitor. +* **New Resource:** sumologic_rum_source (GH-547) +* Add `budgetType` support for sumologic_ingest_budget_v2 (GH-549) BUG FIXES: * Enforce non-empty string validation of `default_normalized_domain` and `domain_mappings` in cse_entity_normalization resource. (GH-540) +* Fixes `sumologic_s3_source` to allow setting `use_versioned_api` parameter to `false`. (GH-555) + +DEPRECATIONS: +* resource_sumologic_ingest_budget : Deprecated in favour of `resource_sumologic_ingest_budget_v2`. ## 2.24.0 (June 22, 2023) FEATURES: diff --git a/sumologic/provider.go b/sumologic/provider.go index acbfca9d..5cac3cf0 100644 --- a/sumologic/provider.go +++ b/sumologic/provider.go @@ -112,6 +112,7 @@ func Provider() terraform.ResourceProvider { "sumologic_local_file_source": resourceSumologicLocalFileSource(), "sumologic_log_search": resourceSumologicLogSearch(), "sumologic_metrics_search": resourceSumologicMetricsSearch(), + "sumologic_rum_source": resourceSumologicRumSource(), }, DataSourcesMap: map[string]*schema.Resource{ "sumologic_cse_log_mapping_vendor_product": dataSourceCSELogMappingVendorAndProduct(), diff --git a/sumologic/resource_sumologic_generic_polling_source.go b/sumologic/resource_sumologic_generic_polling_source.go index 0b10845f..ad7b4463 100644 --- a/sumologic/resource_sumologic_generic_polling_source.go +++ b/sumologic/resource_sumologic_generic_polling_source.go @@ -128,6 +128,14 @@ func resourceSumologicGenericPollingSource() *schema.Resource { "use_versioned_api": { Type: schema.TypeBool, Optional: true, + Default: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + contentType := d.Get("content_type").(string) + if contentType != "AwsS3Bucket" { + return true + } + return false + }, }, "path_expression": { Type: schema.TypeString, @@ -578,7 +586,8 @@ func getPollingPathSettings(d *schema.ResourceData) (PollingPath, error) { pathSettings.BucketName = path["bucket_name"].(string) pathSettings.PathExpression = path["path_expression"].(string) if path["use_versioned_api"] != nil { - pathSettings.UseVersionedApi = path["use_versioned_api"].(bool) + val := path["use_versioned_api"].(bool) + pathSettings.UseVersionedApi = &val } pathSettings.SnsTopicOrSubscriptionArn = getPollingSnsTopicOrSubscriptionArn(d) case "CloudWatchPath", "AwsInventoryPath": diff --git a/sumologic/resource_sumologic_ingest_budget.go b/sumologic/resource_sumologic_ingest_budget.go index 30370a5b..943d0ba7 100644 --- a/sumologic/resource_sumologic_ingest_budget.go +++ b/sumologic/resource_sumologic_ingest_budget.go @@ -1,17 +1,19 @@ package sumologic import ( + "log" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" - "log" ) func resourceSumologicIngestBudget() *schema.Resource { return &schema.Resource{ - Create: resourceSumologicIngestBudgetCreate, - Read: resourceSumologicIngestBudgetRead, - Delete: resourceSumologicIngestBudgetDelete, - Update: resourceSumologicIngestBudgetUpdate, + DeprecationMessage: "Use resource_sumologic_ingest_budget_v2 instead.", + Create: resourceSumologicIngestBudgetCreate, + Read: resourceSumologicIngestBudgetRead, + Delete: resourceSumologicIngestBudgetDelete, + Update: resourceSumologicIngestBudgetUpdate, Importer: &schema.ResourceImporter{ State: resourceSumologicIngestBudgetImport, }, diff --git a/sumologic/resource_sumologic_ingest_budget_v2.go b/sumologic/resource_sumologic_ingest_budget_v2.go index b89217b4..3ca0879a 100644 --- a/sumologic/resource_sumologic_ingest_budget_v2.go +++ b/sumologic/resource_sumologic_ingest_budget_v2.go @@ -71,6 +71,13 @@ func resourceSumologicIngestBudgetV2() *schema.Resource { ForceNew: false, }, + "budget_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: false, + Default: "dailyVolume", + }, + "name": { Type: schema.TypeString, Required: true, @@ -119,6 +126,7 @@ func resourceSumologicIngestBudgetV2Read(d *schema.ResourceData, meta interface{ d.Set("description", ingestBudgetV2.Description) d.Set("action", ingestBudgetV2.Action) d.Set("capacity_bytes", ingestBudgetV2.CapacityBytes) + d.Set("budget_type", ingestBudgetV2.BudgetType) return nil } @@ -150,6 +158,7 @@ func resourceToIngestBudgetV2(d *schema.ResourceData) IngestBudgetV2 { Timezone: d.Get("timezone").(string), ID: d.Id(), Action: d.Get("action").(string), + BudgetType: d.Get("budget_type").(string), Description: d.Get("description").(string), AuditThreshold: d.Get("audit_threshold").(int), CapacityBytes: d.Get("capacity_bytes").(int), diff --git a/sumologic/resource_sumologic_ingest_budget_v2_test.go b/sumologic/resource_sumologic_ingest_budget_v2_test.go index 3aeff3af..48834162 100644 --- a/sumologic/resource_sumologic_ingest_budget_v2_test.go +++ b/sumologic/resource_sumologic_ingest_budget_v2_test.go @@ -31,6 +31,7 @@ func TestAccSumologicIngestBudgetV2_basic(t *testing.T) { testDescription := "description-7hUwr" testAction := "stopCollecting" testCapacityBytes := 1000 + testBudgetType := "dailyVolume" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -38,7 +39,7 @@ func TestAccSumologicIngestBudgetV2_basic(t *testing.T) { CheckDestroy: testAccCheckIngestBudgetV2Destroy(ingestBudgetV2), Steps: []resource.TestStep{ { - Config: testAccCheckSumologicIngestBudgetV2ConfigImported(testName, testScope, testTimezone, testResetTime, testAuditThreshold, testDescription, testAction, testCapacityBytes), + Config: testAccCheckSumologicIngestBudgetV2ConfigImported(testName, testScope, testTimezone, testResetTime, testAuditThreshold, testDescription, testAction, testCapacityBytes, testBudgetType), }, { ResourceName: "sumologic_ingest_budget_v2.foo", @@ -58,13 +59,14 @@ func TestAccSumologicIngestBudgetV2_create(t *testing.T) { testDescription := "description-900AB" testAction := "stopCollecting" testCapacityBytes := 1000 + testBudgetType := "dailyVolume" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckIngestBudgetV2Destroy(ingestBudgetV2), Steps: []resource.TestStep{ { - Config: testAccSumologicIngestBudgetV2(testName, testScope, testTimezone, testResetTime, testAuditThreshold, testDescription, testAction, testCapacityBytes), + Config: testAccSumologicIngestBudgetV2(testName, testScope, testTimezone, testResetTime, testAuditThreshold, testDescription, testAction, testCapacityBytes, testBudgetType), Check: resource.ComposeTestCheckFunc( testAccCheckIngestBudgetV2Exists("sumologic_ingest_budget_v2.test", &ingestBudgetV2, t), testAccCheckIngestBudgetV2Attributes("sumologic_ingest_budget_v2.test"), @@ -76,6 +78,7 @@ func TestAccSumologicIngestBudgetV2_create(t *testing.T) { resource.TestCheckResourceAttr("sumologic_ingest_budget_v2.test", "description", testDescription), resource.TestCheckResourceAttr("sumologic_ingest_budget_v2.test", "action", testAction), resource.TestCheckResourceAttr("sumologic_ingest_budget_v2.test", "capacity_bytes", strconv.Itoa(testCapacityBytes)), + resource.TestCheckResourceAttr("sumologic_ingest_budget_v2.test", "budget_type", testBudgetType), ), }, }, @@ -92,6 +95,7 @@ func TestAccSumologicIngestBudgetV2_update(t *testing.T) { testDescription := "description-2tAk8" testAction := "stopCollecting" testCapacityBytes := 1000 + testBudgetType := "dailyVolume" testUpdatedName := "Developer BudgetUpdate" testUpdatedScope := "_sourceCategory=*prod*nginx*Update" @@ -101,6 +105,7 @@ func TestAccSumologicIngestBudgetV2_update(t *testing.T) { testUpdatedDescription := "description-pY8kDUpdate" testUpdatedAction := "keepCollecting" testUpdatedCapacityBytes := 1001 + testUpdatedBudgetType := "dailyVolume" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -108,7 +113,7 @@ func TestAccSumologicIngestBudgetV2_update(t *testing.T) { CheckDestroy: testAccCheckIngestBudgetV2Destroy(ingestBudgetV2), Steps: []resource.TestStep{ { - Config: testAccSumologicIngestBudgetV2(testName, testScope, testTimezone, testResetTime, testAuditThreshold, testDescription, testAction, testCapacityBytes), + Config: testAccSumologicIngestBudgetV2(testName, testScope, testTimezone, testResetTime, testAuditThreshold, testDescription, testAction, testCapacityBytes, testBudgetType), Check: resource.ComposeTestCheckFunc( testAccCheckIngestBudgetV2Exists("sumologic_ingest_budget_v2.test", &ingestBudgetV2, t), testAccCheckIngestBudgetV2Attributes("sumologic_ingest_budget_v2.test"), @@ -120,10 +125,11 @@ func TestAccSumologicIngestBudgetV2_update(t *testing.T) { resource.TestCheckResourceAttr("sumologic_ingest_budget_v2.test", "description", testDescription), resource.TestCheckResourceAttr("sumologic_ingest_budget_v2.test", "action", testAction), resource.TestCheckResourceAttr("sumologic_ingest_budget_v2.test", "capacity_bytes", strconv.Itoa(testCapacityBytes)), + resource.TestCheckResourceAttr("sumologic_ingest_budget_v2.test", "budget_type", testBudgetType), ), }, { - Config: testAccSumologicIngestBudgetV2Update(testUpdatedName, testUpdatedScope, testUpdatedTimezone, testUpdatedResetTime, testUpdatedAuditThreshold, testUpdatedDescription, testUpdatedAction, testUpdatedCapacityBytes), + Config: testAccSumologicIngestBudgetV2Update(testUpdatedName, testUpdatedScope, testUpdatedTimezone, testUpdatedResetTime, testUpdatedAuditThreshold, testUpdatedDescription, testUpdatedAction, testUpdatedCapacityBytes, testUpdatedBudgetType), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("sumologic_ingest_budget_v2.test", "name", testUpdatedName), resource.TestCheckResourceAttr("sumologic_ingest_budget_v2.test", "scope", testUpdatedScope), @@ -133,6 +139,7 @@ func TestAccSumologicIngestBudgetV2_update(t *testing.T) { resource.TestCheckResourceAttr("sumologic_ingest_budget_v2.test", "description", testUpdatedDescription), resource.TestCheckResourceAttr("sumologic_ingest_budget_v2.test", "action", testUpdatedAction), resource.TestCheckResourceAttr("sumologic_ingest_budget_v2.test", "capacity_bytes", strconv.Itoa(testUpdatedCapacityBytes)), + resource.TestCheckResourceAttr("sumologic_ingest_budget_v2.test", "budget_type", testUpdatedBudgetType), ), }, }, @@ -179,7 +186,7 @@ func testAccCheckIngestBudgetV2Exists(name string, ingestBudgetV2 *IngestBudgetV return nil } } -func testAccCheckSumologicIngestBudgetV2ConfigImported(name string, scope string, timezone string, resetTime string, auditThreshold int, description string, action string, capacityBytes int) string { +func testAccCheckSumologicIngestBudgetV2ConfigImported(name string, scope string, timezone string, resetTime string, auditThreshold int, description string, action string, capacityBytes int, budgetType string) string { return fmt.Sprintf(` resource "sumologic_ingest_budget_v2" "foo" { name = "%s" @@ -190,11 +197,12 @@ resource "sumologic_ingest_budget_v2" "foo" { description = "%s" action = "%s" capacity_bytes = %d + budget_type = "%s" } -`, name, scope, timezone, resetTime, auditThreshold, description, action, capacityBytes) +`, name, scope, timezone, resetTime, auditThreshold, description, action, capacityBytes, budgetType) } -func testAccSumologicIngestBudgetV2(name string, scope string, timezone string, resetTime string, auditThreshold int, description string, action string, capacityBytes int) string { +func testAccSumologicIngestBudgetV2(name string, scope string, timezone string, resetTime string, auditThreshold int, description string, action string, capacityBytes int, budgetType string) string { return fmt.Sprintf(` resource "sumologic_ingest_budget_v2" "test" { name = "%s" @@ -205,11 +213,12 @@ resource "sumologic_ingest_budget_v2" "test" { description = "%s" action = "%s" capacity_bytes = %d + budget_type = "%s" } -`, name, scope, timezone, resetTime, auditThreshold, description, action, capacityBytes) +`, name, scope, timezone, resetTime, auditThreshold, description, action, capacityBytes, budgetType) } -func testAccSumologicIngestBudgetV2Update(name string, scope string, timezone string, resetTime string, auditThreshold int, description string, action string, capacityBytes int) string { +func testAccSumologicIngestBudgetV2Update(name string, scope string, timezone string, resetTime string, auditThreshold int, description string, action string, capacityBytes int, budgetType string) string { return fmt.Sprintf(` resource "sumologic_ingest_budget_v2" "test" { name = "%s" @@ -220,8 +229,9 @@ resource "sumologic_ingest_budget_v2" "test" { description = "%s" action = "%s" capacity_bytes = %d + budget_type = "%s" } -`, name, scope, timezone, resetTime, auditThreshold, description, action, capacityBytes) +`, name, scope, timezone, resetTime, auditThreshold, description, action, capacityBytes, budgetType) } func testAccCheckIngestBudgetV2Attributes(name string) resource.TestCheckFunc { @@ -234,6 +244,7 @@ func testAccCheckIngestBudgetV2Attributes(name string) resource.TestCheckFunc { resource.TestCheckResourceAttrSet(name, "audit_threshold"), resource.TestCheckResourceAttrSet(name, "description"), resource.TestCheckResourceAttrSet(name, "action"), + resource.TestCheckResourceAttrSet(name, "budget_type"), resource.TestCheckResourceAttrSet(name, "capacity_bytes"), ) return f(s) diff --git a/sumologic/resource_sumologic_rum_source.go b/sumologic/resource_sumologic_rum_source.go new file mode 100644 index 00000000..b9e1f557 --- /dev/null +++ b/sumologic/resource_sumologic_rum_source.go @@ -0,0 +1,209 @@ +package sumologic + +import ( + "errors" + "fmt" + "log" + "strconv" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func resourceSumologicRumSource() *schema.Resource { + rumSource := resourceSumologicSource() + rumSource.Create = resourceSumologicRumSourceCreate + rumSource.Read = resourceSumologicRumSourceRead + rumSource.Update = resourceSumologicRumSourceUpdate + rumSource.Importer = &schema.ResourceImporter{ + State: resourceSumologicSourceImport, + } + + rumSource.Schema["content_type"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "Rum", + ValidateFunc: validation.StringInSlice([]string{"Rum"}, false), + } + + rumSource.Schema["path"] = &schema.Schema{ + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "application_name": { + Type: schema.TypeString, + Optional: true, + }, + "service_name": { + Type: schema.TypeString, + Required: true, + }, + "deployment_environment": { + Type: schema.TypeString, + Optional: true, + }, + "sampling_rate": { + Type: schema.TypeFloat, + Optional: true, + ValidateFunc: validation.FloatBetween(0, 1), + }, + "ignore_urls": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + }, + "custom_tags": { + Type: schema.TypeMap, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + }, + "propagate_trace_header_cors_urls": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + }, + "selected_country": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + } + + return rumSource +} + +func resourceSumologicRumSourceCreate(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client) + + if d.Id() == "" { + source, err := resourceToRumSource(d) + if err != nil { + return err + } + + id, err := c.CreateRumSource(source, d.Get("collector_id").(int)) + + if err != nil { + return err + } + + d.SetId(strconv.Itoa(id)) + } + + return resourceSumologicRumSourceRead(d, meta) +} + +func resourceSumologicRumSourceUpdate(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client) + + source, err := resourceToRumSource(d) + if err != nil { + return err + } + + err = c.UpdateRumSource(source, d.Get("collector_id").(int)) + + if err != nil { + return err + } + + return resourceSumologicRumSourceRead(d, meta) +} + +func resourceSumologicRumSourceRead(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client) + + id, _ := strconv.Atoi(d.Id()) + source, err := c.GetRumSource(d.Get("collector_id").(int), id) + + if err != nil { + return err + } + + if source == nil { + log.Printf("[WARN] Rum source not found, removing from state: %v - %v", id, err) + d.SetId("") + + return nil + } + + if err := resourceSumologicSourceRead(d, source.Source); err != nil { + return fmt.Errorf("%s", err) + } + d.Set("content_type", source.ContentType) + + return nil +} + +func resourceToRumSource(d *schema.ResourceData) (RumSource, error) { + source := resourceToSource(d) + source.Type = "HTTP" + + rumSource := RumSource{ + Source: source, + } + + path, errPath := getRumSourcePath(d) + if errPath != nil { + return rumSource, errPath + } + + auth := RumAuthentication{ + Type: "NoAuthentication", + } + + rumThirdPartyResource := RumThirdPartyResource{ + ServiceType: "Rum", + Path: path, + Authentication: auth, + } + + rumSource.RumThirdPartyRef.Resources = append(rumSource.RumThirdPartyRef.Resources, rumThirdPartyResource) + + return rumSource, nil +} + +func getRumSourcePath(d *schema.ResourceData) (RumSourcePath, error) { + rumSourcePath := RumSourcePath{} + paths := d.Get("path").([]interface{}) + + if len(paths) > 0 { + path := paths[0].(map[string]interface{}) + rumSourcePath.Type = "RumPath" + rumSourcePath.ApplicationName = path["application_name"].(string) + rumSourcePath.ServiceName = path["service_name"].(string) + rumSourcePath.DeploymentEnvironment = path["deployment_environment"].(string) + rumSourcePath.SamplingRate = path["sampling_rate"].(float64) + + ignoreUrls_int := path["ignore_urls"].([]interface{}) + IgnoreUrls := make([]string, len(ignoreUrls_int)) + for i, v := range ignoreUrls_int { + IgnoreUrls[i] = v.(string) + } + rumSourcePath.IgnoreUrls = IgnoreUrls + + rumSourcePath.CustomTags = path["custom_tags"].(map[string]interface{}) + + propagateTraceHeaderCorsUrls_int := path["ignore_urls"].([]interface{}) + PropagateTraceHeaderCorsUrls := make([]string, len(propagateTraceHeaderCorsUrls_int)) + for i, v := range propagateTraceHeaderCorsUrls_int { + PropagateTraceHeaderCorsUrls[i] = v.(string) + } + rumSourcePath.PropagateTraceHeaderCorsUrls = PropagateTraceHeaderCorsUrls + + rumSourcePath.SelectedCountry = path["selected_country"].(string) + + return rumSourcePath, nil + } else { + return rumSourcePath, errors.New("[ERROR] Rum path not configured") + } +} diff --git a/sumologic/resource_sumologic_rum_source_test.go b/sumologic/resource_sumologic_rum_source_test.go new file mode 100644 index 00000000..b7339efe --- /dev/null +++ b/sumologic/resource_sumologic_rum_source_test.go @@ -0,0 +1,257 @@ +package sumologic + +import ( + "fmt" + "strconv" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccSumologicRumSource_Mincreate(t *testing.T) { + var rumSource RumSource + var collector Collector + + cName, cDescription, cCategory := getRandomizedParams() + sName, sDescription, sCategory := getRandomizedParams() + + rumSourceResourceName := "sumologic_rum_source.testRumSource" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckRumSourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSumologicMinRumSourceConfig(cName, cDescription, cCategory, sName, sDescription, sCategory), + Check: resource.ComposeTestCheckFunc( + testAccCheckRumSourceExists(rumSourceResourceName, &rumSource), + testAccCheckRumSourceBasicValues(&rumSource, sName, sDescription, sCategory), + testAccCheckCollectorExists("sumologic_collector.testCollector", &collector), + testAccCheckCollectorValues(&collector, cName, cDescription, cCategory, "Etc/UTC", ""), + resource.TestCheckResourceAttrSet(rumSourceResourceName, "id"), + resource.TestCheckResourceAttr(rumSourceResourceName, "name", sName), + resource.TestCheckResourceAttr(rumSourceResourceName, "description", sDescription), + resource.TestCheckResourceAttr(rumSourceResourceName, "category", sCategory), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.service_name", "some_service2"), + ), + }, + }, + }) +} + +func TestAccSumologicRumSource_Fullcreate(t *testing.T) { + var rumSource RumSource + var collector Collector + + cName, cDescription, cCategory := getRandomizedParams() + sName, sDescription, sCategory := getRandomizedParams() + + rumSourceResourceName := "sumologic_rum_source.testRumSource" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckRumSourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSumologicFullRumSourceConfig(cName, cDescription, cCategory, sName, sDescription, sCategory), + Check: resource.ComposeTestCheckFunc( + testAccCheckRumSourceExists(rumSourceResourceName, &rumSource), + testAccCheckRumSourceBasicValues(&rumSource, sName, sDescription, sCategory), + testAccCheckCollectorExists("sumologic_collector.testCollector", &collector), + testAccCheckCollectorValues(&collector, cName, cDescription, cCategory, "Etc/UTC", ""), + resource.TestCheckResourceAttrSet(rumSourceResourceName, "id"), + resource.TestCheckResourceAttr(rumSourceResourceName, "name", sName), + resource.TestCheckResourceAttr(rumSourceResourceName, "description", sDescription), + resource.TestCheckResourceAttr(rumSourceResourceName, "category", sCategory), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.application_name", "some_application"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.service_name", "some_service"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.deployment_environment", "some_environment"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.sampling_rate", "0.5"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.ignore_urls.#", "2"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.ignore_urls.0", "asd.com"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.ignore_urls.1", "dsa.com"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.custom_tags.%", "1"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.custom_tags.some_tag", "some_value"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.propagate_trace_header_cors_urls.#", "2"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.propagate_trace_header_cors_urls.0", "xyz.com"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.propagate_trace_header_cors_urls.1", "zyx.com"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.selected_country", "Poland"), + ), + }, + }, + }) +} + +func TestAccSumologicRumSource_update(t *testing.T) { + var rumSource RumSource + + cName, cDescription, cCategory := getRandomizedParams() + sName, sDescription, sCategory := getRandomizedParams() + sNameUpdated, sDescriptionUpdated, sCategoryUpdated := getRandomizedParams() + + rumSourceResourceName := "sumologic_rum_source.testRumSource" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckRumSourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSumologicMinRumSourceConfig(cName, cDescription, cCategory, sName, sDescription, sCategory), + Check: resource.ComposeTestCheckFunc( + testAccCheckRumSourceExists(rumSourceResourceName, &rumSource), + testAccCheckRumSourceBasicValues(&rumSource, sName, sDescription, sCategory), + resource.TestCheckResourceAttrSet(rumSourceResourceName, "id"), + resource.TestCheckResourceAttr(rumSourceResourceName, "name", sName), + resource.TestCheckResourceAttr(rumSourceResourceName, "description", sDescription), + resource.TestCheckResourceAttr(rumSourceResourceName, "category", sCategory), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.service_name", "some_service2"), + ), + }, + { + Config: testAccSumologicFullRumSourceConfig(cName, cDescription, cCategory, sNameUpdated, sDescriptionUpdated, sCategoryUpdated), + Check: resource.ComposeTestCheckFunc( + testAccCheckRumSourceExists(rumSourceResourceName, &rumSource), + testAccCheckRumSourceBasicValues(&rumSource, sNameUpdated, sDescriptionUpdated, sCategoryUpdated), + resource.TestCheckResourceAttrSet(rumSourceResourceName, "id"), + resource.TestCheckResourceAttr(rumSourceResourceName, "name", sNameUpdated), + resource.TestCheckResourceAttr(rumSourceResourceName, "description", sDescriptionUpdated), + resource.TestCheckResourceAttr(rumSourceResourceName, "category", sCategoryUpdated), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.application_name", "some_application"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.service_name", "some_service"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.deployment_environment", "some_environment"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.sampling_rate", "0.5"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.ignore_urls.#", "2"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.ignore_urls.0", "asd.com"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.ignore_urls.1", "dsa.com"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.custom_tags.%", "1"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.custom_tags.some_tag", "some_value"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.propagate_trace_header_cors_urls.#", "2"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.propagate_trace_header_cors_urls.0", "xyz.com"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.propagate_trace_header_cors_urls.1", "zyx.com"), + resource.TestCheckResourceAttr(rumSourceResourceName, "path.0.selected_country", "Poland"), + ), + }, + }, + }) +} + +func testAccCheckRumSourceExists(n string, rumSource *RumSource) 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("RumSource ID is not set") + } + id, err := strconv.Atoi(rs.Primary.ID) + if err != nil { + return fmt.Errorf("RumSource 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) + rumSourceResp, err := c.GetRumSource(collectorID, id) + if err != nil { + return err + } + *rumSource = *rumSourceResp + return nil + } +} + +func testAccCheckRumSourceBasicValues(rumSource *RumSource, name, description, category string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if rumSource.Name != name { + return fmt.Errorf("bad name, expected \"%s\", got: %#v", name, rumSource.Name) + } + if rumSource.Description != description { + return fmt.Errorf("bad description, expected \"%s\", got: %#v", description, rumSource.Description) + } + if rumSource.Category != category { + return fmt.Errorf("bad category, expected \"%s\", got: %#v", category, rumSource.Category) + } + return nil + } +} + +func testAccCheckRumSourceDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*Client) + for _, rs := range s.RootModule().Resources { + if rs.Type != "sumologic_rum_source" { + continue + } + if rs.Primary.ID == "" { + return fmt.Errorf("Rum Source destruction check: Rum 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.GetRumSource(collectorID, id) + if err != nil { + return fmt.Errorf("Encountered an error: " + err.Error()) + } + if s != nil { + return fmt.Errorf("Rum Source still exists") + } + } + return nil +} + +func testAccSumologicFullRumSourceConfig(cName, cDescription, cCategory, sName, sDescription, sCategory string) string { + return fmt.Sprintf(` +resource "sumologic_collector" "testCollector" { + name = "%s" + description = "%s" + category = "%s" +} + +resource "sumologic_rum_source" "testRumSource" { + name = "%s" + description = "%s" + category = "%s" + collector_id = "${sumologic_collector.testCollector.id}" + path { + application_name = "some_application" + service_name = "some_service" + deployment_environment = "some_environment" + sampling_rate = 0.5 + ignore_urls = ["asd.com", "dsa.com"] + custom_tags = { some_tag = "some_value" } + propagate_trace_header_cors_urls = ["xyz.com", "zyx.com"] + selected_country = "Poland" + } +} +`, cName, cDescription, cCategory, sName, sDescription, sCategory) +} + +func testAccSumologicMinRumSourceConfig(cName, cDescription, cCategory, sName, sDescription, sCategory string) string { + return fmt.Sprintf(` +resource "sumologic_collector" "testCollector" { + name = "%s" + description = "%s" + category = "%s" +} + +resource "sumologic_rum_source" "testRumSource" { + name = "%s" + description = "%s" + category = "%s" + collector_id = "${sumologic_collector.testCollector.id}" + path { + service_name = "some_service2" + } +} +`, cName, cDescription, cCategory, sName, sDescription, sCategory) +} diff --git a/sumologic/resource_sumologic_s3_source_test.go b/sumologic/resource_sumologic_s3_source_test.go index 3159ae0c..8bfb3c38 100644 --- a/sumologic/resource_sumologic_s3_source_test.go +++ b/sumologic/resource_sumologic_s3_source_test.go @@ -25,13 +25,14 @@ func TestAccSumologicS3Source_create(t *testing.T) { s3ResourceName := "sumologic_s3_source.s3" testAwsRoleArn := os.Getenv("SUMOLOGIC_TEST_ROLE_ARN") testAwsBucket := os.Getenv("SUMOLOGIC_TEST_BUCKET_NAME") + useVersionedApi := true resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheckWithAWS(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckS3SourceDestroy, Steps: []resource.TestStep{ { - Config: testAccSumologicS3SourceConfig(cName, cDescription, cCategory, sName, sDescription, sCategory, testAwsRoleArn, testAwsBucket), + Config: testAccSumologicS3SourceConfig(cName, cDescription, cCategory, sName, sDescription, sCategory, testAwsRoleArn, testAwsBucket, useVersionedApi), Check: resource.ComposeTestCheckFunc( testAccCheckS3SourceExists(s3ResourceName, &s3Source), testAccCheckS3SourceValues(&s3Source, sName, sDescription, sCategory), @@ -43,6 +44,7 @@ func TestAccSumologicS3Source_create(t *testing.T) { resource.TestCheckResourceAttr(s3ResourceName, "category", sCategory), resource.TestCheckResourceAttr(s3ResourceName, "content_type", "AwsS3Bucket"), resource.TestCheckResourceAttr(s3ResourceName, "path.0.type", "S3BucketPathExpression"), + resource.TestCheckResourceAttr(s3ResourceName, "path.0.use_versioned_api", "true"), ), }, }, @@ -56,13 +58,15 @@ func TestAccSumologicS3Source_update(t *testing.T) { s3ResourceName := "sumologic_s3_source.s3" testAwsRoleArn := os.Getenv("SUMOLOGIC_TEST_ROLE_ARN") testAwsBucket := os.Getenv("SUMOLOGIC_TEST_BUCKET_NAME") + useVersionedApi := true + useVersionedApiUpdated := false resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheckWithAWS(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckHTTPSourceDestroy, Steps: []resource.TestStep{ { - Config: testAccSumologicS3SourceConfig(cName, cDescription, cCategory, sName, sDescription, sCategory, testAwsRoleArn, testAwsBucket), + Config: testAccSumologicS3SourceConfig(cName, cDescription, cCategory, sName, sDescription, sCategory, testAwsRoleArn, testAwsBucket, useVersionedApi), Check: resource.ComposeTestCheckFunc( testAccCheckS3SourceExists(s3ResourceName, &s3Source), testAccCheckS3SourceValues(&s3Source, sName, sDescription, sCategory), @@ -72,10 +76,11 @@ func TestAccSumologicS3Source_update(t *testing.T) { resource.TestCheckResourceAttr(s3ResourceName, "category", sCategory), resource.TestCheckResourceAttr(s3ResourceName, "content_type", "AwsS3Bucket"), resource.TestCheckResourceAttr(s3ResourceName, "path.0.type", "S3BucketPathExpression"), + resource.TestCheckResourceAttr(s3ResourceName, "path.0.use_versioned_api", "true"), ), }, { - Config: testAccSumologicS3SourceConfig(cName, cDescription, cCategory, sNameUpdated, sDescriptionUpdated, sCategoryUpdated, testAwsRoleArn, testAwsBucket), + Config: testAccSumologicS3SourceConfig(cName, cDescription, cCategory, sNameUpdated, sDescriptionUpdated, sCategoryUpdated, testAwsRoleArn, testAwsBucket, useVersionedApiUpdated), Check: resource.ComposeTestCheckFunc( testAccCheckS3SourceExists(s3ResourceName, &s3Source), testAccCheckS3SourceValues(&s3Source, sNameUpdated, sDescriptionUpdated, sCategoryUpdated), @@ -85,6 +90,7 @@ func TestAccSumologicS3Source_update(t *testing.T) { resource.TestCheckResourceAttr(s3ResourceName, "category", sCategoryUpdated), resource.TestCheckResourceAttr(s3ResourceName, "content_type", "AwsS3Bucket"), resource.TestCheckResourceAttr(s3ResourceName, "path.0.type", "S3BucketPathExpression"), + resource.TestCheckResourceAttr(s3ResourceName, "path.0.use_versioned_api", "false"), ), }, }, @@ -157,7 +163,7 @@ func testAccCheckS3SourceValues(pollingSource *PollingSource, name, description, return nil } } -func testAccSumologicS3SourceConfig(cName, cDescription, cCategory, sName, sDescription, sCategory, testAwsRoleArn, testAwsBucket string) string { +func testAccSumologicS3SourceConfig(cName, cDescription, cCategory, sName, sDescription, sCategory, testAwsRoleArn, testAwsBucket string, useVersionedApi bool) string { return fmt.Sprintf(` resource "sumologic_collector" "test" { name = "%s" @@ -180,8 +186,9 @@ resource "sumologic_s3_source" "s3" { type = "S3BucketPathExpression" bucket_name = "%s" path_expression = "*" + use_versioned_api = "%v" } } -`, cName, cDescription, cCategory, sName, sDescription, sCategory, testAwsRoleArn, testAwsBucket) +`, cName, cDescription, cCategory, sName, sDescription, sCategory, testAwsRoleArn, testAwsBucket, useVersionedApi) } diff --git a/sumologic/sumologic_client.go b/sumologic/sumologic_client.go index e05f7a08..5e30c607 100644 --- a/sumologic/sumologic_client.go +++ b/sumologic/sumologic_client.go @@ -2,7 +2,6 @@ package sumologic import ( "bytes" - "context" "encoding/json" "errors" "fmt" @@ -329,18 +328,9 @@ func (s *Client) Delete(urlPath string) ([]byte, error) { return d, nil } -func checkRetry(ctx context.Context, resp *http.Response, err error) (bool, error) { - // only retry on 429 - if err == nil && resp.StatusCode == http.StatusTooManyRequests { - return true, nil - } - return false, nil -} - func NewClient(accessID, accessKey, authJwt, environment, base_url string, admin bool) (*Client, error) { retryClient := retryablehttp.NewClient() retryClient.RetryMax = 10 - retryClient.CheckRetry = checkRetry // Disable DEBUG logs (https://github.com/hashicorp/go-retryablehttp/issues/31) retryClient.Logger = nil diff --git a/sumologic/sumologic_ingest_budget_v2.go b/sumologic/sumologic_ingest_budget_v2.go index a3347dd1..eac49a78 100644 --- a/sumologic/sumologic_ingest_budget_v2.go +++ b/sumologic/sumologic_ingest_budget_v2.go @@ -96,6 +96,7 @@ type IngestBudgetV2 struct { Action string `json:"action"` ResetTime string `json:"resetTime"` Name string `json:"name"` + BudgetType string `json:"budgetType,omitempty"` ID string `json:"id,omitempty"` Scope string `json:"scope"` Timezone string `json:"timezone"` diff --git a/sumologic/sumologic_polling_source.go b/sumologic/sumologic_polling_source.go index d66daa76..4765574b 100644 --- a/sumologic/sumologic_polling_source.go +++ b/sumologic/sumologic_polling_source.go @@ -51,7 +51,7 @@ type PollingPath struct { CustomServices []string `json:"customServices,omitempty"` TagFilters []TagFilter `json:"tagFilters,omitempty"` SnsTopicOrSubscriptionArn PollingSnsTopicOrSubscriptionArn `json:"snsTopicOrSubscriptionArn,omitempty"` - UseVersionedApi bool `json:"useVersionedApi,omitempty"` + UseVersionedApi *bool `json:"useVersionedApi,omitempty"` } type TagFilter struct { diff --git a/sumologic/sumologic_rum_source.go b/sumologic/sumologic_rum_source.go new file mode 100644 index 00000000..9691d447 --- /dev/null +++ b/sumologic/sumologic_rum_source.go @@ -0,0 +1,105 @@ +package sumologic + +import ( + "encoding/json" + "fmt" +) + +type RumSource struct { + Source + RumThirdPartyRef RumThirdPartyRef `json:"thirdPartyRef"` +} + +type RumThirdPartyRef struct { + Resources []RumThirdPartyResource `json:"resources"` +} + +type RumThirdPartyResource struct { + ServiceType string `json:"serviceType"` + Path RumSourcePath `json:"path,omitempty"` + Authentication RumAuthentication `json:"authentication,omitempty"` +} + +type RumSourcePath struct { + Type string `json:"type"` + ApplicationName string `json:"applicationName,omitempty"` + ServiceName string `json:"serviceName"` + DeploymentEnvironment string `json:"deploymentEnvironment,omitempty"` + SamplingRate float64 `json:"samplingRate,omitempty"` + IgnoreUrls []string `json:"ignoreUrls,omitempty"` + CustomTags map[string]interface{} `json:"customTags,omitempty"` + PropagateTraceHeaderCorsUrls []string `json:"propagateTraceHeaderCorsUrls,omitempty"` + SelectedCountry string `json:"selectedCountry,omitempty"` +} + +type RumAuthentication struct { + Type string `json:"type"` +} + +func (s *Client) CreateRumSource(rumSource RumSource, collectorID int) (int, error) { + + type RumSourceMessage struct { + Source RumSource `json:"source"` + } + + request := RumSourceMessage{ + Source: rumSource, + } + + urlPath := fmt.Sprintf("v1/collectors/%d/sources", collectorID) + body, err := s.Post(urlPath, request) + + if err != nil { + return -1, err + } + + var response RumSourceMessage + + err = json.Unmarshal(body, &response) + if err != nil { + return -1, err + } + + return response.Source.ID, nil +} + +func (s *Client) GetRumSource(collectorID, sourceID int) (*RumSource, error) { + + body, _, err := s.Get(fmt.Sprintf("v1/collectors/%d/sources/%d", collectorID, sourceID)) + if err != nil { + return nil, err + } + + if body == nil { + return nil, nil + } + + type Response struct { + Source RumSource `json:"source"` + } + + var response Response + + err = json.Unmarshal(body, &response) + if err != nil { + return nil, err + } + + return &response.Source, nil +} + +func (s *Client) UpdateRumSource(source RumSource, collectorID int) error { + + type RumSourceMessage struct { + Source RumSource `json:"source"` + } + + request := RumSourceMessage{ + Source: source, + } + + urlPath := fmt.Sprintf("v1/collectors/%d/sources/%d", collectorID, source.ID) + _, err := s.Put(urlPath, request) + + return err +} diff --git a/website/docs/r/cse_match_list.html.markdown b/website/docs/r/cse_match_list.html.markdown index fa2503d3..742118f0 100644 --- a/website/docs/r/cse_match_list.html.markdown +++ b/website/docs/r/cse_match_list.html.markdown @@ -33,6 +33,24 @@ The following arguments are supported: - `target_column` - (Required) Target column. (possible values: Hostname, FileHash, Url, SrcIp, DstIp, Domain, Username, Ip, Asn, Isp, Org, SrcAsn, SrcIsp, SrcOrg, DstAsn, DstIsp, DstOrg or any custom column.) - `items` - (Optional) List of match list items. See [match_list_item schema](#schema-for-match_list_item) for details. +**Note:** When managing CSE match list items outside of terraform, omit the `items` argument and add `items` to the [ignore_changes](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#ignore_changes) list in a lifecycle block on the `sumologic_cse_match_list` resource. As match list items are added or removed outside of terraform, terraform will ignore these changes, protecting match list items from accidental deletion. + +For example: + +```hcl +resource "sumologic_cse_match_list" "match_list" { + default_ttl = 10800 + description = "Match list description" + name = "Match list name" + target_column = "SrcIp" + + lifecycle { + # protects match list items added outside terraform from accidental deletion + ignore_changes = [items] + } +} +``` + ### Schema for `match_list_item` - `description` - (Required) Match list item description. - `value` - (Optional) Match list item value. diff --git a/website/docs/r/ingest_budget_v2.html.markdown b/website/docs/r/ingest_budget_v2.html.markdown index af5dac65..27f7d34c 100644 --- a/website/docs/r/ingest_budget_v2.html.markdown +++ b/website/docs/r/ingest_budget_v2.html.markdown @@ -13,6 +13,7 @@ Provides a [Sumologic Ingest Budget v2][1]. resource "sumologic_ingest_budget_v2" "budget" { name = "testBudget" scope = "_sourceCategory=*prod*nginx*" + budget_type = "dailyVolume" capacity_bytes = 30000000000 description = "For testing purposes" timezone = "Etc/UTC" @@ -28,8 +29,9 @@ The following arguments are supported: * `name` - (Required) Display name of the ingest budget. This must be unique across all of the ingest budgets * `scope` - (Required) A scope is a constraint that will be used to identify the messages on which budget needs to be applied. A scope is consists of key and value separated by =. The field must be enabled in the fields table. - * `capacity_bytes` - (Required) Capacity of the ingest budget, in bytes. + * `capacity_bytes` - (Required) Capacity of the ingest budget, in bytes. It takes a few minutes for Collectors to stop collecting when capacity is reached. We recommend setting a soft limit that is lower than your needed hard limit. The capacity bytes unit varies based on the budgetType field. For `dailyVolume` budgetType the capacity specified is in bytes/day whereas for `minuteVolume` budgetType its bytes/min. * `description` - (Optional) The description of the collector. + * `budget_type` - (Optional) The type of budget. Supported values are: * `dailyVolume` * `minuteVolume`. Default value is `dailyVolume`. * `timezone` - (Optional) The time zone to use for this collector. The value follows the [tzdata][2] naming convention. Defaults to `Etc/UTC` * `action` - (Optional) Action to take when ingest budget's capacity is reached. All actions are audited. Supported values are `stopCollecting` and `keepCollecting`. * `reset_time` - (Optional) Reset time of the ingest budget in HH:MM format. Defaults to `00:00` @@ -40,10 +42,10 @@ The following attributes are exported: * `id` - The internal ID of the ingest budget. ## Import -Ingest budgets can be imported using the name, e.g.: +Ingest budgets can be imported using the budget ID, e.g.: ```hcl -terraform import sumologic_ingest_budget_v2.budget budgetName +terraform import sumologic_ingest_budget_v2.budget 00000000000123AB ``` [1]: https://help.sumologic.com/Beta/Metadata_Ingest_Budgets diff --git a/website/docs/r/log_search.html.markdown b/website/docs/r/log_search.html.markdown index c62fbe95..0cfc3318 100644 --- a/website/docs/r/log_search.html.markdown +++ b/website/docs/r/log_search.html.markdown @@ -16,7 +16,14 @@ resource "sumologic_log_search" "example_log_search" { name = "Demo Search" description = "Demo search description" parent_id = data.sumologic_personal_folder.personalFolder.id - query_string = "_sourceCategory=api error | count by _sourceHost" + query_string = <