Skip to content

Commit a294367

Browse files
pieternnfx
authored andcommitted
Implement dashboard resource
1 parent c9714d5 commit a294367

File tree

5 files changed

+289
-0
lines changed

5 files changed

+289
-0
lines changed

provider/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ func DatabricksProvider() *schema.Provider {
7474
"databricks_azure_blob_mount": storage.ResourceAzureBlobMount(),
7575
"databricks_dbfs_file": storage.ResourceDBFSFile(),
7676

77+
"databricks_sql_dashboard": sqlanalytics.ResourceDashboard(),
7778
"databricks_sql_endpoint": sqlanalytics.ResourceSQLEndpoint(),
7879
"databricks_sql_query": sqlanalytics.ResourceQuery(),
7980
"databricks_sql_visualization": sqlanalytics.ResourceVisualization(),

sqlanalytics/api/dashboard.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package api
2+
3+
import "encoding/json"
4+
5+
// Dashboard ...
6+
type Dashboard struct {
7+
ID string `json:"id"`
8+
Name string `json:"name"`
9+
Tags []string `json:"tags,omitempty"`
10+
Widgets []json.RawMessage `json:"widgets,omitempty"`
11+
}

sqlanalytics/api/wrapper.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,41 @@ func (a Wrapper) UpdateQuery(q *Query) (*Query, error) {
125125
func (a Wrapper) DeleteQuery(q *Query) error {
126126
return a.client.Delete(a.context, fmt.Sprintf("%s/queries/%s", sqlaBasePath, q.ID), nil)
127127
}
128+
129+
// CreateDashboard ...
130+
func (a Wrapper) CreateDashboard(d *Dashboard) (*Dashboard, error) {
131+
var dout Dashboard
132+
err := a.client.Post(a.context, fmt.Sprintf("%s/dashboards", sqlaBasePath), d, &dout)
133+
if err != nil {
134+
return nil, err
135+
}
136+
137+
return &dout, err
138+
}
139+
140+
// ReadDashboard ...
141+
func (a Wrapper) ReadDashboard(d *Dashboard) (*Dashboard, error) {
142+
var dout Dashboard
143+
err := a.client.Get(a.context, fmt.Sprintf("%s/dashboards/%s", sqlaBasePath, d.ID), nil, &dout)
144+
if err != nil {
145+
return nil, err
146+
}
147+
148+
return &dout, nil
149+
}
150+
151+
// UpdateDashboard ...
152+
func (a Wrapper) UpdateDashboard(d *Dashboard) (*Dashboard, error) {
153+
var dout Dashboard
154+
err := a.client.Post(a.context, fmt.Sprintf("%s/dashboards/%s", sqlaBasePath, d.ID), d, &dout)
155+
if err != nil {
156+
return nil, err
157+
}
158+
159+
return &dout, nil
160+
}
161+
162+
// DeleteDashboard ...
163+
func (a Wrapper) DeleteDashboard(d *Dashboard) error {
164+
return a.client.Delete(a.context, fmt.Sprintf("%s/dashboards/%s", sqlaBasePath, d.ID), nil)
165+
}

sqlanalytics/resource_dashboard.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package sqlanalytics
2+
3+
import (
4+
"context"
5+
6+
"github.com/databrickslabs/terraform-provider-databricks/common"
7+
"github.com/databrickslabs/terraform-provider-databricks/sqlanalytics/api"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9+
)
10+
11+
// DashboardEntity defines the parameters that can be set in the resource.
12+
type DashboardEntity struct {
13+
Name string `json:"name"`
14+
Tags []string `json:"tags,omitempty"`
15+
}
16+
17+
func (d *DashboardEntity) toAPIObject(schema map[string]*schema.Schema, data *schema.ResourceData) (*api.Dashboard, error) {
18+
// Extract from ResourceData.
19+
if err := common.DataToStructPointer(data, schema, d); err != nil {
20+
return nil, err
21+
}
22+
23+
// Copy to API object.
24+
var ad api.Dashboard
25+
ad.ID = data.Id()
26+
ad.Name = d.Name
27+
ad.Tags = append([]string{}, d.Tags...)
28+
29+
return &ad, nil
30+
}
31+
32+
func (d *DashboardEntity) fromAPIObject(ad *api.Dashboard, schema map[string]*schema.Schema, data *schema.ResourceData) error {
33+
// Copy from API object.
34+
d.Name = ad.Name
35+
d.Tags = append([]string{}, ad.Tags...)
36+
37+
// Pass to ResourceData.
38+
if err := common.StructToData(*d, schema, data); err != nil {
39+
return err
40+
}
41+
42+
// Overwrite `tags` in case they're empty on the server side.
43+
// This would have been skipped by `common.StructToData` because of slice emptiness.
44+
// Ideally, the reflection code also sets empty values, but we'd risk
45+
// clobbering values we actually want to keep around in existing code.
46+
data.Set("tags", ad.Tags)
47+
return nil
48+
}
49+
50+
// ResourceDashboard ...
51+
func ResourceDashboard() *schema.Resource {
52+
s := common.StructToSchema(
53+
DashboardEntity{},
54+
func(m map[string]*schema.Schema) map[string]*schema.Schema {
55+
return m
56+
})
57+
58+
return common.Resource{
59+
Create: func(ctx context.Context, data *schema.ResourceData, c *common.DatabricksClient) error {
60+
var d DashboardEntity
61+
ad, err := d.toAPIObject(s, data)
62+
if err != nil {
63+
return err
64+
}
65+
66+
adNew, err := api.NewWrapper(ctx, c).CreateDashboard(ad)
67+
if err != nil {
68+
return err
69+
}
70+
71+
// No need to set anything because the resource is going to be
72+
// read immediately after being created.
73+
data.SetId(adNew.ID)
74+
return nil
75+
},
76+
Read: func(ctx context.Context, data *schema.ResourceData, c *common.DatabricksClient) error {
77+
var d DashboardEntity
78+
ad, err := d.toAPIObject(s, data)
79+
if err != nil {
80+
return err
81+
}
82+
83+
adNew, err := api.NewWrapper(ctx, c).ReadDashboard(ad)
84+
if err != nil {
85+
return err
86+
}
87+
88+
return d.fromAPIObject(adNew, s, data)
89+
},
90+
Update: func(ctx context.Context, data *schema.ResourceData, c *common.DatabricksClient) error {
91+
var d DashboardEntity
92+
ad, err := d.toAPIObject(s, data)
93+
if err != nil {
94+
return err
95+
}
96+
97+
_, err = api.NewWrapper(ctx, c).UpdateDashboard(ad)
98+
if err != nil {
99+
return err
100+
}
101+
102+
// No need to set anything because the resource is going to be
103+
// read immediately after being created.
104+
return nil
105+
},
106+
Delete: func(ctx context.Context, data *schema.ResourceData, c *common.DatabricksClient) error {
107+
var d DashboardEntity
108+
ad, err := d.toAPIObject(s, data)
109+
if err != nil {
110+
return err
111+
}
112+
113+
return api.NewWrapper(ctx, c).DeleteDashboard(ad)
114+
},
115+
Schema: s,
116+
}.ToResource()
117+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package sqlanalytics
2+
3+
import (
4+
"testing"
5+
6+
"github.com/databrickslabs/terraform-provider-databricks/qa"
7+
"github.com/databrickslabs/terraform-provider-databricks/sqlanalytics/api"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestDashboardCreate(t *testing.T) {
12+
d, err := qa.ResourceFixture{
13+
Fixtures: []qa.HTTPFixture{
14+
{
15+
Method: "POST",
16+
Resource: "/api/2.0/preview/sql/dashboards",
17+
ExpectedRequest: api.Dashboard{
18+
Name: "Dashboard name",
19+
Tags: []string{"t1", "t2"},
20+
},
21+
Response: api.Dashboard{
22+
ID: "xyz",
23+
Name: "Dashboard name",
24+
Tags: []string{"t1", "t2"},
25+
},
26+
},
27+
{
28+
Method: "GET",
29+
Resource: "/api/2.0/preview/sql/dashboards/xyz",
30+
Response: api.Dashboard{
31+
ID: "xyz",
32+
Name: "Dashboard name",
33+
Tags: []string{"t1", "t2"},
34+
},
35+
},
36+
},
37+
Resource: ResourceDashboard(),
38+
Create: true,
39+
State: map[string]interface{}{
40+
"name": "Dashboard name",
41+
"tags": []interface{}{"t1", "t2"},
42+
},
43+
}.Apply(t)
44+
45+
assert.NoError(t, err, err)
46+
assert.Equal(t, "xyz", d.Id(), "Resource ID should not be empty")
47+
assert.Equal(t, "Dashboard name", d.Get("name"))
48+
}
49+
50+
func TestDashboardRead(t *testing.T) {
51+
d, err := qa.ResourceFixture{
52+
Fixtures: []qa.HTTPFixture{
53+
{
54+
Method: "GET",
55+
Resource: "/api/2.0/preview/sql/dashboards/xyz",
56+
Response: api.Dashboard{
57+
ID: "xyz",
58+
Name: "Dashboard name",
59+
Tags: []string{"t1", "t2"},
60+
},
61+
},
62+
},
63+
Resource: ResourceDashboard(),
64+
Read: true,
65+
ID: "xyz",
66+
}.Apply(t)
67+
68+
assert.NoError(t, err, err)
69+
assert.Equal(t, "xyz", d.Id(), "Resource ID should not be empty")
70+
}
71+
72+
func TestDashboardUpdate(t *testing.T) {
73+
d, err := qa.ResourceFixture{
74+
Fixtures: []qa.HTTPFixture{
75+
{
76+
Method: "POST",
77+
Resource: "/api/2.0/preview/sql/dashboards/xyz",
78+
Response: api.Dashboard{
79+
ID: "xyz",
80+
Name: "Dashboard renamed",
81+
Tags: []string{"t2", "t3"},
82+
},
83+
},
84+
{
85+
Method: "GET",
86+
Resource: "/api/2.0/preview/sql/dashboards/xyz",
87+
Response: api.Dashboard{
88+
ID: "xyz",
89+
Name: "Dashboard renamed",
90+
Tags: []string{"t2", "t3"},
91+
},
92+
},
93+
},
94+
Resource: ResourceDashboard(),
95+
Update: true,
96+
ID: "xyz",
97+
State: map[string]interface{}{
98+
"name": "Dashboard renamed",
99+
"tags": []interface{}{"t2", "t3"},
100+
},
101+
}.Apply(t)
102+
103+
assert.NoError(t, err, err)
104+
assert.Equal(t, "xyz", d.Id(), "Resource ID should not be empty")
105+
}
106+
107+
func TestDashboardDelete(t *testing.T) {
108+
d, err := qa.ResourceFixture{
109+
Fixtures: []qa.HTTPFixture{
110+
{
111+
Method: "DELETE",
112+
Resource: "/api/2.0/preview/sql/dashboards/xyz",
113+
},
114+
},
115+
Resource: ResourceDashboard(),
116+
Delete: true,
117+
ID: "xyz",
118+
}.Apply(t)
119+
120+
assert.NoError(t, err, err)
121+
assert.Equal(t, "xyz", d.Id(), "Resource ID should not be empty")
122+
}

0 commit comments

Comments
 (0)