Skip to content

Commit

Permalink
Create dashboards and panels (#47)
Browse files Browse the repository at this point in the history
* Dashboards support

* Dashboard Update and Delete Support

* Fix: Dashboard Entry, Dashboard Update, SplunkDashboard Object, Tests

* Dashboard name suggestions

* Multiline dashboard XML example

* Multiline dashboard XML example - correcting quotes

* Multiline dashboard XML example - correcting quotes

* Correcting test without multiline XML

Co-authored-by: ajayaraman <[email protected]>
  • Loading branch information
Karthika and ajayaraman authored Jan 6, 2021
1 parent f37163b commit 1c1963e
Show file tree
Hide file tree
Showing 7 changed files with 398 additions and 2 deletions.
3 changes: 2 additions & 1 deletion client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/splunk/terraform-provider-splunk/client/utils"
"io"
"net/http"
"net/url"
Expand All @@ -16,6 +15,8 @@ import (
"strconv"
"strings"
"time"

"github.com/splunk/terraform-provider-splunk/client/utils"
)

// Declare constants for service package
Expand Down
71 changes: 71 additions & 0 deletions client/data_ui_views.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package client

import (
"net/http"

"github.com/splunk/terraform-provider-splunk/client/models"

"github.com/google/go-querystring/query"
)

func (client *Client) CreateDashboardObject(owner string, app string, splunkDashboardsObj *models.SplunkDashboardsObject) error {
values, err := query.Values(&splunkDashboardsObj)
if err != nil {
return err
}
endpoint := client.BuildSplunkURL(nil, "servicesNS", owner, app, "data", "ui", "views")
resp, err := client.Post(endpoint, values)
if err != nil {
return err
}
defer resp.Body.Close()

return nil
}

func (client *Client) ReadDashboardObject(name, owner, app string) (*http.Response, error) {
endpoint := client.BuildSplunkURL(nil, "servicesNS", owner, app, "data", "ui", "views", name)
resp, err := client.Get(endpoint)
if err != nil {
return nil, err
}

return resp, nil
}

func (client *Client) UpdateDashboardObject(owner string, app string, name string, splunkDashboardsObj *models.SplunkDashboardsObject) error {
values, err := query.Values(&splunkDashboardsObj)
values.Del("name")
if err != nil {
return err
}
endpoint := client.BuildSplunkURL(nil, "servicesNS", owner, app, "data", "ui", "views", name)
resp, err := client.Post(endpoint, values)
if err != nil {
return err
}
defer resp.Body.Close()

return nil
}

func (client *Client) DeleteDashboardObject(owner string, app string, name string) (*http.Response, error) {
endpoint := client.BuildSplunkURL(nil, "servicesNS", owner, app, "data", "ui", "views", name)
resp, err := client.Delete(endpoint)
if err != nil {
return nil, err
}
defer resp.Body.Close()

return resp, nil
}

func (client *Client) ReadAllDashboardObject() (*http.Response, error) {
endpoint := client.BuildSplunkURL(nil, "servicesNS", "-", "-", "data", "ui", "views")
resp, err := client.Get(endpoint)
if err != nil {
return nil, err
}

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

// DashboardResponse HTTP Input Response Schema
type DashboardResponse struct {
Entry []DashboardEntry `json:"entry"`
Messages []ErrorMessage `json:"messages"`
}

type DashboardEntry struct {
Name string `json:"name"`
ACL ACLObject `json:"acl"`
Content SplunkDashboardsObject `json:"content"`
}

type SplunkDashboardsObject struct {
Name string `json:"name,omitempty" url:"name,omitempty"`
EAIData string `json:"eai:data,omitempty" url:"eai:data,omitempty"`
}
31 changes: 31 additions & 0 deletions docs/resources/data_ui_views.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Resource: splunk_data_ui_views
Create and manage splunk dashboards/views.
## Example Usage
```
resource "splunk_data_ui_views" "dashboard" {
name = "Terraform_Test_Dashboard"
eai_data = <<EOF
<dashboard>
<label>
Terraform Test Dashboard
</label>
</dashboard>
EOF
acl {
owner = "admin"
app = "search"
}
}
```

## Argument Reference
For latest resource argument reference: https://docs.splunk.com/Documentation/Splunk/8.1.1/RESTREF/RESTknowledge#data.2Fui.2Fviews

This resource block supports the following arguments:
* `name` - (Required) Dashboard name.
* `eai:data` - (Required) Dashboard XML definition.

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

* `id` - The ID of the dashboard
4 changes: 3 additions & 1 deletion splunk/provider.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package splunk

import (
"github.com/splunk/terraform-provider-splunk/client"
"time"

"github.com/splunk/terraform-provider-splunk/client"

"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/terraform"
)
Expand Down Expand Up @@ -90,6 +91,7 @@ func providerResources() map[string]*schema.Resource {
"splunk_saved_searches": savedSearches(),
"splunk_indexes": index(),
"splunk_configs_conf": configsConf(),
"splunk_data_ui_views": splunkDashboards(),
}
}

Expand Down
196 changes: 196 additions & 0 deletions splunk/resource_splunk_data_ui_views.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package splunk

import (
"encoding/json"
"errors"
"fmt"
"net/http"
"regexp"

"github.com/splunk/terraform-provider-splunk/client/models"

"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func splunkDashboards() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Dashboard Name.",
},
"eai_data": {
Type: schema.TypeString,
Required: true,
Description: "Dashboard XML definition.",
},
"acl": aclSchema(),
},
Read: splunkDashboardsRead,
Create: splunkDashboardsCreate,
Delete: splunkDashboardsDelete,
Update: splunkDashboardsUpdate,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
}
}

// Functions
func splunkDashboardsCreate(d *schema.ResourceData, meta interface{}) error {
provider := meta.(*SplunkProvider)
name := d.Get("name").(string)
splunkDashboardsObj := getSplunkDashboardsConfig(d)
aclObject := &models.ACLObject{}
if r, ok := d.GetOk("acl"); ok {
aclObject = getACLConfig(r.([]interface{}))
} else {
aclObject.App = "search"
aclObject.Owner = "admin"
aclObject.Sharing = "user"
}
err := (*provider.Client).CreateDashboardObject(aclObject.Owner, aclObject.App, splunkDashboardsObj)
if err != nil {
return err
}

if _, ok := d.GetOk("acl"); ok {
aclObject.Sharing = "user" // hard-coding to avoid to user input; because changing object sharing permissions messes deletion of object
err = (*provider.Client).UpdateAcl(aclObject.Owner, aclObject.App, name, aclObject, "data", "ui", "views")
if err != nil {
return err
}
}

d.SetId(name)
return splunkDashboardsRead(d, meta)
}

func splunkDashboardsRead(d *schema.ResourceData, meta interface{}) error {
provider := meta.(*SplunkProvider)
name := d.Id()
resp, err := (*provider.Client).ReadAllDashboardObject()
if err != nil {
return err
}
defer resp.Body.Close()

entry, err := getDashboardByName(name, resp)
if err != nil {
return err
}

if entry == nil {
return fmt.Errorf("Unable to find resource: %v", name)
}

resp, err = (*provider.Client).ReadDashboardObject(name, entry.ACL.Owner, entry.ACL.App)
if err != nil {
return err
}
defer resp.Body.Close()

entry, err = getDashboardByName(name, resp)
if err != nil {
return err
}

if entry == nil {
return fmt.Errorf("Unable to find resource: %v", name)
}

if err = d.Set("name", entry.Name); err != nil {
return err
}

if err = d.Set("eai_data", entry.Content.EAIData); err != nil {
return err
}

err = d.Set("acl", flattenACL(&entry.ACL))
if err != nil {
return err
}

return nil
}

func splunkDashboardsUpdate(d *schema.ResourceData, meta interface{}) error {
provider := meta.(*SplunkProvider)
name := d.Get("name").(string)
splunkDashboardsObj := getSplunkDashboardsConfig(d)
aclObject := getACLConfig(d.Get("acl").([]interface{}))
err := (*provider.Client).UpdateDashboardObject(aclObject.Owner, aclObject.App, name, splunkDashboardsObj)
if err != nil {
return err
}

//ACL update
if _, ok := d.GetOk("acl"); ok {
aclObject.Sharing = "user" // hard-coding to avoid to user input; because changing object sharing permissions messes deletion of object
err = (*provider.Client).UpdateAcl(aclObject.Owner, aclObject.App, name, aclObject, "data", "ui", "views")
if err != nil {
return err
}
}

return splunkDashboardsRead(d, meta)
}

func splunkDashboardsDelete(d *schema.ResourceData, meta interface{}) error {
provider := meta.(*SplunkProvider)
name := d.Id()
aclObject := getACLConfig(d.Get("acl").([]interface{}))
resp, err := (*provider.Client).DeleteDashboardObject(aclObject.Owner, aclObject.App, name)
if err != nil {
return err
}
defer resp.Body.Close()

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

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

// Helpers
func getSplunkDashboardsConfig(d *schema.ResourceData) (splunkDashboardsObject *models.SplunkDashboardsObject) {
splunkDashboardsObject = &models.SplunkDashboardsObject{}
splunkDashboardsObject.Name = d.Get("name").(string)
splunkDashboardsObject.EAIData = d.Get("eai_data").(string)
return splunkDashboardsObject
}

func getDashboardByName(name string, httpResponse *http.Response) (dashboardEntry *models.DashboardEntry, err error) {
response := &models.DashboardResponse{}
switch httpResponse.StatusCode {
case 200, 201:

decoder := json.NewDecoder(httpResponse.Body)
err := decoder.Decode(response)
if err != nil {
return nil, err
}
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 dashboardEntry, err
}

return dashboardEntry, nil
}
Loading

0 comments on commit 1c1963e

Please sign in to comment.