Skip to content

Commit

Permalink
Added new resource to manage lookup table files (#196)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
akohring authored Nov 8, 2024
1 parent 2905cfe commit b82e604
Show file tree
Hide file tree
Showing 13 changed files with 415 additions and 0 deletions.
13 changes: 13 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ jobs:
ports:
- 8000:8000
- 8089:8089
volumes:
- ${{ github.workspace }}:/workspace

steps:
- name: Set up Go 1.x
Expand All @@ -44,5 +46,16 @@ jobs:
- name: Build
run: go build -v .

- name: Set up Terraform 1.1.7
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.1.7"

- name: Terraform init
run: terraform -chdir=terraform init

- name: Terraform apply
run: terraform -chdir=terraform apply --auto-approve

- name: Test
run: TF_ACC=1 SPLUNK_HOME=/opt/splunk SPLUNK_URL=localhost:8089 SPLUNK_USERNAME=admin SPLUNK_PASSWORD=password go test ./... -v
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Ignore dist file
.idea
.terraform
.terraform.lock.hcl
dist
terraform-provider-splunk
terraform.tfstate
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 1.4.27
* Support for lookup table files

## 1.4.26
* Fix: Add retry mechanism to dashboard's acl endpoint

Expand Down
4 changes: 4 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type Client struct {
host string
httpClient *http.Client
userAgent string
urlEncoded bool
}

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

Expand Down
30 changes: 30 additions & 0 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,36 @@ func TestNewRequestUserAgentHeader(t *testing.T) {
}
}

func TestNewRequestWithoutContentTypeHeader(t *testing.T) {
client, err := NewDefaultSplunkdClient()
if err != nil {
t.Error(err)
}
req, err := client.NewRequest(MethodGet, testURL, nil)
if err != nil {
t.Errorf("NewRequest returns unexpected error %v", err)
}
if req.Header["Content-Type"] != nil {
t.Errorf("NewRequest Content-Type is %v, want nil", req.Header["Content-Type"])
}
}

func TestNewRequestWithContentTypeHeader(t *testing.T) {
client, err := NewDefaultSplunkdClient()
if err != nil {
t.Error(err)
}
client.urlEncoded = true
req, err := client.NewRequest(MethodGet, testURL, nil)
if err != nil {
t.Errorf("NewRequest returns unexpected error %v", err)
}
expectedContentType := []string{"application/x-www-form-urlencoded"}
if got, want := req.Header["Content-Type"], expectedContentType; !reflect.DeepEqual(got, want) {
t.Errorf("NewRequest Content-Type is %v, want %v", got, want)
}
}

func TestNewRequestSessionKey(t *testing.T) {
client, err := NewDefaultSplunkdClient()
if err != nil {
Expand Down
65 changes: 65 additions & 0 deletions client/lookup_table_file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package client

import (
"fmt"
"log"
"net/http"
"net/http/httputil"
)

func (client *Client) CreateLookupTableFile(name string, owner string, app string, contents string) error {
values := []byte(fmt.Sprintf("namespace=%s&lookup_file=%s&owner=%s&contents=%s", app, name, owner, contents))
endpoint := client.BuildSplunkURL(nil, "services", "data", "lookup_edit", "lookup_contents")
client.urlEncoded = true
resp, err := client.Post(endpoint, values)
if err != nil {
return err
}

respBody, error := httputil.DumpResponse(resp, true)
if error != nil {
log.Printf("[ERROR] Error occured during CreateLookup %s", error)
}

log.Printf("[DEBUG] Response object returned from CreateLookup is: %s", string(respBody))

defer resp.Body.Close()
return nil
}

func (client *Client) ReadLookupTableFile(name, owner, app string) (*http.Response, error) {
values := []byte(fmt.Sprintf("namespace=%s&lookup_file=%s&owner=%s", app, name, owner))
client.urlEncoded = true
endpoint := client.BuildSplunkURL(nil, "services", "data", "lookup_edit", "lookup_data")
resp, err := client.Post(endpoint, values)
return resp, err
}

func (client *Client) UpdateLookupTableFile(name string, owner string, app string, contents string) error {
values := []byte(fmt.Sprintf("namespace=%s&lookup_file=%s&owner=%s&contents=%s", app, name, owner, contents))
endpoint := client.BuildSplunkURL(nil, "services", "data", "lookup_edit", "lookup_contents")
client.urlEncoded = true
resp, err := client.Post(endpoint, values)
if err != nil {
return err
}
defer resp.Body.Close()
return nil
}

func (client *Client) DeleteLookupTableFile(name string, owner string, app string) (*http.Response, error) {
endpoint := client.BuildSplunkURL(nil, "servicesNS", owner, app, "data", "lookup-table-files", name)
resp, err := client.Delete(endpoint)
if err != nil {
return nil, err
}

respBody, error := httputil.DumpResponse(resp, true)
if error != nil {
log.Printf("[ERROR] Error occured during DeleteLookup %s", error)
}

log.Printf("[DEBUG] Response object returned from DeleteLookup is: %s", string(respBody))

return resp, nil
}
8 changes: 8 additions & 0 deletions client/models/lookup_table_file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package models

type LookupTableFile struct {
App string `json:"namespace,omitempty" url:"namespace,omitempty"`
Owner string `json:"owner,omitempty" url:"owner,omitempty"`
FileName string `json:"lookup_file,omitempty" url:"lookup_file,omitempty"`
FileContents string `json:"contents,omitempty" url:"contents,omitempty"`
}
31 changes: 31 additions & 0 deletions docs/resources/lookup_table_file.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Resource: splunk_lookup_table_file
Create and manage lookup table files.

## Example Usage
```
resource "splunk_lookup_table_file" "lookup_table_file" {
app = "search"
owner = "nobody"
file_name = "lookup.csv"
file_contents = [
["status", "status_description", "status_type"],
["100", "Continue", "Informational"],
["101", "Switching Protocols", "Informational"],
["200", "OK", "Successful"]
]
}
```

## Argument Reference
For latest resource argument reference: https://docs.splunk.com/Documentation/Splunk/latest/Knowledge/LookupexampleinSplunkWeb

This resource block supports the following arguments:
* `app` - (Required) The app context for the resource.
* `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.
* `file_name` - (Required) A name for the lookup table file. Generally ends with ".csv"
* `file_contents` - (Required) The column header and row value contents for the lookup table file.

## Attribute Reference
In addition to all arguments above, This resource block exports the following arguments:

* `id` - The ID of the lookup table file resource
1 change: 1 addition & 0 deletions splunk/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ func providerResources() map[string]*schema.Resource {
"splunk_inputs_tcp_cooked": inputsTCPCooked(),
"splunk_inputs_tcp_splunk_tcp_token": inputsTCPSplunkTCPToken(),
"splunk_inputs_tcp_ssl": inputsTCPSSL(),
"splunk_lookup_table_file": lookupTableFile(),
"splunk_outputs_tcp_default": outputsTCPDefault(),
"splunk_outputs_tcp_server": outputsTCPServer(),
"splunk_outputs_tcp_group": outputsTCPGroup(),
Expand Down
134 changes: 134 additions & 0 deletions splunk/resource_splunk_lookup_table_file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package splunk

import (
"encoding/json"
"errors"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/splunk/terraform-provider-splunk/client/models"
"io"
)

func lookupTableFile() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"app": {
Type: schema.TypeString,
ForceNew: true,
Required: true,
Description: "The parent app to the lookup.",
},
"owner": {
Type: schema.TypeString,
ForceNew: true,
Required: true,
Description: "The owner of the lookup.",
},
"file_name": {
Type: schema.TypeString,
ForceNew: true,
Required: true,
Description: "A file name for the lookup.",
},
"file_contents": {
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
Description: "The contents of the lookup.",
},
},
Create: lookupTableFileCreate,
Read: lookupTableFileRead,
Update: lookupTableFileUpdate,
Delete: lookupTableFileDelete,
}
}

func lookupTableFileCreate(d *schema.ResourceData, meta interface{}) error {
provider := meta.(*SplunkProvider)
lookupTableFile := getLookupTableFile(d)

err := (*provider.Client).CreateLookupTableFile(lookupTableFile.FileName, lookupTableFile.Owner, lookupTableFile.App, lookupTableFile.FileContents)
if err != nil {
return err
}

d.SetId(lookupTableFile.FileName)
return lookupTableFileRead(d, meta)
}

func lookupTableFileRead(d *schema.ResourceData, meta interface{}) error {
provider := meta.(*SplunkProvider)
lookupTableFile := getLookupTableFile(d)

resp, err := (*provider.Client).ReadLookupTableFile(lookupTableFile.FileName, lookupTableFile.Owner, lookupTableFile.App)
if err != nil {
return err
}
defer resp.Body.Close()

bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return err
}

var fileContents [][]string
if err := json.Unmarshal(bodyBytes, &fileContents); err != nil {
return err
}

if err = d.Set("file_contents", fileContents); err != nil {
return err
}

return nil
}

func lookupTableFileUpdate(d *schema.ResourceData, meta interface{}) error {
provider := meta.(*SplunkProvider)
lookupTableFile := getLookupTableFile(d)

err := (*provider.Client).UpdateLookupTableFile(lookupTableFile.FileName, lookupTableFile.Owner, lookupTableFile.App, lookupTableFile.FileContents)
if err != nil {
return err
}

return lookupTableFileRead(d, meta)
}

func lookupTableFileDelete(d *schema.ResourceData, meta interface{}) error {
provider := meta.(*SplunkProvider)
lookupTableFile := getLookupTableFile(d)

resp, err := (*provider.Client).DeleteLookupTableFile(lookupTableFile.FileName, lookupTableFile.Owner, lookupTableFile.App)
if err != nil {
return err
}
defer resp.Body.Close()

switch resp.StatusCode {
case 200, 201:
return nil

default:
errorResponse := &models.InputsUDPResponse{}
_ = json.NewDecoder(resp.Body).Decode(errorResponse)
err := errors.New(errorResponse.Messages[0].Text)
return err
}
}

func getLookupTableFile(d *schema.ResourceData) (lookupTableFile *models.LookupTableFile) {
fileContents, _ := json.Marshal(d.Get("file_contents"))
lookupTableFile = &models.LookupTableFile{
App: d.Get("app").(string),
Owner: d.Get("owner").(string),
FileName: d.Get("file_name").(string),
FileContents: string(fileContents),
}
return lookupTableFile
}
Loading

0 comments on commit b82e604

Please sign in to comment.