Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create dashboards and panels #47

Merged
merged 9 commits into from
Jan 6, 2021
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/dashboards.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package client
ani0989 marked this conversation as resolved.
Show resolved Hide resolved

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/dashboards.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package models
ani0989 marked this conversation as resolved.
Show resolved Hide resolved

// 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"`
}
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_dashboards": splunkDashboards(),
ani0989 marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
198 changes: 198 additions & 0 deletions splunk/resource_splunk_dashboards.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package splunk
ani0989 marked this conversation as resolved.
Show resolved Hide resolved

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: "App context for the dashboard.",
ani0989 marked this conversation as resolved.
Show resolved Hide resolved
},
"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()
// We first get list of inputs to get owner and app name for the specific input
ani0989 marked this conversation as resolved.
Show resolved Hide resolved
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)
}

// Now we read the input configuration with proper owner and app
ani0989 marked this conversation as resolved.
Show resolved Hide resolved
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