Skip to content

Commit d497347

Browse files
alexottrauchy
andauthored
[Feature] Skip calling Read after Create/Update operations for notebooks (#4173)
## Changes <!-- Summary of your changes that are easy to understand --> It's based on #4190. It was found that the `import` API returns `object_id` as a result of its execution, so we don't really need to call the `Read` operation that will call `get-status` to fill all attributes (when we create a notebook using the `SOURCE` format). The other formats (`DBC` and `JUPYTER`) will still call the `Read` to fill in the missing parameters, such as `language`. It also changed the `language` to `optional,computed` so we can safely set the value and don't trigger the diff. This should help when we import a large number of notebooks, i.e., when applying exported resources. ## Tests <!-- How is this tested? Please see the checklist below and also describe any other relevant tests --> - [x] `make test` run locally - [ ] ~relevant change in `docs/` folder~ - [x] covered with integration tests in `internal/acceptance` - [x] relevant acceptance tests are passing - [ ] ~using Go SDK~ --------- Co-authored-by: Omer Lachish <[email protected]>
1 parent e241751 commit d497347

File tree

9 files changed

+296
-104
lines changed

9 files changed

+296
-104
lines changed

exporter/importables.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1485,7 +1485,15 @@ var resourcesMap map[string]importable = map[string]importable{
14851485
ic.emitWorkspaceObjectParentDirectory(r)
14861486
return r.Data.Set("source", fileName)
14871487
},
1488-
ShouldOmitField: shouldOmitMd5Field,
1488+
ShouldOmitField: func(ic *importContext, pathString string, as *schema.Schema, d *schema.ResourceData) bool {
1489+
switch pathString {
1490+
case "language":
1491+
return d.Get("language") == ""
1492+
case "format":
1493+
return d.Get("format") == "SOURCE"
1494+
}
1495+
return shouldOmitMd5Field(ic, pathString, as, d)
1496+
},
14891497
Depends: []reference{
14901498
{Path: "source", File: true},
14911499
{Path: "path", Resource: "databricks_directory", MatchType: MatchLongestPrefix,

internal/acceptance/notebook_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,31 @@ func TestAccNotebookResourceScalability(t *testing.T) {
1717
}`,
1818
})
1919
}
20+
21+
func TestAccNotebookResourceDbcUpdate(t *testing.T) {
22+
WorkspaceLevel(t, Step{
23+
Template: `resource "databricks_notebook" "this" {
24+
source = "{var.CWD}/../../workspace/acceptance/testdata/acc-test-update1.dbc"
25+
path = "/Shared/provider-test/dbc_{var.STICKY_RANDOM}"
26+
}`,
27+
}, Step{
28+
Template: `resource "databricks_notebook" "this" {
29+
source = "{var.CWD}/../../workspace/acceptance/testdata/acc-test-update2.dbc"
30+
path = "/Shared/provider-test/dbc_{var.STICKY_RANDOM}"
31+
}`,
32+
})
33+
}
34+
35+
func TestAccNotebookResourceJupiterUpdate(t *testing.T) {
36+
WorkspaceLevel(t, Step{
37+
Template: `resource "databricks_notebook" "this" {
38+
source = "{var.CWD}/../../workspace/acceptance/testdata/acc-test-update1.ipynb"
39+
path = "/Shared/provider-test/jupiter_{var.STICKY_RANDOM}"
40+
}`,
41+
}, Step{
42+
Template: `resource "databricks_notebook" "this" {
43+
source = "{var.CWD}/../../workspace/acceptance/testdata/acc-test-update2.ipynb"
44+
path = "/Shared/provider-test/jupiter_{var.STICKY_RANDOM}"
45+
}`,
46+
})
47+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Test files for acceptance tests
2+
3+
- Test to emulate notebook update logic for notebooks in `JUPYTER` format:
4+
- `acc-test-update1.ipynb` - original file used for notebook creation
5+
- `acc-test-update2.ipynb` - modified notebook, triggering notebook update.
6+
- Test to emulate notebook update logic for notebooks in `DBC` format:
7+
- `acc-test-update1.dbc` - original file used for notebook creation
8+
- `acc-test-update2.dbc` - modified notebook, triggering notebook update.
9+
1.08 KB
Binary file not shown.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": 0,
6+
"metadata": {
7+
"application/vnd.databricks.v1+cell": {
8+
"cellMetadata": {},
9+
"inputWidgets": {},
10+
"nuid": "b921d27d-a2bd-4ec8-bc2d-92ccc095246f",
11+
"showTitle": false,
12+
"tableResultSettingsMap": {},
13+
"title": ""
14+
}
15+
},
16+
"outputs": [],
17+
"source": [
18+
"print(\"Hello World\")"
19+
]
20+
}
21+
],
22+
"metadata": {
23+
"application/vnd.databricks.v1+notebook": {
24+
"dashboards": [],
25+
"environmentMetadata": {
26+
"base_environment": "",
27+
"client": "1"
28+
},
29+
"language": "python",
30+
"notebookMetadata": {
31+
"pythonIndentUnit": 4
32+
},
33+
"notebookName": "1",
34+
"widgets": {}
35+
},
36+
"language_info": {
37+
"name": "python"
38+
}
39+
},
40+
"nbformat": 4,
41+
"nbformat_minor": 0
42+
}
1.18 KB
Binary file not shown.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": 0,
6+
"metadata": {
7+
"application/vnd.databricks.v1+cell": {
8+
"cellMetadata": {},
9+
"inputWidgets": {},
10+
"nuid": "769d5d72-59f3-417b-9f8c-08785a9c4423",
11+
"showTitle": false,
12+
"tableResultSettingsMap": {},
13+
"title": ""
14+
}
15+
},
16+
"outputs": [],
17+
"source": [
18+
"print(\"First Hello\")"
19+
]
20+
},
21+
{
22+
"cell_type": "code",
23+
"execution_count": 0,
24+
"metadata": {
25+
"application/vnd.databricks.v1+cell": {
26+
"cellMetadata": {},
27+
"inputWidgets": {},
28+
"nuid": "b921d27d-a2bd-4ec8-bc2d-92ccc095246f",
29+
"showTitle": false,
30+
"tableResultSettingsMap": {},
31+
"title": ""
32+
}
33+
},
34+
"outputs": [],
35+
"source": [
36+
"print(\"Hello World\")"
37+
]
38+
}
39+
],
40+
"metadata": {
41+
"application/vnd.databricks.v1+notebook": {
42+
"dashboards": [],
43+
"environmentMetadata": {
44+
"base_environment": "",
45+
"client": "1"
46+
},
47+
"language": "python",
48+
"notebookMetadata": {
49+
"pythonIndentUnit": 4
50+
},
51+
"notebookName": "1",
52+
"widgets": {}
53+
},
54+
"language_info": {
55+
"name": "python"
56+
}
57+
},
58+
"nbformat": 4,
59+
"nbformat_minor": 0
60+
}

workspace/resource_notebook.go

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ type ObjectStatus struct {
6363
ResourceId string `json:"resource_id,omitempty"`
6464
}
6565

66+
type ImportResponse struct {
67+
ObjectID int64 `json:"object_id,omitempty"`
68+
}
69+
6670
// ExportPath contains the base64 content of the notebook
6771
type ExportPath struct {
6872
Content string `json:"content,omitempty"`
@@ -101,12 +105,14 @@ type NotebooksAPI struct {
101105
var mtx = &sync.Mutex{}
102106

103107
// Create creates a notebook given the content and path
104-
func (a NotebooksAPI) Create(r ImportPath) error {
108+
func (a NotebooksAPI) Create(r ImportPath) (ImportResponse, error) {
105109
if r.Format == "DBC" {
106110
mtx.Lock()
107111
defer mtx.Unlock()
108112
}
109-
return a.client.Post(a.context, "/workspace/import", r, nil)
113+
var response ImportResponse
114+
err := a.client.Post(a.context, "/workspace/import", r, &response)
115+
return response, err
110116
}
111117

112118
// Read returns the notebook metadata and not the contents
@@ -215,31 +221,29 @@ func (a NotebooksAPI) Delete(path string, recursive bool) error {
215221
}, nil)
216222
}
217223

224+
func setComputedProperties(d *schema.ResourceData, c *common.DatabricksClient) {
225+
d.Set("url", c.FormatURL("#workspace", d.Id()))
226+
d.Set("workspace_path", "/Workspace"+d.Id())
227+
}
228+
218229
// ResourceNotebook manages notebooks
219230
func ResourceNotebook() common.Resource {
220231
s := FileContentSchema(map[string]*schema.Schema{
221232
"language": {
222233
Type: schema.TypeString,
223234
Optional: true,
235+
Computed: true, // we need it because it will be filled by the provider or backend
224236
ValidateFunc: validation.StringInSlice([]string{
225237
Scala,
226238
Python,
227239
R,
228240
SQL,
229241
}, false),
230-
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
231-
source := d.Get("source").(string)
232-
if source == "" {
233-
return false
234-
}
235-
ext := strings.ToLower(filepath.Ext(source))
236-
return old == extMap[ext].Language
237-
},
238242
},
239243
"format": {
240244
Type: schema.TypeString,
241245
Optional: true,
242-
Default: "SOURCE",
246+
Computed: true,
243247
ValidateFunc: validation.StringInSlice([]string{
244248
"SOURCE",
245249
"DBC",
@@ -270,6 +274,9 @@ func ResourceNotebook() common.Resource {
270274
return common.Resource{
271275
Schema: s,
272276
SchemaVersion: 1,
277+
CanSkipReadAfterCreateAndUpdate: func(d *schema.ResourceData) bool {
278+
return d.Get("format").(string) == "SOURCE"
279+
},
273280
Create: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
274281
content, err := ReadContent(d)
275282
if err != nil {
@@ -284,6 +291,10 @@ func ResourceNotebook() common.Resource {
284291
Path: path,
285292
Overwrite: true,
286293
}
294+
if createNotebook.Format == "" && createNotebook.Language != "" {
295+
createNotebook.Format = "SOURCE"
296+
d.Set("format", createNotebook.Format)
297+
}
287298
if createNotebook.Language == "" {
288299
// TODO: check what happens with empty source
289300
ext := strings.ToLower(filepath.Ext(d.Get("source").(string)))
@@ -293,8 +304,9 @@ func ResourceNotebook() common.Resource {
293304
createNotebook.Overwrite = extMap[ext].Overwrite
294305
// by default it's SOURCE, but for DBC we have to change it
295306
d.Set("format", createNotebook.Format)
307+
d.Set("language", createNotebook.Language)
296308
}
297-
err = notebooksAPI.Create(createNotebook)
309+
resp, err := notebooksAPI.Create(createNotebook)
298310
if err != nil {
299311
if isParentDoesntExistError(err) {
300312
parent := filepath.ToSlash(filepath.Dir(path))
@@ -303,16 +315,32 @@ func ResourceNotebook() common.Resource {
303315
if err != nil {
304316
return err
305317
}
306-
err = notebooksAPI.Create(createNotebook)
318+
resp, err = notebooksAPI.Create(createNotebook)
307319
}
308320
if err != nil {
309321
return err
310322
}
311323
}
324+
if d.Get("object_type").(string) == "" {
325+
d.Set("object_type", Notebook)
326+
}
327+
d.Set("object_id", resp.ObjectID)
312328
d.SetId(path)
329+
setComputedProperties(d, c)
313330
return nil
314331
},
315332
Read: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
333+
oldFormat := d.Get("format").(string)
334+
if oldFormat == "" {
335+
source := d.Get("source").(string)
336+
// check if `source` is set, and if it is, use file extension to determine format
337+
if source != "" {
338+
ext := strings.ToLower(filepath.Ext(source))
339+
oldFormat = extMap[ext].Format
340+
} else {
341+
oldFormat = "SOURCE"
342+
}
343+
}
316344
w, err := c.WorkspaceClient()
317345
if err != nil {
318346
return err
@@ -323,9 +351,12 @@ func ResourceNotebook() common.Resource {
323351
if err != nil {
324352
return err
325353
}
326-
d.Set("url", c.FormatURL("#workspace", d.Id()))
327-
d.Set("workspace_path", "/Workspace"+objectStatus.Path)
328-
return common.StructToData(objectStatus, s, d)
354+
setComputedProperties(d, c)
355+
err = common.StructToData(objectStatus, s, d)
356+
if err != nil {
357+
return err
358+
}
359+
return d.Set("format", oldFormat)
329360
},
330361
Update: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
331362
notebooksAPI := NewNotebooksAPI(ctx, c)
@@ -334,25 +365,33 @@ func ResourceNotebook() common.Resource {
334365
return err
335366
}
336367
format := d.Get("format").(string)
368+
var resp ImportResponse
337369
if format == "DBC" {
338370
// Overwrite cannot be used for source format when importing a folder
339371
err = notebooksAPI.Delete(d.Id(), true)
340372
if err != nil {
341373
return err
342374
}
343-
return notebooksAPI.Create(ImportPath{
375+
resp, err = notebooksAPI.Create(ImportPath{
344376
Content: base64.StdEncoding.EncodeToString(content),
345377
Format: format,
346378
Path: d.Id(),
347379
})
380+
} else {
381+
resp, err = notebooksAPI.Create(ImportPath{
382+
Content: base64.StdEncoding.EncodeToString(content),
383+
Language: d.Get("language").(string),
384+
Format: format,
385+
Overwrite: true,
386+
Path: d.Id(),
387+
})
348388
}
349-
return notebooksAPI.Create(ImportPath{
350-
Content: base64.StdEncoding.EncodeToString(content),
351-
Language: d.Get("language").(string),
352-
Format: format,
353-
Overwrite: true,
354-
Path: d.Id(),
355-
})
389+
if err != nil {
390+
return err
391+
}
392+
d.Set("object_id", resp.ObjectID)
393+
setComputedProperties(d, c)
394+
return nil
356395
},
357396
Delete: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
358397
objType := d.Get("object_type")

0 commit comments

Comments
 (0)