Skip to content

Commit c5aa302

Browse files
authored
Exporter: add support for generation of dependencies between resources (#3377)
* Exporter: add support for generation of dependencies between resources Now we can correct dependencies between some UC objects and their parent's grants in case if the owner of these objects is different than the current user and grants should be adjusted to provide a possibility to create sub-resources. This fixes #3057 * Add code coverage
1 parent 04ed892 commit c5aa302

File tree

6 files changed

+167
-42
lines changed

6 files changed

+167
-42
lines changed

exporter/context.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1672,6 +1672,57 @@ func (ic *importContext) dataToHcl(i importable, path []string,
16721672
return fmt.Errorf("unsupported schema type: %v", path)
16731673
}
16741674
}
1675+
// Generate `depends_on` only for top-level resource because `dataToHcl` is called recursively
1676+
if len(path) == 0 && len(res.DependsOn) > 0 {
1677+
notIgnoredResources := []*resource{}
1678+
for _, dr := range res.DependsOn {
1679+
dr := dr
1680+
if dr.Data == nil {
1681+
tdr := ic.Scope.FindById(dr.Resource, dr.ID)
1682+
if tdr == nil {
1683+
log.Printf("[WARN] can't find resource %s in scope", dr)
1684+
continue
1685+
}
1686+
dr = tdr
1687+
}
1688+
if ic.Importables[dr.Resource].Ignore == nil || !ic.Importables[dr.Resource].Ignore(ic, dr) {
1689+
found := false
1690+
for _, v := range notIgnoredResources {
1691+
if v.ID == dr.ID && v.Resource == dr.Resource {
1692+
found = true
1693+
break
1694+
}
1695+
}
1696+
if !found {
1697+
notIgnoredResources = append(notIgnoredResources, dr)
1698+
}
1699+
}
1700+
}
1701+
if len(notIgnoredResources) > 0 {
1702+
toks := hclwrite.Tokens{}
1703+
toks = append(toks, &hclwrite.Token{
1704+
Type: hclsyntax.TokenOBrack,
1705+
Bytes: []byte{'['},
1706+
})
1707+
for i, dr := range notIgnoredResources {
1708+
if i > 0 {
1709+
toks = append(toks, &hclwrite.Token{
1710+
Type: hclsyntax.TokenComma,
1711+
Bytes: []byte{','},
1712+
})
1713+
}
1714+
toks = append(toks, hclwrite.TokensForTraversal(hcl.Traversal{
1715+
hcl.TraverseRoot{Name: dr.Resource},
1716+
hcl.TraverseAttr{Name: ic.ResourceName(dr)},
1717+
})...)
1718+
}
1719+
toks = append(toks, &hclwrite.Token{
1720+
Type: hclsyntax.TokenCBrack,
1721+
Bytes: []byte{']'},
1722+
})
1723+
body.SetAttributeRaw("depends_on", toks)
1724+
}
1725+
}
16751726
return nil
16761727
}
16771728

exporter/context_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import (
1010
"github.com/databricks/terraform-provider-databricks/provider"
1111
"github.com/databricks/terraform-provider-databricks/qa"
1212
"github.com/databricks/terraform-provider-databricks/workspace"
13+
"github.com/hashicorp/hcl/v2/hclwrite"
1314
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
15+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
1416
"github.com/stretchr/testify/assert"
1517
"github.com/stretchr/testify/require"
1618
)
@@ -452,3 +454,45 @@ func TestExtractResourceIdFromImportBlockString(t *testing.T) {
452454
}`)
453455
assert.Equal(t, "", id)
454456
}
457+
458+
func TestGenerateDependsOn(t *testing.T) {
459+
ic := importContextForTest()
460+
ic.incremental = true
461+
462+
f := hclwrite.NewEmptyFile()
463+
body := f.Body()
464+
dr := &resource{
465+
Resource: "databricks_catalog",
466+
ID: "test",
467+
Data: ic.Resources["databricks_catalog"].Data(
468+
&terraform.InstanceState{
469+
ID: "test",
470+
Attributes: map[string]string{
471+
"name": "test",
472+
},
473+
}),
474+
}
475+
ic.Scope.Append(dr)
476+
r := &resource{
477+
Resource: "databricks_schema",
478+
Name: "test.schema",
479+
Data: ic.Resources["databricks_schema"].Data(
480+
&terraform.InstanceState{
481+
ID: "test",
482+
Attributes: map[string]string{
483+
"catalog_name": "test",
484+
"name": "schema",
485+
},
486+
}),
487+
DependsOn: []*resource{dr,
488+
{Resource: dr.Resource, ID: dr.ID},
489+
{Resource: dr.Resource, ID: "unknown"},
490+
},
491+
}
492+
r.AddDependsOn(&resource{Resource: dr.Resource, ID: "test2", Data: dr.Data})
493+
resourceBlock := body.AppendNewBlock("resource", []string{r.Resource, r.Name})
494+
err := ic.dataToHcl(ic.Importables[r.Resource], []string{}, ic.Resources[r.Resource], r, resourceBlock.Body())
495+
require.NoError(t, err)
496+
formatted := hclwrite.Format(f.Bytes())
497+
assert.Contains(t, string(formatted), "depends_on = [databricks_catalog.test, databricks_catalog.test2]")
498+
}

exporter/importables.go

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2390,7 +2390,11 @@ var resourcesMap map[string]importable = map[string]importable{
23902390
common.DataToStructPointer(r.Data, s, &cat)
23912391

23922392
// Emit: UC Connection, List schemas, Catalog grants, ...
2393-
ic.emitUCGrantsWithOwner("catalog/"+cat.Name, r)
2393+
owner, catalogGrantsResource := ic.emitUCGrantsWithOwner("catalog/"+cat.Name, r)
2394+
dependsOn := []*resource{}
2395+
if owner != "" && owner != ic.meUserName {
2396+
dependsOn = append(dependsOn, catalogGrantsResource)
2397+
}
23942398
// TODO: emit owner? Should we do this? Because it's a account-level identity... Create a separate function for that...
23952399
if cat.ConnectionName != "" {
23962400
ic.Emit(&resource{
@@ -2409,8 +2413,9 @@ var resourcesMap map[string]importable = map[string]importable{
24092413
continue
24102414
}
24112415
ic.EmitIfUpdatedAfterMillis(&resource{
2412-
Resource: "databricks_schema",
2413-
ID: schema.FullName,
2416+
Resource: "databricks_schema",
2417+
ID: schema.FullName,
2418+
DependsOn: dependsOn,
24142419
}, schema.UpdatedAt, fmt.Sprintf("schema '%s'", schema.FullName))
24152420
}
24162421
}
@@ -2470,14 +2475,20 @@ var resourcesMap map[string]importable = map[string]importable{
24702475
schemaFullName := r.ID
24712476
catalogName := r.Data.Get("catalog_name").(string)
24722477
schemaName := r.Data.Get("name").(string)
2473-
ic.emitUCGrantsWithOwner("schema/"+schemaFullName, r)
2478+
owner, schemaGrantResource := ic.emitUCGrantsWithOwner("schema/"+schemaFullName, r)
2479+
dependsOn := []*resource{}
2480+
if owner != "" && owner != ic.meUserName {
2481+
dependsOn = append(dependsOn, schemaGrantResource)
2482+
}
2483+
// TODO: think if we need to emit upstream dependencies in case if we're going bottom-up
24742484
ic.Emit(&resource{
24752485
Resource: "databricks_catalog",
24762486
ID: catalogName,
24772487
})
2488+
// r.AddDependsOn(&resource{Resource: "databricks_grants", ID: "catalog/" + catalogName})
2489+
2490+
// TODO: somehow add depends on catalog's grant...
24782491
// TODO: emit owner? See comment in catalog resource
2479-
// TODO: list tables
2480-
// list registered models
24812492
models, err := ic.workspaceClient.RegisteredModels.ListAll(ic.Context,
24822493
catalog.ListRegisteredModelsRequest{
24832494
CatalogName: catalogName,
@@ -2488,8 +2499,9 @@ var resourcesMap map[string]importable = map[string]importable{
24882499
}
24892500
for _, model := range models {
24902501
ic.EmitIfUpdatedAfterMillis(&resource{
2491-
Resource: "databricks_registered_model",
2492-
ID: model.FullName,
2502+
Resource: "databricks_registered_model",
2503+
ID: model.FullName,
2504+
DependsOn: dependsOn,
24932505
}, model.UpdatedAt, fmt.Sprintf("registered model '%s'", model.FullName))
24942506
}
24952507
// list volumes
@@ -2503,11 +2515,12 @@ var resourcesMap map[string]importable = map[string]importable{
25032515
}
25042516
for _, volume := range volumes {
25052517
ic.EmitIfUpdatedAfterMillis(&resource{
2506-
Resource: "databricks_volume",
2507-
ID: volume.FullName,
2518+
Resource: "databricks_volume",
2519+
ID: volume.FullName,
2520+
DependsOn: dependsOn,
25082521
}, volume.UpdatedAt, fmt.Sprintf("volume '%s'", volume.FullName))
25092522
}
2510-
2523+
// list tables
25112524
tables, err := ic.workspaceClient.Tables.ListAll(ic.Context, catalog.ListTablesRequest{
25122525
CatalogName: catalogName,
25132526
SchemaName: schemaName,
@@ -2519,13 +2532,17 @@ var resourcesMap map[string]importable = map[string]importable{
25192532
switch table.TableType {
25202533
case "MANAGED", "EXTERNAL", "VIEW":
25212534
ic.EmitIfUpdatedAfterMillis(&resource{
2522-
Resource: "databricks_sql_table",
2523-
ID: table.FullName,
2535+
Resource: "databricks_sql_table",
2536+
ID: table.FullName,
2537+
DependsOn: dependsOn,
25242538
}, table.UpdatedAt, fmt.Sprintf("table '%s'", table.FullName))
25252539
default:
25262540
log.Printf("[DEBUG] Skipping table %s of type %s", table.FullName, table.TableType)
25272541
}
25282542
}
2543+
// TODO: list VectorSearch indexes
2544+
2545+
// TODO: list online tables
25292546

25302547
return nil
25312548
},
@@ -2543,15 +2560,12 @@ var resourcesMap map[string]importable = map[string]importable{
25432560
volumeFullName := r.ID
25442561
ic.emitUCGrantsWithOwner("volume/"+volumeFullName, r)
25452562

2546-
catalogName := r.Data.Get("catalog_name").(string)
2563+
schemaFullName := r.Data.Get("catalog_name").(string) + "." + r.Data.Get("schema_name").(string)
25472564
ic.Emit(&resource{
25482565
Resource: "databricks_schema",
2549-
ID: catalogName + "." + r.Data.Get("schema_name").(string),
2550-
})
2551-
ic.Emit(&resource{
2552-
Resource: "databricks_catalog",
2553-
ID: catalogName,
2566+
ID: schemaFullName,
25542567
})
2568+
// r.AddDependsOn(&resource{Resource: "databricks_grants", ID: "schema/" + schemaFullName})
25552569
// TODO: emit owner? See comment in catalog resource
25562570
return nil
25572571
},
@@ -2576,15 +2590,12 @@ var resourcesMap map[string]importable = map[string]importable{
25762590
Import: func(ic *importContext, r *resource) error {
25772591
tableFullName := r.ID
25782592
ic.emitUCGrantsWithOwner("table/"+tableFullName, r)
2579-
catalogName := r.Data.Get("catalog_name").(string)
2593+
schemaFullName := r.Data.Get("catalog_name").(string) + "." + r.Data.Get("schema_name").(string)
25802594
ic.Emit(&resource{
25812595
Resource: "databricks_schema",
2582-
ID: catalogName + "." + r.Data.Get("schema_name").(string),
2583-
})
2584-
ic.Emit(&resource{
2585-
Resource: "databricks_catalog",
2586-
ID: catalogName,
2596+
ID: schemaFullName,
25872597
})
2598+
// r.AddDependsOn(&resource{Resource: "databricks_grants", ID: "schema/" + schemaFullName})
25882599
// TODO: emit owner? See comment in catalog resource
25892600
return nil
25902601
},
@@ -2702,10 +2713,12 @@ var resourcesMap map[string]importable = map[string]importable{
27022713
Service: "uc-external-locations",
27032714
Import: func(ic *importContext, r *resource) error {
27042715
ic.emitUCGrantsWithOwner("external_location/"+r.ID, r)
2716+
credentialName := r.Data.Get("credential_name").(string)
27052717
ic.Emit(&resource{
27062718
Resource: "databricks_storage_credential",
2707-
ID: r.Data.Get("credential_name").(string),
2719+
ID: credentialName,
27082720
})
2721+
// r.AddDependsOn(&resource{Resource: "databricks_grants", ID: "storage_credential/" + credentialName})
27092722
return nil
27102723
},
27112724
List: func(ic *importContext) error {
@@ -2862,15 +2875,12 @@ var resourcesMap map[string]importable = map[string]importable{
28622875
Import: func(ic *importContext, r *resource) error {
28632876
modelFullName := r.ID
28642877
ic.emitUCGrantsWithOwner("model/"+modelFullName, r)
2865-
catalogName := r.Data.Get("catalog_name").(string)
2878+
schemaFullName := r.Data.Get("catalog_name").(string) + "." + r.Data.Get("schema_name").(string)
28662879
ic.Emit(&resource{
28672880
Resource: "databricks_schema",
2868-
ID: catalogName + "." + r.Data.Get("catalog_name").(string),
2869-
})
2870-
ic.Emit(&resource{
2871-
Resource: "databricks_catalog",
2872-
ID: catalogName,
2881+
ID: schemaFullName,
28732882
})
2883+
// r.AddDependsOn(&resource{Resource: "databricks_grants", ID: "schema/" + schemaFullName})
28742884
// TODO: emit owner? See comment in catalog resource
28752885
return nil
28762886
},
@@ -2985,6 +2995,7 @@ var resourcesMap map[string]importable = map[string]importable{
29852995
Resource: "databricks_volume",
29862996
ID: volumeId,
29872997
})
2998+
// r.AddDependsOn(&resource{Resource: "databricks_grants", ID: "volume/" + volumeId})
29882999
}
29893000

29903001
// download & store file

exporter/importables_test.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1974,10 +1974,9 @@ func TestVolumes(t *testing.T) {
19741974
Data: d,
19751975
})
19761976
assert.NoError(t, err)
1977-
require.Equal(t, 3, len(ic.testEmits))
1977+
require.Equal(t, 2, len(ic.testEmits))
19781978
assert.True(t, ic.testEmits["databricks_grants[<unknown>] (id: volume/vtest)"])
19791979
assert.True(t, ic.testEmits["databricks_schema[<unknown>] (id: ctest.stest)"])
1980-
assert.True(t, ic.testEmits["databricks_catalog[<unknown>] (id: ctest)"])
19811980

19821981
//
19831982
shouldOmitFunc := resourcesMap["databricks_volume"].ShouldOmitField
@@ -2004,10 +2003,9 @@ func TestSqlTables(t *testing.T) {
20042003
Data: d,
20052004
})
20062005
assert.NoError(t, err)
2007-
require.Equal(t, 3, len(ic.testEmits))
2006+
require.Equal(t, 2, len(ic.testEmits))
20082007
assert.True(t, ic.testEmits["databricks_grants[<unknown>] (id: table/ttest)"])
20092008
assert.True(t, ic.testEmits["databricks_schema[<unknown>] (id: ctest.stest)"])
2010-
assert.True(t, ic.testEmits["databricks_catalog[<unknown>] (id: ctest)"])
20112009

20122010
//
20132011
shouldOmitFunc := resourcesMap["databricks_sql_table"].ShouldOmitField
@@ -2035,10 +2033,9 @@ func TestRegisteredModels(t *testing.T) {
20352033
Data: d,
20362034
})
20372035
assert.NoError(t, err)
2038-
require.Equal(t, 3, len(ic.testEmits))
2036+
require.Equal(t, 2, len(ic.testEmits))
20392037
assert.True(t, ic.testEmits["databricks_grants[<unknown>] (id: model/mtest)"])
2040-
assert.True(t, ic.testEmits["databricks_schema[<unknown>] (id: ctest.ctest)"])
2041-
assert.True(t, ic.testEmits["databricks_catalog[<unknown>] (id: ctest)"])
2038+
assert.True(t, ic.testEmits["databricks_schema[<unknown>] (id: ctest.stest)"])
20422039

20432040
//
20442041
shouldOmitFunc := resourcesMap["databricks_registered_model"].ShouldOmitField

exporter/model.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@ type resource struct {
238238
Data *schema.ResourceData
239239
// Arbitrary data to be used by importable
240240
ExtraData map[string]any
241+
// References to dependencies - it could be fully resolved resource, with Data, etc., or it could be just resource type + ID
242+
DependsOn []*resource
241243
}
242244

243245
func (r *resource) AddExtraData(key string, value any) {
@@ -247,6 +249,10 @@ func (r *resource) AddExtraData(key string, value any) {
247249
r.ExtraData[key] = value
248250
}
249251

252+
func (r *resource) AddDependsOn(dep *resource) {
253+
r.DependsOn = append(r.DependsOn, dep)
254+
}
255+
250256
func (r *resource) GetExtraData(key string) (any, bool) {
251257
if r.ExtraData == nil {
252258
return nil, false
@@ -397,3 +403,15 @@ func (a *importedResources) Sorted() []*resource {
397403
sort.Sort(c)
398404
return c
399405
}
406+
407+
func (a *importedResources) FindById(resourceType, id string) *resource {
408+
defer a.mutex.RLocker().Unlock()
409+
a.mutex.RLocker().Lock()
410+
for _, r := range a.resources {
411+
if r.Resource == resourceType && r.ID == id {
412+
return r
413+
}
414+
}
415+
416+
return nil
417+
}

exporter/util.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,17 +1338,21 @@ func generateIgnoreObjectWithoutName(resourceType string) func(ic *importContext
13381338
}
13391339
}
13401340

1341-
func (ic *importContext) emitUCGrantsWithOwner(id string, parentResource *resource) {
1341+
func (ic *importContext) emitUCGrantsWithOwner(id string, parentResource *resource) (string, *resource) {
13421342
gr := &resource{
13431343
Resource: "databricks_grants",
13441344
ID: id,
13451345
}
1346+
var owner string
13461347
if parentResource.Data != nil {
1347-
if owner, ok := parentResource.Data.GetOk("owner"); ok {
1348-
gr.AddExtraData("owner", owner)
1348+
ownerRaw, ok := parentResource.Data.GetOk("owner")
1349+
if ok {
1350+
gr.AddExtraData("owner", ownerRaw)
1351+
owner = ownerRaw.(string)
13491352
}
13501353
}
13511354
ic.Emit(gr)
1355+
return owner, gr
13521356
}
13531357

13541358
func (ic *importContext) addTfVar(name, value string) {

0 commit comments

Comments
 (0)