diff --git a/CHANGELOG.md b/CHANGELOG.md index db2d4cb3..9c2b5e6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## 2.26.1 (Unreleased) FEATURES: +* **New Resource:** sumologic_cse_tag_schema (GH-575) * **New Resource:** sumologic_cse_context_action (GH-573) ## 2.26.0 (September 7, 2023) diff --git a/sumologic/provider.go b/sumologic/provider.go index 5dbffc9d..2b5df9d6 100644 --- a/sumologic/provider.go +++ b/sumologic/provider.go @@ -43,6 +43,7 @@ func Provider() terraform.ResourceProvider { }, }, ResourcesMap: map[string]*schema.Resource{ + "sumologic_cse_tag_schema": resourceSumologicCSETagSchema(), "sumologic_cse_context_action": resourceSumologicCSEContextAction(), "sumologic_cse_automation": resourceSumologicCSEAutomation(), "sumologic_cse_entity_normalization_configuration": resourceSumologicCSEEntityNormalizationConfiguration(), diff --git a/sumologic/resource_sumologic_cse_tag_schema.go b/sumologic/resource_sumologic_cse_tag_schema.go new file mode 100644 index 00000000..ccd05bb1 --- /dev/null +++ b/sumologic/resource_sumologic_cse_tag_schema.go @@ -0,0 +1,178 @@ +package sumologic + +import ( + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "log" +) + +func resourceSumologicCSETagSchema() *schema.Resource { + return &schema.Resource{ + Create: resourceSumologicCSETagSchemaCreate, + Read: resourceSumologicCSETagSchemaRead, + Delete: resourceSumologicCSETagSchemaDelete, + Update: resourceSumologicCSETagSchemaUpdate, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + }, + "label": { + Type: schema.TypeString, + Required: true, + }, + "content_types": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.All( + validation.StringIsNotEmpty, + validation.StringInSlice([]string{"customInsight", "entity", "rule", "threatIntelligence"}, false)), + }, + }, + "free_form": { + Type: schema.TypeBool, + Required: true, + }, + "value_options": getValueOptionsSchema(), + }, + } +} + +func getValueOptionsSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Type: schema.TypeString, + Required: true, + }, + "label": { + Type: schema.TypeString, + Optional: true, + }, + "link": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + } +} + +func resourceSumologicCSETagSchemaRead(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client) + + var CSETagSchema *CSETagSchema + key := d.Id() + + CSETagSchema, err := c.GetCSETagSchema(key) + if err != nil { + log.Printf("[WARN] CSE Tag Schema not found when looking by key: %s, err: %v", key, err) + + } + + if CSETagSchema == nil { + log.Printf("[WARN] CSE tag Schema not found, removing from state: %v - %v", key, err) + d.SetId("") + return nil + } + + d.Set("key", CSETagSchema.Key) + d.Set("label", CSETagSchema.Label) + d.Set("content_types", CSETagSchema.ContentTypes) + d.Set("value_options", valueOptionsArrayToResource(CSETagSchema.ValueOptionObjects)) + d.Set("free_form", CSETagSchema.FreeForm) + + return nil +} + +func valueOptionsArrayToResource(valueOptions []ValueOption) []map[string]interface{} { + result := make([]map[string]interface{}, len(valueOptions)) + + for i, valueOption := range valueOptions { + result[i] = map[string]interface{}{ + "value": valueOption.Value, + "label": valueOption.Label, + "link": valueOption.Link, + } + } + + return result +} + +func resourceSumologicCSETagSchemaDelete(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client) + + return c.DeleteCSETagSchema(d.Id()) + +} + +func resourceSumologicCSETagSchemaCreate(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client) + + key, err := c.CreateCSETagSchema(CSECreateUpdateTagSchema{ + Key: d.Get("key").(string), + Label: d.Get("label").(string), + ContentTypes: resourceFieldsToStringArray(d.Get("content_types").([]interface{})), + ValueOptions: resourceToValueOptionArray(d.Get("value_options").([]interface{})), + FreeForm: d.Get("free_form").(bool), + }) + + if err != nil { + return err + } + + d.SetId(key) + + return resourceSumologicCSETagSchemaRead(d, meta) +} + +func resourceToValueOptionArray(resouceValueOptions []interface{}) []ValueOption { + result := make([]ValueOption, len(resouceValueOptions)) + + for i, resourceValueOption := range resouceValueOptions { + result[i] = ValueOption{ + Value: resourceValueOption.(map[string]interface{})["value"].(string), + Label: resourceValueOption.(map[string]interface{})["label"].(string), + Link: resourceValueOption.(map[string]interface{})["link"].(string), + } + } + + return result +} + +func resourceSumologicCSETagSchemaUpdate(d *schema.ResourceData, meta interface{}) error { + CSETagSchema, err := resourceToCSETagSchema(d) + if err != nil { + return err + } + c := meta.(*Client) + if err = c.UpdateCSETagSchema(CSETagSchema); err != nil { + return err + } + + return resourceSumologicCSETagSchemaRead(d, meta) +} + +func resourceToCSETagSchema(d *schema.ResourceData) (CSECreateUpdateTagSchema, error) { + key := d.Id() + if key == "" { + return CSECreateUpdateTagSchema{}, nil + } + + return CSECreateUpdateTagSchema{ + Key: key, + Label: d.Get("label").(string), + ContentTypes: resourceFieldsToStringArray(d.Get("content_types").([]interface{})), + ValueOptions: resourceToValueOptionArray(d.Get("value_options").([]interface{})), + FreeForm: d.Get("free_form").(bool), + }, nil +} diff --git a/sumologic/resource_sumologic_cse_tag_schema_test.go b/sumologic/resource_sumologic_cse_tag_schema_test.go new file mode 100644 index 00000000..531b126f --- /dev/null +++ b/sumologic/resource_sumologic_cse_tag_schema_test.go @@ -0,0 +1,142 @@ +package sumologic + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccSumologicSCETagSchema_create_update(t *testing.T) { + SkipCseTest(t) + + var TagSchema CSETagSchema + nKey := "location" + nLabel := "Label" + nContentTypes := []string{"entity"} + nFreeForm := true + nVOValue := "value" + nVOLabel := "label" + nVOLink := "http://foo.bar.com" + uLabel := "uLabel" + resourceName := "sumologic_cse_tag_schema.tag_schema" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCSETagSchemaDestroy, + Steps: []resource.TestStep{ + { + Config: testCreateCSETagSchemaConfig(nKey, nLabel, nContentTypes, nFreeForm, nVOValue, nVOLabel, nVOLink), + Check: resource.ComposeTestCheckFunc( + testCheckCSETagSchemaExists(resourceName, &TagSchema), + testCheckTagSchemaValues(&TagSchema, nKey, nLabel, nContentTypes, nFreeForm, nVOValue, nVOLabel, nVOLink), + resource.TestCheckResourceAttrSet(resourceName, "id"), + ), + }, + { + Config: testCreateCSETagSchemaConfig(nKey, uLabel, nContentTypes, nFreeForm, nVOValue, nVOLabel, nVOLink), + Check: resource.ComposeTestCheckFunc( + testCheckCSETagSchemaExists(resourceName, &TagSchema), + testCheckTagSchemaValues(&TagSchema, nKey, uLabel, nContentTypes, nFreeForm, nVOValue, nVOLabel, nVOLink), + ), + }, + }, + }) +} + +func testAccCSETagSchemaDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "sumologic_cse_tag_schema" { + continue + } + + if rs.Primary.ID == "" { + return fmt.Errorf("CSE Tag Schema destruction check: CSE Tag Schema key is not set") + } + + s, err := client.GetCSETagSchema(rs.Primary.ID) + if err != nil { + return fmt.Errorf("Encountered an error: " + err.Error()) + } + if s != nil { + return fmt.Errorf("Tag Schema still exists") + } + } + return nil +} + +func testCreateCSETagSchemaConfig(nKey string, nLabel string, nContentTypes []string, nFreeForm bool, nVOValue string, nVOLabel string, nVOLink string) string { + + return fmt.Sprintf(` +resource "sumologic_cse_tag_schema" "tag_schema" { + key = "%s" + label = "%s" + content_types = ["%s"] + free_form = "%t" + value_options { + value = "%s" + label = "%s" + link = "%s" + } +} +`, nKey, nLabel, nContentTypes[0], nFreeForm, nVOValue, nVOLabel, nVOLink) +} + +func testCheckCSETagSchemaExists(n string, TagSchema *CSETagSchema) 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("Tag Schema key is not set") + } + + c := testAccProvider.Meta().(*Client) + TagSchemaResp, err := c.GetCSETagSchema(rs.Primary.ID) + if err != nil { + return err + } + + *TagSchema = *TagSchemaResp + + return nil + } +} + +func testCheckTagSchemaValues(TagSchema *CSETagSchema, nKey string, nLabel string, nContentTypes []string, nFreeForm bool, nVOValue string, nVOLabel string, nVOLink string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if TagSchema.Key != nKey { + return fmt.Errorf("bad key, expected \"%s\", got: %#v", nKey, TagSchema.Key) + } + if TagSchema.Label != nLabel { + return fmt.Errorf("bad label, expected \"%s\", got: %#v", nLabel, TagSchema.Label) + } + if TagSchema.ContentTypes != nil { + if len(TagSchema.ContentTypes) != len(nContentTypes) { + return fmt.Errorf("bad content_types list lenght, expected \"%d\", got: %d", len(nContentTypes), len(TagSchema.ContentTypes)) + } + if TagSchema.ContentTypes[0] != nContentTypes[0] { + return fmt.Errorf("bad content_types in list, expected \"%s\", got: %s", nContentTypes[0], TagSchema.ContentTypes[0]) + } + } + if TagSchema.FreeForm != nFreeForm { + return fmt.Errorf("bad free_form field, expected \"%t\", got: %#v", nFreeForm, TagSchema.FreeForm) + } + if TagSchema.ValueOptionObjects[0].Value != nVOValue { + return fmt.Errorf("bad value_option.value field, expected \"%s\", got: %#v", nVOValue, TagSchema.ValueOptionObjects[0].Value) + } + if TagSchema.ValueOptionObjects[0].Label != nVOLabel { + return fmt.Errorf("bad value_option.label field, expected \"%s\", got: %#v", nVOLabel, TagSchema.ValueOptionObjects[0].Label) + } + if TagSchema.ValueOptionObjects[0].Link != nVOLink { + return fmt.Errorf("bad value_option.link field, expected \"%s\", got: %#v", nVOLink, TagSchema.ValueOptionObjects[0].Link) + } + + return nil + } +} diff --git a/sumologic/sumologic_cse_tag_schema.go b/sumologic/sumologic_cse_tag_schema.go new file mode 100644 index 00000000..44585e72 --- /dev/null +++ b/sumologic/sumologic_cse_tag_schema.go @@ -0,0 +1,95 @@ +package sumologic + +import ( + "encoding/json" + "fmt" +) + +func (s *Client) GetCSETagSchema(key string) (*CSETagSchema, error) { + data, _, err := s.Get(fmt.Sprintf("sec/v1/tag-schemas/%s", key)) + if err != nil { + return nil, err + } + + if data == nil { + return nil, nil + } + + var response CSETagSchemaResponse + err = json.Unmarshal(data, &response) + if err != nil { + return nil, err + } + + return &response.CSETagSchema, nil +} + +func (s *Client) DeleteCSETagSchema(id string) error { + _, err := s.Delete(fmt.Sprintf("sec/v1/tag-schemas/%s", id)) + + return err +} + +func (s *Client) CreateCSETagSchema(CSECreateUpdateTagSchema CSECreateUpdateTagSchema) (string, error) { + + request := CSETagSchemaRequestCreateUpdate{ + CSECreateUpdateTagSchema, + } + + var response CSETagSchemaResponse + + responseBody, err := s.Post("sec/v1/tag-schemas", request) + if err != nil { + return "", err + } + + err = json.Unmarshal(responseBody, &response) + + if err != nil { + return "", err + } + + return response.CSETagSchema.Key, nil +} + +func (s *Client) UpdateCSETagSchema(CSECreateUpdateTagSchema CSECreateUpdateTagSchema) error { + url := fmt.Sprintf("sec/v1/tag-schemas/") + + request := CSETagSchemaRequestCreateUpdate{ + CSECreateUpdateTagSchema, + } + + _, err := s.Put(url, request) + + return err +} + +type CSETagSchemaRequestCreateUpdate struct { + CSETagSchema CSECreateUpdateTagSchema `json:"fields"` +} + +type CSETagSchemaResponse struct { + CSETagSchema CSETagSchema `json:"data"` +} + +type CSECreateUpdateTagSchema struct { + Key string `json:"key,omitempty"` + Label string `json:"label,omitempty"` + ContentTypes []string `json:"contentTypes"` + FreeForm bool `json:"freeform"` + ValueOptions []ValueOption `json:"valueOptions,omitempty"` +} + +type CSETagSchema struct { + Key string `json:"key,omitempty"` + Label string `json:"label,omitempty"` + ContentTypes []string `json:"contentTypeEnums"` + FreeForm bool `json:"freeform"` + ValueOptionObjects []ValueOption `json:"valueOptionObjects,omitempty"` +} + +type ValueOption struct { + Value string `json:"value"` + Label string `json:"label,omitempty"` + Link string `json:"link,omitempty"` +} diff --git a/website/docs/r/cse_context_action.html.markdown b/website/docs/r/cse_context_action.html.markdown index 2dc34b60..deee0605 100644 --- a/website/docs/r/cse_context_action.html.markdown +++ b/website/docs/r/cse_context_action.html.markdown @@ -5,7 +5,7 @@ description: |- Provides a Sumologic CSE Context Action --- -# sumologic_cse_automation +# sumologic_cse_context_action Provides a Sumologic CSE Context Action. ## Example Usage diff --git a/website/docs/r/cse_tag_schema.html.markdown b/website/docs/r/cse_tag_schema.html.markdown new file mode 100644 index 00000000..c57735fc --- /dev/null +++ b/website/docs/r/cse_tag_schema.html.markdown @@ -0,0 +1,51 @@ +--- +layout: "sumologic" +page_title: "SumoLogic: sumologic_cse_tag_schema" +description: |- + Provides a Sumologic CSE Tag Schema +--- + +# sumologic_cse_tag_schemna +Provides a Sumologic CSE Tag Schema. + +## Example Usage +```hcl +resource "sumologic_cse_tag_schema" "tag_schema" { + key = "location" + label = "label" + content_types = ["entity"] + free_form = "true" + value_options { + value = "option value" + label = option label" + link = "http://foo.bar.com" + } +} + +``` + +## Argument reference + +The following arguments are supported: + +- `key` - (Required) Tag Schema key. +- `label` - (Required) Tag Schema label. +- `content_types` - (Required) Applicable content types. Valid values: "customInsight", "entity", "rule", "threatIntelligence". +- `free_form` - (Required) Whether the tag schema accepts free form custom values. +- `value_options` - (At least one need to be added) + + `value` - (Required) Value option value. + + `label` - (Required) Value option label. + + `link` - (Optional) Value option link. + + + +The following attributes are exported: + +- `id` - The internal ID of the Tag Schema. + +## Import + +Tag Schema can be imported using the field id, e.g.: +```hcl +terraform import sumologic_cse_tag_schema.tag_schema id +```