Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e52006d
workspace_id support of SDKv2 resources using Go SDK
Divyansh-db Mar 18, 2026
456b19f
Merge branch 'main' into divyansh-vijayvergia_data/dwsid-sdkv2-go-sdk
Divyansh-db Mar 19, 2026
a52eab2
fixed test
Divyansh-db Mar 19, 2026
aa55a8a
fixed existing behaviour of integration tests
Divyansh-db Mar 19, 2026
cba7b53
Merge branch 'main' into divyansh-vijayvergia_data/dwsid-sdkv2-go-sdk
Divyansh-db Mar 19, 2026
bdec6fc
Merge branch 'main' into divyansh-vijayvergia_data/dwsid-sdkv2-go-sdk
Divyansh-db Mar 19, 2026
3ce9a13
added support for SDKv2 resources which do not use go sdk
Divyansh-db Mar 20, 2026
85d60da
Merge branch 'main' into divyansh-vijayvergia_data/dwsid-sdkv2-go-sdk
Divyansh-db Mar 20, 2026
d93748a
refactored acceptance tests
Divyansh-db Mar 20, 2026
ee64a9d
fixed tests
Divyansh-db Mar 20, 2026
4ac2e20
Merge branch 'main' into divyansh-vijayvergia_data/dwsid-sdkv2-go-sdk
Divyansh-db Mar 20, 2026
6d4ef97
fixed tests
Divyansh-db Mar 20, 2026
7116c4b
fixed test names
Divyansh-db Mar 22, 2026
3f42734
fixed integration test
Divyansh-db Mar 22, 2026
3f1ac9d
d.set() returns error
Divyansh-db Mar 22, 2026
603bc63
workspace_id resolution from host made lazy
Divyansh-db Mar 22, 2026
e4135ad
made error message more intuitive
Divyansh-db Mar 22, 2026
f86123b
removed host type from a check
Divyansh-db Mar 22, 2026
104c332
reverted to warning
Divyansh-db Mar 22, 2026
ac77de3
fixed tests
Divyansh-db Mar 27, 2026
b4c9f75
use lazy caching and hard error
Divyansh-db Mar 27, 2026
33b2076
Merge branch 'main' into dwsid-sdkv2-go-sdk
Divyansh-db Mar 27, 2026
514cc7c
Merge branch 'main' into dwsid-sdkv2-go-sdk
Divyansh-db Mar 27, 2026
e5043dd
made workspace_id optional+computed
Divyansh-db Mar 27, 2026
e57c5ab
remove stale tests
Divyansh-db Mar 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions catalog/catalog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ func TestAccCatalog_ProviderConfig_Match(t *testing.T) {
`, workspaceIDStr)),
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectResourceAction("databricks_catalog.this", plancheck.ResourceActionUpdate),
plancheck.ExpectResourceAction("databricks_catalog.this", plancheck.ResourceActionNoop),
},
},
})
Expand Down Expand Up @@ -354,7 +354,7 @@ func TestAccCatalog_ProviderConfig_Recreate(t *testing.T) {
`),
ConfigPlanChecks: resource.ConfigPlanChecks{
PostApplyPreRefresh: []plancheck.PlanCheck{
plancheck.ExpectResourceAction("databricks_catalog.this", plancheck.ResourceActionUpdate),
plancheck.ExpectResourceAction("databricks_catalog.this", plancheck.ResourceActionDestroyBeforeCreate),
},
},
PlanOnly: true,
Expand Down Expand Up @@ -382,7 +382,7 @@ func TestAccCatalog_ProviderConfig_Remove(t *testing.T) {
Template: catalogProviderConfigTemplate(catalogName, ""),
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectResourceAction("databricks_catalog.this", plancheck.ResourceActionUpdate),
plancheck.ExpectResourceAction("databricks_catalog.this", plancheck.ResourceActionNoop),
},
},
})
Expand Down
3 changes: 2 additions & 1 deletion catalog/resource_catalog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -844,7 +844,8 @@ func TestCatalogSuppressCaseSensitivity(t *testing.T) {
"storage_location": {Old: "", New: "", NewComputed: true, NewRemoved: false, RequiresNew: false, Sensitive: false},
"provisioning_info.#": {Old: "", New: "", NewComputed: true, NewRemoved: false, RequiresNew: false, Sensitive: false},
"effective_predictive_optimization_flag.#": {Old: "", New: "", NewComputed: true, NewRemoved: false, RequiresNew: false, Sensitive: false},
"full_name": {Old: "", New: "", NewComputed: true, NewRemoved: false, RequiresNew: false, Sensitive: false},
"full_name": {Old: "", New: "", NewComputed: true, NewRemoved: false, RequiresNew: false, Sensitive: false},
"provider_config.#": {Old: "", New: "", NewComputed: true, NewRemoved: false, RequiresNew: false, Sensitive: false},
},
HCL: `
name = "A"
Expand Down
2 changes: 2 additions & 0 deletions catalog/resource_sql_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1883,6 +1883,8 @@ func TestResourceSqlTable_Diff_ExistingResource(t *testing.T) {
if expectedDiff == nil {
expectedDiff = make(map[string]*terraform.ResourceAttrDiff)
}
// provider_config is Optional+Computed, so it always appears as computed in the diff
expectedDiff["provider_config.#"] = &terraform.ResourceAttrDiff{Old: "", New: "", NewComputed: true}
qa.ResourceFixture{
HCL: `
catalog_name = "main"
Expand Down
28 changes: 24 additions & 4 deletions common/client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

74 changes: 73 additions & 1 deletion common/resource.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

114 changes: 106 additions & 8 deletions common/unified_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import (
"context"
"fmt"
"regexp"
"strconv"

"github.com/databricks/databricks-sdk-go"
"github.com/databricks/databricks-sdk-go/client"
"github.com/databricks/databricks-sdk-go/config"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
Expand Down Expand Up @@ -48,6 +50,7 @@ func AddNamespaceInSchema(m map[string]*schema.Schema) map[string]*schema.Schema
m["provider_config"] = &schema.Schema{
Type: schema.TypeList,
Optional: true,
Computed: true,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would current users not using provider_config see any diff when they upgrade their terraform version during terraform plan?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also should check make diff-schema since we are making schema changes

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would current users not using provider_config see any diff when they upgrade their terraform version during terraform plan?

It does not show in plan as it is suppressed by our customised diff for this field.

make diff-schema return "no changes to schema"

MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
Expand All @@ -64,6 +67,7 @@ func AddNamespaceInSchema(m map[string]*schema.Schema) map[string]*schema.Schema
// NamespaceCustomizeSchema is used to customize the schema for the provider configuration
// for a single schema.
func NamespaceCustomizeSchema(s *CustomizableSchema) {
s.SchemaPath("provider_config").SetComputed()
s.SchemaPath("provider_config", "workspace_id").SetValidateFunc(workspaceIDValidateFunc())
}

Expand All @@ -74,6 +78,7 @@ func NamespaceCustomizeSchemaMap(m map[string]*schema.Schema) map[string]*schema
if !ok {
panic("provider_config not found in schema")
}
providerConfig.Computed = true
elem, ok := providerConfig.Elem.(*schema.Resource)
if !ok {
panic("provider_config.Elem is not a *schema.Resource")
Expand All @@ -84,11 +89,63 @@ func NamespaceCustomizeSchemaMap(m map[string]*schema.Schema) map[string]*schema
return m
}

// namespaceForceNew marks the workspace_id field as ForceNew if it changed.
func namespaceForceNew(d *schema.ResourceDiff) error {
oldWorkspaceID, newWorkspaceID := d.GetChange(workspaceIDSchemaKey)
if oldWorkspaceID != "" && newWorkspaceID != "" && oldWorkspaceID != newWorkspaceID {
if err := d.ForceNew(workspaceIDSchemaKey); err != nil {
// namespaceForceNew is used to customize the diff for the provider configuration
// in a resource diff. It resolves effective workspace IDs (accounting for
// workspace_id fallback) and triggers ForceNew when the effective
// workspace changes.
//
// With provider_config marked as Optional+Computed, Terraform preserves the state
// value when the config doesn't specify provider_config. This means d.GetChange()
// shows no change in that case. We use GetRawConfigAt to inspect the actual user
// config and determine the true effective new workspace ID.
func namespaceForceNew(ctx context.Context, d *schema.ResourceDiff, c *DatabricksClient) error {
workspaceIDKey := workspaceIDSchemaKey

// Get the old (state) workspace ID.
oldWsID, _ := d.GetChange(workspaceIDKey)
oldEffective, _ := oldWsID.(string)

// Determine the new effective workspace ID by inspecting the raw config.
// With Optional+Computed, d.GetChange may return the preserved state value
// rather than reflecting the actual config, so we check the raw config directly.
configWsID, configHasProviderConfig := workspaceIDFromRawDiffConfig(d)

var newEffective string
if configHasProviderConfig {
// Config explicitly sets provider_config.workspace_id
newEffective = configWsID
} else {
// Config does not have provider_config; use workspace_id
newEffective = c.Config.WorkspaceID
}
// Fallback to cachedWorkspaceID (resolved from provider host during init).
// This handles workspace host changes: if the user switches from
// host=ws-A to host=ws-B, the cached ID reflects the new workspace.
if newEffective == "" && c.cachedWorkspaceID != 0 {
newEffective = strconv.FormatInt(c.cachedWorkspaceID, 10)
}

// If a resource has a workspace ID in state but the new effective
// workspace ID is empty (user removed workspace_id and there's no
// explicit provider_config), error out. This prevents silently continuing
// to operate against a workspace the user thought they disconnected from.
if oldEffective != "" && newEffective == "" && c.Config.HostType() != config.WorkspaceHost {
return fmt.Errorf("resource has provider_config.workspace_id = %q in state, "+
"but managing workspace-level resources requires a workspace_id and "+
"none was found in provider_config or the provider configuration", oldEffective)
}

if oldEffective != "" && newEffective != "" && oldEffective != newEffective {
// When Optional+Computed preserves the old state value (config removes
// provider_config), the SDK sees no diff for workspace_id. ForceNew
// requires HasChange, so we SetNew on the top-level provider_config key
// to create the diff entry before calling ForceNew.
if !d.HasChange(workspaceIDKey) {
if err := d.SetNew("provider_config", []map[string]interface{}{{"workspace_id": newEffective}}); err != nil {
return err
}
}
if err := d.ForceNew(workspaceIDKey); err != nil {
return err
}
}
Expand Down Expand Up @@ -127,10 +184,29 @@ func NamespaceValidateWorkspaceID(ctx context.Context, d *schema.ResourceDiff, c
return nil
}

// workspaceIDFromRawDiffConfig extracts the workspace ID from the raw config
// of a ResourceDiff. Returns the workspace ID string and whether provider_config
// is present in the config.
func workspaceIDFromRawDiffConfig(d *schema.ResourceDiff) (string, bool) {
path := cty.Path{
cty.GetAttrStep{Name: "provider_config"},
cty.IndexStep{Key: cty.NumberIntVal(0)},
cty.GetAttrStep{Name: "workspace_id"},
}
rawValue, diags := d.GetRawConfigAt(path)
if diags.HasError() || rawValue.IsNull() || !rawValue.IsKnown() {
return "", false
}
if rawValue.Type() == cty.String {
return rawValue.AsString(), true
}
return "", false
}

// NamespaceCustomizeDiff is used to customize the diff for the provider configuration
// in a resource diff.
func NamespaceCustomizeDiff(ctx context.Context, d *schema.ResourceDiff, c *DatabricksClient) error {
if err := namespaceForceNew(d); err != nil {
if err := namespaceForceNew(ctx, d, c); err != nil {
return err
}
return NamespaceValidateWorkspaceID(ctx, d, c)
Expand Down Expand Up @@ -168,9 +244,15 @@ func (c *DatabricksClient) DatabricksClientForUnifiedProvider(ctx context.Contex
if !ok {
return nil, fmt.Errorf("workspace_id must be a string")
}
// If the workspace_id is not passed in the resource configuration, we don't need to create a new client
// and can return the current client.
// If the workspace_id is not passed in the resource configuration,
// fall back to workspace_id for account-level providers.
// We don't error here when no workspace_id is found because the caller may
// use AccountOrWorkspaceRequest to route to account APIs, which doesn't need
// a workspace-scoped client.
if workspaceID == "" {
if c.DatabricksClient != nil && c.Config.HostType() == config.AccountHost && c.Config.WorkspaceID != "" {
return c.getDatabricksClientForUnifiedProvider(ctx, c.Config.WorkspaceID)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this? If workspaceID is not passed through provider_config or in the c.Config then we can just return the client, is this something that should be moved after this check?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is necessary to see if workspace_id is defined at the provider level. Suppose a user upgrades TF, shifts to Account/Unified host with workspace_id at provider level, for the first plan/apply, they should get a databricks client resolved from the workspace_id. But if this check does not exist, then it will simply return c.

Copy link
Copy Markdown
Contributor Author

@Divyansh-db Divyansh-db Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current migration (Suppose a user upgrades TF, shifts to Account/Unified host with workspace_id at resource level - provider_config, for the first plan/apply) using provider_config might have issues as it will aslo return 'c' instead of resolving from workspace_id defined in provider_config

return c, nil
}
return c.getDatabricksClientForUnifiedProvider(ctx, workspaceID)
Expand Down Expand Up @@ -209,6 +291,22 @@ func (c *DatabricksClient) getDatabricksClientForUnifiedProvider(ctx context.Con
}, nil
}

func workspaceIDFromRawConfig(d *schema.ResourceData) (string, bool) {
path := cty.Path{
cty.GetAttrStep{Name: "provider_config"},
cty.IndexStep{Key: cty.NumberIntVal(0)},
cty.GetAttrStep{Name: "workspace_id"},
}
rawValue, diags := d.GetRawConfigAt(path)
if diags.HasError() || rawValue.IsNull() || !rawValue.IsKnown() {
return "", false
}
if rawValue.Type() == cty.String {
return rawValue.AsString(), true
}
return "", false
}

// setCachedDatabricksClient sets the cached Databricks Client.
func (c *DatabricksClient) setCachedDatabricksClient(ctx context.Context, workspaceID string) error {
// Acquire the lock to avoid race conditions.
Expand Down
Loading
Loading