diff --git a/CHANGELOG.md b/CHANGELOG.md index aaf11345..382cb6e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.5.0 +* New resource: `splunk_saved_event_types` for managing saved event types knowledge objects. +* Fix: saved search `action_email_include_*` fields (results_link, view_link, search, trigger, trigger_time) — removed omitempty from URL parameters so value 0 is sent to Splunk and can be set in config +* Change: saved search `action_email_include_results_link` and `action_email_include_view_link` now default to 1 to match Splunk savedsearches.conf; removed Computed so defaults apply +* Fix: HEC token list uses GET /services/data/inputs/http so tokens are found after create. + ## 1.4.36 * Fix: saved search action_email_include_results_link handles 0 value * Fix: saved search action_email_include_view_link handles 0 value diff --git a/client/inputs_http_event_collector.go b/client/inputs_http_event_collector.go index d793281b..0d7bb2ce 100644 --- a/client/inputs_http_event_collector.go +++ b/client/inputs_http_event_collector.go @@ -58,9 +58,12 @@ func (client *Client) DeleteHttpEventCollectorObject(name, owner, app string) (* return resp, nil } -// services/data/inputs/http +// ReadAllHttpEventCollectorObject returns the list of HTTP Event Collector tokens. +// Per Splunk RESTREF (https://docs.splunk.com/Documentation/Splunk/latest/RESTREF/RESTinput#data.2Finputs.2Fhttp), +// the documented list endpoint is GET /services/data/inputs/http (global, no servicesNS). +// GET /servicesNS/-/-/data/inputs/http returns empty in some deployments (see issue #56). func (client *Client) ReadAllHttpEventCollectorObject() (*http.Response, error) { - endpoint := client.BuildSplunkURL(nil, "servicesNS", "-", "-", "data", "inputs", "http") + endpoint := client.BuildSplunkURL(nil, "services", "data", "inputs", "http") resp, err := client.Get(endpoint) if err != nil { return nil, err diff --git a/client/models/saved_event_types.go b/client/models/saved_event_types.go new file mode 100644 index 00000000..bb2d30b8 --- /dev/null +++ b/client/models/saved_event_types.go @@ -0,0 +1,22 @@ +package models + +type SavedEventTypesResponse struct { + Entry []SavedEventTypesEntry `json:"entry"` + Messages []ErrorMessage `json:"messages"` +} + +type SavedEventTypesEntry struct { + Name string `json:"name"` + ACL ACLObject `json:"acl"` + Content SavedEventTypeObject `json:"content"` +} + +type SavedEventTypeObject struct { + Name string `json:"name"` + Description string `json:"description" url:"description,omitempty"` + Disabled bool `json:"disabled,omitempty" url:"disabled,omitempty"` + Color string `json:"color,omitempty" url:"color,omitempty"` + Priority int `json:"priority,omitempty" url:"priority,omitempty"` + Search string `json:"search,omitempty" url:"search,omitempty"` + Tags []string `json:"tags,omitempty" url:"tags,omitempty"` +} diff --git a/client/models/saved_searches.go b/client/models/saved_searches.go index 3666ecd5..10b64952 100644 --- a/client/models/saved_searches.go +++ b/client/models/saved_searches.go @@ -23,9 +23,9 @@ type SavedSearchObject struct { ActionEmailFrom string `json:"action.email.from,omitempty" url:"action.email.from,omitempty"` ActionEmailHostname string `json:"action.email.hostname,omitempty" url:"action.email.hostname,omitempty"` ActionEmailIncludeResultsLink int `json:"action.email.include.results_link,string,omitempty" url:"action.email.include.results_link"` - ActionEmailIncludeSearch int `json:"action.email.include.search,string,omitempty" url:"action.email.include.search,omitempty"` - ActionEmailIncludeTrigger int `json:"action.email.include.trigger,string,omitempty" url:"action.email.include.trigger,omitempty"` - ActionEmailIncludeTriggerTime int `json:"action.email.include.trigger_time,string,omitempty" url:"action.email.include.trigger_time,omitempty"` + ActionEmailIncludeSearch int `json:"action.email.include.search,string,omitempty" url:"action.email.include.search"` + ActionEmailIncludeTrigger int `json:"action.email.include.trigger,string,omitempty" url:"action.email.include.trigger"` + ActionEmailIncludeTriggerTime int `json:"action.email.include.trigger_time,string,omitempty" url:"action.email.include.trigger_time"` ActionEmailIncludeViewLink int `json:"action.email.include.view_link,string,omitempty" url:"action.email.include.view_link"` ActionEmailInline bool `json:"action.email.inline" url:"action.email.inline"` ActionEmailMailserver string `json:"action.email.mailserver,omitempty" url:"action.email.mailserver,omitempty"` diff --git a/client/saved_event_types.go b/client/saved_event_types.go new file mode 100644 index 00000000..042a629e --- /dev/null +++ b/client/saved_event_types.go @@ -0,0 +1,69 @@ +package client + +import ( + "github.com/splunk/terraform-provider-splunk/client/models" + "net/http" + + "github.com/google/go-querystring/query" +) + +func (client *Client) CreateSavedEventTypes(name, owner, app string, savedEventTypeObject *models.SavedEventTypeObject) error { + values, err := query.Values(savedEventTypeObject) + values.Add("name", name) + if err != nil { + return err + } + + endpoint := client.BuildSplunkURL(nil, "servicesNS", owner, app, "saved", "eventtypes") + resp, err := client.Post(endpoint, values) + if err != nil { + return err + } + defer resp.Body.Close() + return nil +} + +func (client *Client) ReadSavedEventTypes(name, owner, app string) (*http.Response, error) { + endpoint := client.BuildSplunkURL(nil, "servicesNS", owner, app, "saved", "eventtypes", name) + resp, err := client.Get(endpoint) + if err != nil { + return nil, err + } + + return resp, nil +} + +func (client *Client) UpdateSavedEventTypes(name string, owner string, app string, savedEventTypeObject *models.SavedEventTypeObject) error { + values, err := query.Values(&savedEventTypeObject) + if err != nil { + return err + } + endpoint := client.BuildSplunkURL(nil, "servicesNS", owner, app, "saved", "eventtypes", name) + resp, err := client.Post(endpoint, values) + if err != nil { + return err + } + defer resp.Body.Close() + return nil +} + +func (client *Client) DeleteSavedEventTypes(name, owner, app string) (*http.Response, error) { + endpoint := client.BuildSplunkURL(nil, "servicesNS", owner, app, "saved", "eventtypes", name) + resp, err := client.Delete(endpoint) + if err != nil { + return nil, err + } + + return resp, nil +} + +// services/saved/eventtypes +func (client *Client) ReadAllSavedEventTypes() (*http.Response, error) { + endpoint := client.BuildSplunkURL(nil, "servicesNS", "-", "-", "saved", "eventtypes") + resp, err := client.Get(endpoint) + if err != nil { + return nil, err + } + + return resp, nil +} diff --git a/docs/resources/saved_event_types.md b/docs/resources/saved_event_types.md new file mode 100644 index 00000000..505efd2e --- /dev/null +++ b/docs/resources/saved_event_types.md @@ -0,0 +1,38 @@ +# Resource: splunk_saved_event_types +Create and manage saved event types (knowledge objects). + +## Example Usage +``` +resource "splunk_saved_event_types" "test" { + name = "test" + description = "Test New event description" + disabled = false + priority = 1 + search = "index=main" + color = "et_blue" + tags = ["tag"] + acl { + owner = "admin" + sharing = "app" + app = "launcher" + } +} +``` + +## Argument Reference +For latest resource argument reference: https://docs.splunk.com/Documentation/Splunk/latest/RESTREF/RESTknowledge#saved.2Feventtypes + +This resource block supports the following arguments: +* `name` - (Required) A name for the event type. +* `description` - (Optional) Human-readable description of this event type. +* `search` - (Required) Event type search string. +* `color`- (Optional) Color for this event type. The supported colors are: none, et_blue, et_green, et_magenta, et_orange, et_purple, et_red, et_sky, et_teal, et_yellow. +* `disabled` - (Optional) If True, disables the event type. +* `priority` - (Optional) Specify an integer from 1 to 10 for the value used to determine the order in which the matching event types of an event are displayed. 1 is the highest priority. +* `tags` - (Optional) [Deprecated] Use tags.conf.spec file to assign tags to groups of events with related field values. +* `acl` - (Optional) The app/user context that is the namespace for the resource + +## Attribute Reference +In addition to all arguments above, This resource block exports the following arguments: + +* `id` - The ID of the saved search event type diff --git a/docs/resources/saved_searches.md b/docs/resources/saved_searches.md index 2c22a681..f1c1b39d 100644 --- a/docs/resources/saved_searches.md +++ b/docs/resources/saved_searches.md @@ -44,11 +44,11 @@ This resource block supports the following arguments: - `action_email_format` - (Optional) Valid values: (table | plain | html | raw | csv)Specify the format of text in the email. This value also applies to any attachments. - `action_email_from` - (Optional) Email address from which the email action originates.Defaults to splunk@$LOCALHOST or whatever value is set in alert_actions.conf. - `action_email_hostname` - (Optional) Sets the hostname used in the web link (url) sent in email actions.This value accepts two forms:hostname (for example, splunkserver, splunkserver.example.com) -- `action_email_include_results_link` - (Optional) Specify whether to include a link to the results. Defaults to 0. -- `action_email_include_search` - (Optional) Specify whether to include the search that caused an email to be sent. Defaults to 0. -- `action_email_include_trigger` - (Optional) Specify whether to show the trigger condition that caused the alert to fire. Defaults to 0. -- `action_email_include_trigger_time` - (Optional) Specify whether to show the time that the alert was fired. Defaults to 0. -- `action_email_include_view_link` - (Optional) Specify whether to show the title and a link to enable the user to edit the saved search. Defaults to 0. +- `action_email_include_results_link` - (Optional) Specify whether to include a link to the results. Defaults to 1 (true). [1|0] +- `action_email_include_search` - (Optional) Specify whether to include the search that caused an email to be sent. [1|0] +- `action_email_include_trigger` - (Optional) Specify whether to show the trigger condition that caused the alert to fire. [1|0] +- `action_email_include_trigger_time` - (Optional) Specify whether to show the time that the alert was fired. [1|0] +- `action_email_include_view_link` - (Optional) Specify whether to show the title and a link to enable the user to edit the saved search. Defaults to 1 (true). [1|0] - `action_email_inline` - (Optional) Indicates whether the search results are contained in the body of the email.Results can be either inline or attached to an email. - `action_email_mailserver` - (Optional) Set the address of the MTA server to be used to send the emails.Defaults to or whatever is set in alert_actions.conf. - `action_email_max_results` - (Optional) Sets the global maximum number of search results to send when email.action is enabled. Defaults to 100. diff --git a/examples/splunk/hec_token/main.tf b/examples/splunk/hec_token/main.tf new file mode 100644 index 00000000..f591ac35 --- /dev/null +++ b/examples/splunk/hec_token/main.tf @@ -0,0 +1,44 @@ +# Example: HEC token creation (no indexer acknowledgement). +# Uses SPLUNK_URL, SPLUNK_USERNAME, SPLUNK_PASSWORD. Run: terraform init && terraform apply + +terraform { + required_providers { + random = { + source = "hashicorp/random" + version = ">= 3.0" + } + splunk = { + source = "splunk/splunk" + version = ">= 1.4.0" + } + } +} + +provider "splunk" {} + +resource "splunk_indexes" "test_index_1" { + name = "test_index_1" + max_hot_buckets = 6 + max_total_data_size_mb = 1000000 +} + +resource "splunk_global_http_event_collector" "http" { + disabled = false + enable_ssl = true + port = 8088 +} + +resource "random_uuid" "no_ack" {} + +resource "splunk_inputs_http_event_collector" "no_ack" { + name = "some-name" + token = random_uuid.no_ack.result + index = "test_index_1" + indexes = ["test_index_1"] + disabled = false + use_ack = 0 + depends_on = [ + splunk_indexes.test_index_1, + splunk_global_http_event_collector.http, + ] +} diff --git a/examples/splunk/saved_event_types/main.tf b/examples/splunk/saved_event_types/main.tf new file mode 100644 index 00000000..7f850fd1 --- /dev/null +++ b/examples/splunk/saved_event_types/main.tf @@ -0,0 +1,28 @@ +# Example: saved event type (knowledge object). +# Uses SPLUNK_URL, SPLUNK_USERNAME, SPLUNK_PASSWORD. Run: terraform init && terraform apply +# See: https://docs.splunk.com/Documentation/Splunk/latest/RESTREF/RESTknowledge#saved.2Feventtypes + +terraform { + required_providers { + splunk = { + source = "splunk/splunk" + version = ">= 1.4.0" + } + } +} + +provider "splunk" {} + +resource "splunk_saved_event_types" "example" { + name = "terraform-example-event-type" + search = "index=main sourcetype=access_combined" + description = "Example event type created by Terraform" + disabled = false + priority = 1 + color = "et_green" + acl { + owner = "admin" + sharing = "app" + app = "search" + } +} diff --git a/examples/splunk/saved_search_email_include/main.tf b/examples/splunk/saved_search_email_include/main.tf new file mode 100644 index 00000000..c0ec65f6 --- /dev/null +++ b/examples/splunk/saved_search_email_include/main.tf @@ -0,0 +1,46 @@ +# One-off config to create a saved search with all action_email_include_* params +# for manual verification in Splunk UI. Uses SPLUNK_URL, SPLUNK_USERNAME, +# SPLUNK_PASSWORD (and SPLUNK_HOME if needed). Run: terraform init && terraform apply +# Then in Splunk: Settings → Saved searches → "Test Email Include Links Zero". +# Clean up with: terraform destroy + +terraform { + required_providers { + splunk = { + source = "splunk/splunk" + version = ">= 1.4.0" + } + } +} + +provider "splunk" {} + +resource "splunk_saved_searches" "test" { + name = "Test Email Include Links Zero" + search = "index=main" + actions = "email" + action_email_include_results_link = 0 + action_email_include_view_link = 0 + action_email_include_search = 0 + action_email_include_trigger = 1 + action_email_include_trigger_time = 1 + action_email_format = "table" + action_email_max_time = "5m" + action_email_max_results = 10 + action_email_send_csv = 1 + action_email_send_results = false + action_email_subject = "Splunk Alert: $name$" + action_email_to = "splunk@splunk.com" + action_email_track_alert = true + alert_track = true + dispatch_earliest_time = "rt-15m" + dispatch_latest_time = "rt-0m" + dispatch_index_earliest = "-10m" + dispatch_index_latest = "-5m" + cron_schedule = "*/5 * * * *" + acl { + owner = "admin" + sharing = "app" + app = "launcher" + } +} diff --git a/splunk/provider.go b/splunk/provider.go index 48242c23..f7645b94 100644 --- a/splunk/provider.go +++ b/splunk/provider.go @@ -97,6 +97,7 @@ func providerResources() map[string]*schema.Resource { "splunk_indexes": index(), "splunk_configs_conf": configsConf(), "splunk_data_ui_views": splunkDashboards(), + "splunk_saved_event_types": savedEventTypes(), } } diff --git a/splunk/resource_splunk_inputs_http_event_collector_test.go b/splunk/resource_splunk_inputs_http_event_collector_test.go index e7847b6a..1fef60a9 100644 --- a/splunk/resource_splunk_inputs_http_event_collector_test.go +++ b/splunk/resource_splunk_inputs_http_event_collector_test.go @@ -71,6 +71,23 @@ resource "splunk_inputs_http_event_collector" "new-token" { } ` +// hecTokenListEndpointConfig creates a HEC token with no acl block (default nobody/splunk_httpinput). +const hecTokenListEndpointConfig = ` +resource "splunk_global_http_event_collector" "http" { + disabled = false + enable_ssl = true +} + +resource "splunk_inputs_http_event_collector" "list_endpoint_test" { + name = "test-hec-list-endpoint" + index = "main" + indexes = ["main"] + disabled = false + use_ack = 0 + depends_on = ["splunk_global_http_event_collector.http"] +} +` + func TestAccSplunkHttpEventCollectorInput(t *testing.T) { resourceName := "splunk_inputs_http_event_collector.new-token" resource.Test(t, resource.TestCase{ @@ -163,6 +180,38 @@ func TestAccSplunkHttpEventCollectorInputWithToken(t *testing.T) { }) } +// TestAccSplunkHttpEventCollectorInputListEndpoint creates a HEC token with default ACL (no acl block) +// and asserts it can be read back. After create, the provider runs Read(), which lists tokens via +// ReadAllHttpEventCollectorObject(), finds the entry by name, then reads the token with that entry’s +// owner and app. The test passes only if the list returns the token and the subsequent read succeeds. +func TestAccSplunkHttpEventCollectorInputListEndpoint(t *testing.T) { + resourceName := "splunk_inputs_http_event_collector.list_endpoint_test" + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccSplunkHttpEventCollectorInputDestroyResources, + Steps: []resource.TestStep{ + { + Config: hecTokenListEndpointConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", "test-hec-list-endpoint"), + resource.TestCheckResourceAttr(resourceName, "index", "main"), + resource.TestCheckResourceAttr(resourceName, "indexes.#", "1"), + resource.TestCheckResourceAttr(resourceName, "indexes.0", "main"), + resource.TestCheckResourceAttr(resourceName, "disabled", "false"), + resource.TestCheckResourceAttr(resourceName, "use_ack", "0"), + resource.TestCheckResourceAttrSet(resourceName, "token"), + resource.TestCheckResourceAttr(resourceName, "acl.#", "1"), + resource.TestCheckResourceAttr(resourceName, "acl.0.app", "splunk_httpinput"), + resource.TestCheckResourceAttr(resourceName, "acl.0.owner", "nobody"), + ), + }, + }, + }) +} + func testAccSplunkHttpEventCollectorInputDestroyResources(s *terraform.State) error { client, err := newTestClient() if err != nil { diff --git a/splunk/resource_splunk_saved_event_types.go b/splunk/resource_splunk_saved_event_types.go new file mode 100644 index 00000000..b5c3d7c7 --- /dev/null +++ b/splunk/resource_splunk_saved_event_types.go @@ -0,0 +1,248 @@ +package splunk + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "regexp" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/splunk/terraform-provider-splunk/client/models" +) + +func savedEventTypes() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Saved event type name", + }, + "search": { + Type: schema.TypeString, + Required: true, + Description: "Search terms for this event type.", + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: "Human-readable description of this event type", + }, + "disabled": { + Type: schema.TypeBool, + Optional: true, + Description: "If True, disables the event type", + }, + "color": { + Type: schema.TypeString, + Optional: true, + Description: "Color for this event type. The supported colors are: none, et_blue, et_green, et_magenta, et_orange, et_purple, et_red, et_sky, et_teal, et_yellow.", + }, + "priority": { + Type: schema.TypeInt, + Optional: true, + Description: "Specifies the order in which matching event types are displayed for an event. 1 is the highest, and 10 is the lowest.", + }, + "tags": { + Type: schema.TypeList, + Optional: true, + Description: "[Deprecated] Use tags.conf.spec file to assign tags to groups of events with related field values. ", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "acl": aclSchema(), + }, + Create: savedEventTypesCreate, + Read: savedEventTypesRead, + Update: savedEventTypesUpdate, + Delete: savedEventTypesDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + } +} + +func savedEventTypesCreate(d *schema.ResourceData, meta interface{}) error { + provider := meta.(*SplunkProvider) + name := d.Get("name").(string) + savedEventTypeConfig := getSavedEventTypeConfig(d) + aclObject := &models.ACLObject{} + if r, ok := d.GetOk("acl"); ok { + aclObject = getACLConfig(r.([]interface{})) + } else { + aclObject.App = "search" + aclObject.Owner = "nobody" + } + err := (*provider.Client).CreateSavedEventTypes(name, aclObject.Owner, aclObject.App, savedEventTypeConfig) + if err != nil { + return err + } + + if _, ok := d.GetOk("acl"); ok { + err := (*provider.Client).UpdateAcl(aclObject.Owner, aclObject.App, name, aclObject, "saved", "eventtypes") + if err != nil { + return err + } + } + + d.SetId(name) + return savedEventTypesRead(d, meta) +} + +func savedEventTypesRead(d *schema.ResourceData, meta interface{}) error { + provider := meta.(*SplunkProvider) + + name := d.Id() + // We first get list of searches to get owner and app name for the specific input + resp, err := (*provider.Client).ReadAllSavedEventTypes() + if err != nil { + return err + } + defer resp.Body.Close() + + entry, err := getSavedEventTypeConfigByName(name, resp) + if err != nil { + return err + } + + if entry == nil { + return fmt.Errorf("Unable to find resource: %v", name) + } + + // Now we read the configuration with proper owner and app + resp, err = (*provider.Client).ReadSavedEventTypes(name, entry.ACL.Owner, entry.ACL.App) + if err != nil { + return err + } + defer resp.Body.Close() + + entry, err = getSavedEventTypeConfigByName(name, resp) + if err != nil { + return err + } + + if entry == nil { + return fmt.Errorf("Unable to find resource: %v", name) + } + + if err = d.Set("name", d.Id()); err != nil { + return err + } + if err = d.Set("description", entry.Content.Description); err != nil { + return err + } + if err = d.Set("disabled", entry.Content.Disabled); err != nil { + return err + } + if err = d.Set("color", entry.Content.Color); err != nil { + return err + } + if err = d.Set("priority", entry.Content.Priority); err != nil { + return err + } + if err = d.Set("search", entry.Content.Search); err != nil { + return err + } + + if err = d.Set("tags", entry.Content.Tags); err != nil { + return err + } + + err = d.Set("acl", flattenACL(&entry.ACL)) + if err != nil { + return err + } + + return nil +} + +func savedEventTypesUpdate(d *schema.ResourceData, meta interface{}) error { + provider := meta.(*SplunkProvider) + savedEventTypesConfig := getSavedEventTypeConfig(d) + aclObject := getACLConfig(d.Get("acl").([]interface{})) + + // Update will create a new resource with private `user` permissions if resource had shared permissions set + var owner string + if aclObject.Sharing != "user" { + owner = "nobody" + } else { + owner = aclObject.Owner + } + + err := (*provider.Client).UpdateSavedEventTypes(d.Id(), owner, aclObject.App, savedEventTypesConfig) + if err != nil { + return err + } + + // Update ACL + err = (*provider.Client).UpdateAcl(owner, aclObject.App, d.Id(), aclObject, "saved", "eventtypes") + if err != nil { + return err + } + + return savedEventTypesRead(d, meta) +} + +func savedEventTypesDelete(d *schema.ResourceData, meta interface{}) error { + provider := meta.(*SplunkProvider) + aclObject := getACLConfig(d.Get("acl").([]interface{})) + resp, err := (*provider.Client).DeleteSavedEventTypes(d.Id(), aclObject.Owner, aclObject.App) + if err != nil { + return err + } + defer resp.Body.Close() + + switch resp.StatusCode { + case 200, 201: + return nil + + default: + errorResponse := &models.SavedEventTypesResponse{} + _ = json.NewDecoder(resp.Body).Decode(errorResponse) + if len(errorResponse.Messages) > 0 { + return errors.New(errorResponse.Messages[0].Text) + } + return fmt.Errorf("delete failed with status %d", resp.StatusCode) + } +} + +func getSavedEventTypeConfig(d *schema.ResourceData) (savedEventTypeObj *models.SavedEventTypeObject) { + savedEventTypeObj = &models.SavedEventTypeObject{ + Description: d.Get("description").(string), + Disabled: d.Get("disabled").(bool), + Color: d.Get("color").(string), + Priority: d.Get("priority").(int), + Search: d.Get("search").(string), + } + + if val, ok := d.GetOk("tags"); ok { + for _, v := range val.([]interface{}) { + savedEventTypeObj.Tags = append(savedEventTypeObj.Tags, v.(string)) + } + } + + return savedEventTypeObj +} + +func getSavedEventTypeConfigByName(name string, httpResponse *http.Response) (savedEventTypesEntry *models.SavedEventTypesEntry, err error) { + response := &models.SavedEventTypesResponse{} + switch httpResponse.StatusCode { + case 200, 201: + _ = json.NewDecoder(httpResponse.Body).Decode(&response) + re := regexp.MustCompile(`(.*)`) + for _, entry := range response.Entry { + if name == re.FindStringSubmatch(entry.Name)[1] { + return &entry, nil + } + } + + default: + _ = json.NewDecoder(httpResponse.Body).Decode(response) + err := errors.New(response.Messages[0].Text) + return savedEventTypesEntry, err + } + + return savedEventTypesEntry, nil +} diff --git a/splunk/resource_splunk_saved_event_types_test.go b/splunk/resource_splunk_saved_event_types_test.go new file mode 100644 index 00000000..c28263cd --- /dev/null +++ b/splunk/resource_splunk_saved_event_types_test.go @@ -0,0 +1,75 @@ +package splunk + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "net/http" + "testing" +) + +const newSavedEventTypes = ` +resource "splunk_saved_event_types" "event-type" { + name = "test-acc-saved-event-type" + description = "Test New event description" + disabled = false + priority = 1 + search = "index=main" + color = "et_blue" + tags = ["tag"] + acl { + owner = "admin" + sharing = "app" + app = "search" + } +} +` + +func TestAccSplunkSavedEventTypes(t *testing.T) { + resourceName := "splunk_saved_event_types.event-type" + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccSplunkSavedEventsDestroyResources, + Steps: []resource.TestStep{ + { + Config: newSavedEventTypes, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", "test-acc-saved-event-type"), + resource.TestCheckResourceAttr(resourceName, "search", "index=main"), + resource.TestCheckResourceAttr(resourceName, "description", "Test New event description"), + resource.TestCheckResourceAttr(resourceName, "disabled", "false"), + resource.TestCheckResourceAttr(resourceName, "priority", "1"), + resource.TestCheckResourceAttr(resourceName, "color", "et_blue"), + resource.TestCheckResourceAttr(resourceName, "tags.#", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "tag"), + ), + }, + { + ResourceName: "splunk_saved_event_types.event-type", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccSplunkSavedEventsDestroyResources(s *terraform.State) error { + client, err := newTestClient() + if err != nil { + return err + } + for _, rs := range s.RootModule().Resources { + switch rs.Type { + case "splunk_saved_event_types": + endpoint := client.BuildSplunkURL(nil, "servicesNS", "nobody", "search", "saved", "eventtypes", rs.Primary.ID) + resp, err := client.Get(endpoint) + if resp.StatusCode != http.StatusNotFound { + return fmt.Errorf("error: %s: %s", rs.Primary.ID, err) + } + } + } + return nil +} diff --git a/splunk/resource_splunk_saved_searches.go b/splunk/resource_splunk_saved_searches.go index 3977bc92..941fc1d2 100644 --- a/splunk/resource_splunk_saved_searches.go +++ b/splunk/resource_splunk_saved_searches.go @@ -160,8 +160,8 @@ func savedSearches() *schema.Resource { "action_email_include_results_link": { Type: schema.TypeInt, Optional: true, - Computed: true, - Description: "Specify whether to include a link to the results. [1|0]", + Default: 1, + Description: "Specify whether to include a link to the results. Defaults to 1 (true). [1|0]", }, "action_email_include_search": { Type: schema.TypeInt, @@ -184,8 +184,8 @@ func savedSearches() *schema.Resource { "action_email_include_view_link": { Type: schema.TypeInt, Optional: true, - Computed: true, - Description: "Specify whether to show the title and a link to enable the user to edit the saved search. [1|0]", + Default: 1, + Description: "Specify whether to show the title and a link to enable the user to edit the saved search. Defaults to 1 (true). [1|0]", }, "action_email_inline": { Type: schema.TypeBool,