diff --git a/client/models/http_event_collector.go b/client/models/http_event_collector.go index 29959360..05ed348e 100644 --- a/client/models/http_event_collector.go +++ b/client/models/http_event_collector.go @@ -20,5 +20,5 @@ type HttpEventCollectorObject struct { SourceType string `json:"sourcetype,omitempty" url:"sourcetype,omitempty"` Token string `json:"token,omitempty" url:"token,omitempty"` Disabled bool `json:"disabled,omitempty" url:"disabled"` - UseACK int `json:"useACK,string,omitempty" url:"useACK"` + UseACK SplunkBoolInt `json:"useACK,omitempty" url:"useACK"` } diff --git a/client/models/splunk_bool_int.go b/client/models/splunk_bool_int.go new file mode 100644 index 00000000..044a5b2e --- /dev/null +++ b/client/models/splunk_bool_int.go @@ -0,0 +1,38 @@ +package models + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" +) + +// SplunkBoolInt handles Splunk Cloud API responses that return boolean strings +// ("true"/"false") for numeric fields instead of integer strings ("0"/"1"). +// This inconsistency exists between Splunk Enterprise and Splunk Cloud APIs. +type SplunkBoolInt int + +func (b *SplunkBoolInt) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err == nil { + switch strings.ToLower(strings.TrimSpace(s)) { + case "true", "1": + *b = 1 + case "false", "0": + *b = 0 + default: + return fmt.Errorf("cannot parse %q as SplunkBoolInt", s) + } + return nil + } + var i int + if err := json.Unmarshal(data, &i); err != nil { + return fmt.Errorf("cannot parse %s as SplunkBoolInt: %w", data, err) + } + *b = SplunkBoolInt(i) + return nil +} + +func (b SplunkBoolInt) MarshalJSON() ([]byte, error) { + return json.Marshal(strconv.Itoa(int(b))) +} diff --git a/client/models/splunk_bool_int_test.go b/client/models/splunk_bool_int_test.go new file mode 100644 index 00000000..458b0cd8 --- /dev/null +++ b/client/models/splunk_bool_int_test.go @@ -0,0 +1,75 @@ +package models + +import ( + "encoding/json" + "testing" +) + +func TestSplunkBoolInt_UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + input string + expected SplunkBoolInt + wantErr bool + }{ + // Splunk Cloud returns boolean strings + {name: "string false", input: `"false"`, expected: 0}, + {name: "string true", input: `"true"`, expected: 1}, + // Splunk Enterprise returns integer strings + {name: "string 0", input: `"0"`, expected: 0}, + {name: "string 1", input: `"1"`, expected: 1}, + // Direct integer values + {name: "int 0", input: `0`, expected: 0}, + {name: "int 1", input: `1`, expected: 1}, + // Invalid + {name: "invalid string", input: `"foo"`, wantErr: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var b SplunkBoolInt + err := json.Unmarshal([]byte(tt.input), &b) + if (err != nil) != tt.wantErr { + t.Errorf("UnmarshalJSON(%s) error = %v, wantErr %v", tt.input, err, tt.wantErr) + return + } + if !tt.wantErr && b != tt.expected { + t.Errorf("UnmarshalJSON(%s) = %d, want %d", tt.input, b, tt.expected) + } + }) + } +} + +func TestSplunkBoolInt_MarshalJSON(t *testing.T) { + tests := []struct { + input SplunkBoolInt + expected string + }{ + {input: 0, expected: `"0"`}, + {input: 1, expected: `"1"`}, + } + + for _, tt := range tests { + data, err := json.Marshal(tt.input) + if err != nil { + t.Errorf("MarshalJSON(%d) error = %v", tt.input, err) + continue + } + if string(data) != tt.expected { + t.Errorf("MarshalJSON(%d) = %s, want %s", tt.input, data, tt.expected) + } + } +} + +func TestHttpEventCollectorObject_UnmarshalUseACK(t *testing.T) { + // Simulates the Splunk Cloud API response that was causing the bug + splunkCloudResponse := `{"useACK": "false", "index": "main"}` + + var obj HttpEventCollectorObject + if err := json.Unmarshal([]byte(splunkCloudResponse), &obj); err != nil { + t.Fatalf("failed to unmarshal Splunk Cloud response: %v", err) + } + if obj.UseACK != 0 { + t.Errorf("UseACK = %d, want 0", obj.UseACK) + } +} diff --git a/splunk/resource_splunk_inputs_http_event_collector.go b/splunk/resource_splunk_inputs_http_event_collector.go index bab009c2..3a99def3 100644 --- a/splunk/resource_splunk_inputs_http_event_collector.go +++ b/splunk/resource_splunk_inputs_http_event_collector.go @@ -178,7 +178,7 @@ func httpEventCollectorInputRead(d *schema.ResourceData, meta interface{}) error return err } - if err = d.Set("use_ack", entry.Content.UseACK); err != nil { + if err = d.Set("use_ack", int(entry.Content.UseACK)); err != nil { return err } @@ -238,7 +238,7 @@ func getHttpEventCollectorConfig(d *schema.ResourceData) (httpInputConfigObject httpInputConfigObject.Indexes = d.Get("indexes").([]interface{}) httpInputConfigObject.Source = d.Get("source").(string) httpInputConfigObject.SourceType = d.Get("sourcetype").(string) - httpInputConfigObject.UseACK = d.Get("use_ack").(int) + httpInputConfigObject.UseACK = models.SplunkBoolInt(d.Get("use_ack").(int)) httpInputConfigObject.Disabled = d.Get("disabled").(bool) return httpInputConfigObject }