diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 53855f39cd..6c694624c4 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -6,6 +6,8 @@ ### New Features and Improvements +* Add `provider_config` support for manual plugin framework resources and data sources([#5127](https://github.com/databricks/terraform-provider-databricks/pull/5127)) + ### Bug Fixes ### Documentation diff --git a/internal/providers/pluginfw/products/app/data_app.go b/internal/providers/pluginfw/products/app/data_app.go index 2d36537fa0..28bf2cc05b 100644 --- a/internal/providers/pluginfw/products/app/data_app.go +++ b/internal/providers/pluginfw/products/app/data_app.go @@ -11,7 +11,6 @@ import ( "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/tfschema" "github.com/databricks/terraform-provider-databricks/internal/service/apps_tf" "github.com/hashicorp/terraform-plugin-framework/datasource" - "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -26,18 +25,20 @@ type dataSourceApp struct { type dataApp struct { Name types.String `tfsdk:"name"` App types.Object `tfsdk:"app"` + tfschema.Namespace } func (dataApp) ApplySchemaCustomizations(attrs map[string]tfschema.AttributeBuilder) map[string]tfschema.AttributeBuilder { attrs["name"] = attrs["name"].SetRequired() attrs["app"] = attrs["app"].SetComputed() - + attrs["provider_config"] = attrs["provider_config"].SetOptional() return attrs } func (dataApp) GetComplexFieldTypes(context.Context) map[string]reflect.Type { return map[string]reflect.Type{ - "app": reflect.TypeOf(apps_tf.App{}), + "app": reflect.TypeOf(apps_tf.App{}), + "provider_config": reflect.TypeOf(tfschema.ProviderConfigData{}), } } @@ -59,19 +60,26 @@ func (a *dataSourceApp) Configure(ctx context.Context, req datasource.ConfigureR func (a *dataSourceApp) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { ctx = pluginfwcontext.SetUserAgentInDataSourceContext(ctx, resourceName) - w, diags := a.client.GetWorkspaceClient() + + var config dataApp + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + workspaceID, diags := tfschema.GetWorkspaceIDDataSource(ctx, config.ProviderConfig) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - var name types.String - resp.Diagnostics.Append(req.Config.GetAttribute(ctx, path.Root("name"), &name)...) + w, diags := a.client.GetWorkspaceClientForUnifiedProviderWithDiagnostics(ctx, workspaceID) + resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - appGoSdk, err := w.Apps.GetByName(ctx, name.ValueString()) + appGoSdk, err := w.Apps.GetByName(ctx, config.Name.ValueString()) if err != nil { resp.Diagnostics.AddError("failed to read app", err.Error()) return @@ -82,7 +90,13 @@ func (a *dataSourceApp) Read(ctx context.Context, req datasource.ReadRequest, re if resp.Diagnostics.HasError() { return } - dataApp := dataApp{Name: name, App: newApp.ToObjectValue(ctx)} + dataApp := dataApp{ + Name: config.Name, + App: newApp.ToObjectValue(ctx), + Namespace: tfschema.Namespace{ + ProviderConfig: config.ProviderConfig, + }, + } resp.Diagnostics.Append(resp.State.Set(ctx, dataApp)...) if resp.Diagnostics.HasError() { return diff --git a/internal/providers/pluginfw/products/app/data_apps.go b/internal/providers/pluginfw/products/app/data_apps.go index 19ebeeb6ed..07210b013f 100644 --- a/internal/providers/pluginfw/products/app/data_apps.go +++ b/internal/providers/pluginfw/products/app/data_apps.go @@ -26,17 +26,19 @@ type dataSourceApps struct { type dataApps struct { Apps types.List `tfsdk:"app"` + tfschema.Namespace } func (dataApps) ApplySchemaCustomizations(attrs map[string]tfschema.AttributeBuilder) map[string]tfschema.AttributeBuilder { attrs["app"] = attrs["app"].SetComputed() - + attrs["provider_config"] = attrs["provider_config"].SetOptional() return attrs } func (dataApps) GetComplexFieldTypes(context.Context) map[string]reflect.Type { return map[string]reflect.Type{ - "app": reflect.TypeOf(apps_tf.App{}), + "app": reflect.TypeOf(apps_tf.App{}), + "provider_config": reflect.TypeOf(tfschema.ProviderConfigData{}), } } @@ -58,7 +60,20 @@ func (a *dataSourceApps) Configure(ctx context.Context, req datasource.Configure func (a *dataSourceApps) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { ctx = pluginfwcontext.SetUserAgentInDataSourceContext(ctx, resourceName) - w, diags := a.client.GetWorkspaceClient() + + var config dataApps + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + workspaceID, diags := tfschema.GetWorkspaceIDDataSource(ctx, config.ProviderConfig) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + w, diags := a.client.GetWorkspaceClientForUnifiedProviderWithDiagnostics(ctx, workspaceID) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return @@ -79,7 +94,12 @@ func (a *dataSourceApps) Read(ctx context.Context, req datasource.ReadRequest, r } apps = append(apps, app.ToObjectValue(ctx)) } - dataApps := dataApps{Apps: types.ListValueMust(apps_tf.App{}.Type(ctx), apps)} + dataApps := dataApps{ + Apps: types.ListValueMust(apps_tf.App{}.Type(ctx), apps), + Namespace: tfschema.Namespace{ + ProviderConfig: config.ProviderConfig, + }, + } resp.Diagnostics.Append(resp.State.Set(ctx, dataApps)...) if resp.Diagnostics.HasError() { return diff --git a/internal/providers/pluginfw/products/app/resource_app.go b/internal/providers/pluginfw/products/app/resource_app.go index d3fe6c3377..113f5e478b 100644 --- a/internal/providers/pluginfw/products/app/resource_app.go +++ b/internal/providers/pluginfw/products/app/resource_app.go @@ -32,15 +32,23 @@ const ( type appResource struct { apps_tf.App NoCompute types.Bool `tfsdk:"no_compute"` + tfschema.Namespace } func (a appResource) ApplySchemaCustomizations(s map[string]tfschema.AttributeBuilder) map[string]tfschema.AttributeBuilder { s["no_compute"] = s["no_compute"].SetOptional() + s["provider_config"] = s["provider_config"].SetOptional() s["compute_size"] = s["compute_size"].SetComputed() s = apps_tf.App{}.ApplySchemaCustomizations(s) return s } +func (a appResource) GetComplexFieldTypes(ctx context.Context) map[string]reflect.Type { + attrs := a.App.GetComplexFieldTypes(ctx) + attrs["provider_config"] = reflect.TypeOf(tfschema.ProviderConfig{}) + return attrs +} + func ResourceApp() resource.Resource { return &resourceApp{} } @@ -94,17 +102,25 @@ func (a *resourceApp) Configure(ctx context.Context, req resource.ConfigureReque func (a *resourceApp) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { ctx = pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) - w, diags := a.client.GetWorkspaceClient() + + var app appResource + resp.Diagnostics.Append(req.Plan.Get(ctx, &app)...) + if resp.Diagnostics.HasError() { + return + } + + workspaceID, diags := tfschema.GetWorkspaceIDResource(ctx, app.ProviderConfig) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - var app appResource - resp.Diagnostics.Append(req.Plan.Get(ctx, &app)...) + w, diags := a.client.GetWorkspaceClientForUnifiedProviderWithDiagnostics(ctx, workspaceID) + resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } + var appGoSdk apps.App resp.Diagnostics.Append(converters.TfSdkToGoSdkStruct(ctx, app, &appGoSdk)...) if resp.Diagnostics.HasError() { @@ -133,6 +149,7 @@ func (a *resourceApp) Create(ctx context.Context, req resource.CreateRequest, re return } newApp.NoCompute = app.NoCompute + newApp.ProviderConfig = app.ProviderConfig resp.Diagnostics.Append(resp.State.Set(ctx, newApp)...) if resp.Diagnostics.HasError() { return @@ -150,6 +167,7 @@ func (a *resourceApp) Create(ctx context.Context, req resource.CreateRequest, re if resp.Diagnostics.HasError() { return } + newApp.ProviderConfig = app.ProviderConfig resp.Diagnostics.Append(resp.State.Set(ctx, newApp)...) if resp.Diagnostics.HasError() { return @@ -195,14 +213,21 @@ func (a *resourceApp) waitForApp(ctx context.Context, w *databricks.WorkspaceCli func (a *resourceApp) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { ctx = pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) - w, diags := a.client.GetWorkspaceClient() + + var app appResource + resp.Diagnostics.Append(req.State.Get(ctx, &app)...) + if resp.Diagnostics.HasError() { + return + } + + workspaceID, diags := tfschema.GetWorkspaceIDResource(ctx, app.ProviderConfig) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - var app appResource - resp.Diagnostics.Append(req.State.Get(ctx, &app)...) + w, diags := a.client.GetWorkspaceClientForUnifiedProviderWithDiagnostics(ctx, workspaceID) + resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } @@ -219,6 +244,7 @@ func (a *resourceApp) Read(ctx context.Context, req resource.ReadRequest, resp * return } newApp.NoCompute = app.NoCompute + newApp.ProviderConfig = app.ProviderConfig resp.Diagnostics.Append(resp.State.Set(ctx, newApp)...) if resp.Diagnostics.HasError() { return @@ -227,14 +253,21 @@ func (a *resourceApp) Read(ctx context.Context, req resource.ReadRequest, resp * func (a *resourceApp) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { ctx = pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) - w, diags := a.client.GetWorkspaceClient() + + var app appResource + resp.Diagnostics.Append(req.Plan.Get(ctx, &app)...) + if resp.Diagnostics.HasError() { + return + } + + workspaceID, diags := tfschema.GetWorkspaceIDResource(ctx, app.ProviderConfig) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - var app appResource - resp.Diagnostics.Append(req.Plan.Get(ctx, &app)...) + w, diags := a.client.GetWorkspaceClientForUnifiedProviderWithDiagnostics(ctx, workspaceID) + resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } @@ -259,6 +292,7 @@ func (a *resourceApp) Update(ctx context.Context, req resource.UpdateRequest, re } // Modifying no_compute after creation has no effect. newApp.NoCompute = app.NoCompute + newApp.ProviderConfig = app.ProviderConfig resp.Diagnostics.Append(resp.State.Set(ctx, newApp)...) if resp.Diagnostics.HasError() { return @@ -267,14 +301,21 @@ func (a *resourceApp) Update(ctx context.Context, req resource.UpdateRequest, re func (a *resourceApp) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { ctx = pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) - w, diags := a.client.GetWorkspaceClient() + + var app appResource + resp.Diagnostics.Append(req.State.Get(ctx, &app)...) + if resp.Diagnostics.HasError() { + return + } + + workspaceID, diags := tfschema.GetWorkspaceIDResource(ctx, app.ProviderConfig) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - var app appResource - resp.Diagnostics.Append(req.State.Get(ctx, &app)...) + w, diags := a.client.GetWorkspaceClientForUnifiedProviderWithDiagnostics(ctx, workspaceID) + resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } diff --git a/internal/providers/pluginfw/products/app/resource_app_acc_test.go b/internal/providers/pluginfw/products/app/resource_app_acc_test.go index cb4ae75805..3346a9a7a0 100644 --- a/internal/providers/pluginfw/products/app/resource_app_acc_test.go +++ b/internal/providers/pluginfw/products/app/resource_app_acc_test.go @@ -1,13 +1,17 @@ package app_test import ( + "context" "fmt" "regexp" + "strconv" "testing" + "github.com/databricks/databricks-sdk-go" "github.com/databricks/terraform-provider-databricks/internal/acceptance" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const baseResources = ` @@ -186,3 +190,78 @@ func TestAccAppResource_NoCompute(t *testing.T) { }, }) } + +func appTemplate(provider_config string) string { + return fmt.Sprintf(` + resource "databricks_secret_scope" "this" { + name = "tf-{var.STICKY_RANDOM}" + } + resource "databricks_secret" "this" { + scope = databricks_secret_scope.this.name + key = "tf-{var.STICKY_RANDOM}" + string_value = "secret" + } + resource "databricks_app" "this" { + %s + no_compute = true + name = "tf-{var.STICKY_RANDOM}" + description = "no_compute app" + resources = [{ + name = "secret" + description = "secret for app" + secret = { + scope = databricks_secret_scope.this.name + key = databricks_secret.this.key + permission = "MANAGE" + } + }] + } + `, provider_config) +} + +func TestAccApp_ProviderConfig_Invalid(t *testing.T) { + acceptance.UnityWorkspaceLevel(t, acceptance.Step{ + Template: appTemplate(` + provider_config = { + workspace_id = "invalid" + } + `), + ExpectError: regexp.MustCompile( + `Attribute provider_config\.workspace_id\s+workspace_id must be a valid integer`, + ), + PlanOnly: true, + }) +} + +func TestAccApp_ProviderConfig_Mismatched(t *testing.T) { + acceptance.UnityWorkspaceLevel(t, acceptance.Step{ + Template: appTemplate(` + provider_config = { + workspace_id = "123" + } + `), + ExpectError: regexp.MustCompile( + `(?s)failed to get workspace client.*workspace_id mismatch` + + `.*please check the workspace_id provided in ` + + `provider_config`, + ), + }) +} + +func TestAccApp_ProviderConfig_Apply(t *testing.T) { + acceptance.LoadUcwsEnv(t) + ctx := context.Background() + w := databricks.Must(databricks.NewWorkspaceClient()) + workspaceID, err := w.CurrentWorkspaceID(ctx) + require.NoError(t, err) + workspaceIDStr := strconv.FormatInt(workspaceID, 10) + acceptance.UnityWorkspaceLevel(t, acceptance.Step{ + Template: appTemplate(``), + }, acceptance.Step{ + Template: appTemplate(fmt.Sprintf(` + provider_config = { + workspace_id = "%s" + } + `, workspaceIDStr)), + }) +} diff --git a/internal/providers/pluginfw/products/catalog/data_functions.go b/internal/providers/pluginfw/products/catalog/data_functions.go index c60b8a62b6..e25f7ba407 100644 --- a/internal/providers/pluginfw/products/catalog/data_functions.go +++ b/internal/providers/pluginfw/products/catalog/data_functions.go @@ -36,6 +36,7 @@ type FunctionsData struct { SchemaName types.String `tfsdk:"schema_name"` IncludeBrowse types.Bool `tfsdk:"include_browse"` Functions types.List `tfsdk:"functions"` + tfschema.Namespace } func (FunctionsData) ApplySchemaCustomizations(attrs map[string]tfschema.AttributeBuilder) map[string]tfschema.AttributeBuilder { @@ -43,13 +44,14 @@ func (FunctionsData) ApplySchemaCustomizations(attrs map[string]tfschema.Attribu attrs["schema_name"] = attrs["schema_name"].SetRequired() attrs["include_browse"] = attrs["include_browse"].SetOptional() attrs["functions"] = attrs["functions"].SetOptional().SetComputed() - + attrs["provider_config"] = attrs["provider_config"].SetOptional() return attrs } func (FunctionsData) GetComplexFieldTypes(context.Context) map[string]reflect.Type { return map[string]reflect.Type{ - "functions": reflect.TypeOf(catalog_tf.FunctionInfo{}), + "functions": reflect.TypeOf(catalog_tf.FunctionInfo{}), + "provider_config": reflect.TypeOf(tfschema.ProviderConfigData{}), } } @@ -73,18 +75,26 @@ func (d *FunctionsDataSource) Configure(_ context.Context, req datasource.Config func (d *FunctionsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { ctx = pluginfwcontext.SetUserAgentInDataSourceContext(ctx, dataSourceName) - w, diags := d.Client.GetWorkspaceClient() + + var functions FunctionsData + diags := req.Config.Get(ctx, &functions) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - var functions FunctionsData - diags = req.Config.Get(ctx, &functions) + workspaceID, diags := tfschema.GetWorkspaceIDDataSource(ctx, functions.ProviderConfig) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + w, diags := d.Client.GetWorkspaceClientForUnifiedProviderWithDiagnostics(ctx, workspaceID) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } + catalogName := functions.CatalogName.ValueString() schemaName := functions.SchemaName.ValueString() functionsInfosSdk, err := w.Functions.ListAll(ctx, catalog.ListFunctionsRequest{ diff --git a/internal/providers/pluginfw/products/cluster/data_cluster.go b/internal/providers/pluginfw/products/cluster/data_cluster.go index e43740f3b3..baa397da5a 100644 --- a/internal/providers/pluginfw/products/cluster/data_cluster.go +++ b/internal/providers/pluginfw/products/cluster/data_cluster.go @@ -37,19 +37,21 @@ type ClusterInfo struct { ClusterId types.String `tfsdk:"cluster_id"` Name types.String `tfsdk:"cluster_name"` ClusterInfo types.List `tfsdk:"cluster_info"` + tfschema.Namespace } func (ClusterInfo) ApplySchemaCustomizations(attrs map[string]tfschema.AttributeBuilder) map[string]tfschema.AttributeBuilder { attrs["cluster_id"] = attrs["cluster_id"].SetOptional().SetComputed() attrs["cluster_name"] = attrs["cluster_name"].SetOptional().SetComputed() attrs["cluster_info"] = attrs["cluster_info"].SetOptional().SetComputed() - + attrs["provider_config"] = attrs["provider_config"].SetOptional() return attrs } func (ClusterInfo) GetComplexFieldTypes(context.Context) map[string]reflect.Type { return map[string]reflect.Type{ - "cluster_info": reflect.TypeOf(compute_tf.ClusterDetails_SdkV2{}), + "cluster_info": reflect.TypeOf(compute_tf.ClusterDetails_SdkV2{}), + "provider_config": reflect.TypeOf(tfschema.ProviderConfigData{}), } } @@ -73,14 +75,21 @@ func (d *ClusterDataSource) Configure(_ context.Context, req datasource.Configur func (d *ClusterDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { ctx = pluginfwcontext.SetUserAgentInDataSourceContext(ctx, dataSourceName) - w, diags := d.Client.GetWorkspaceClient() + + var clusterInfo ClusterInfo + resp.Diagnostics.Append(req.Config.Get(ctx, &clusterInfo)...) + if resp.Diagnostics.HasError() { + return + } + + workspaceID, diags := tfschema.GetWorkspaceIDDataSource(ctx, clusterInfo.ProviderConfig) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - var clusterInfo ClusterInfo - resp.Diagnostics.Append(req.Config.Get(ctx, &clusterInfo)...) + w, diags := d.Client.GetWorkspaceClientForUnifiedProviderWithDiagnostics(ctx, workspaceID) + resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } diff --git a/internal/providers/pluginfw/products/dashboards/data_dashboards.go b/internal/providers/pluginfw/products/dashboards/data_dashboards.go index 52b7b0603b..7f650bf191 100644 --- a/internal/providers/pluginfw/products/dashboards/data_dashboards.go +++ b/internal/providers/pluginfw/products/dashboards/data_dashboards.go @@ -34,18 +34,20 @@ type DashboardsDataSource struct { type DashboardsInfo struct { DashboardNameContains types.String `tfsdk:"dashboard_name_contains"` Dashboards types.List `tfsdk:"dashboards"` + tfschema.Namespace } func (DashboardsInfo) ApplySchemaCustomizations(attrs map[string]tfschema.AttributeBuilder) map[string]tfschema.AttributeBuilder { attrs["dashboard_name_contains"] = attrs["dashboard_name_contains"].SetOptional() attrs["dashboards"] = attrs["dashboards"].SetComputed() - + attrs["provider_config"] = attrs["provider_config"].SetOptional() return attrs } func (DashboardsInfo) GetComplexFieldTypes(context.Context) map[string]reflect.Type { return map[string]reflect.Type{ - "dashboards": reflect.TypeOf(dashboards_tf.Dashboard{}), + "dashboards": reflect.TypeOf(dashboards_tf.Dashboard{}), + "provider_config": reflect.TypeOf(tfschema.ProviderConfigData{}), } } @@ -74,16 +76,24 @@ func AppendDiagAndCheckErrors(resp *datasource.ReadResponse, diags diag.Diagnost func (d *DashboardsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { ctx = pluginfwcontext.SetUserAgentInDataSourceContext(ctx, dataSourceName) - w, diags := d.Client.GetWorkspaceClient() - if AppendDiagAndCheckErrors(resp, diags) { - return - } var dashboardInfo DashboardsInfo if AppendDiagAndCheckErrors(resp, req.Config.Get(ctx, &dashboardInfo)) { return } + workspaceID, diags := tfschema.GetWorkspaceIDDataSource(ctx, dashboardInfo.ProviderConfig) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + w, diags := d.Client.GetWorkspaceClientForUnifiedProviderWithDiagnostics(ctx, workspaceID) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + dashboardName := strings.ToLower(dashboardInfo.DashboardNameContains.ValueString()) dashboardsGoSdk, err := w.Lakeview.ListAll(ctx, dashboards.ListDashboardsRequest{}) diff --git a/internal/providers/pluginfw/products/notificationdestinations/data_notification_destinations.go b/internal/providers/pluginfw/products/notificationdestinations/data_notification_destinations.go index 1d5f5d6b7b..17283fc93d 100755 --- a/internal/providers/pluginfw/products/notificationdestinations/data_notification_destinations.go +++ b/internal/providers/pluginfw/products/notificationdestinations/data_notification_destinations.go @@ -37,19 +37,21 @@ type NotificationDestinationsInfo struct { DisplayNameContains types.String `tfsdk:"display_name_contains"` Type types.String `tfsdk:"type"` NotificationDestinations types.List `tfsdk:"notification_destinations"` + tfschema.Namespace } func (NotificationDestinationsInfo) ApplySchemaCustomizations(attrs map[string]tfschema.AttributeBuilder) map[string]tfschema.AttributeBuilder { attrs["display_name_contains"] = attrs["display_name_contains"].SetOptional() attrs["type"] = attrs["type"].SetOptional() attrs["notification_destinations"] = attrs["notification_destinations"].SetComputed() - + attrs["provider_config"] = attrs["provider_config"].SetOptional() return attrs } func (NotificationDestinationsInfo) GetComplexFieldTypes(context.Context) map[string]reflect.Type { return map[string]reflect.Type{ "notification_destinations": reflect.TypeOf(settings_tf.ListNotificationDestinationsResult{}), + "provider_config": reflect.TypeOf(tfschema.ProviderConfigData{}), } } @@ -93,16 +95,24 @@ func AppendDiagAndCheckErrors(resp *datasource.ReadResponse, diags diag.Diagnost func (d *NotificationDestinationsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { ctx = pluginfwcontext.SetUserAgentInDataSourceContext(ctx, dataSourceName) - w, diags := d.Client.GetWorkspaceClient() - if AppendDiagAndCheckErrors(resp, diags) { - return - } var notificationInfo NotificationDestinationsInfo if AppendDiagAndCheckErrors(resp, req.Config.Get(ctx, ¬ificationInfo)) { return } + workspaceID, diags := tfschema.GetWorkspaceIDDataSource(ctx, notificationInfo.ProviderConfig) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + w, diags := d.Client.GetWorkspaceClientForUnifiedProviderWithDiagnostics(ctx, workspaceID) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + notificationType := notificationInfo.Type.ValueString() notificationDisplayName := strings.ToLower(notificationInfo.DisplayNameContains.ValueString()) diff --git a/internal/providers/pluginfw/products/registered_model/data_registered_model.go b/internal/providers/pluginfw/products/registered_model/data_registered_model.go index 6dd8257b74..08b71a16a1 100644 --- a/internal/providers/pluginfw/products/registered_model/data_registered_model.go +++ b/internal/providers/pluginfw/products/registered_model/data_registered_model.go @@ -38,6 +38,7 @@ type RegisteredModelData struct { IncludeAliases types.Bool `tfsdk:"include_aliases"` IncludeBrowse types.Bool `tfsdk:"include_browse"` ModelInfo types.List `tfsdk:"model_info"` + tfschema.Namespace } func (RegisteredModelData) ApplySchemaCustomizations(attrs map[string]tfschema.AttributeBuilder) map[string]tfschema.AttributeBuilder { @@ -45,12 +46,14 @@ func (RegisteredModelData) ApplySchemaCustomizations(attrs map[string]tfschema.A attrs["include_aliases"] = attrs["include_aliases"].SetOptional() attrs["include_browse"] = attrs["include_browse"].SetOptional() attrs["model_info"] = attrs["model_info"].SetOptional().SetComputed() + attrs["provider_config"] = attrs["provider_config"].SetOptional() return attrs } func (RegisteredModelData) GetComplexFieldTypes(context.Context) map[string]reflect.Type { return map[string]reflect.Type{ - "model_info": reflect.TypeOf(catalog_tf.RegisteredModelInfo_SdkV2{}), + "model_info": reflect.TypeOf(catalog_tf.RegisteredModelInfo_SdkV2{}), + "provider_config": reflect.TypeOf(tfschema.ProviderConfigData{}), } } @@ -74,14 +77,21 @@ func (d *RegisteredModelDataSource) Configure(_ context.Context, req datasource. func (d *RegisteredModelDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { ctx = pluginfwcontext.SetUserAgentInDataSourceContext(ctx, dataSourceName) - w, diags := d.Client.GetWorkspaceClient() + + var registeredModel RegisteredModelData + diags := req.Config.Get(ctx, ®isteredModel) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - var registeredModel RegisteredModelData - diags = req.Config.Get(ctx, ®isteredModel) + workspaceID, diags := tfschema.GetWorkspaceIDDataSource(ctx, registeredModel.ProviderConfig) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + w, diags := d.Client.GetWorkspaceClientForUnifiedProviderWithDiagnostics(ctx, workspaceID) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return diff --git a/internal/providers/pluginfw/products/registered_model/data_registered_model_versions.go b/internal/providers/pluginfw/products/registered_model/data_registered_model_versions.go index 3d17b589ae..aba6c29666 100644 --- a/internal/providers/pluginfw/products/registered_model/data_registered_model_versions.go +++ b/internal/providers/pluginfw/products/registered_model/data_registered_model_versions.go @@ -29,17 +29,20 @@ type RegisteredModelVersionsDataSource struct { type RegisteredModelVersionsData struct { FullName types.String `tfsdk:"full_name"` ModelVersions types.List `tfsdk:"model_versions"` + tfschema.Namespace } func (RegisteredModelVersionsData) ApplySchemaCustomizations(attrs map[string]tfschema.AttributeBuilder) map[string]tfschema.AttributeBuilder { attrs["full_name"] = attrs["full_name"].SetRequired() attrs["model_versions"] = attrs["model_versions"].SetOptional().SetComputed() + attrs["provider_config"] = attrs["provider_config"].SetOptional() return attrs } func (RegisteredModelVersionsData) GetComplexFieldTypes(context.Context) map[string]reflect.Type { return map[string]reflect.Type{ - "model_versions": reflect.TypeOf(catalog_tf.ModelVersionInfo_SdkV2{}), + "model_versions": reflect.TypeOf(catalog_tf.ModelVersionInfo_SdkV2{}), + "provider_config": reflect.TypeOf(tfschema.ProviderConfigData{}), } } @@ -62,18 +65,25 @@ func (d *RegisteredModelVersionsDataSource) Configure(_ context.Context, req dat } func (d *RegisteredModelVersionsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - w, diags := d.Client.GetWorkspaceClient() + var registeredModelVersions RegisteredModelVersionsData + diags := req.Config.Get(ctx, ®isteredModelVersions) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - var registeredModelVersions RegisteredModelVersionsData - diags = req.Config.Get(ctx, ®isteredModelVersions) + workspaceID, diags := tfschema.GetWorkspaceIDDataSource(ctx, registeredModelVersions.ProviderConfig) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + w, diags := d.Client.GetWorkspaceClientForUnifiedProviderWithDiagnostics(ctx, workspaceID) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } + modelFullName := registeredModelVersions.FullName.ValueString() modelVersions, err := w.ModelVersions.ListByFullName(ctx, modelFullName) if err != nil { diff --git a/internal/providers/pluginfw/products/serving/data_serving_endpoints.go b/internal/providers/pluginfw/products/serving/data_serving_endpoints.go index 53a95a070d..f1cef2589d 100644 --- a/internal/providers/pluginfw/products/serving/data_serving_endpoints.go +++ b/internal/providers/pluginfw/products/serving/data_serving_endpoints.go @@ -28,17 +28,19 @@ type ServingEndpointsDataSource struct { type ServingEndpointsData struct { Endpoints types.List `tfsdk:"endpoints"` + tfschema.Namespace } func (ServingEndpointsData) ApplySchemaCustomizations(attrs map[string]tfschema.AttributeBuilder) map[string]tfschema.AttributeBuilder { attrs["endpoints"] = attrs["endpoints"].SetOptional().SetComputed() - + attrs["provider_config"] = attrs["provider_config"].SetOptional() return attrs } func (ServingEndpointsData) GetComplexFieldTypes(context.Context) map[string]reflect.Type { return map[string]reflect.Type{ - "endpoints": reflect.TypeOf(serving_tf.ServingEndpoint_SdkV2{}), + "endpoints": reflect.TypeOf(serving_tf.ServingEndpoint_SdkV2{}), + "provider_config": reflect.TypeOf(tfschema.ProviderConfigData{}), } } @@ -61,18 +63,25 @@ func (d *ServingEndpointsDataSource) Configure(_ context.Context, req datasource } func (d *ServingEndpointsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - w, diags := d.Client.GetWorkspaceClient() + var endpoints ServingEndpointsData + diags := req.Config.Get(ctx, &endpoints) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - var endpoints ServingEndpointsData - diags = req.Config.Get(ctx, &endpoints) + workspaceID, diags := tfschema.GetWorkspaceIDDataSource(ctx, endpoints.ProviderConfig) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } + + w, diags := d.Client.GetWorkspaceClientForUnifiedProviderWithDiagnostics(ctx, workspaceID) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + endpointsInfoSdk, err := w.ServingEndpoints.ListAll(ctx) if err != nil { if apierr.IsMissing(err) { diff --git a/internal/providers/pluginfw/products/sharing/data_share.go b/internal/providers/pluginfw/products/sharing/data_share.go index 7395102aaf..4a62ebca8c 100644 --- a/internal/providers/pluginfw/products/sharing/data_share.go +++ b/internal/providers/pluginfw/products/sharing/data_share.go @@ -2,6 +2,7 @@ package sharing import ( "context" + "reflect" "github.com/databricks/databricks-sdk-go/apierr" "github.com/databricks/databricks-sdk-go/service/sharing" @@ -27,12 +28,29 @@ type ShareDataSource struct { Client *common.DatabricksClient } +type ShareData struct { + sharing_tf.ShareInfo + tfschema.Namespace +} + +func (s ShareData) GetComplexFieldTypes(ctx context.Context) map[string]reflect.Type { + types := s.ShareInfo.GetComplexFieldTypes(ctx) + types["provider_config"] = reflect.TypeOf(tfschema.ProviderConfigData{}) + return types +} + +func (s ShareData) ApplySchemaCustomizations(attrs map[string]tfschema.AttributeBuilder) map[string]tfschema.AttributeBuilder { + s.ShareInfo.ApplySchemaCustomizations(attrs) + attrs["provider_config"] = attrs["provider_config"].SetOptional() + return attrs +} + func (d *ShareDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = pluginfwcommon.GetDatabricksProductionName(dataSourceNameShare) } func (d *ShareDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { - attrs, blocks := tfschema.DataSourceStructToSchemaMap(ctx, sharing_tf.ShareInfo{}, nil) + attrs, blocks := tfschema.DataSourceStructToSchemaMap(ctx, ShareData{}, nil) resp.Schema = schema.Schema{ Attributes: attrs, Blocks: blocks, @@ -47,14 +65,20 @@ func (d *ShareDataSource) Configure(_ context.Context, req datasource.ConfigureR func (d *ShareDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { ctx = pluginfwcontext.SetUserAgentInDataSourceContext(ctx, dataSourceNameShare) - w, diags := d.Client.GetWorkspaceClient() + + var config ShareData + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + workspaceID, diags := tfschema.GetWorkspaceIDDataSource(ctx, config.ProviderConfig) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - var config sharing_tf.ShareInfo - diags = req.Config.Get(ctx, &config) + w, diags := d.Client.GetWorkspaceClientForUnifiedProviderWithDiagnostics(ctx, workspaceID) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return @@ -73,11 +97,14 @@ func (d *ShareDataSource) Read(ctx context.Context, req datasource.ReadRequest, return } - var shareInfoTfSdk sharing_tf.ShareInfo + var shareInfoTfSdk ShareData resp.Diagnostics.Append(converters.GoSdkToTfSdkStruct(ctx, share, &shareInfoTfSdk)...) if resp.Diagnostics.HasError() { return } + shareInfoTfSdk.Namespace = tfschema.Namespace{ + ProviderConfig: config.ProviderConfig, + } resp.Diagnostics.Append(resp.State.Set(ctx, shareInfoTfSdk)...) } diff --git a/internal/providers/pluginfw/products/sharing/data_share_acc_test.go b/internal/providers/pluginfw/products/sharing/data_share_acc_test.go new file mode 100644 index 0000000000..881d1b2f39 --- /dev/null +++ b/internal/providers/pluginfw/products/sharing/data_share_acc_test.go @@ -0,0 +1,76 @@ +package sharing_test + +import ( + "context" + "fmt" + "regexp" + "strconv" + "testing" + + "github.com/databricks/databricks-sdk-go" + "github.com/databricks/terraform-provider-databricks/internal/acceptance" + "github.com/stretchr/testify/require" +) + +func dataSourceShareTemplate(provider_config string) string { + return fmt.Sprintf(` + resource "databricks_share" "myshare" { + name = "{var.STICKY_RANDOM}-share-config" + object { + name = databricks_schema.schema1.id + data_object_type = "SCHEMA" + } + } + data "databricks_share" "this" { + %s + name = databricks_share.myshare.name + } +`, provider_config) +} + +func TestAccShareData_ProviderConfig_Invalid(t *testing.T) { + acceptance.UnityWorkspaceLevel(t, acceptance.Step{ + Template: preTestTemplateSchema + dataSourceShareTemplate(` + provider_config = { + workspace_id = "invalid" + } + `), + ExpectError: regexp.MustCompile( + `Attribute provider_config\.workspace_id\s+workspace_id must be a valid integer`, + ), + PlanOnly: true, + }) +} + +func TestAccShareData_ProviderConfig_Mismatched(t *testing.T) { + acceptance.UnityWorkspaceLevel(t, acceptance.Step{ + Template: preTestTemplateSchema + dataSourceShareTemplate(` + provider_config = { + workspace_id = "123" + } + `), + ExpectError: regexp.MustCompile( + `(?s)failed to get workspace client.*workspace_id mismatch` + + `.*please check the workspace_id provided in ` + + `provider_config`, + ), + }) +} + +func TestAccShareData_ProviderConfig_Apply(t *testing.T) { + acceptance.LoadUcwsEnv(t) + ctx := context.Background() + w := databricks.Must(databricks.NewWorkspaceClient()) + workspaceID, err := w.CurrentWorkspaceID(ctx) + require.NoError(t, err) + workspaceIDStr := strconv.FormatInt(workspaceID, 10) + acceptance.UnityWorkspaceLevel(t, acceptance.Step{ + Template: preTestTemplateSchema + dataSourceShareTemplate(``), + }, acceptance.Step{ + Template: preTestTemplateSchema + dataSourceShareTemplate(fmt.Sprintf(` + provider_config = { + workspace_id = "%s" + } + `, workspaceIDStr)), + }) +} diff --git a/internal/providers/pluginfw/products/sharing/data_shares.go b/internal/providers/pluginfw/products/sharing/data_shares.go index 01fa3b2157..39822cf478 100644 --- a/internal/providers/pluginfw/products/sharing/data_shares.go +++ b/internal/providers/pluginfw/products/sharing/data_shares.go @@ -19,28 +19,31 @@ const dataSourceNameShares = "shares" type SharesList struct { Shares types.List `tfsdk:"shares"` + tfschema.Namespace } -func (SharesList) ApplySchemaCustomizations(attrs map[string]tfschema.AttributeBuilder) map[string]tfschema.AttributeBuilder { - attrs["shares"] = attrs["shares"].SetComputed().SetOptional() - - return attrs -} - -func (SharesList) GetComplexFieldTypes(context.Context) map[string]reflect.Type { +func (s SharesList) GetComplexFieldTypes(context.Context) map[string]reflect.Type { return map[string]reflect.Type{ - "shares": reflect.TypeOf(types.String{}), + "shares": reflect.TypeOf(types.String{}), + "provider_config": reflect.TypeOf(tfschema.ProviderConfigData{}), } } -func (SharesList) ToObjectType(ctx context.Context) types.ObjectType { +func (s SharesList) ToObjectType(ctx context.Context) types.ObjectType { return types.ObjectType{ AttrTypes: map[string]attr.Type{ - "shares": types.ListType{ElemType: types.StringType}, + "shares": types.ListType{ElemType: types.StringType}, + "provider_config": tfschema.ProviderConfigData{}.Type(ctx), }, } } +func (s SharesList) ApplySchemaCustomizations(attrs map[string]tfschema.AttributeBuilder) map[string]tfschema.AttributeBuilder { + attrs["shares"] = attrs["shares"].SetComputed().SetOptional() + attrs["provider_config"] = attrs["provider_config"].SetOptional() + return attrs +} + func DataSourceShares() datasource.DataSource { return &SharesDataSource{} } @@ -71,12 +74,26 @@ func (d *SharesDataSource) Configure(_ context.Context, req datasource.Configure func (d *SharesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { ctx = pluginfwcontext.SetUserAgentInDataSourceContext(ctx, dataSourceNameShares) - w, diags := d.Client.GetWorkspaceClient() + + var config SharesList + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + workspaceID, diags := tfschema.GetWorkspaceIDDataSource(ctx, config.ProviderConfig) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } + w, clientDiags := d.Client.GetWorkspaceClientForUnifiedProviderWithDiagnostics(ctx, workspaceID) + + resp.Diagnostics.Append(clientDiags...) + if resp.Diagnostics.HasError() { + return + } + shares, err := w.Shares.ListAll(ctx, sharing.ListSharesRequest{}) if err != nil { resp.Diagnostics.AddError("Failed to fetch shares", err.Error()) @@ -88,5 +105,11 @@ func (d *SharesDataSource) Read(ctx context.Context, req datasource.ReadRequest, shareNames[i] = types.StringValue(share.Name) } - resp.Diagnostics.Append(resp.State.Set(ctx, SharesList{Shares: types.ListValueMust(types.StringType, shareNames)})...) + newState := SharesList{ + Shares: types.ListValueMust(types.StringType, shareNames), + Namespace: tfschema.Namespace{ + ProviderConfig: config.ProviderConfig, + }, + } + resp.Diagnostics.Append(resp.State.Set(ctx, newState)...) } diff --git a/internal/providers/pluginfw/products/sharing/data_shares_acc_test.go b/internal/providers/pluginfw/products/sharing/data_shares_acc_test.go index c6237f0095..01b726c082 100644 --- a/internal/providers/pluginfw/products/sharing/data_shares_acc_test.go +++ b/internal/providers/pluginfw/products/sharing/data_shares_acc_test.go @@ -1,6 +1,8 @@ package sharing_test import ( + "fmt" + "regexp" "strconv" "testing" @@ -88,3 +90,48 @@ func TestUcAccDataSourceShares(t *testing.T) { Check: checkSharesDataSourcePopulated(t), }) } + +func dataSourceSharesTemplate(provider_config string) string { + return fmt.Sprintf(` + resource "databricks_share" "myshare" { + name = "{var.STICKY_RANDOM}-share-config" + object { + name = databricks_schema.schema1.id + data_object_type = "SCHEMA" + } + } + data "databricks_shares" "this" { + %s + depends_on = [databricks_share.myshare] + } +`, provider_config) +} + +func TestAccSharesData_ProviderConfig_Invalid(t *testing.T) { + acceptance.UnityWorkspaceLevel(t, acceptance.Step{ + Template: preTestTemplateSchema + dataSourceSharesTemplate(` + provider_config = { + workspace_id = "invalid" + } + `), + ExpectError: regexp.MustCompile( + `Attribute provider_config\.workspace_id\s+workspace_id must be a valid integer`, + ), + PlanOnly: true, + }) +} + +func TestAccSharesData_ProviderConfig_Mismatched(t *testing.T) { + acceptance.UnityWorkspaceLevel(t, acceptance.Step{ + Template: preTestTemplateSchema + dataSourceSharesTemplate(` + provider_config = { + workspace_id = "123" + } + `), + ExpectError: regexp.MustCompile( + `(?s)failed to get workspace client.*workspace_id mismatch` + + `.*please check the workspace_id provided in ` + + `provider_config`, + ), + }) +} diff --git a/internal/providers/pluginfw/products/volume/data_volumes.go b/internal/providers/pluginfw/products/volume/data_volumes.go index e5772cce4b..593d1cee21 100644 --- a/internal/providers/pluginfw/products/volume/data_volumes.go +++ b/internal/providers/pluginfw/products/volume/data_volumes.go @@ -34,18 +34,21 @@ type VolumesList struct { CatalogName types.String `tfsdk:"catalog_name"` SchemaName types.String `tfsdk:"schema_name"` Ids types.List `tfsdk:"ids"` + tfschema.Namespace } func (VolumesList) ApplySchemaCustomizations(attrs map[string]tfschema.AttributeBuilder) map[string]tfschema.AttributeBuilder { attrs["catalog_name"] = attrs["catalog_name"].SetRequired() attrs["schema_name"] = attrs["schema_name"].SetRequired() attrs["ids"] = attrs["ids"].SetOptional().SetComputed() + attrs["provider_config"] = attrs["provider_config"].SetOptional() return attrs } func (VolumesList) GetComplexFieldTypes(context.Context) map[string]reflect.Type { return map[string]reflect.Type{ - "ids": reflect.TypeOf(types.String{}), + "ids": reflect.TypeOf(types.String{}), + "provider_config": reflect.TypeOf(tfschema.ProviderConfigData{}), } } @@ -69,20 +72,28 @@ func (d *VolumesDataSource) Configure(_ context.Context, req datasource.Configur func (d *VolumesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { ctx = pluginfwcontext.SetUserAgentInDataSourceContext(ctx, dataSourceName) - w, diags := d.Client.GetWorkspaceClient() + + var volumesList VolumesList + diags := req.Config.Get(ctx, &volumesList) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } + var listVolumesRequest catalog.ListVolumesRequest + converters.TfSdkToGoSdkStruct(ctx, volumesList, &listVolumesRequest) - var volumesList VolumesList - diags = req.Config.Get(ctx, &volumesList) + workspaceID, diags := tfschema.GetWorkspaceIDDataSource(ctx, volumesList.ProviderConfig) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - var listVolumesRequest catalog.ListVolumesRequest - converters.TfSdkToGoSdkStruct(ctx, volumesList, &listVolumesRequest) + + w, clientDiags := d.Client.GetWorkspaceClientForUnifiedProviderWithDiagnostics(ctx, workspaceID) + resp.Diagnostics.Append(clientDiags...) + if resp.Diagnostics.HasError() { + return + } + volumes, err := w.Volumes.ListAll(ctx, listVolumesRequest) if err != nil { if apierr.IsMissing(err) { diff --git a/internal/providers/pluginfw/tfschema/unified_provider.go b/internal/providers/pluginfw/tfschema/unified_provider.go index 3aff327dd9..dda83c496b 100644 --- a/internal/providers/pluginfw/tfschema/unified_provider.go +++ b/internal/providers/pluginfw/tfschema/unified_provider.go @@ -141,3 +141,53 @@ func GetWorkspaceID_SdkV2(ctx context.Context, providerConfig types.List) (strin return workspaceID, diags } + +// GetWorkspaceIDResource extracts the workspace ID from a provider_config object (for resources). +// It returns the workspace ID string and any diagnostics encountered during extraction. +// If the provider_config is not set, it returns an empty string with no diagnostics. +func GetWorkspaceIDResource(ctx context.Context, providerConfig types.Object) (string, diag.Diagnostics) { + var diags diag.Diagnostics + var workspaceID string + + if providerConfig.IsNull() { + return workspaceID, diags + } + + var namespace ProviderConfig + diags.Append(providerConfig.As(ctx, &namespace, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + })...) + if diags.HasError() { + return workspaceID, diags + } + + workspaceID = namespace.WorkspaceID.ValueString() + + return workspaceID, diags +} + +// GetWorkspaceIDDataSource extracts the workspace ID from a provider_config object (for data sources). +// It returns the workspace ID string and any diagnostics encountered during extraction. +// If the provider_config is not set, it returns an empty string with no diagnostics. +func GetWorkspaceIDDataSource(ctx context.Context, providerConfig types.Object) (string, diag.Diagnostics) { + var diags diag.Diagnostics + var workspaceID string + + if providerConfig.IsNull() { + return workspaceID, diags + } + + var namespace ProviderConfigData + diags.Append(providerConfig.As(ctx, &namespace, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + })...) + if diags.HasError() { + return workspaceID, diags + } + + workspaceID = namespace.WorkspaceID.ValueString() + + return workspaceID, diags +} diff --git a/internal/providers/pluginfw/tfschema/unified_provider_test.go b/internal/providers/pluginfw/tfschema/unified_provider_test.go index cc7bc619ac..7510c5d470 100644 --- a/internal/providers/pluginfw/tfschema/unified_provider_test.go +++ b/internal/providers/pluginfw/tfschema/unified_provider_test.go @@ -133,3 +133,183 @@ func TestGetWorkspaceID_SdkV2(t *testing.T) { }) } } + +func TestGetWorkspaceIDResource(t *testing.T) { + ctx := context.Background() + + tests := []struct { + name string + setupProviderConfig func() types.Object + expectedWorkspaceID string + expectError bool + }{ + { + name: "valid workspace ID", + setupProviderConfig: func() types.Object { + providerConfig := ProviderConfig{ + WorkspaceID: types.StringValue("123456789"), + } + return providerConfig.ToObjectValue(ctx) + }, + expectedWorkspaceID: "123456789", + expectError: false, + }, + { + name: "null provider_config", + setupProviderConfig: func() types.Object { + return types.ObjectNull(ProviderConfig{}.Type(ctx).(types.ObjectType).AttrTypes) + }, + expectedWorkspaceID: "", + expectError: false, + }, + { + name: "unknown provider_config", + setupProviderConfig: func() types.Object { + return types.ObjectUnknown(ProviderConfig{}.Type(ctx).(types.ObjectType).AttrTypes) + }, + expectedWorkspaceID: "", + expectError: false, + }, + { + name: "empty workspace ID string", + setupProviderConfig: func() types.Object { + providerConfig := ProviderConfig{ + WorkspaceID: types.StringValue(""), + } + return providerConfig.ToObjectValue(ctx) + }, + expectedWorkspaceID: "", + expectError: false, + }, + { + name: "null workspace ID in object", + setupProviderConfig: func() types.Object { + providerConfig := ProviderConfig{ + WorkspaceID: types.StringNull(), + } + return providerConfig.ToObjectValue(ctx) + }, + expectedWorkspaceID: "", + expectError: false, + }, + { + name: "incompatible object structure", + setupProviderConfig: func() types.Object { + // Create an object with wrong attribute types to trigger conversion error + attrTypes := map[string]attr.Type{ + "workspace_id": types.Int64Type, + } + attrValues := map[string]attr.Value{ + "workspace_id": types.Int64Value(123), + } + return types.ObjectValueMust(attrTypes, attrValues) + }, + expectedWorkspaceID: "", + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + providerConfigObject := tt.setupProviderConfig() + workspaceID, diags := GetWorkspaceIDResource(ctx, providerConfigObject) + + if tt.expectError { + assert.True(t, diags.HasError(), "Expected diagnostics error") + } else { + assert.False(t, diags.HasError(), "Expected no diagnostics error") + } + assert.Equal(t, tt.expectedWorkspaceID, workspaceID, "Workspace ID mismatch") + }) + } +} + +func TestGetWorkspaceIDDataSource(t *testing.T) { + ctx := context.Background() + + tests := []struct { + name string + setupProviderConfig func() types.Object + expectedWorkspaceID string + expectError bool + }{ + { + name: "valid workspace ID", + setupProviderConfig: func() types.Object { + providerConfig := ProviderConfigData{ + WorkspaceID: types.StringValue("123456789"), + } + return providerConfig.ToObjectValue(ctx) + }, + expectedWorkspaceID: "123456789", + expectError: false, + }, + { + name: "null provider_config", + setupProviderConfig: func() types.Object { + return types.ObjectNull(ProviderConfigData{}.Type(ctx).(types.ObjectType).AttrTypes) + }, + expectedWorkspaceID: "", + expectError: false, + }, + { + name: "unknown provider_config", + setupProviderConfig: func() types.Object { + return types.ObjectUnknown(ProviderConfigData{}.Type(ctx).(types.ObjectType).AttrTypes) + }, + expectedWorkspaceID: "", + expectError: false, + }, + { + name: "empty workspace ID string", + setupProviderConfig: func() types.Object { + providerConfig := ProviderConfigData{ + WorkspaceID: types.StringValue(""), + } + return providerConfig.ToObjectValue(ctx) + }, + expectedWorkspaceID: "", + expectError: false, + }, + { + name: "null workspace ID in object", + setupProviderConfig: func() types.Object { + providerConfig := ProviderConfigData{ + WorkspaceID: types.StringNull(), + } + return providerConfig.ToObjectValue(ctx) + }, + expectedWorkspaceID: "", + expectError: false, + }, + { + name: "incompatible object structure", + setupProviderConfig: func() types.Object { + // Create an object with wrong attribute types to trigger conversion error + attrTypes := map[string]attr.Type{ + "workspace_id": types.Int64Type, + } + attrValues := map[string]attr.Value{ + "workspace_id": types.Int64Value(123), + } + return types.ObjectValueMust(attrTypes, attrValues) + }, + expectedWorkspaceID: "", + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + providerConfigObject := tt.setupProviderConfig() + workspaceID, diags := GetWorkspaceIDDataSource(ctx, providerConfigObject) + + if tt.expectError { + assert.True(t, diags.HasError(), "Expected diagnostics error") + } else { + assert.False(t, diags.HasError(), "Expected no diagnostics error") + } + assert.Equal(t, tt.expectedWorkspaceID, workspaceID, "Workspace ID mismatch") + }) + } +}