Skip to content

Commit b82e604

Browse files
authored
Added new resource to manage lookup table files (#196)
* Added new resource to manage lookup table files * Corrected typo in documentation * Removed whitespace trim since it is not needed * Updated lookup read to detect remote changes * Converted lookup table file_contents usage from a string to a [][]string * Added splunk app for lookup file editing * Fixed missing quote * Added names to terraform workflow steps * Moved terraform chdir to global options * Added volume mount for app addons * Reworked service volume mount * Updated changelog
1 parent 2905cfe commit b82e604

File tree

13 files changed

+415
-0
lines changed

13 files changed

+415
-0
lines changed

.github/workflows/test.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ jobs:
2222
ports:
2323
- 8000:8000
2424
- 8089:8089
25+
volumes:
26+
- ${{ github.workspace }}:/workspace
2527

2628
steps:
2729
- name: Set up Go 1.x
@@ -44,5 +46,16 @@ jobs:
4446
- name: Build
4547
run: go build -v .
4648

49+
- name: Set up Terraform 1.1.7
50+
uses: hashicorp/setup-terraform@v3
51+
with:
52+
terraform_version: "1.1.7"
53+
54+
- name: Terraform init
55+
run: terraform -chdir=terraform init
56+
57+
- name: Terraform apply
58+
run: terraform -chdir=terraform apply --auto-approve
59+
4760
- name: Test
4861
run: TF_ACC=1 SPLUNK_HOME=/opt/splunk SPLUNK_URL=localhost:8089 SPLUNK_USERNAME=admin SPLUNK_PASSWORD=password go test ./... -v

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Ignore dist file
22
.idea
33
.terraform
4+
.terraform.lock.hcl
45
dist
56
terraform-provider-splunk
67
terraform.tfstate

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## 1.4.27
2+
* Support for lookup table files
3+
14
## 1.4.26
25
* Fix: Add retry mechanism to dashboard's acl endpoint
36

client/client.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type Client struct {
4343
host string
4444
httpClient *http.Client
4545
userAgent string
46+
urlEncoded bool
4647
}
4748

4849
// NewRequest creates a new HTTP Request and set proper header
@@ -60,6 +61,9 @@ func (c *Client) NewRequest(httpMethod, url string, body io.Reader) (*http.Reque
6061
}
6162
request.Header.Set("Accept", "application/json")
6263
request.Header.Set("User-Agent", c.userAgent)
64+
if c.urlEncoded {
65+
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
66+
}
6367
return request, nil
6468
}
6569

client/client_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,36 @@ func TestNewRequestUserAgentHeader(t *testing.T) {
222222
}
223223
}
224224

225+
func TestNewRequestWithoutContentTypeHeader(t *testing.T) {
226+
client, err := NewDefaultSplunkdClient()
227+
if err != nil {
228+
t.Error(err)
229+
}
230+
req, err := client.NewRequest(MethodGet, testURL, nil)
231+
if err != nil {
232+
t.Errorf("NewRequest returns unexpected error %v", err)
233+
}
234+
if req.Header["Content-Type"] != nil {
235+
t.Errorf("NewRequest Content-Type is %v, want nil", req.Header["Content-Type"])
236+
}
237+
}
238+
239+
func TestNewRequestWithContentTypeHeader(t *testing.T) {
240+
client, err := NewDefaultSplunkdClient()
241+
if err != nil {
242+
t.Error(err)
243+
}
244+
client.urlEncoded = true
245+
req, err := client.NewRequest(MethodGet, testURL, nil)
246+
if err != nil {
247+
t.Errorf("NewRequest returns unexpected error %v", err)
248+
}
249+
expectedContentType := []string{"application/x-www-form-urlencoded"}
250+
if got, want := req.Header["Content-Type"], expectedContentType; !reflect.DeepEqual(got, want) {
251+
t.Errorf("NewRequest Content-Type is %v, want %v", got, want)
252+
}
253+
}
254+
225255
func TestNewRequestSessionKey(t *testing.T) {
226256
client, err := NewDefaultSplunkdClient()
227257
if err != nil {

client/lookup_table_file.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package client
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"net/http"
7+
"net/http/httputil"
8+
)
9+
10+
func (client *Client) CreateLookupTableFile(name string, owner string, app string, contents string) error {
11+
values := []byte(fmt.Sprintf("namespace=%s&lookup_file=%s&owner=%s&contents=%s", app, name, owner, contents))
12+
endpoint := client.BuildSplunkURL(nil, "services", "data", "lookup_edit", "lookup_contents")
13+
client.urlEncoded = true
14+
resp, err := client.Post(endpoint, values)
15+
if err != nil {
16+
return err
17+
}
18+
19+
respBody, error := httputil.DumpResponse(resp, true)
20+
if error != nil {
21+
log.Printf("[ERROR] Error occured during CreateLookup %s", error)
22+
}
23+
24+
log.Printf("[DEBUG] Response object returned from CreateLookup is: %s", string(respBody))
25+
26+
defer resp.Body.Close()
27+
return nil
28+
}
29+
30+
func (client *Client) ReadLookupTableFile(name, owner, app string) (*http.Response, error) {
31+
values := []byte(fmt.Sprintf("namespace=%s&lookup_file=%s&owner=%s", app, name, owner))
32+
client.urlEncoded = true
33+
endpoint := client.BuildSplunkURL(nil, "services", "data", "lookup_edit", "lookup_data")
34+
resp, err := client.Post(endpoint, values)
35+
return resp, err
36+
}
37+
38+
func (client *Client) UpdateLookupTableFile(name string, owner string, app string, contents string) error {
39+
values := []byte(fmt.Sprintf("namespace=%s&lookup_file=%s&owner=%s&contents=%s", app, name, owner, contents))
40+
endpoint := client.BuildSplunkURL(nil, "services", "data", "lookup_edit", "lookup_contents")
41+
client.urlEncoded = true
42+
resp, err := client.Post(endpoint, values)
43+
if err != nil {
44+
return err
45+
}
46+
defer resp.Body.Close()
47+
return nil
48+
}
49+
50+
func (client *Client) DeleteLookupTableFile(name string, owner string, app string) (*http.Response, error) {
51+
endpoint := client.BuildSplunkURL(nil, "servicesNS", owner, app, "data", "lookup-table-files", name)
52+
resp, err := client.Delete(endpoint)
53+
if err != nil {
54+
return nil, err
55+
}
56+
57+
respBody, error := httputil.DumpResponse(resp, true)
58+
if error != nil {
59+
log.Printf("[ERROR] Error occured during DeleteLookup %s", error)
60+
}
61+
62+
log.Printf("[DEBUG] Response object returned from DeleteLookup is: %s", string(respBody))
63+
64+
return resp, nil
65+
}

client/models/lookup_table_file.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package models
2+
3+
type LookupTableFile struct {
4+
App string `json:"namespace,omitempty" url:"namespace,omitempty"`
5+
Owner string `json:"owner,omitempty" url:"owner,omitempty"`
6+
FileName string `json:"lookup_file,omitempty" url:"lookup_file,omitempty"`
7+
FileContents string `json:"contents,omitempty" url:"contents,omitempty"`
8+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Resource: splunk_lookup_table_file
2+
Create and manage lookup table files.
3+
4+
## Example Usage
5+
```
6+
resource "splunk_lookup_table_file" "lookup_table_file" {
7+
app = "search"
8+
owner = "nobody"
9+
file_name = "lookup.csv"
10+
file_contents = [
11+
["status", "status_description", "status_type"],
12+
["100", "Continue", "Informational"],
13+
["101", "Switching Protocols", "Informational"],
14+
["200", "OK", "Successful"]
15+
]
16+
}
17+
```
18+
19+
## Argument Reference
20+
For latest resource argument reference: https://docs.splunk.com/Documentation/Splunk/latest/Knowledge/LookupexampleinSplunkWeb
21+
22+
This resource block supports the following arguments:
23+
* `app` - (Required) The app context for the resource.
24+
* `owner` - (Required) User name of resource owner. Defaults to the resource creator. Required for updating any knowledge object ACL properties. nobody = All users may access the resource, but write access to the resource might be restricted.
25+
* `file_name` - (Required) A name for the lookup table file. Generally ends with ".csv"
26+
* `file_contents` - (Required) The column header and row value contents for the lookup table file.
27+
28+
## Attribute Reference
29+
In addition to all arguments above, This resource block exports the following arguments:
30+
31+
* `id` - The ID of the lookup table file resource

splunk/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ func providerResources() map[string]*schema.Resource {
8585
"splunk_inputs_tcp_cooked": inputsTCPCooked(),
8686
"splunk_inputs_tcp_splunk_tcp_token": inputsTCPSplunkTCPToken(),
8787
"splunk_inputs_tcp_ssl": inputsTCPSSL(),
88+
"splunk_lookup_table_file": lookupTableFile(),
8889
"splunk_outputs_tcp_default": outputsTCPDefault(),
8990
"splunk_outputs_tcp_server": outputsTCPServer(),
9091
"splunk_outputs_tcp_group": outputsTCPGroup(),
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package splunk
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
7+
"github.com/splunk/terraform-provider-splunk/client/models"
8+
"io"
9+
)
10+
11+
func lookupTableFile() *schema.Resource {
12+
return &schema.Resource{
13+
Schema: map[string]*schema.Schema{
14+
"app": {
15+
Type: schema.TypeString,
16+
ForceNew: true,
17+
Required: true,
18+
Description: "The parent app to the lookup.",
19+
},
20+
"owner": {
21+
Type: schema.TypeString,
22+
ForceNew: true,
23+
Required: true,
24+
Description: "The owner of the lookup.",
25+
},
26+
"file_name": {
27+
Type: schema.TypeString,
28+
ForceNew: true,
29+
Required: true,
30+
Description: "A file name for the lookup.",
31+
},
32+
"file_contents": {
33+
Type: schema.TypeList,
34+
Required: true,
35+
Elem: &schema.Schema{
36+
Type: schema.TypeList,
37+
Elem: &schema.Schema{
38+
Type: schema.TypeString,
39+
},
40+
},
41+
Description: "The contents of the lookup.",
42+
},
43+
},
44+
Create: lookupTableFileCreate,
45+
Read: lookupTableFileRead,
46+
Update: lookupTableFileUpdate,
47+
Delete: lookupTableFileDelete,
48+
}
49+
}
50+
51+
func lookupTableFileCreate(d *schema.ResourceData, meta interface{}) error {
52+
provider := meta.(*SplunkProvider)
53+
lookupTableFile := getLookupTableFile(d)
54+
55+
err := (*provider.Client).CreateLookupTableFile(lookupTableFile.FileName, lookupTableFile.Owner, lookupTableFile.App, lookupTableFile.FileContents)
56+
if err != nil {
57+
return err
58+
}
59+
60+
d.SetId(lookupTableFile.FileName)
61+
return lookupTableFileRead(d, meta)
62+
}
63+
64+
func lookupTableFileRead(d *schema.ResourceData, meta interface{}) error {
65+
provider := meta.(*SplunkProvider)
66+
lookupTableFile := getLookupTableFile(d)
67+
68+
resp, err := (*provider.Client).ReadLookupTableFile(lookupTableFile.FileName, lookupTableFile.Owner, lookupTableFile.App)
69+
if err != nil {
70+
return err
71+
}
72+
defer resp.Body.Close()
73+
74+
bodyBytes, err := io.ReadAll(resp.Body)
75+
if err != nil {
76+
return err
77+
}
78+
79+
var fileContents [][]string
80+
if err := json.Unmarshal(bodyBytes, &fileContents); err != nil {
81+
return err
82+
}
83+
84+
if err = d.Set("file_contents", fileContents); err != nil {
85+
return err
86+
}
87+
88+
return nil
89+
}
90+
91+
func lookupTableFileUpdate(d *schema.ResourceData, meta interface{}) error {
92+
provider := meta.(*SplunkProvider)
93+
lookupTableFile := getLookupTableFile(d)
94+
95+
err := (*provider.Client).UpdateLookupTableFile(lookupTableFile.FileName, lookupTableFile.Owner, lookupTableFile.App, lookupTableFile.FileContents)
96+
if err != nil {
97+
return err
98+
}
99+
100+
return lookupTableFileRead(d, meta)
101+
}
102+
103+
func lookupTableFileDelete(d *schema.ResourceData, meta interface{}) error {
104+
provider := meta.(*SplunkProvider)
105+
lookupTableFile := getLookupTableFile(d)
106+
107+
resp, err := (*provider.Client).DeleteLookupTableFile(lookupTableFile.FileName, lookupTableFile.Owner, lookupTableFile.App)
108+
if err != nil {
109+
return err
110+
}
111+
defer resp.Body.Close()
112+
113+
switch resp.StatusCode {
114+
case 200, 201:
115+
return nil
116+
117+
default:
118+
errorResponse := &models.InputsUDPResponse{}
119+
_ = json.NewDecoder(resp.Body).Decode(errorResponse)
120+
err := errors.New(errorResponse.Messages[0].Text)
121+
return err
122+
}
123+
}
124+
125+
func getLookupTableFile(d *schema.ResourceData) (lookupTableFile *models.LookupTableFile) {
126+
fileContents, _ := json.Marshal(d.Get("file_contents"))
127+
lookupTableFile = &models.LookupTableFile{
128+
App: d.Get("app").(string),
129+
Owner: d.Get("owner").(string),
130+
FileName: d.Get("file_name").(string),
131+
FileContents: string(fileContents),
132+
}
133+
return lookupTableFile
134+
}

0 commit comments

Comments
 (0)