Skip to content

Commit 9aa72ad

Browse files
nkvuongalexott
andauthored
[Feature] Refactor databricks_mlflow_experiment to Go SDK (#4606)
## Changes - Clean up to use structs from go sdk - Correct the attributes (`tags` is missing and `description` does not exist) - Suppress diff in `databricks_mlflow_experiment` name, close #4601 ## Tests <!-- How is this tested? Please see the checklist below and also describe any other relevant tests --> - [x] `make test` run locally - [x] covered with integration tests in `internal/acceptance` - [x] using Go SDK --------- Co-authored-by: Alex Ott <[email protected]>
1 parent 956dd4f commit 9aa72ad

File tree

5 files changed

+103
-97
lines changed

5 files changed

+103
-97
lines changed

NEXT_CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
### Bug Fixes
88

9+
* Suppress diff in `databricks_mlflow_experiment` name ([#4606](https://github.com/databricks/terraform-provider-databricks/pull/4606))
10+
911
### Documentation
1012

1113
* Add import instructions for `databricks_share` and `databricks_recipient` ([#4608](https://github.com/databricks/terraform-provider-databricks/pull/4608))

docs/resources/mlflow_experiment.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,15 @@ data "databricks_current_user" "me" {}
1313
resource "databricks_mlflow_experiment" "this" {
1414
name = "${data.databricks_current_user.me.home}/Sample"
1515
artifact_location = "dbfs:/tmp/my-experiment"
16-
description = "My MLflow experiment description"
16+
17+
tags {
18+
key = "key1"
19+
value = "value1"
20+
}
21+
tags {
22+
key = "key2"
23+
value = "value2"
24+
}
1725
}
1826
```
1927

@@ -23,7 +31,7 @@ The following arguments are supported:
2331

2432
* `name` - (Required) Name of MLflow experiment. It must be an absolute path within the Databricks workspace, e.g. `/Users/<some-username>/my-experiment`. For more information about changes to experiment naming conventions, see [mlflow docs](https://docs.databricks.com/applications/mlflow/experiments.html#experiment-migration).
2533
* `artifact_location` - Path to dbfs:/ or s3:// artifact location of the MLflow experiment.
26-
* `description` - The description of the MLflow experiment.
34+
* `tags` - Tags for the MLflow experiment.
2735

2836
## Attribute Reference
2937

mlflow/mlflow_experiment_test.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,17 @@ func TestAccMLflowExperiment(t *testing.T) {
1212
resource "databricks_mlflow_experiment" "e1" {
1313
name = "/Shared/tf-{var.RANDOM}"
1414
artifact_location = "dbfs:/tmp/tf-{var.RANDOM}"
15-
description = "tf-{var.RANDOM} description"
15+
}
16+
`,
17+
})
18+
}
19+
20+
func TestAccMLflowExperimentWithDiff(t *testing.T) {
21+
acceptance.WorkspaceLevel(t, acceptance.Step{
22+
Template: `
23+
resource "databricks_mlflow_experiment" "e1" {
24+
name = "/Workspace/Shared/tf-{var.RANDOM}/"
25+
artifact_location = "dbfs:/tmp/tf-{var.RANDOM}"
1626
}
1727
`,
1828
})

mlflow/resource_mlflow_experiment.go

Lines changed: 51 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2,101 +2,83 @@ package mlflow
22

33
import (
44
"context"
5+
"log"
6+
"strings"
57

8+
"github.com/databricks/databricks-sdk-go/service/ml"
69
"github.com/databricks/terraform-provider-databricks/common"
710
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
811
)
912

10-
// Experiment defines the response object from the API
11-
type Experiment struct {
12-
Name string `json:"name"`
13-
Description string `json:"description,omitempty"`
14-
ExperimentId string `json:"experiment_id,omitempty" tf:"computed"`
15-
ArtifactLocation string `json:"artifact_location,omitempty" tf:"force_new,suppress_diff"`
16-
LifecycleStage string `json:"lifecycle_stage,omitempty" tf:"computed"`
17-
LastUpdateTime int64 `json:"last_update_time,omitempty" tf:"computed"`
18-
CreationTime int64 `json:"creation_time,omitempty" tf:"computed"`
19-
}
20-
21-
type experimentUpdate struct {
22-
ExperimentId string `json:"experiment_id"`
23-
NewName string `json:"new_name"`
24-
}
25-
26-
type experimentWrapper struct {
27-
Experiment Experiment `json:"experiment"`
28-
}
29-
30-
// ExperimentsAPI ...
31-
type ExperimentsAPI struct {
32-
client *common.DatabricksClient
33-
context context.Context
34-
}
13+
func experimentNameSuppressDiff(k, old, new string, d *schema.ResourceData) bool {
3514

36-
// NewExperimentsAPI ...
37-
func NewExperimentsAPI(ctx context.Context, m any) ExperimentsAPI {
38-
return ExperimentsAPI{m.(*common.DatabricksClient), ctx}
39-
}
40-
41-
// Create ...
42-
func (a ExperimentsAPI) Create(d *Experiment) error {
43-
return a.client.Post(a.context, "/mlflow/experiments/create", d, &d)
44-
}
45-
46-
// Read ...
47-
func (a ExperimentsAPI) Read(experimentId string) (*Experiment, error) {
48-
var d experimentWrapper
49-
err := a.client.Get(a.context, "/mlflow/experiments/get", map[string]string{
50-
"experiment_id": experimentId,
51-
}, &d)
52-
if err != nil {
53-
return nil, err
15+
if strings.TrimSuffix(strings.TrimPrefix(new, "/Workspace"), "/") == strings.TrimSuffix(strings.TrimPrefix(old, "/Workspace"), "/") {
16+
log.Printf("[DEBUG] Ignoring configuration drift from %s to %s", old, new)
17+
return true
5418
}
55-
return &d.Experiment, nil
56-
}
57-
58-
// Update ...
59-
func (a ExperimentsAPI) Update(e *experimentUpdate) error {
60-
return a.client.Post(a.context, "/mlflow/experiments/update", e, &e)
61-
}
62-
63-
// Delete ...
64-
func (a ExperimentsAPI) Delete(id string) error {
65-
return a.client.Post(a.context, "/mlflow/experiments/delete", map[string]string{
66-
"experiment_id": id,
67-
}, nil)
19+
return false
6820
}
6921

7022
func ResourceMlflowExperiment() common.Resource {
7123
s := common.StructToSchema(
72-
Experiment{},
73-
common.NoCustomize)
24+
ml.Experiment{},
25+
func(m map[string]*schema.Schema) map[string]*schema.Schema {
26+
for _, p := range []string{"creation_time", "experiment_id", "last_update_time", "lifecycle_stage", "tags"} {
27+
common.CustomizeSchemaPath(m, p).SetComputed()
28+
}
29+
common.CustomizeSchemaPath(m, "artifact_location").SetForceNew().SetSuppressDiff()
30+
common.CustomizeSchemaPath(m, "name").SetRequired().SetCustomSuppressDiff(experimentNameSuppressDiff)
31+
// the API never accepts description, but we need to keep this for backwards compatibility
32+
m["description"] = &schema.Schema{
33+
Optional: true,
34+
Type: schema.TypeString,
35+
Deprecated: "Remove the description attribute as it no longer is used and will be removed in a future version.",
36+
}
37+
return m
38+
})
7439

7540
return common.Resource{
7641
Create: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
77-
var e Experiment
78-
common.DataToStructPointer(d, s, &e)
79-
if err := NewExperimentsAPI(ctx, c).Create(&e); err != nil {
42+
w, err := c.WorkspaceClient()
43+
if err != nil {
44+
return err
45+
}
46+
var create ml.CreateExperiment
47+
common.DataToStructPointer(d, s, &create)
48+
response, err := w.Experiments.CreateExperiment(ctx, create)
49+
if err != nil {
8050
return err
8151
}
82-
d.SetId(e.ExperimentId)
52+
d.SetId(response.ExperimentId)
8353
return nil
8454
},
8555
Read: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
86-
e, err := NewExperimentsAPI(ctx, c).Read(d.Id())
56+
w, err := c.WorkspaceClient()
8757
if err != nil {
8858
return err
8959
}
90-
return common.StructToData(*e, s, d)
60+
e, err := w.Experiments.GetExperiment(ctx, ml.GetExperimentRequest{ExperimentId: d.Id()})
61+
if err != nil {
62+
return err
63+
}
64+
return common.StructToData(e.Experiment, s, d)
9165
},
9266
Update: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
93-
var e Experiment
94-
common.DataToStructPointer(d, s, &e)
95-
updateDoc := experimentUpdate{ExperimentId: d.Id(), NewName: e.Name}
96-
return NewExperimentsAPI(ctx, c).Update(&updateDoc)
67+
w, err := c.WorkspaceClient()
68+
if err != nil {
69+
return err
70+
}
71+
return w.Experiments.UpdateExperiment(ctx, ml.UpdateExperiment{
72+
ExperimentId: d.Id(),
73+
NewName: d.Get("name").(string),
74+
})
9775
},
9876
Delete: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
99-
return NewExperimentsAPI(ctx, c).Delete(d.Id())
77+
w, err := c.WorkspaceClient()
78+
if err != nil {
79+
return err
80+
}
81+
return w.Experiments.DeleteExperiment(ctx, ml.DeleteExperiment{ExperimentId: d.Id()})
10082
},
10183
StateUpgraders: []schema.StateUpgrader{},
10284
Schema: s,

mlflow/resource_mlflow_experiment_test.go

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ package mlflow
33
import (
44
"testing"
55

6+
"github.com/databricks/databricks-sdk-go/service/ml"
67
"github.com/databricks/terraform-provider-databricks/qa"
78
"github.com/stretchr/testify/assert"
89
)
910

10-
func e() Experiment {
11-
return Experiment{
11+
func e() ml.Experiment {
12+
return ml.Experiment{
1213
Name: "xyz",
1314
}
1415
}
@@ -27,8 +28,8 @@ func TestExperimentCreate(t *testing.T) {
2728
{
2829
Method: "GET",
2930
Resource: "/api/2.0/mlflow/experiments/get?experiment_id=123456790123456",
30-
Response: experimentWrapper{
31-
Experiment: re,
31+
Response: ml.GetExperimentResponse{
32+
Experiment: &re,
3233
},
3334
},
3435
},
@@ -81,8 +82,8 @@ func TestExperimentCreateGetError(t *testing.T) {
8182
{
8283
Method: "GET",
8384
Resource: "/api/2.0/mlflow/experiments/get?experiment_id=123456790123456",
84-
Response: experimentWrapper{
85-
Experiment: re,
85+
Response: ml.GetExperimentResponse{
86+
Experiment: &re,
8687
},
8788
Status: 400,
8889
},
@@ -105,8 +106,8 @@ func TestExperimentRead(t *testing.T) {
105106
{
106107
Method: "GET",
107108
Resource: "/api/2.0/mlflow/experiments/get?experiment_id=123456790123456",
108-
Response: experimentWrapper{
109-
Experiment: re,
109+
Response: ml.GetExperimentResponse{
110+
Experiment: &re,
110111
},
111112
},
112113
},
@@ -127,8 +128,8 @@ func TestExperimentReadGetError(t *testing.T) {
127128
{
128129
Method: "GET",
129130
Resource: "/api/2.0/mlflow/experiments/get?experiment_id=123456790123456",
130-
Response: experimentWrapper{
131-
Experiment: re,
131+
Response: ml.GetExperimentResponse{
132+
Experiment: &re,
132133
},
133134
Status: 400,
134135
},
@@ -142,17 +143,17 @@ func TestExperimentReadGetError(t *testing.T) {
142143
}
143144

144145
func TestExperimentUpdate(t *testing.T) {
145-
resPost := Experiment{
146+
resPost := ml.Experiment{
146147
ExperimentId: "123456790123456",
147148
Name: "123",
148149
}
149-
d, err := qa.ResourceFixture{
150+
qa.ResourceFixture{
150151
Fixtures: []qa.HTTPFixture{
151152
{
152153
Method: "GET",
153154
Resource: "/api/2.0/mlflow/experiments/get?experiment_id=123456790123456",
154-
Response: experimentWrapper{
155-
Experiment: resPost,
155+
Response: ml.GetExperimentResponse{
156+
Experiment: &resPost,
156157
},
157158
},
158159
{
@@ -167,15 +168,14 @@ func TestExperimentUpdate(t *testing.T) {
167168
HCL: `
168169
name = "123"
169170
`,
170-
}.Apply(t)
171-
172-
assert.NoError(t, err)
173-
assert.Equal(t, resPost.ExperimentId, d.Id(), "Resource ID should not be empty")
174-
assert.Equal(t, resPost.Name, d.Get("name"), "Name should be updated")
171+
}.ApplyAndExpectData(t, map[string]any{
172+
"name": resPost.Name,
173+
"id": resPost.ExperimentId,
174+
})
175175
}
176176

177177
func TestExperimentUpdatePostError(t *testing.T) {
178-
resPost := Experiment{
178+
resPost := ml.Experiment{
179179
ExperimentId: "123456790123456",
180180
Name: "123",
181181
}
@@ -203,7 +203,7 @@ func TestExperimentDelete(t *testing.T) {
203203
r := map[string]string{
204204
"experiment_id": "123456790123456",
205205
}
206-
d, err := qa.ResourceFixture{
206+
qa.ResourceFixture{
207207
Fixtures: []qa.HTTPFixture{
208208
{
209209
Method: "POST",
@@ -217,10 +217,7 @@ func TestExperimentDelete(t *testing.T) {
217217
HCL: `
218218
name = "xyz"
219219
`,
220-
}.Apply(t)
221-
222-
assert.NoError(t, err)
223-
assert.Equal(t, r["experiment_id"], d.Id(), "Resource ID should not be empty")
220+
}.ApplyNoError(t)
224221
}
225222

226223
func TestExperimentDeleteError(t *testing.T) {
@@ -246,3 +243,10 @@ func TestExperimentDeleteError(t *testing.T) {
246243

247244
assert.Error(t, err)
248245
}
246+
247+
func TestExperimentNameSuppressDiff(t *testing.T) {
248+
assert.True(t, experimentNameSuppressDiff("", "/Workspace/EXPERIMENT/", "/EXPERIMENT", nil))
249+
assert.True(t, experimentNameSuppressDiff("", "/Workspace/EXPERIMENT", "/EXPERIMENT", nil))
250+
assert.True(t, experimentNameSuppressDiff("", "/EXPERIMENT/", "/EXPERIMENT", nil))
251+
assert.False(t, experimentNameSuppressDiff("", "/new_name", "/EXPERIMENT/", nil))
252+
}

0 commit comments

Comments
 (0)