From 0e1ce3c030dec5f2267d61d4a30179cfff8a1341 Mon Sep 17 00:00:00 2001 From: Piotr Wardecki Date: Wed, 21 Feb 2024 16:07:47 +0100 Subject: [PATCH 1/8] DET-422: Added initial support for custom rule size for threshold rule. --- .../resource_sumologic_cse_threshold_rule.go | 76 ++++++------ ...ource_sumologic_cse_threshold_rule_test.go | 108 +++++++++++++++++- sumologic/sumologic_cse_rule_common.go | 9 +- sumologic/sumologic_cse_threshold_rule.go | 37 +++--- 4 files changed, 171 insertions(+), 59 deletions(-) diff --git a/sumologic/resource_sumologic_cse_threshold_rule.go b/sumologic/resource_sumologic_cse_threshold_rule.go index 76d50491..7a3145d2 100644 --- a/sumologic/resource_sumologic_cse_threshold_rule.go +++ b/sumologic/resource_sumologic_cse_threshold_rule.go @@ -79,6 +79,10 @@ func resourceSumologicCSEThresholdRule() *schema.Resource { Type: schema.TypeString, Required: true, }, + "window_size_millis": { + Type: schema.TypeString, + Optional: true, + }, }, } } @@ -114,7 +118,9 @@ func resourceSumologicCSEThresholdRuleRead(d *schema.ResourceData, meta interfac d.Set("summary_expression", CSEThresholdRuleGet.SummaryExpression) d.Set("tags", CSEThresholdRuleGet.Tags) d.Set("window_size", CSEThresholdRuleGet.WindowSizeName) - + if CSEThresholdRuleGet.WindowSizeName == "CUSTOM" { + d.Set("window_size_millis", CSEThresholdRuleGet.WindowSize) + } return nil } @@ -130,22 +136,23 @@ func resourceSumologicCSEThresholdRuleCreate(d *schema.ResourceData, meta interf if d.Id() == "" { id, err := c.CreateCSEThresholdRule(CSEThresholdRule{ - CountDistinct: d.Get("count_distinct").(bool), - CountField: d.Get("count_field").(string), - Description: d.Get("description").(string), - Enabled: d.Get("enabled").(bool), - EntitySelectors: resourceToEntitySelectorArray(d.Get("entity_selectors").([]interface{})), - Expression: d.Get("expression").(string), - GroupByFields: resourceToStringArray(d.Get("group_by_fields").([]interface{})), - IsPrototype: d.Get("is_prototype").(bool), - Limit: d.Get("limit").(int), - Name: d.Get("name").(string), - Severity: d.Get("severity").(int), - Stream: "record", - SummaryExpression: d.Get("summary_expression").(string), - Tags: resourceToStringArray(d.Get("tags").([]interface{})), - Version: 1, - WindowSize: windowSizeField(d.Get("window_size").(string)), + CountDistinct: d.Get("count_distinct").(bool), + CountField: d.Get("count_field").(string), + Description: d.Get("description").(string), + Enabled: d.Get("enabled").(bool), + EntitySelectors: resourceToEntitySelectorArray(d.Get("entity_selectors").([]interface{})), + Expression: d.Get("expression").(string), + GroupByFields: resourceToStringArray(d.Get("group_by_fields").([]interface{})), + IsPrototype: d.Get("is_prototype").(bool), + Limit: d.Get("limit").(int), + Name: d.Get("name").(string), + Severity: d.Get("severity").(int), + Stream: "record", + SummaryExpression: d.Get("summary_expression").(string), + Tags: resourceToStringArray(d.Get("tags").([]interface{})), + Version: 1, + WindowSize: windowSizeField(d.Get("window_size").(string)), + WindowSizeMilliseconds: d.Get("window_size_millis").(string), }) if err != nil { @@ -178,22 +185,23 @@ func resourceToCSEThresholdRule(d *schema.ResourceData) (CSEThresholdRule, error } return CSEThresholdRule{ - ID: id, - CountDistinct: d.Get("count_distinct").(bool), - CountField: d.Get("count_field").(string), - Description: d.Get("description").(string), - Enabled: d.Get("enabled").(bool), - EntitySelectors: resourceToEntitySelectorArray(d.Get("entity_selectors").([]interface{})), - Expression: d.Get("expression").(string), - GroupByFields: resourceToStringArray(d.Get("group_by_fields").([]interface{})), - IsPrototype: d.Get("is_prototype").(bool), - Limit: d.Get("limit").(int), - Name: d.Get("name").(string), - Severity: d.Get("severity").(int), - Stream: "record", - SummaryExpression: d.Get("summary_expression").(string), - Tags: resourceToStringArray(d.Get("tags").([]interface{})), - Version: 1, - WindowSize: windowSizeField(d.Get("window_size").(string)), + ID: id, + CountDistinct: d.Get("count_distinct").(bool), + CountField: d.Get("count_field").(string), + Description: d.Get("description").(string), + Enabled: d.Get("enabled").(bool), + EntitySelectors: resourceToEntitySelectorArray(d.Get("entity_selectors").([]interface{})), + Expression: d.Get("expression").(string), + GroupByFields: resourceToStringArray(d.Get("group_by_fields").([]interface{})), + IsPrototype: d.Get("is_prototype").(bool), + Limit: d.Get("limit").(int), + Name: d.Get("name").(string), + Severity: d.Get("severity").(int), + Stream: "record", + SummaryExpression: d.Get("summary_expression").(string), + Tags: resourceToStringArray(d.Get("tags").([]interface{})), + Version: 1, + WindowSize: windowSizeField(d.Get("window_size").(string)), + WindowSizeMilliseconds: d.Get("window_size_millis").(string), }, nil } diff --git a/sumologic/resource_sumologic_cse_threshold_rule_test.go b/sumologic/resource_sumologic_cse_threshold_rule_test.go index 5c79c84b..100c9fc2 100644 --- a/sumologic/resource_sumologic_cse_threshold_rule_test.go +++ b/sumologic/resource_sumologic_cse_threshold_rule_test.go @@ -8,6 +8,71 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/terraform" ) +func TestAccSumologicCSEThresholdRuleWithCustomWindowSize_createAndUpdate(t *testing.T) { + SkipCseTest(t) + + var thresholdRule CSEThresholdRule + countDistinct := true + countField := "dstDevice_hostname" + description := "Test description" + enabled := true + entitySelectorEntityType := "_ip" + entitySelectorExpression := "srcDevice_ip" + expression := "foo = bar" + groupByField := "destPort" + isPrototype := false + limit := 20 + name := "Test Threshold Rule With Custom WindowSize" + severity := 5 + summaryExpression := "Signal Summary" + tag := "foo" + windowSize := "CUSTOM" + windowSizeMillis := "10800000" // 3h + updatedWindowSizeMillis := "14400000" // 4h + + resourceName := "sumologic_cse_threshold_rule.threshold_rule" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCSEThresholdRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testCreateCSEThresholdRuleConfigWithCustomWindowSize(countDistinct, countField, + description, enabled, entitySelectorEntityType, + entitySelectorExpression, expression, groupByField, isPrototype, + limit, name, severity, summaryExpression, tag, windowSize, windowSizeMillis), + Check: resource.ComposeTestCheckFunc( + testCheckCSEThresholdRuleExists(resourceName, &thresholdRule), + testCheckThresholdRuleValues(&thresholdRule, countDistinct, countField, + description, enabled, entitySelectorEntityType, + entitySelectorExpression, expression, groupByField, isPrototype, + limit, name, severity, summaryExpression, tag, windowSize, windowSizeMillis), + resource.TestCheckResourceAttrSet(resourceName, "id"), + ), + }, + { + Config: testCreateCSEThresholdRuleConfigWithCustomWindowSize(countDistinct, countField, + description, enabled, entitySelectorEntityType, + entitySelectorExpression, expression, groupByField, isPrototype, + limit, name, severity, summaryExpression, tag, windowSize, updatedWindowSizeMillis), + Check: resource.ComposeTestCheckFunc( + testCheckCSEThresholdRuleExists(resourceName, &thresholdRule), + testCheckThresholdRuleValues(&thresholdRule, countDistinct, countField, + description, enabled, entitySelectorEntityType, + entitySelectorExpression, expression, groupByField, isPrototype, + limit, name, severity, summaryExpression, tag, windowSize, updatedWindowSizeMillis), + resource.TestCheckResourceAttrSet(resourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccSumologicCSEThresholdRule_createAndUpdate(t *testing.T) { SkipCseTest(t) @@ -46,7 +111,7 @@ func TestAccSumologicCSEThresholdRule_createAndUpdate(t *testing.T) { testCheckThresholdRuleValues(&thresholdRule, countDistinct, countField, description, enabled, entitySelectorEntityType, entitySelectorExpression, expression, groupByField, isPrototype, - limit, name, severity, summaryExpression, tag, windowSize), + limit, name, severity, summaryExpression, tag, windowSize, ""), resource.TestCheckResourceAttrSet(resourceName, "id"), ), }, @@ -60,7 +125,7 @@ func TestAccSumologicCSEThresholdRule_createAndUpdate(t *testing.T) { testCheckThresholdRuleValues(&thresholdRule, countDistinct, countField, description, enabled, entitySelectorEntityType, entitySelectorExpression, expression, groupByField, isPrototype, - limit, nameUpdated, severity, summaryExpression, tag, windowSizeUpdated), + limit, nameUpdated, severity, summaryExpression, tag, windowSizeUpdated, ""), resource.TestCheckResourceAttrSet(resourceName, "id"), ), }, @@ -96,6 +161,38 @@ func testAccCSEThresholdRuleDestroy(s *terraform.State) error { return nil } +func testCreateCSEThresholdRuleConfigWithCustomWindowSize( + countDistinct bool, countField string, description string, enabled bool, + entitySelectorEntityType string, entitySelectorExpression string, + expression string, groupByField string, isPrototype bool, limit int, + name string, severity int, summaryExpression string, tag string, + windowSize string, windowSizeMillis string) string { + return fmt.Sprintf(` +resource "sumologic_cse_threshold_rule" "threshold_rule" { + count_distinct = %t + count_field = "%s" + description = "%s" + enabled = %t + entity_selectors { + entity_type = "%s" + expression = "%s" + } + expression = "%s" + group_by_fields = ["%s"] + is_prototype = %t + limit = %d + name = "%s" + severity = %d + summary_expression = "%s" + tags = ["%s"] + window_size = "%s" + window_size_millis = "%s" +} +`, countDistinct, countField, description, enabled, entitySelectorEntityType, + entitySelectorExpression, expression, groupByField, isPrototype, limit, name, + severity, summaryExpression, tag, windowSize, windowSizeMillis) +} + func testCreateCSEThresholdRuleConfig( countDistinct bool, countField string, description string, enabled bool, entitySelectorEntityType string, entitySelectorExpression string, @@ -154,7 +251,7 @@ func testCheckThresholdRuleValues(thresholdRule *CSEThresholdRule, countDistinct countField string, description string, enabled bool, entitySelectorEntityType string, entitySelectorExpression string, expression string, groupByField string, isPrototype bool, limit int, name string, severity int, summaryExpression string, - tag string, windowSize string) resource.TestCheckFunc { + tag string, windowSize string, windowSizeMillis string) resource.TestCheckFunc { return func(s *terraform.State) error { if thresholdRule.CountDistinct != countDistinct { return fmt.Errorf("bad countDistinct, expected \"%t\", got %#v", countDistinct, thresholdRule.CountDistinct) @@ -199,7 +296,10 @@ func testCheckThresholdRuleValues(thresholdRule *CSEThresholdRule, countDistinct return fmt.Errorf("bad tag, expected \"%s\", got %#v", tag, thresholdRule.Tags[0]) } if thresholdRule.WindowSizeName != windowSize { - return fmt.Errorf("bad windowSize, expected \"%s\", got %#v", windowSize, thresholdRule.WindowSize) + return fmt.Errorf("bad windowSize, expected \"%s\", got %#v", windowSize, thresholdRule.WindowSizeName) + } + if thresholdRule.WindowSizeName == "CUSTOM" && string(thresholdRule.WindowSize) != windowSizeMillis { + return fmt.Errorf("bad WindowSize, expected \"%s\", got %#v", windowSizeMillis, thresholdRule.WindowSize) } return nil diff --git a/sumologic/sumologic_cse_rule_common.go b/sumologic/sumologic_cse_rule_common.go index 0598197b..ac84428b 100644 --- a/sumologic/sumologic_cse_rule_common.go +++ b/sumologic/sumologic_cse_rule_common.go @@ -1,6 +1,8 @@ package sumologic import ( + "strings" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" ) @@ -158,10 +160,11 @@ type EntitySelector struct { EntityType string `json:"entityType"` } -// Use explicit windowSizeField type so that we can ignore it when unmarshalling +// Use explicit windowSizeField type so that we can unmarshall Int value as String type windowSizeField string -func (r windowSizeField) UnmarshalJSON(data []byte) error { - r = "" +func (r *windowSizeField) UnmarshalJSON(data []byte) error { + cleanedData := strings.Trim(string(data), `"`) + *r = windowSizeField(cleanedData) return nil } diff --git a/sumologic/sumologic_cse_threshold_rule.go b/sumologic/sumologic_cse_threshold_rule.go index ff8b07a3..4e6db710 100644 --- a/sumologic/sumologic_cse_threshold_rule.go +++ b/sumologic/sumologic_cse_threshold_rule.go @@ -73,22 +73,23 @@ type CSEThresholdRuleResponse struct { } type CSEThresholdRule struct { - ID string `json:"id,omitempty"` - CountDistinct bool `json:"countDistinct"` - CountField string `json:"countField"` - Description string `json:"description"` - Enabled bool `json:"enabled"` - EntitySelectors []EntitySelector `json:"entitySelectors"` - Expression string `json:"expression"` - GroupByFields []string `json:"groupByFields"` - IsPrototype bool `json:"isPrototype"` - Limit int `json:"limit"` - Name string `json:"name"` - Severity int `json:"score"` - Stream string `json:"stream"` - SummaryExpression string `json:"summaryExpression"` - Tags []string `json:"tags"` - Version int `json:"version"` - WindowSize windowSizeField `json:"windowSize,omitempty"` - WindowSizeName string `json:"windowSizeName,omitempty"` + ID string `json:"id,omitempty"` + CountDistinct bool `json:"countDistinct"` + CountField string `json:"countField"` + Description string `json:"description"` + Enabled bool `json:"enabled"` + EntitySelectors []EntitySelector `json:"entitySelectors"` + Expression string `json:"expression"` + GroupByFields []string `json:"groupByFields"` + IsPrototype bool `json:"isPrototype"` + Limit int `json:"limit"` + Name string `json:"name"` + Severity int `json:"score"` + Stream string `json:"stream"` + SummaryExpression string `json:"summaryExpression"` + Tags []string `json:"tags"` + Version int `json:"version"` + WindowSize windowSizeField `json:"windowSize,omitempty"` + WindowSizeName string `json:"windowSizeName,omitempty"` + WindowSizeMilliseconds string `json:"windowSizeMilliseconds,omitempty"` } From f46855acaaa27cefcc0bb0552d79a324518d044e Mon Sep 17 00:00:00 2001 From: Piotr Wardecki Date: Wed, 21 Feb 2024 16:26:14 +0100 Subject: [PATCH 2/8] DET-422: go fmt. --- sumologic/resource_sumologic_cse_threshold_rule.go | 6 ++++-- sumologic/resource_sumologic_cse_threshold_rule_test.go | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/sumologic/resource_sumologic_cse_threshold_rule.go b/sumologic/resource_sumologic_cse_threshold_rule.go index 7a3145d2..a4ecd716 100644 --- a/sumologic/resource_sumologic_cse_threshold_rule.go +++ b/sumologic/resource_sumologic_cse_threshold_rule.go @@ -1,9 +1,11 @@ package sumologic import ( + "log" + "strings" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" - "log" ) func resourceSumologicCSEThresholdRule() *schema.Resource { @@ -118,7 +120,7 @@ func resourceSumologicCSEThresholdRuleRead(d *schema.ResourceData, meta interfac d.Set("summary_expression", CSEThresholdRuleGet.SummaryExpression) d.Set("tags", CSEThresholdRuleGet.Tags) d.Set("window_size", CSEThresholdRuleGet.WindowSizeName) - if CSEThresholdRuleGet.WindowSizeName == "CUSTOM" { + if strings.EqualFold(CSEThresholdRuleGet.WindowSizeName, "CUSTOM") { d.Set("window_size_millis", CSEThresholdRuleGet.WindowSize) } return nil diff --git a/sumologic/resource_sumologic_cse_threshold_rule_test.go b/sumologic/resource_sumologic_cse_threshold_rule_test.go index 100c9fc2..28a0e7f4 100644 --- a/sumologic/resource_sumologic_cse_threshold_rule_test.go +++ b/sumologic/resource_sumologic_cse_threshold_rule_test.go @@ -2,13 +2,14 @@ package sumologic import ( "fmt" + "strings" "testing" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/terraform" ) -func TestAccSumologicCSEThresholdRuleWithCustomWindowSize_createAndUpdate(t *testing.T) { +func TestAccSumologicCSEThresholdRule_createAndUpdateWithCustomWindowSize(t *testing.T) { SkipCseTest(t) var thresholdRule CSEThresholdRule @@ -298,7 +299,7 @@ func testCheckThresholdRuleValues(thresholdRule *CSEThresholdRule, countDistinct if thresholdRule.WindowSizeName != windowSize { return fmt.Errorf("bad windowSize, expected \"%s\", got %#v", windowSize, thresholdRule.WindowSizeName) } - if thresholdRule.WindowSizeName == "CUSTOM" && string(thresholdRule.WindowSize) != windowSizeMillis { + if strings.EqualFold(thresholdRule.WindowSizeName, "CUSTOM") && string(thresholdRule.WindowSize) != windowSizeMillis { return fmt.Errorf("bad WindowSize, expected \"%s\", got %#v", windowSizeMillis, thresholdRule.WindowSize) } From 248a367f365e9d1e85f1d8c8d90b32081b74e919 Mon Sep 17 00:00:00 2001 From: Piotr Wardecki Date: Tue, 5 Mar 2024 15:21:22 +0100 Subject: [PATCH 3/8] DET-422: Test refactor + new scenarios. --- ...ource_sumologic_cse_threshold_rule_test.go | 335 ++++++++---------- 1 file changed, 148 insertions(+), 187 deletions(-) diff --git a/sumologic/resource_sumologic_cse_threshold_rule_test.go b/sumologic/resource_sumologic_cse_threshold_rule_test.go index 28a0e7f4..deb1c866 100644 --- a/sumologic/resource_sumologic_cse_threshold_rule_test.go +++ b/sumologic/resource_sumologic_cse_threshold_rule_test.go @@ -1,71 +1,92 @@ package sumologic import ( + "bytes" "fmt" "strings" "testing" + "text/template" + "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/stretchr/testify/assert" ) func TestAccSumologicCSEThresholdRule_createAndUpdateWithCustomWindowSize(t *testing.T) { SkipCseTest(t) - var thresholdRule CSEThresholdRule - countDistinct := true - countField := "dstDevice_hostname" - description := "Test description" - enabled := true - entitySelectorEntityType := "_ip" - entitySelectorExpression := "srcDevice_ip" - expression := "foo = bar" - groupByField := "destPort" - isPrototype := false - limit := 20 - name := "Test Threshold Rule With Custom WindowSize" - severity := 5 - summaryExpression := "Signal Summary" - tag := "foo" - windowSize := "CUSTOM" - windowSizeMillis := "10800000" // 3h - updatedWindowSizeMillis := "14400000" // 4h + payload := getCSEThresholdRuleTestPayload() + payload.WindowSize = "CUSTOM" + payload.WindowSizeMilliseconds = "10800000" //3h + + updatedPayload := payload + updatedPayload.WindowSizeMilliseconds = "14400000" //4h + var thresholdRule CSEThresholdRule resourceName := "sumologic_cse_threshold_rule.threshold_rule" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCSEThresholdRuleDestroy, Steps: []resource.TestStep{ - { - Config: testCreateCSEThresholdRuleConfigWithCustomWindowSize(countDistinct, countField, - description, enabled, entitySelectorEntityType, - entitySelectorExpression, expression, groupByField, isPrototype, - limit, name, severity, summaryExpression, tag, windowSize, windowSizeMillis), + { // create + Config: testCreateCSEThresholdRuleConfigWithCustomWindowSize(t, &payload), Check: resource.ComposeTestCheckFunc( testCheckCSEThresholdRuleExists(resourceName, &thresholdRule), - testCheckThresholdRuleValues(&thresholdRule, countDistinct, countField, - description, enabled, entitySelectorEntityType, - entitySelectorExpression, expression, groupByField, isPrototype, - limit, name, severity, summaryExpression, tag, windowSize, windowSizeMillis), + testCheckCSEThresholdRuleValues(t, &payload, &thresholdRule), resource.TestCheckResourceAttrSet(resourceName, "id"), ), }, - { - Config: testCreateCSEThresholdRuleConfigWithCustomWindowSize(countDistinct, countField, - description, enabled, entitySelectorEntityType, - entitySelectorExpression, expression, groupByField, isPrototype, - limit, name, severity, summaryExpression, tag, windowSize, updatedWindowSizeMillis), + { // update + Config: testCreateCSEThresholdRuleConfigWithCustomWindowSize(t, &updatedPayload), Check: resource.ComposeTestCheckFunc( testCheckCSEThresholdRuleExists(resourceName, &thresholdRule), - testCheckThresholdRuleValues(&thresholdRule, countDistinct, countField, - description, enabled, entitySelectorEntityType, - entitySelectorExpression, expression, groupByField, isPrototype, - limit, name, severity, summaryExpression, tag, windowSize, updatedWindowSizeMillis), + testCheckCSEThresholdRuleValues(t, &updatedPayload, &thresholdRule), + ), + }, + { // import + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} +func TestAccSumologicCSEThresholdRule_createAndUpdateToCustomWindowSize(t *testing.T) { + SkipCseTest(t) + + payload := getCSEThresholdRuleTestPayload() + payload.WindowSize = "T30M" + payload.WindowSizeMilliseconds = "irrelevant" + + updatedPayload := payload + updatedPayload.WindowSize = "CUSTOM" + updatedPayload.WindowSizeMilliseconds = "14400000" //4h + + var thresholdRule CSEThresholdRule + resourceName := "sumologic_cse_threshold_rule.threshold_rule" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCSEThresholdRuleDestroy, + Steps: []resource.TestStep{ + { // create + Config: testCreateCSEThresholdRuleConfigWithCustomWindowSize(t, &payload), + Check: resource.ComposeTestCheckFunc( + testCheckCSEThresholdRuleExists(resourceName, &thresholdRule), + testCheckCSEThresholdRuleValues(t, &payload, &thresholdRule), resource.TestCheckResourceAttrSet(resourceName, "id"), ), }, - { + { // update + Config: testCreateCSEThresholdRuleConfigWithCustomWindowSize(t, &updatedPayload), + Check: resource.ComposeTestCheckFunc( + testCheckCSEThresholdRuleExists(resourceName, &thresholdRule), + testCheckCSEThresholdRuleValues(t, &updatedPayload, &thresholdRule), + ), + }, + { // import ResourceName: resourceName, ImportState: true, ImportStateVerify: true, @@ -77,60 +98,37 @@ func TestAccSumologicCSEThresholdRule_createAndUpdateWithCustomWindowSize(t *tes func TestAccSumologicCSEThresholdRule_createAndUpdate(t *testing.T) { SkipCseTest(t) - var thresholdRule CSEThresholdRule - countDistinct := true - countField := "dstDevice_hostname" - description := "Test description" - enabled := true - entitySelectorEntityType := "_ip" - entitySelectorExpression := "srcDevice_ip" - expression := "foo = bar" - groupByField := "destPort" - isPrototype := false - limit := 20 - name := "Test Threshold Rule" - severity := 5 - summaryExpression := "Signal Summary" - tag := "foo" - windowSize := "T30M" + payload := getCSEThresholdRuleTestPayload() + payload.WindowSizeName = "T30M" + payload.WindowSizeMilliseconds = "irrelevant" + + updatedPayload := payload + updatedPayload.Name = fmt.Sprintf("Updated Threshold Rule %s", uuid.New()) + updatedPayload.WindowSizeName = "T12H" - nameUpdated := "Updated Threshold Rule" - windowSizeUpdated := "T12H" + var thresholdRule CSEThresholdRule resourceName := "sumologic_cse_threshold_rule.threshold_rule" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCSEThresholdRuleDestroy, Steps: []resource.TestStep{ - { - Config: testCreateCSEThresholdRuleConfig(countDistinct, countField, - description, enabled, entitySelectorEntityType, - entitySelectorExpression, expression, groupByField, isPrototype, - limit, name, severity, summaryExpression, tag, windowSize), + { // create + Config: testCreateCSEThresholdRuleConfigWithCustomWindowSize(t, &payload), Check: resource.ComposeTestCheckFunc( testCheckCSEThresholdRuleExists(resourceName, &thresholdRule), - testCheckThresholdRuleValues(&thresholdRule, countDistinct, countField, - description, enabled, entitySelectorEntityType, - entitySelectorExpression, expression, groupByField, isPrototype, - limit, name, severity, summaryExpression, tag, windowSize, ""), + testCheckCSEThresholdRuleValues(t, &payload, &thresholdRule), resource.TestCheckResourceAttrSet(resourceName, "id"), ), }, - { - Config: testCreateCSEThresholdRuleConfig(countDistinct, countField, - description, enabled, entitySelectorEntityType, - entitySelectorExpression, expression, groupByField, isPrototype, - limit, nameUpdated, severity, summaryExpression, tag, windowSizeUpdated), + { // update + Config: testCreateCSEThresholdRuleConfigWithCustomWindowSize(t, &updatedPayload), Check: resource.ComposeTestCheckFunc( testCheckCSEThresholdRuleExists(resourceName, &thresholdRule), - testCheckThresholdRuleValues(&thresholdRule, countDistinct, countField, - description, enabled, entitySelectorEntityType, - entitySelectorExpression, expression, groupByField, isPrototype, - limit, nameUpdated, severity, summaryExpression, tag, windowSizeUpdated, ""), - resource.TestCheckResourceAttrSet(resourceName, "id"), + testCheckCSEThresholdRuleValues(t, &updatedPayload, &thresholdRule), ), }, - { + { // import ResourceName: resourceName, ImportState: true, ImportStateVerify: true, @@ -162,67 +160,66 @@ func testAccCSEThresholdRuleDestroy(s *terraform.State) error { return nil } -func testCreateCSEThresholdRuleConfigWithCustomWindowSize( - countDistinct bool, countField string, description string, enabled bool, - entitySelectorEntityType string, entitySelectorExpression string, - expression string, groupByField string, isPrototype bool, limit int, - name string, severity int, summaryExpression string, tag string, - windowSize string, windowSizeMillis string) string { - return fmt.Sprintf(` -resource "sumologic_cse_threshold_rule" "threshold_rule" { - count_distinct = %t - count_field = "%s" - description = "%s" - enabled = %t - entity_selectors { - entity_type = "%s" - expression = "%s" - } - expression = "%s" - group_by_fields = ["%s"] - is_prototype = %t - limit = %d - name = "%s" - severity = %d - summary_expression = "%s" - tags = ["%s"] - window_size = "%s" - window_size_millis = "%s" -} -`, countDistinct, countField, description, enabled, entitySelectorEntityType, - entitySelectorExpression, expression, groupByField, isPrototype, limit, name, - severity, summaryExpression, tag, windowSize, windowSizeMillis) -} +func testCreateCSEThresholdRuleConfigWithCustomWindowSize(t *testing.T, payload *CSEThresholdRule) string { + resourceTemlpate := ` + resource "sumologic_cse_threshold_rule" "threshold_rule" { + count_distinct = {{ .CountDistinct }} + count_field = "{{ .CountField }}" + description = "{{ .Description }}" + enabled = {{ .Enabled }} + {{ range .EntitySelectors }} + entity_selectors { + entity_type = "{{ .EntityType }}" + expression = "{{ .Expression }}" + } + {{ end }} + expression = "{{ js .Expression }}" + group_by_fields = {{ quoteStringArray .GroupByFields }} + is_prototype = {{ .IsPrototype }} + limit = {{ .Limit }} + name = "{{ .Name }}" + severity = {{ .Severity }} + summary_expression = "{{ .SummaryExpression }}" + tags = {{ quoteStringArray .Tags }} + window_size = "{{ .WindowSize }}" + {{ if eq .WindowSize "CUSTOM" }} + window_size_millis = "{{ .WindowSizeMilliseconds }}" + {{ end }} + } + ` -func testCreateCSEThresholdRuleConfig( - countDistinct bool, countField string, description string, enabled bool, - entitySelectorEntityType string, entitySelectorExpression string, - expression string, groupByField string, isPrototype bool, limit int, - name string, severity int, summaryExpression string, tag string, - windowSize string) string { - return fmt.Sprintf(` -resource "sumologic_cse_threshold_rule" "threshold_rule" { - count_distinct = %t - count_field = "%s" - description = "%s" - enabled = %t - entity_selectors { - entity_type = "%s" - expression = "%s" - } - expression = "%s" - group_by_fields = ["%s"] - is_prototype = %t - limit = %d - name = "%s" - severity = %d - summary_expression = "%s" - tags = ["%s"] - window_size = "%s" + configTemplate := template.Must(template.New("threshold_rule").Funcs(template.FuncMap{ + "quoteStringArray": func(arr []string) string { + return `["` + strings.Join(arr, `","`) + `"]` + }, + }).Parse(resourceTemlpate)) + + var buffer bytes.Buffer + if err := configTemplate.Execute(&buffer, payload); err != nil { + t.Error(err) + } + + return buffer.String() } -`, countDistinct, countField, description, enabled, entitySelectorEntityType, - entitySelectorExpression, expression, groupByField, isPrototype, limit, name, - severity, summaryExpression, tag, windowSize) + +func getCSEThresholdRuleTestPayload() CSEThresholdRule { + return CSEThresholdRule{ + CountDistinct: true, + CountField: "dstDevice_hostname", + Description: "Test description", + Enabled: true, + EntitySelectors: []EntitySelector{{EntityType: "_ip", Expression: "srcDevice_ip"}}, + Expression: "foo = bar", + GroupByFields: []string{"destPort"}, + IsPrototype: false, + Limit: 20, + Name: fmt.Sprintf("Test Threshold Rule With Custom WindowSize %s", uuid.New()), + Severity: 5, + SummaryExpression: "Signal Summary", + Tags: []string{"foo"}, + WindowSize: windowSizeField("CUSTOM"), + WindowSizeMilliseconds: "10800000", + } } func testCheckCSEThresholdRuleExists(n string, thresholdRule *CSEThresholdRule) resource.TestCheckFunc { @@ -248,61 +245,25 @@ func testCheckCSEThresholdRuleExists(n string, thresholdRule *CSEThresholdRule) } } -func testCheckThresholdRuleValues(thresholdRule *CSEThresholdRule, countDistinct bool, - countField string, description string, enabled bool, entitySelectorEntityType string, - entitySelectorExpression string, expression string, groupByField string, - isPrototype bool, limit int, name string, severity int, summaryExpression string, - tag string, windowSize string, windowSizeMillis string) resource.TestCheckFunc { +func testCheckCSEThresholdRuleValues(t *testing.T, expected *CSEThresholdRule, actual *CSEThresholdRule) resource.TestCheckFunc { return func(s *terraform.State) error { - if thresholdRule.CountDistinct != countDistinct { - return fmt.Errorf("bad countDistinct, expected \"%t\", got %#v", countDistinct, thresholdRule.CountDistinct) - } - if thresholdRule.CountField != countField { - return fmt.Errorf("bad countField, expected \"%s\", got %#v", countField, thresholdRule.CountField) - } - if thresholdRule.Description != description { - return fmt.Errorf("bad description, expected \"%s\", got %#v", description, thresholdRule.Description) + assert.Equal(t, expected.CountDistinct, actual.CountDistinct) + assert.Equal(t, expected.CountField, actual.CountField) + assert.Equal(t, expected.Description, actual.Description) + assert.Equal(t, expected.Enabled, actual.Enabled) + assert.Equal(t, expected.EntitySelectors, actual.EntitySelectors) + assert.Equal(t, expected.Expression, actual.Expression) + assert.Equal(t, expected.GroupByFields, actual.GroupByFields) + assert.Equal(t, expected.IsPrototype, actual.IsPrototype) + assert.Equal(t, expected.Limit, actual.Limit) + assert.Equal(t, expected.Name, actual.Name) + assert.Equal(t, expected.Severity, actual.Severity) + assert.Equal(t, expected.SummaryExpression, actual.SummaryExpression) + assert.Equal(t, expected.Tags, actual.Tags) + assert.Equal(t, string(expected.WindowSize), actual.WindowSizeName) + if strings.EqualFold(actual.WindowSizeName, "CUSTOM") { + assert.Equal(t, expected.WindowSizeMilliseconds, string(actual.WindowSize)) } - if thresholdRule.Enabled != enabled { - return fmt.Errorf("bad enabled, expected \"%t\", got %#v", enabled, thresholdRule.Enabled) - } - if thresholdRule.EntitySelectors[0].EntityType != entitySelectorEntityType { - return fmt.Errorf("bad entitySelectorEntityType, expected \"%s\", got %#v", entitySelectorEntityType, thresholdRule.EntitySelectors[0].EntityType) - } - if thresholdRule.EntitySelectors[0].Expression != entitySelectorExpression { - return fmt.Errorf("bad entitySelectorExpression, expected \"%s\", got %#v", entitySelectorExpression, thresholdRule.EntitySelectors[0].Expression) - } - if thresholdRule.Expression != expression { - return fmt.Errorf("bad expression, expected \"%s\", got %#v", expression, thresholdRule.Expression) - } - if thresholdRule.GroupByFields[0] != groupByField { - return fmt.Errorf("bad groupByField, expected \"%s\", got %#v", groupByField, thresholdRule.GroupByFields[0]) - } - if thresholdRule.IsPrototype != isPrototype { - return fmt.Errorf("bad isPrototype, expected \"%t\", got %#v", isPrototype, thresholdRule.IsPrototype) - } - if thresholdRule.Limit != limit { - return fmt.Errorf("bad limit, expected \"%d\", got %#v", limit, thresholdRule.Limit) - } - if thresholdRule.Name != name { - return fmt.Errorf("bad name, expected \"%s\", got %#v", name, thresholdRule.Name) - } - if thresholdRule.Severity != severity { - return fmt.Errorf("bad severity, expected \"%d\", got %#v", severity, thresholdRule.Severity) - } - if thresholdRule.SummaryExpression != summaryExpression { - return fmt.Errorf("bad summaryExpression, expected \"%s\", got %#v", summaryExpression, thresholdRule.SummaryExpression) - } - if thresholdRule.Tags[0] != tag { - return fmt.Errorf("bad tag, expected \"%s\", got %#v", tag, thresholdRule.Tags[0]) - } - if thresholdRule.WindowSizeName != windowSize { - return fmt.Errorf("bad windowSize, expected \"%s\", got %#v", windowSize, thresholdRule.WindowSizeName) - } - if strings.EqualFold(thresholdRule.WindowSizeName, "CUSTOM") && string(thresholdRule.WindowSize) != windowSizeMillis { - return fmt.Errorf("bad WindowSize, expected \"%s\", got %#v", windowSizeMillis, thresholdRule.WindowSize) - } - return nil } } From dd795c38bda9892166b533f14ede9b00733de3b0 Mon Sep 17 00:00:00 2001 From: Piotr Wardecki Date: Tue, 5 Mar 2024 16:52:30 +0100 Subject: [PATCH 4/8] DET-422:Docs and fixes. --- sumologic/resource_sumologic_cse_threshold_rule_test.go | 4 ++-- website/docs/r/cse_threshold_rule.html.markdown | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/sumologic/resource_sumologic_cse_threshold_rule_test.go b/sumologic/resource_sumologic_cse_threshold_rule_test.go index deb1c866..a29d181a 100644 --- a/sumologic/resource_sumologic_cse_threshold_rule_test.go +++ b/sumologic/resource_sumologic_cse_threshold_rule_test.go @@ -99,12 +99,12 @@ func TestAccSumologicCSEThresholdRule_createAndUpdate(t *testing.T) { SkipCseTest(t) payload := getCSEThresholdRuleTestPayload() - payload.WindowSizeName = "T30M" + payload.WindowSize = "T30M" payload.WindowSizeMilliseconds = "irrelevant" updatedPayload := payload updatedPayload.Name = fmt.Sprintf("Updated Threshold Rule %s", uuid.New()) - updatedPayload.WindowSizeName = "T12H" + updatedPayload.WindowSize = "T12H" var thresholdRule CSEThresholdRule resourceName := "sumologic_cse_threshold_rule.threshold_rule" diff --git a/website/docs/r/cse_threshold_rule.html.markdown b/website/docs/r/cse_threshold_rule.html.markdown index be27b85f..be7000bb 100644 --- a/website/docs/r/cse_threshold_rule.html.markdown +++ b/website/docs/r/cse_threshold_rule.html.markdown @@ -50,7 +50,8 @@ The following arguments are supported: - `severity` - (Required) The severity of the generated Signals - `summary_expression` - (Optional) The summary of the generated Signals - `tags` - (Required) The tags of the generated Signals -- `window_size` - (Required) How long of a window to aggregate records for. Current acceptable values are T05M, T10M, T30M, T60M, T24H, T12H, or T05D. +- `window_size` - (Required) How long of a window to aggregate records for. Current acceptable values are T05M, T10M, T30M, T60M, T24H, T12H, T05D or CUSTOM + + `window_size_millis` - (Optional) Used only when `window_size` is set to CUSTOM. Window size in milliseconds ranging from 1 minute to 5 days ("60000" to "432000000"). The following attributes are exported: From c52386dda8347a82fe60326b4c569620f6171414 Mon Sep 17 00:00:00 2001 From: Piotr Wardecki Date: Wed, 6 Mar 2024 14:00:26 +0100 Subject: [PATCH 5/8] DET-422: Test refactor. --- .../resource_sumologic_cse_threshold_rule_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sumologic/resource_sumologic_cse_threshold_rule_test.go b/sumologic/resource_sumologic_cse_threshold_rule_test.go index a29d181a..13b7a97f 100644 --- a/sumologic/resource_sumologic_cse_threshold_rule_test.go +++ b/sumologic/resource_sumologic_cse_threshold_rule_test.go @@ -31,7 +31,7 @@ func TestAccSumologicCSEThresholdRule_createAndUpdateWithCustomWindowSize(t *tes CheckDestroy: testAccCSEThresholdRuleDestroy, Steps: []resource.TestStep{ { // create - Config: testCreateCSEThresholdRuleConfigWithCustomWindowSize(t, &payload), + Config: testCreateCSEThresholdRuleConfig(t, &payload), Check: resource.ComposeTestCheckFunc( testCheckCSEThresholdRuleExists(resourceName, &thresholdRule), testCheckCSEThresholdRuleValues(t, &payload, &thresholdRule), @@ -39,7 +39,7 @@ func TestAccSumologicCSEThresholdRule_createAndUpdateWithCustomWindowSize(t *tes ), }, { // update - Config: testCreateCSEThresholdRuleConfigWithCustomWindowSize(t, &updatedPayload), + Config: testCreateCSEThresholdRuleConfig(t, &updatedPayload), Check: resource.ComposeTestCheckFunc( testCheckCSEThresholdRuleExists(resourceName, &thresholdRule), testCheckCSEThresholdRuleValues(t, &updatedPayload, &thresholdRule), @@ -72,7 +72,7 @@ func TestAccSumologicCSEThresholdRule_createAndUpdateToCustomWindowSize(t *testi CheckDestroy: testAccCSEThresholdRuleDestroy, Steps: []resource.TestStep{ { // create - Config: testCreateCSEThresholdRuleConfigWithCustomWindowSize(t, &payload), + Config: testCreateCSEThresholdRuleConfig(t, &payload), Check: resource.ComposeTestCheckFunc( testCheckCSEThresholdRuleExists(resourceName, &thresholdRule), testCheckCSEThresholdRuleValues(t, &payload, &thresholdRule), @@ -80,7 +80,7 @@ func TestAccSumologicCSEThresholdRule_createAndUpdateToCustomWindowSize(t *testi ), }, { // update - Config: testCreateCSEThresholdRuleConfigWithCustomWindowSize(t, &updatedPayload), + Config: testCreateCSEThresholdRuleConfig(t, &updatedPayload), Check: resource.ComposeTestCheckFunc( testCheckCSEThresholdRuleExists(resourceName, &thresholdRule), testCheckCSEThresholdRuleValues(t, &updatedPayload, &thresholdRule), @@ -114,7 +114,7 @@ func TestAccSumologicCSEThresholdRule_createAndUpdate(t *testing.T) { CheckDestroy: testAccCSEThresholdRuleDestroy, Steps: []resource.TestStep{ { // create - Config: testCreateCSEThresholdRuleConfigWithCustomWindowSize(t, &payload), + Config: testCreateCSEThresholdRuleConfig(t, &payload), Check: resource.ComposeTestCheckFunc( testCheckCSEThresholdRuleExists(resourceName, &thresholdRule), testCheckCSEThresholdRuleValues(t, &payload, &thresholdRule), @@ -122,7 +122,7 @@ func TestAccSumologicCSEThresholdRule_createAndUpdate(t *testing.T) { ), }, { // update - Config: testCreateCSEThresholdRuleConfigWithCustomWindowSize(t, &updatedPayload), + Config: testCreateCSEThresholdRuleConfig(t, &updatedPayload), Check: resource.ComposeTestCheckFunc( testCheckCSEThresholdRuleExists(resourceName, &thresholdRule), testCheckCSEThresholdRuleValues(t, &updatedPayload, &thresholdRule), @@ -160,7 +160,7 @@ func testAccCSEThresholdRuleDestroy(s *terraform.State) error { return nil } -func testCreateCSEThresholdRuleConfigWithCustomWindowSize(t *testing.T, payload *CSEThresholdRule) string { +func testCreateCSEThresholdRuleConfig(t *testing.T, payload *CSEThresholdRule) string { resourceTemlpate := ` resource "sumologic_cse_threshold_rule" "threshold_rule" { count_distinct = {{ .CountDistinct }} From 88c19e627a58fd89a38dd573ced98f0b6c15cf9d Mon Sep 17 00:00:00 2001 From: Piotr Wardecki Date: Wed, 6 Mar 2024 13:54:31 +0100 Subject: [PATCH 6/8] DET-422: Custom WindowSize for CSE Chain Rule. --- .../resource_sumologic_cse_chain_rule.go | 67 ++-- .../resource_sumologic_cse_chain_rule_test.go | 308 ++++++++++-------- sumologic/sumologic_cse_chain_rule.go | 31 +- website/docs/r/cse_chain_rule.html.markdown | 3 +- 4 files changed, 237 insertions(+), 172 deletions(-) diff --git a/sumologic/resource_sumologic_cse_chain_rule.go b/sumologic/resource_sumologic_cse_chain_rule.go index 026d5098..bb5a7ede 100644 --- a/sumologic/resource_sumologic_cse_chain_rule.go +++ b/sumologic/resource_sumologic_cse_chain_rule.go @@ -1,9 +1,11 @@ package sumologic import ( + "log" + "strings" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" - "log" ) func resourceSumologicCSEChainRule() *schema.Resource { @@ -83,6 +85,10 @@ func resourceSumologicCSEChainRule() *schema.Resource { Type: schema.TypeString, Required: true, }, + "window_size_millis": { + Type: schema.TypeString, + Optional: true, + }, }, } } @@ -116,6 +122,9 @@ func resourceSumologicCSEChainRuleRead(d *schema.ResourceData, meta interface{}) d.Set("summary_expression", CSEChainRuleGet.SummaryExpression) d.Set("tags", CSEChainRuleGet.Tags) d.Set("window_size", CSEChainRuleGet.WindowSizeName) + if strings.EqualFold(CSEChainRuleGet.WindowSizeName, "CUSTOM") { + d.Set("window_size_millis", CSEChainRuleGet.WindowSize) + } return nil } @@ -132,19 +141,20 @@ func resourceSumologicCSEChainRuleCreate(d *schema.ResourceData, meta interface{ if d.Id() == "" { id, err := c.CreateCSEChainRule(CSEChainRule{ - Description: d.Get("description").(string), - Enabled: d.Get("enabled").(bool), - EntitySelectors: resourceToEntitySelectorArray(d.Get("entity_selectors").([]interface{})), - ExpressionsAndLimits: resourceToExpressionsAndLimitsArray(d.Get("expressions_and_limits").([]interface{})), - GroupByFields: resourceToStringArray(d.Get("group_by_fields").([]interface{})), - IsPrototype: d.Get("is_prototype").(bool), - Ordered: d.Get("ordered").(bool), - Name: d.Get("name").(string), - Severity: d.Get("severity").(int), - Stream: "record", - SummaryExpression: d.Get("summary_expression").(string), - Tags: resourceToStringArray(d.Get("tags").([]interface{})), - WindowSize: windowSizeField(d.Get("window_size").(string)), + Description: d.Get("description").(string), + Enabled: d.Get("enabled").(bool), + EntitySelectors: resourceToEntitySelectorArray(d.Get("entity_selectors").([]interface{})), + ExpressionsAndLimits: resourceToExpressionsAndLimitsArray(d.Get("expressions_and_limits").([]interface{})), + GroupByFields: resourceToStringArray(d.Get("group_by_fields").([]interface{})), + IsPrototype: d.Get("is_prototype").(bool), + Ordered: d.Get("ordered").(bool), + Name: d.Get("name").(string), + Severity: d.Get("severity").(int), + Stream: "record", + SummaryExpression: d.Get("summary_expression").(string), + Tags: resourceToStringArray(d.Get("tags").([]interface{})), + WindowSize: windowSizeField(d.Get("window_size").(string)), + WindowSizeMilliseconds: d.Get("window_size_millis").(string), }) if err != nil { @@ -203,19 +213,20 @@ func resourceToCSEChainRule(d *schema.ResourceData) (CSEChainRule, error) { } return CSEChainRule{ - ID: id, - Description: d.Get("description").(string), - Enabled: d.Get("enabled").(bool), - EntitySelectors: resourceToEntitySelectorArray(d.Get("entity_selectors").([]interface{})), - ExpressionsAndLimits: resourceToExpressionsAndLimitsArray(d.Get("expressions_and_limits").([]interface{})), - GroupByFields: resourceToStringArray(d.Get("group_by_fields").([]interface{})), - IsPrototype: d.Get("is_prototype").(bool), - Ordered: d.Get("ordered").(bool), - Name: d.Get("name").(string), - Severity: d.Get("severity").(int), - Stream: "record", - SummaryExpression: d.Get("summary_expression").(string), - Tags: resourceToStringArray(d.Get("tags").([]interface{})), - WindowSize: windowSizeField(d.Get("window_size").(string)), + ID: id, + Description: d.Get("description").(string), + Enabled: d.Get("enabled").(bool), + EntitySelectors: resourceToEntitySelectorArray(d.Get("entity_selectors").([]interface{})), + ExpressionsAndLimits: resourceToExpressionsAndLimitsArray(d.Get("expressions_and_limits").([]interface{})), + GroupByFields: resourceToStringArray(d.Get("group_by_fields").([]interface{})), + IsPrototype: d.Get("is_prototype").(bool), + Ordered: d.Get("ordered").(bool), + Name: d.Get("name").(string), + Severity: d.Get("severity").(int), + Stream: "record", + SummaryExpression: d.Get("summary_expression").(string), + Tags: resourceToStringArray(d.Get("tags").([]interface{})), + WindowSize: windowSizeField(d.Get("window_size").(string)), + WindowSizeMilliseconds: d.Get("window_size_millis").(string), }, nil } diff --git a/sumologic/resource_sumologic_cse_chain_rule_test.go b/sumologic/resource_sumologic_cse_chain_rule_test.go index fb19fc33..7c7a583e 100644 --- a/sumologic/resource_sumologic_cse_chain_rule_test.go +++ b/sumologic/resource_sumologic_cse_chain_rule_test.go @@ -1,72 +1,135 @@ package sumologic import ( + "bytes" "fmt" + "strings" "testing" + "text/template" + "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/stretchr/testify/assert" ) -func TestAccSumologicCSEChainRule_createAndUpdate(t *testing.T) { +func TestAccSumologicCSEChainRule_createAndUpdateWithCustomWindowSize(t *testing.T) { + SkipCseTest(t) + + payload := getCSEChainRuleTestPayload() + payload.WindowSize = "CUSTOM" + payload.WindowSizeMilliseconds = "10800000" // 3h + + updatedPayload := payload + updatedPayload.WindowSizeMilliseconds = "14400000" // 4h + + var chainRule CSEChainRule + resourceName := "sumologic_cse_chain_rule.chain_rule" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCSEChainRuleDestroy, + Steps: []resource.TestStep{ + { // create + Config: testCreateCSEChainRuleConfig(t, &payload), + Check: resource.ComposeTestCheckFunc( + testCheckCSEChainRuleExists(resourceName, &chainRule), + testCheckCSEChainRuleValues(t, &payload, &chainRule), + resource.TestCheckResourceAttrSet(resourceName, "id"), + ), + }, + { // update + Config: testCreateCSEChainRuleConfig(t, &updatedPayload), + Check: resource.ComposeTestCheckFunc( + testCheckCSEChainRuleExists(resourceName, &chainRule), + testCheckCSEChainRuleValues(t, &updatedPayload, &chainRule), + ), + }, + { // import + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccSumologicCSEChainRule_createAndUpdateToCustomWindowSize(t *testing.T) { SkipCseTest(t) - var ChainRule CSEChainRule - description := "Test description" - enabled := true - entitySelectorEntityType := "_ip" - entitySelectorExpression := "srcDevice_ip" - expression1 := "foo = bar" - limit1 := 5 - expression2 := "baz = qux" - limit2 := 1 - groupByField := "destPort" - isPrototype := false - ordered := true - name := "Test Chain Rule" - severity := 5 - summaryExpression := "Signal Summary" - tag := "foo" - windowSize := "T30M" - - nameUpdated := "Updated Chain Rule" - windowSizeUpdated := "T12H" + payload := getCSEChainRuleTestPayload() + payload.WindowSize = "T30M" + payload.WindowSizeMilliseconds = "irrelevant" + + updatedPayload := payload + updatedPayload.WindowSize = "CUSTOM" + updatedPayload.WindowSizeMilliseconds = "14400000" // 4h + var chainRule CSEChainRule resourceName := "sumologic_cse_chain_rule.chain_rule" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCSEChainRuleDestroy, Steps: []resource.TestStep{ - { - Config: testCreateCSEChainRuleConfig(description, enabled, - entitySelectorEntityType, entitySelectorExpression, expression1, - limit1, expression2, limit2, groupByField, isPrototype, ordered, - name, severity, summaryExpression, tag, windowSize), + { // create + Config: testCreateCSEChainRuleConfig(t, &payload), Check: resource.ComposeTestCheckFunc( - testCheckCSEChainRuleExists(resourceName, &ChainRule), - testCheckChainRuleValues(&ChainRule, description, enabled, - entitySelectorEntityType, entitySelectorExpression, expression1, - limit1, expression2, limit2, groupByField, isPrototype, ordered, - name, severity, summaryExpression, tag, windowSize), + testCheckCSEChainRuleExists(resourceName, &chainRule), + testCheckCSEChainRuleValues(t, &payload, &chainRule), resource.TestCheckResourceAttrSet(resourceName, "id"), ), }, - { - Config: testCreateCSEChainRuleConfig(description, enabled, - entitySelectorEntityType, entitySelectorExpression, expression1, - limit1, expression2, limit2, groupByField, isPrototype, ordered, - nameUpdated, severity, summaryExpression, tag, windowSizeUpdated), + { // update + Config: testCreateCSEChainRuleConfig(t, &updatedPayload), + Check: resource.ComposeTestCheckFunc( + testCheckCSEChainRuleExists(resourceName, &chainRule), + testCheckCSEChainRuleValues(t, &updatedPayload, &chainRule), + ), + }, + { // import + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccSumologicCSEChainRule_createAndUpdate(t *testing.T) { + SkipCseTest(t) + + payload := getCSEChainRuleTestPayload() + payload.WindowSize = "T30M" + payload.WindowSizeMilliseconds = "irrelevant" + + updatedPayload := payload + updatedPayload.Name = fmt.Sprintf("Updated Chain Rule %s", uuid.New()) + updatedPayload.WindowSize = "T12H" + + var chainRule CSEChainRule + resourceName := "sumologic_cse_chain_rule.chain_rule" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCSEChainRuleDestroy, + Steps: []resource.TestStep{ + { // create + Config: testCreateCSEChainRuleConfig(t, &payload), Check: resource.ComposeTestCheckFunc( - testCheckCSEChainRuleExists(resourceName, &ChainRule), - testCheckChainRuleValues(&ChainRule, description, enabled, - entitySelectorEntityType, entitySelectorExpression, expression1, - limit1, expression2, limit2, groupByField, isPrototype, ordered, - nameUpdated, severity, summaryExpression, tag, windowSizeUpdated), + testCheckCSEChainRuleExists(resourceName, &chainRule), + testCheckCSEChainRuleValues(t, &payload, &chainRule), resource.TestCheckResourceAttrSet(resourceName, "id"), ), }, - { + { // update + Config: testCreateCSEChainRuleConfig(t, &updatedPayload), + Check: resource.ComposeTestCheckFunc( + testCheckCSEChainRuleExists(resourceName, &chainRule), + testCheckCSEChainRuleValues(t, &updatedPayload, &chainRule), + ), + }, + { // import ResourceName: resourceName, ImportState: true, ImportStateVerify: true, @@ -98,40 +161,67 @@ func testAccCSEChainRuleDestroy(s *terraform.State) error { return nil } -func testCreateCSEChainRuleConfig( - description string, enabled bool, entitySelectorEntityType string, - entitySelectorExpression string, expression1 string, limit1 int, - expression2 string, limit2 int, groupByField string, isPrototype bool, - ordered bool, name string, severity int, summaryExpression string, tag string, - windowSize string) string { - return fmt.Sprintf(` -resource "sumologic_cse_chain_rule" "chain_rule" { - description = "%s" - enabled = %t - entity_selectors { - entity_type = "%s" - expression = "%s" - } - expressions_and_limits { - expression = "%s" - limit = %d - } - expressions_and_limits { - expression = "%s" - limit = %d - } - group_by_fields = ["%s"] - is_prototype = %t - ordered = %t - name = "%s" - severity = %d - summary_expression = "%s" - tags = ["%s"] - window_size = "%s" +func testCreateCSEChainRuleConfig(t *testing.T, payload *CSEChainRule) string { + resourceTemplate := ` + resource "sumologic_cse_chain_rule" "chain_rule" { + description = "{{ .Description }}" + enabled = {{ .Enabled }} + {{ range .EntitySelectors }} + entity_selectors { + entity_type = "{{ .EntityType }}" + expression = "{{ .Expression }}" + } + {{ end }} + {{ range .ExpressionsAndLimits }} + expressions_and_limits { + expression = "{{ .Expression }}" + limit = {{ .Limit }} + } + {{ end }} + group_by_fields = {{ quoteStringArray .GroupByFields }} + is_prototype = {{ .IsPrototype }} + ordered = {{ .Ordered }} + name = "{{ .Name }}" + severity = {{ .Severity }} + summary_expression = "{{ .SummaryExpression }}" + tags = {{ quoteStringArray .Tags }} + window_size = "{{ .WindowSize }}" + {{ if eq .WindowSize "CUSTOM" }} + window_size_millis = "{{ .WindowSizeMilliseconds }}" + {{ end }} + } + ` + + configTemplate := template.Must(template.New("chain_rule").Funcs(template.FuncMap{ + "quoteStringArray": func(arr []string) string { + return `["` + strings.Join(arr, `","`) + `"]` + }, + }).Parse(resourceTemplate)) + + var buffer bytes.Buffer + if err := configTemplate.Execute(&buffer, payload); err != nil { + t.Error(err) + } + + return buffer.String() } -`, description, enabled, entitySelectorEntityType, entitySelectorExpression, - expression1, limit1, expression2, limit2, groupByField, isPrototype, ordered, - name, severity, summaryExpression, tag, windowSize) + +func getCSEChainRuleTestPayload() CSEChainRule { + return CSEChainRule{ + Description: "Test description", + Enabled: true, + EntitySelectors: []EntitySelector{{EntityType: "_ip", Expression: "srcDevice_ip"}}, + ExpressionsAndLimits: []ExpressionAndLimit{{Expression: "foo = bar", Limit: 5}, {Expression: "baz = qux", Limit: 1}}, + GroupByFields: []string{"destPort"}, + IsPrototype: false, + Ordered: true, + Name: fmt.Sprintf("Test Chain Rule %s", uuid.New()), + Severity: 5, + SummaryExpression: "Signal Summary", + Tags: []string{"foo"}, + WindowSize: windowSizeField("CUSTOM"), + WindowSizeMilliseconds: "10800000", + } } func testCheckCSEChainRuleExists(n string, ChainRule *CSEChainRule) resource.TestCheckFunc { @@ -157,61 +247,23 @@ func testCheckCSEChainRuleExists(n string, ChainRule *CSEChainRule) resource.Tes } } -func testCheckChainRuleValues(ChainRule *CSEChainRule, description string, - enabled bool, entitySelectorEntityType string, entitySelectorExpression string, - expression1 string, limit1 int, expression2 string, limit2 int, groupByField string, - isPrototype bool, ordered bool, name string, severity int, summaryExpression string, - tag string, windowSize string) resource.TestCheckFunc { +func testCheckCSEChainRuleValues(t *testing.T, expected *CSEChainRule, actual *CSEChainRule) resource.TestCheckFunc { return func(s *terraform.State) error { - if ChainRule.Description != description { - return fmt.Errorf("bad description, expected \"%s\", got %#v", description, ChainRule.Description) - } - if ChainRule.Enabled != enabled { - return fmt.Errorf("bad enabled, expected \"%t\", got %#v", enabled, ChainRule.Enabled) - } - if ChainRule.EntitySelectors[0].EntityType != entitySelectorEntityType { - return fmt.Errorf("bad entitySelectorEntityType, expected \"%s\", got %#v", entitySelectorEntityType, ChainRule.EntitySelectors[0].EntityType) - } - if ChainRule.EntitySelectors[0].Expression != entitySelectorExpression { - return fmt.Errorf("bad entitySelectorExpression, expected \"%s\", got %#v", entitySelectorExpression, ChainRule.EntitySelectors[0].Expression) - } - if ChainRule.ExpressionsAndLimits[0].Expression != expression1 { - return fmt.Errorf("bad expression1, expected \"%s\", got %#v", expression1, ChainRule.ExpressionsAndLimits[0].Expression) - } - if ChainRule.ExpressionsAndLimits[0].Limit != limit1 { - return fmt.Errorf("bad limit1, expected \"%d\", got %#v", limit1, ChainRule.ExpressionsAndLimits[0].Limit) - } - if ChainRule.ExpressionsAndLimits[1].Expression != expression2 { - return fmt.Errorf("bad expression2, expected \"%s\", got %#v", expression2, ChainRule.ExpressionsAndLimits[1].Expression) - } - if ChainRule.ExpressionsAndLimits[1].Limit != limit2 { - return fmt.Errorf("bad limit2, expected \"%d\", got %#v", limit2, ChainRule.ExpressionsAndLimits[1].Limit) + assert.Equal(t, expected.Description, actual.Description) + assert.Equal(t, expected.Enabled, actual.Enabled) + assert.Equal(t, expected.EntitySelectors, actual.EntitySelectors) + assert.Equal(t, expected.ExpressionsAndLimits, actual.ExpressionsAndLimits) + assert.Equal(t, expected.GroupByFields, actual.GroupByFields) + assert.Equal(t, expected.IsPrototype, actual.IsPrototype) + assert.Equal(t, expected.Ordered, actual.Ordered) + assert.Equal(t, expected.Name, actual.Name) + assert.Equal(t, expected.Severity, actual.Severity) + assert.Equal(t, expected.SummaryExpression, actual.SummaryExpression) + assert.Equal(t, expected.Tags, actual.Tags) + assert.Equal(t, string(expected.WindowSize), actual.WindowSizeName) + if strings.EqualFold(actual.WindowSizeName, "CUSTOM") { + assert.Equal(t, expected.WindowSizeMilliseconds, string(actual.WindowSize)) } - if ChainRule.GroupByFields[0] != groupByField { - return fmt.Errorf("bad groupByField, expected \"%s\", got %#v", groupByField, ChainRule.GroupByFields[0]) - } - if ChainRule.IsPrototype != isPrototype { - return fmt.Errorf("bad isPrototype, expected \"%t\", got %#v", isPrototype, ChainRule.IsPrototype) - } - if ChainRule.Ordered != ordered { - return fmt.Errorf("bad ordered, expected \"%t\", got %#v", ordered, ChainRule.Ordered) - } - if ChainRule.Name != name { - return fmt.Errorf("bad name, expected \"%s\", got %#v", name, ChainRule.Name) - } - if ChainRule.Severity != severity { - return fmt.Errorf("bad severity, expected \"%d\", got %#v", severity, ChainRule.Severity) - } - if ChainRule.SummaryExpression != summaryExpression { - return fmt.Errorf("bad summaryExpression, expected \"%s\", got %#v", summaryExpression, ChainRule.SummaryExpression) - } - if ChainRule.Tags[0] != tag { - return fmt.Errorf("bad tag, expected \"%s\", got %#v", tag, ChainRule.Tags[0]) - } - if ChainRule.WindowSizeName != windowSize { - return fmt.Errorf("bad windowSize, expected \"%s\", got %#v", windowSize, ChainRule.WindowSizeName) - } - return nil } } diff --git a/sumologic/sumologic_cse_chain_rule.go b/sumologic/sumologic_cse_chain_rule.go index 649cd015..838aaa84 100644 --- a/sumologic/sumologic_cse_chain_rule.go +++ b/sumologic/sumologic_cse_chain_rule.go @@ -78,19 +78,20 @@ type ExpressionAndLimit struct { } type CSEChainRule struct { - ID string `json:"id,omitempty"` - Description string `json:"description"` - Enabled bool `json:"enabled"` - EntitySelectors []EntitySelector `json:"entitySelectors"` - ExpressionsAndLimits []ExpressionAndLimit `json:"expressionsAndLimits"` - GroupByFields []string `json:"groupByFields"` - IsPrototype bool `json:"isPrototype"` - Ordered bool `json:"ordered"` - Name string `json:"name"` - Severity int `json:"score"` - Stream string `json:"stream"` - SummaryExpression string `json:"summaryExpression"` - Tags []string `json:"tags"` - WindowSize windowSizeField `json:"windowSize,omitempty"` - WindowSizeName string `json:"windowSizeName,omitempty"` + ID string `json:"id,omitempty"` + Description string `json:"description"` + Enabled bool `json:"enabled"` + EntitySelectors []EntitySelector `json:"entitySelectors"` + ExpressionsAndLimits []ExpressionAndLimit `json:"expressionsAndLimits"` + GroupByFields []string `json:"groupByFields"` + IsPrototype bool `json:"isPrototype"` + Ordered bool `json:"ordered"` + Name string `json:"name"` + Severity int `json:"score"` + Stream string `json:"stream"` + SummaryExpression string `json:"summaryExpression"` + Tags []string `json:"tags"` + WindowSize windowSizeField `json:"windowSize,omitempty"` + WindowSizeName string `json:"windowSizeName,omitempty"` + WindowSizeMilliseconds string `json:"windowSizeMilliseconds,omitempty"` } diff --git a/website/docs/r/cse_chain_rule.html.markdown b/website/docs/r/cse_chain_rule.html.markdown index f8ccdccd..b7a7085b 100644 --- a/website/docs/r/cse_chain_rule.html.markdown +++ b/website/docs/r/cse_chain_rule.html.markdown @@ -55,7 +55,8 @@ The following arguments are supported: - `severity` - (Required) The severity of the generated Signals - `summary_expression` - (Optional) The summary of the generated Signals - `tags` - (Required) The tags of the generated Signals -- `window_size` - (Required) How long of a window to aggregate records for. Current acceptable values are T05M, T10M, T30M, T60M, T24H, T12H, or T05D. +- `window_size` - (Required) How long of a window to aggregate records for. Current acceptable values are T05M, T10M, T30M, T60M, T24H, T12H, T05D or CUSTOM + + `window_size_millis` - (Optional) Used only when `window_size` is set to CUSTOM. Window size in milliseconds ranging from 1 minute to 5 days ("60000" to "432000000"). The following attributes are exported: From 6e50a9ff7c0aae0abe443a79057cbc7c12af9bc3 Mon Sep 17 00:00:00 2001 From: Piotr Wardecki Date: Wed, 6 Mar 2024 15:09:51 +0100 Subject: [PATCH 7/8] DET-422: Custom WindowSize for CSE Aggregation Rule. --- ...resource_sumologic_cse_aggregation_rule.go | 80 ++-- ...rce_sumologic_cse_aggregation_rule_test.go | 352 ++++++++++-------- sumologic/sumologic_cse_aggregation_rule.go | 37 +- .../docs/r/cse_aggregation_rule.html.markdown | 3 +- 4 files changed, 267 insertions(+), 205 deletions(-) diff --git a/sumologic/resource_sumologic_cse_aggregation_rule.go b/sumologic/resource_sumologic_cse_aggregation_rule.go index 7d5f7177..36986a4d 100644 --- a/sumologic/resource_sumologic_cse_aggregation_rule.go +++ b/sumologic/resource_sumologic_cse_aggregation_rule.go @@ -1,9 +1,11 @@ package sumologic import ( + "log" + "strings" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" - "log" ) func resourceSumologicCSEAggregationRule() *schema.Resource { @@ -99,6 +101,10 @@ func resourceSumologicCSEAggregationRule() *schema.Resource { Type: schema.TypeString, Required: true, }, + "window_size_millis": { + Type: schema.TypeString, + Optional: true, + }, }, } } @@ -136,7 +142,9 @@ func resourceSumologicCSEAggregationRuleRead(d *schema.ResourceData, meta interf d.Set("tags", CSEAggregationRuleGet.Tags) d.Set("trigger_expression", CSEAggregationRuleGet.TriggerExpression) d.Set("window_size", CSEAggregationRuleGet.WindowSizeName) - + if strings.EqualFold(CSEAggregationRuleGet.WindowSizeName, "CUSTOM") { + d.Set("window_size_millis", CSEAggregationRuleGet.WindowSize) + } return nil } @@ -152,22 +160,23 @@ func resourceSumologicCSEAggregationRuleCreate(d *schema.ResourceData, meta inte if d.Id() == "" { id, err := c.CreateCSEAggregationRule(CSEAggregationRule{ - AggregationFunctions: resourceToAggregationFunctionsArray(d.Get("aggregation_functions").([]interface{})), - DescriptionExpression: d.Get("description_expression").(string), - Enabled: d.Get("enabled").(bool), - EntitySelectors: resourceToEntitySelectorArray(d.Get("entity_selectors").([]interface{})), - GroupByEntity: d.Get("group_by_entity").(bool), - GroupByFields: resourceToStringArray(d.Get("group_by_fields").([]interface{})), - IsPrototype: d.Get("is_prototype").(bool), - MatchExpression: d.Get("match_expression").(string), - Name: d.Get("name").(string), - NameExpression: d.Get("name_expression").(string), - SeverityMapping: resourceToSeverityMapping(d.Get("severity_mapping").([]interface{})[0]), - Stream: "record", - SummaryExpression: d.Get("summary_expression").(string), - Tags: resourceToStringArray(d.Get("tags").([]interface{})), - TriggerExpression: d.Get("trigger_expression").(string), - WindowSize: windowSizeField(d.Get("window_size").(string)), + AggregationFunctions: resourceToAggregationFunctionsArray(d.Get("aggregation_functions").([]interface{})), + DescriptionExpression: d.Get("description_expression").(string), + Enabled: d.Get("enabled").(bool), + EntitySelectors: resourceToEntitySelectorArray(d.Get("entity_selectors").([]interface{})), + GroupByEntity: d.Get("group_by_entity").(bool), + GroupByFields: resourceToStringArray(d.Get("group_by_fields").([]interface{})), + IsPrototype: d.Get("is_prototype").(bool), + MatchExpression: d.Get("match_expression").(string), + Name: d.Get("name").(string), + NameExpression: d.Get("name_expression").(string), + SeverityMapping: resourceToSeverityMapping(d.Get("severity_mapping").([]interface{})[0]), + Stream: "record", + SummaryExpression: d.Get("summary_expression").(string), + Tags: resourceToStringArray(d.Get("tags").([]interface{})), + TriggerExpression: d.Get("trigger_expression").(string), + WindowSize: windowSizeField(d.Get("window_size").(string)), + WindowSizeMilliseconds: d.Get("window_size_millis").(string), }) if err != nil { @@ -229,22 +238,23 @@ func resourceToCSEAggregationRule(d *schema.ResourceData) (CSEAggregationRule, e } return CSEAggregationRule{ - ID: id, - AggregationFunctions: resourceToAggregationFunctionsArray(d.Get("aggregation_functions").([]interface{})), - DescriptionExpression: d.Get("description_expression").(string), - Enabled: d.Get("enabled").(bool), - EntitySelectors: resourceToEntitySelectorArray(d.Get("entity_selectors").([]interface{})), - GroupByEntity: d.Get("group_by_entity").(bool), - GroupByFields: resourceToStringArray(d.Get("group_by_fields").([]interface{})), - IsPrototype: d.Get("is_prototype").(bool), - MatchExpression: d.Get("match_expression").(string), - Name: d.Get("name").(string), - NameExpression: d.Get("name_expression").(string), - SeverityMapping: resourceToSeverityMapping(d.Get("severity_mapping").([]interface{})[0]), - Stream: "record", - SummaryExpression: d.Get("summary_expression").(string), - Tags: resourceToStringArray(d.Get("tags").([]interface{})), - TriggerExpression: d.Get("trigger_expression").(string), - WindowSize: windowSizeField(d.Get("window_size").(string)), + ID: id, + AggregationFunctions: resourceToAggregationFunctionsArray(d.Get("aggregation_functions").([]interface{})), + DescriptionExpression: d.Get("description_expression").(string), + Enabled: d.Get("enabled").(bool), + EntitySelectors: resourceToEntitySelectorArray(d.Get("entity_selectors").([]interface{})), + GroupByEntity: d.Get("group_by_entity").(bool), + GroupByFields: resourceToStringArray(d.Get("group_by_fields").([]interface{})), + IsPrototype: d.Get("is_prototype").(bool), + MatchExpression: d.Get("match_expression").(string), + Name: d.Get("name").(string), + NameExpression: d.Get("name_expression").(string), + SeverityMapping: resourceToSeverityMapping(d.Get("severity_mapping").([]interface{})[0]), + Stream: "record", + SummaryExpression: d.Get("summary_expression").(string), + Tags: resourceToStringArray(d.Get("tags").([]interface{})), + TriggerExpression: d.Get("trigger_expression").(string), + WindowSize: windowSizeField(d.Get("window_size").(string)), + WindowSizeMilliseconds: d.Get("window_size_millis").(string), }, nil } diff --git a/sumologic/resource_sumologic_cse_aggregation_rule_test.go b/sumologic/resource_sumologic_cse_aggregation_rule_test.go index 0b6dacc6..9952ac25 100644 --- a/sumologic/resource_sumologic_cse_aggregation_rule_test.go +++ b/sumologic/resource_sumologic_cse_aggregation_rule_test.go @@ -1,79 +1,135 @@ package sumologic import ( + "bytes" "fmt" + "strings" "testing" + "text/template" + "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/stretchr/testify/assert" ) -func TestAccSumologicCSEAggregationRule_createAndUpdate(t *testing.T) { +func TestAccSumologicCSEAggregationRule_createAndUpdateWithCustomWindowSize(t *testing.T) { + SkipCseTest(t) + + payload := getCSEAggregationRuleTestPayload() + payload.WindowSize = "CUSTOM" + payload.WindowSizeMilliseconds = "10800000" // 3h + + updatedPayload := payload + updatedPayload.WindowSizeMilliseconds = "14400000" // 4h + + var aggregationRule CSEAggregationRule + resourceName := "sumologic_cse_aggregation_rule.aggregation_rule" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCSEAggregationRuleDestroy, + Steps: []resource.TestStep{ + { // create + Config: testCreateCSEAggregationRuleConfig(t, &payload), + Check: resource.ComposeTestCheckFunc( + testCheckCSEAggregationRuleExists(resourceName, &aggregationRule), + testCheckCSEAggregationRuleValues(t, &payload, &aggregationRule), + resource.TestCheckResourceAttrSet(resourceName, "id"), + ), + }, + { // update + Config: testCreateCSEAggregationRuleConfig(t, &updatedPayload), + Check: resource.ComposeTestCheckFunc( + testCheckCSEAggregationRuleExists(resourceName, &aggregationRule), + testCheckCSEAggregationRuleValues(t, &updatedPayload, &aggregationRule), + ), + }, + { // import + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccSumologicCSEAggregationRule_createAndUpdateToCustomWindowSize(t *testing.T) { SkipCseTest(t) - var AggregationRule CSEAggregationRule - aggregationFunctionName := "distinct_eventid_count" - aggregationFunction := "count_distinct" - aggregationFunctionArgument := "metadata_deviceEventId" - descriptionExpression := "Test description" - enabled := true - entitySelectorEntityType := "_ip" - entitySelectorExpression := "srcDevice_ip" - groupByEntity := true - groupByField := "dstDevice_hostname" - isPrototype := false - matchExpression := "foo = bar" - name := "Test Aggregation Rule" - nameExpression := "Signal Name" - severityMappingType := "constant" - severityMappingDefault := 5 - summaryExpression := "Signal Summary" - triggerExpression := "foo = bar" - tag := "foo" - windowSize := "T30M" - - nameUpdated := "Updated Aggregation Rule" - tagUpdated := "bar" + payload := getCSEAggregationRuleTestPayload() + payload.WindowSize = "T30M" + payload.WindowSizeMilliseconds = "irrelevant" + + updatedPayload := payload + updatedPayload.WindowSize = "CUSTOM" + updatedPayload.WindowSizeMilliseconds = "14400000" // 4h + var aggregationRule CSEAggregationRule resourceName := "sumologic_cse_aggregation_rule.aggregation_rule" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCSEAggregationRuleDestroy, Steps: []resource.TestStep{ - { - Config: testCreateCSEAggregationRuleConfig(aggregationFunctionName, aggregationFunction, - aggregationFunctionArgument, descriptionExpression, enabled, entitySelectorEntityType, - entitySelectorExpression, groupByEntity, groupByField, isPrototype, matchExpression, - name, nameExpression, severityMappingType, severityMappingDefault, summaryExpression, - triggerExpression, tag, windowSize), + { // create + Config: testCreateCSEAggregationRuleConfig(t, &payload), Check: resource.ComposeTestCheckFunc( - testCheckCSEAggregationRuleExists(resourceName, &AggregationRule), - testCheckAggregationRuleValues(&AggregationRule, aggregationFunctionName, - aggregationFunction, aggregationFunctionArgument, descriptionExpression, enabled, - entitySelectorEntityType, entitySelectorExpression, groupByEntity, groupByField, - isPrototype, matchExpression, name, nameExpression, severityMappingType, - severityMappingDefault, summaryExpression, triggerExpression, tag, windowSize), + testCheckCSEAggregationRuleExists(resourceName, &aggregationRule), + testCheckCSEAggregationRuleValues(t, &payload, &aggregationRule), resource.TestCheckResourceAttrSet(resourceName, "id"), ), }, - { - Config: testCreateCSEAggregationRuleConfig(aggregationFunctionName, aggregationFunction, - aggregationFunctionArgument, descriptionExpression, enabled, entitySelectorEntityType, - entitySelectorExpression, groupByEntity, groupByField, isPrototype, matchExpression, - nameUpdated, nameExpression, severityMappingType, severityMappingDefault, summaryExpression, - triggerExpression, tagUpdated, windowSize), + { // update + Config: testCreateCSEAggregationRuleConfig(t, &updatedPayload), + Check: resource.ComposeTestCheckFunc( + testCheckCSEAggregationRuleExists(resourceName, &aggregationRule), + testCheckCSEAggregationRuleValues(t, &updatedPayload, &aggregationRule), + ), + }, + { // import + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccSumologicCSEAggregationRule_createAndUpdate(t *testing.T) { + SkipCseTest(t) + + payload := getCSEAggregationRuleTestPayload() + payload.WindowSize = "T30M" + payload.WindowSizeMilliseconds = "irrelevant" + + updatedPayload := payload + updatedPayload.Name = fmt.Sprintf("Updated Aggregation Rule %s", uuid.New()) + updatedPayload.WindowSize = "T12H" + + var aggregationRule CSEAggregationRule + resourceName := "sumologic_cse_aggregation_rule.aggregation_rule" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCSEAggregationRuleDestroy, + Steps: []resource.TestStep{ + { // create + Config: testCreateCSEAggregationRuleConfig(t, &payload), Check: resource.ComposeTestCheckFunc( - testCheckCSEAggregationRuleExists(resourceName, &AggregationRule), - testCheckAggregationRuleValues(&AggregationRule, aggregationFunctionName, - aggregationFunction, aggregationFunctionArgument, descriptionExpression, enabled, - entitySelectorEntityType, entitySelectorExpression, groupByEntity, groupByField, - isPrototype, matchExpression, nameUpdated, nameExpression, severityMappingType, - severityMappingDefault, summaryExpression, triggerExpression, tagUpdated, windowSize), + testCheckCSEAggregationRuleExists(resourceName, &aggregationRule), + testCheckCSEAggregationRuleValues(t, &payload, &aggregationRule), resource.TestCheckResourceAttrSet(resourceName, "id"), ), }, - { + { // update + Config: testCreateCSEAggregationRuleConfig(t, &updatedPayload), + Check: resource.ComposeTestCheckFunc( + testCheckCSEAggregationRuleExists(resourceName, &aggregationRule), + testCheckCSEAggregationRuleValues(t, &updatedPayload, &aggregationRule), + ), + }, + { // import ResourceName: resourceName, ImportState: true, ImportStateVerify: true, @@ -105,46 +161,85 @@ func testAccCSEAggregationRuleDestroy(s *terraform.State) error { return nil } -func testCreateCSEAggregationRuleConfig( - aggregationFunctionName string, aggregationFunction string, - aggregationFunctionArgument string, descriptionExpression string, enabled bool, - entitySelectorEntityType string, entitySelectorExpression string, groupByEntity bool, - groupByField string, isPrototype bool, matchExpression string, name string, - nameExpression string, severityMappingType string, severityMappingDefault int, - summaryExpression string, triggerExpression string, tag string, windowSize string) string { - return fmt.Sprintf(` -resource "sumologic_cse_aggregation_rule" "aggregation_rule" { - aggregation_functions { - name = "%s" - function = "%s" - arguments = ["%s"] +func testCreateCSEAggregationRuleConfig(t *testing.T, payload *CSEAggregationRule) string { + resourceTemplate := ` + resource "sumologic_cse_aggregation_rule" "aggregation_rule" { + {{ range .AggregationFunctions }} + aggregation_functions { + name = "{{ .Name }}" + function = "{{ .Function }}" + arguments = {{ quoteStringArray .Arguments }} + } + {{ end }} + description_expression = "{{ .DescriptionExpression }}" + enabled = {{ .Enabled }} + {{ range .EntitySelectors }} + entity_selectors { + entity_type = "{{ .EntityType }}" + expression = "{{ .Expression }}" + } + {{ end }} + group_by_entity = {{ .GroupByEntity }} + group_by_fields = {{ quoteStringArray .GroupByFields }} + is_prototype = {{ .IsPrototype }} + match_expression = "{{ js .MatchExpression }}" + name = "{{ .Name }}" + name_expression = "{{ .NameExpression }}" + severity_mapping { + type = "{{ .SeverityMapping.Type }}" + default = {{ .SeverityMapping.Default }} + } + summary_expression = "{{ .SummaryExpression }}" + trigger_expression = "{{ js .TriggerExpression }}" + tags = {{ quoteStringArray .Tags }} + window_size = "{{ .WindowSize }}" + {{ if eq .WindowSize "CUSTOM" }} + window_size_millis = "{{ .WindowSizeMilliseconds }}" + {{ end }} + } + ` + + configTemplate := template.Must(template.New("aggregation_rule").Funcs(template.FuncMap{ + "quoteStringArray": func(arr []string) string { + return `["` + strings.Join(arr, `","`) + `"]` + }, + "js": func(in string) string { + escaped := strings.Replace(in, `"`, `\"`, -1) + escaped = strings.Replace(escaped, `$`, `$$`, -1) // Escape Terraform interpolation + return escaped + }, + }).Parse(resourceTemplate)) + + var buffer bytes.Buffer + if err := configTemplate.Execute(&buffer, payload); err != nil { + t.Error(err) } - description_expression = "%s" - enabled = %t - entity_selectors { - entity_type = "%s" - expression = "%s" - } - group_by_entity = %t - group_by_fields = ["%s"] - is_prototype = %t - match_expression = "%s" - name = "%s" - name_expression = "%s" - severity_mapping { - type = "%s" - default = %d - } - summary_expression = "%s" - trigger_expression = "%s" - tags = ["%s"] - window_size = "%s" + + return buffer.String() } -`, aggregationFunctionName, aggregationFunction, aggregationFunctionArgument, - descriptionExpression, enabled, entitySelectorEntityType, entitySelectorExpression, - groupByEntity, groupByField, isPrototype, matchExpression, name, nameExpression, - severityMappingType, severityMappingDefault, summaryExpression, triggerExpression, - tag, windowSize) + +func getCSEAggregationRuleTestPayload() CSEAggregationRule { + return CSEAggregationRule{ + AggregationFunctions: []AggregationFunction{{Name: "distinct_eventid_count", Function: "count_distinct", Arguments: []string{"metadata_deviceEventId"}}}, + DescriptionExpression: "Test description", + Enabled: true, + EntitySelectors: []EntitySelector{{EntityType: "_ip", Expression: "srcDevice_ip"}}, + GroupByEntity: true, + GroupByFields: []string{"dstDevice_hostname"}, + IsPrototype: false, + MatchExpression: "foo = bar", + Name: fmt.Sprintf("Test Aggregation Rule %s", uuid.New()), + NameExpression: "Signal Name", + SeverityMapping: SeverityMapping{ + Type: "constant", + Default: 5, + }, + SummaryExpression: "Signal Summary", + TriggerExpression: "foo = bar", + Tags: []string{"foo"}, + WindowSize: windowSizeField("CUSTOM"), + WindowSizeMilliseconds: "10800000", + } } func testCheckCSEAggregationRuleExists(n string, AggregationRule *CSEAggregationRule) resource.TestCheckFunc { @@ -155,7 +250,7 @@ func testCheckCSEAggregationRuleExists(n string, AggregationRule *CSEAggregation } if rs.Primary.ID == "" { - return fmt.Errorf("match rule ID is not set") + return fmt.Errorf("aggregation rule ID is not set") } c := testAccProvider.Meta().(*Client) @@ -170,71 +265,26 @@ func testCheckCSEAggregationRuleExists(n string, AggregationRule *CSEAggregation } } -func testCheckAggregationRuleValues(AggregationRule *CSEAggregationRule, aggregationFunctionName string, - aggregationFunction string, aggregationFunctionArgument string, descriptionExpression string, - enabled bool, entitySelectorEntityType string, entitySelectorExpression string, groupByEntity bool, - groupByField string, isPrototype bool, matchExpression string, name string, nameExpression string, - severityMappingType string, severityMappingDefault int, summaryExpression string, - triggerExpression string, tag string, windowSize string) resource.TestCheckFunc { +func testCheckCSEAggregationRuleValues(t *testing.T, expected *CSEAggregationRule, actual *CSEAggregationRule) resource.TestCheckFunc { return func(s *terraform.State) error { - if AggregationRule.AggregationFunctions[0].Name != aggregationFunctionName { - return fmt.Errorf("bad aggregationFunctionName, expected \"%s\", got %#v", aggregationFunctionName, AggregationRule.AggregationFunctions[0].Name) - } - if AggregationRule.AggregationFunctions[0].Function != aggregationFunction { - return fmt.Errorf("bad aggregationFunction, expected \"%s\", got %#v", aggregationFunction, AggregationRule.AggregationFunctions[0].Function) - } - if AggregationRule.AggregationFunctions[0].Arguments[0] != aggregationFunctionArgument { - return fmt.Errorf("bad aggregationFunctionArgument, expected \"%s\", got %#v", aggregationFunctionArgument, AggregationRule.AggregationFunctions[0].Arguments[0]) - } - if AggregationRule.DescriptionExpression != descriptionExpression { - return fmt.Errorf("bad descriptionExpression, expected \"%s\", got %#v", descriptionExpression, AggregationRule.DescriptionExpression) - } - if AggregationRule.Enabled != enabled { - return fmt.Errorf("bad enabled, expected \"%t\", got %#v", enabled, AggregationRule.Enabled) - } - if AggregationRule.EntitySelectors[0].EntityType != entitySelectorEntityType { - return fmt.Errorf("bad entitySelectorEntityType, expected \"%s\", got %#v", entitySelectorEntityType, AggregationRule.EntitySelectors[0].EntityType) - } - if AggregationRule.EntitySelectors[0].Expression != entitySelectorExpression { - return fmt.Errorf("bad entitySelectorExpression, expected \"%s\", got %#v", entitySelectorExpression, AggregationRule.EntitySelectors[0].Expression) - } - if AggregationRule.GroupByEntity != groupByEntity { - return fmt.Errorf("bad groupByEntity, expected \"%t\", got %#v", groupByEntity, AggregationRule.GroupByEntity) - } - if AggregationRule.GroupByFields[0] != groupByField { - return fmt.Errorf("bad groupByField, expected \"%s\", got %#v", groupByField, AggregationRule.GroupByFields[0]) - } - if AggregationRule.IsPrototype != isPrototype { - return fmt.Errorf("bad isPrototype, expected \"%t\", got %#v", isPrototype, AggregationRule.IsPrototype) + assert.Equal(t, expected.AggregationFunctions, actual.AggregationFunctions) + assert.Equal(t, expected.DescriptionExpression, actual.DescriptionExpression) + assert.Equal(t, expected.Enabled, actual.Enabled) + assert.Equal(t, expected.EntitySelectors, actual.EntitySelectors) + assert.Equal(t, expected.GroupByEntity, actual.GroupByEntity) + assert.Equal(t, expected.GroupByFields, actual.GroupByFields) + assert.Equal(t, expected.IsPrototype, actual.IsPrototype) + assert.Equal(t, expected.MatchExpression, actual.MatchExpression) + assert.Equal(t, expected.Name, actual.Name) + assert.Equal(t, expected.NameExpression, actual.NameExpression) + assert.Equal(t, expected.SeverityMapping, actual.SeverityMapping) + assert.Equal(t, expected.SummaryExpression, actual.SummaryExpression) + assert.Equal(t, expected.TriggerExpression, actual.TriggerExpression) + assert.Equal(t, expected.Tags, actual.Tags) + assert.Equal(t, string(expected.WindowSize), actual.WindowSizeName) + if strings.EqualFold(actual.WindowSizeName, "CUSTOM") { + assert.Equal(t, expected.WindowSizeMilliseconds, string(actual.WindowSize)) } - if AggregationRule.MatchExpression != matchExpression { - return fmt.Errorf("bad matchExpression, expected \"%s\", got %#v", matchExpression, AggregationRule.MatchExpression) - } - if AggregationRule.Name != name { - return fmt.Errorf("bad name, expected \"%s\", got %#v", name, AggregationRule.Name) - } - if AggregationRule.NameExpression != nameExpression { - return fmt.Errorf("bad nameExpression, expected \"%s\", got %#v", nameExpression, AggregationRule.NameExpression) - } - if AggregationRule.SeverityMapping.Type != severityMappingType { - return fmt.Errorf("bad severityMappingType, expected \"%s\", got %#v", severityMappingType, AggregationRule.SeverityMapping.Type) - } - if AggregationRule.SeverityMapping.Default != severityMappingDefault { - return fmt.Errorf("bad severityMappingDefault, expected \"%d\", got %#v", severityMappingDefault, AggregationRule.SeverityMapping.Default) - } - if AggregationRule.SummaryExpression != summaryExpression { - return fmt.Errorf("bad summaryExpression, expected \"%s\", got %#v", summaryExpression, AggregationRule.SummaryExpression) - } - if AggregationRule.Tags[0] != tag { - return fmt.Errorf("bad tag, expected \"%s\", got %#v", tag, AggregationRule.Tags[0]) - } - if AggregationRule.TriggerExpression != triggerExpression { - return fmt.Errorf("bad triggerExpression, expected \"%s\", got %#v", triggerExpression, AggregationRule.TriggerExpression) - } - if AggregationRule.WindowSizeName != windowSize { - return fmt.Errorf("bad windowSize, expected \"%s\", got %#v", windowSize, AggregationRule.WindowSize) - } - return nil } } diff --git a/sumologic/sumologic_cse_aggregation_rule.go b/sumologic/sumologic_cse_aggregation_rule.go index 2f51d6e3..353ab33d 100644 --- a/sumologic/sumologic_cse_aggregation_rule.go +++ b/sumologic/sumologic_cse_aggregation_rule.go @@ -79,22 +79,23 @@ type AggregationFunction struct { } type CSEAggregationRule struct { - ID string `json:"id,omitempty"` - AggregationFunctions []AggregationFunction `json:"aggregationFunctions"` - DescriptionExpression string `json:"descriptionExpression"` - Enabled bool `json:"enabled"` - EntitySelectors []EntitySelector `json:"entitySelectors"` - GroupByEntity bool `json:"groupByAsset"` - GroupByFields []string `json:"groupByFields"` - IsPrototype bool `json:"isPrototype"` - MatchExpression string `json:"matchExpression"` - Name string `json:"name"` - NameExpression string `json:"nameExpression"` - SeverityMapping SeverityMapping `json:"scoreMapping"` - Stream string `json:"stream"` - SummaryExpression string `json:"summaryExpression"` - TriggerExpression string `json:"triggerExpression"` - Tags []string `json:"tags"` - WindowSize windowSizeField `json:"windowSize,omitempty"` - WindowSizeName string `json:"windowSizeName,omitempty"` + ID string `json:"id,omitempty"` + AggregationFunctions []AggregationFunction `json:"aggregationFunctions"` + DescriptionExpression string `json:"descriptionExpression"` + Enabled bool `json:"enabled"` + EntitySelectors []EntitySelector `json:"entitySelectors"` + GroupByEntity bool `json:"groupByAsset"` + GroupByFields []string `json:"groupByFields"` + IsPrototype bool `json:"isPrototype"` + MatchExpression string `json:"matchExpression"` + Name string `json:"name"` + NameExpression string `json:"nameExpression"` + SeverityMapping SeverityMapping `json:"scoreMapping"` + Stream string `json:"stream"` + SummaryExpression string `json:"summaryExpression"` + TriggerExpression string `json:"triggerExpression"` + Tags []string `json:"tags"` + WindowSize windowSizeField `json:"windowSize,omitempty"` + WindowSizeName string `json:"windowSizeName,omitempty"` + WindowSizeMilliseconds string `json:"windowSizeMilliseconds,omitempty"` } diff --git a/website/docs/r/cse_aggregation_rule.html.markdown b/website/docs/r/cse_aggregation_rule.html.markdown index 3627e181..ff5ec753 100644 --- a/website/docs/r/cse_aggregation_rule.html.markdown +++ b/website/docs/r/cse_aggregation_rule.html.markdown @@ -69,7 +69,8 @@ The following arguments are supported: - `summary_expression` - (Optional) The summary of the generated Signals - `tags` - (Required) The tags of the generated Signals - `trigger_expression` - (Required) The expression to determine whether a Signal should be created based on the aggregation results -- `window_size` - (Required) How long of a window to aggregate records for. Current acceptable values are T05M, T10M, T30M, T60M, T24H, T12H, or T05D. +- `window_size` - (Required) How long of a window to aggregate records for. Current acceptable values are T05M, T10M, T30M, T60M, T24H, T12H, T05D or CUSTOM + + `window_size_millis` - (Optional) Used only when `window_size` is set to CUSTOM. Window size in milliseconds ranging from 1 minute to 5 days ("60000" to "432000000"). The following attributes are exported: From 77a889b1f97a241fa7b17c596440dafaac78d220 Mon Sep 17 00:00:00 2001 From: Piotr Wardecki Date: Thu, 7 Mar 2024 17:03:24 +0100 Subject: [PATCH 8/8] DET-422: Changlelog update. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0e43bb8..f3fc8893 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## 2.28.4 (Unreleased) +ENHANCEMENTS: +* Added support for custom window sizes for the CSE Rules (Aggregation, Chain, Threshold). (GH-623) ## 2.28.3 (March 5, 2024)