Skip to content

Commit 22da2ce

Browse files
authored
[Internal] Add provider_config schema support for plugin framework (#5104)
## Changes <!-- Summary of your changes that are easy to understand --> Add `provider_config` schema support for plugin framework resources. ## Tests <!-- How is this tested? Please see the checklist below and also describe any other relevant tests --> Unit tests
1 parent db61465 commit 22da2ce

File tree

4 files changed

+238
-0
lines changed

4 files changed

+238
-0
lines changed

NEXT_CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@
1919
### Exporter
2020

2121
### Internal Changes
22+
23+
* Add provider_config support for plugin framework ([#5104](https://github.com/databricks/terraform-provider-databricks/pull/5104))
2224
* Refactor `catalog_test.go` to use internal plan checks ([#5112](https://github.com/databricks/terraform-provider-databricks/pull/5112)).

internal/providers/pluginfw/tfschema/struct_to_schema_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"github.com/databricks/terraform-provider-databricks/common"
1111
tfcommon "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/common"
1212
"github.com/hashicorp/terraform-plugin-framework/attr"
13+
datasource_schema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
14+
resource_schema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
1315
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
1416
"github.com/hashicorp/terraform-plugin-framework/types"
1517
"github.com/stretchr/testify/assert"
@@ -42,6 +44,36 @@ func (TestIntTfSdk) ApplySchemaCustomizations(attrs map[string]AttributeBuilder)
4244
return attrs
4345
}
4446

47+
type TestNamespaceResourceTfSdk struct {
48+
Namespace
49+
}
50+
51+
func (a TestNamespaceResourceTfSdk) ApplySchemaCustomizations(s map[string]AttributeBuilder) map[string]AttributeBuilder {
52+
s["provider_config"] = s["provider_config"].SetOptional()
53+
return s
54+
}
55+
56+
func (a TestNamespaceResourceTfSdk) GetComplexFieldTypes(ctx context.Context) map[string]reflect.Type {
57+
return map[string]reflect.Type{
58+
"provider_config": reflect.TypeOf(ProviderConfig{}),
59+
}
60+
}
61+
62+
type TestNamespaceDataSourceTfSdk struct {
63+
Namespace
64+
}
65+
66+
func (a TestNamespaceDataSourceTfSdk) ApplySchemaCustomizations(s map[string]AttributeBuilder) map[string]AttributeBuilder {
67+
s["provider_config"] = s["provider_config"].SetOptional()
68+
return s
69+
}
70+
71+
func (a TestNamespaceDataSourceTfSdk) GetComplexFieldTypes(ctx context.Context) map[string]reflect.Type {
72+
return map[string]reflect.Type{
73+
"provider_config": reflect.TypeOf(ProviderConfigData{}),
74+
}
75+
}
76+
4577
type TestComputedTfSdk struct {
4678
ComputedTag types.String `tfsdk:"computedtag"`
4779
MultipleTags types.String `tfsdk:"multipletags"`
@@ -220,6 +252,18 @@ var tests = []struct {
220252
}),
221253
},
222254
},
255+
{
256+
"namespace resource conversion",
257+
TestNamespaceResourceTfSdk{Namespace: Namespace{ProviderConfig: types.ObjectValueMust(ProviderConfig{}.Type(context.Background()).(types.ObjectType).AttrTypes, map[string]attr.Value{
258+
"workspace_id": types.StringValue("1234567890"),
259+
})}},
260+
},
261+
{
262+
"namespace data source conversion",
263+
TestNamespaceDataSourceTfSdk{Namespace: Namespace{ProviderConfig: types.ObjectValueMust(ProviderConfigData{}.Type(context.Background()).(types.ObjectType).AttrTypes, map[string]attr.Value{
264+
"workspace_id": types.StringValue("1234567890"),
265+
})}},
266+
},
223267
}
224268

225269
// StructToSchemaConversionTestCase runs a single test case to verify StructToSchema works for both data source and resource.
@@ -263,6 +307,22 @@ func TestStructToSchemaOptionalVsRequiredField(t *testing.T) {
263307
assert.True(t, data_scm.Attributes["enabled"].IsRequired())
264308
}
265309

310+
func TestStructToSchemaNamespace(t *testing.T) {
311+
// Test that provider_config is an optional field.
312+
scm := ResourceStructToSchema(context.Background(), TestNamespaceResourceTfSdk{}, nil)
313+
assert.True(t, scm.Attributes["provider_config"].IsOptional())
314+
315+
data_scm := DataSourceStructToSchema(context.Background(), TestNamespaceDataSourceTfSdk{}, nil)
316+
assert.True(t, data_scm.Attributes["provider_config"].IsOptional())
317+
318+
// Test that workspace_id is a required field.
319+
scm = ResourceStructToSchema(context.Background(), TestNamespaceResourceTfSdk{}, nil)
320+
assert.True(t, scm.Attributes["provider_config"].(resource_schema.SingleNestedAttribute).Attributes["workspace_id"].IsRequired())
321+
322+
data_scm = DataSourceStructToSchema(context.Background(), TestNamespaceDataSourceTfSdk{}, nil)
323+
assert.True(t, data_scm.Attributes["provider_config"].(datasource_schema.SingleNestedAttribute).Attributes["workspace_id"].IsRequired())
324+
}
325+
266326
func testStructToSchemaPanics(t *testing.T, testStruct any, expectedError string) {
267327
defer func() {
268328
err := recover()
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package tfschema
2+
3+
import (
4+
"context"
5+
"reflect"
6+
7+
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
8+
"github.com/hashicorp/terraform-plugin-framework/attr"
9+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
10+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
11+
"github.com/hashicorp/terraform-plugin-framework/types"
12+
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
13+
)
14+
15+
// Namespace is used to store the namespace for unified terraform provider
16+
// across resources and data sources onboarded to plugin framework.
17+
// Resources and data sources will use the underlying ProviderConfig and ProviderConfigData
18+
// type respectively to store the provider configurations.
19+
type Namespace struct {
20+
ProviderConfig types.Object `tfsdk:"provider_config"`
21+
}
22+
23+
// ProviderConfig is used to store the provider configurations for unified terraform provider
24+
// across resources onboarded to plugin framework.
25+
type ProviderConfig struct {
26+
WorkspaceID types.String `tfsdk:"workspace_id"`
27+
}
28+
29+
// ApplySchemaCustomizations applies the schema customizations to the ProviderConfig type.
30+
func (r ProviderConfig) ApplySchemaCustomizations(attrs map[string]AttributeBuilder) map[string]AttributeBuilder {
31+
attrs["workspace_id"] = attrs["workspace_id"].SetRequired()
32+
attrs["workspace_id"] = attrs["workspace_id"].(StringAttributeBuilder).AddPlanModifier(
33+
stringplanmodifier.RequiresReplaceIf(workspaceIDPlanModifier, "", ""))
34+
attrs["workspace_id"] = attrs["workspace_id"].(StringAttributeBuilder).AddValidator(stringvalidator.LengthAtLeast(1))
35+
return attrs
36+
}
37+
38+
// workspaceIDPlanModifier is a plan modifier that requires replacement if the
39+
// workspace_id changes from one non-empty value to another
40+
func workspaceIDPlanModifier(ctx context.Context, req planmodifier.StringRequest, resp *stringplanmodifier.RequiresReplaceIfFuncResponse) {
41+
// Require replacement if workspace_id changes from one non-empty value to another
42+
oldValue := req.StateValue.ValueString()
43+
newValue := req.PlanValue.ValueString()
44+
45+
if oldValue != "" && newValue != "" && oldValue != newValue {
46+
resp.RequiresReplace = true
47+
}
48+
}
49+
50+
// GetComplexFieldTypes returns a map of the types of elements in complex fields in ProviderConfig.
51+
func (r ProviderConfig) GetComplexFieldTypes(ctx context.Context) map[string]reflect.Type {
52+
return map[string]reflect.Type{}
53+
}
54+
55+
// ToObjectValue returns the object value for the resource
56+
func (r ProviderConfig) ToObjectValue(ctx context.Context) basetypes.ObjectValue {
57+
return types.ObjectValueMust(
58+
r.Type(ctx).(basetypes.ObjectType).AttrTypes,
59+
map[string]attr.Value{
60+
"workspace_id": r.WorkspaceID,
61+
},
62+
)
63+
}
64+
65+
// Type returns the object type for the ProviderConfig type.
66+
func (r ProviderConfig) Type(ctx context.Context) attr.Type {
67+
return types.ObjectType{
68+
AttrTypes: map[string]attr.Type{
69+
"workspace_id": types.StringType,
70+
},
71+
}
72+
}
73+
74+
// ProviderConfigData is used to store the provider configurations for unified terraform provider
75+
// across data sources onboarded to plugin framework.
76+
type ProviderConfigData struct {
77+
WorkspaceID types.String `tfsdk:"workspace_id"`
78+
}
79+
80+
// ApplySchemaCustomizations applies the schema customizations to the ProviderConfigData type.
81+
func (r ProviderConfigData) ApplySchemaCustomizations(attrs map[string]AttributeBuilder) map[string]AttributeBuilder {
82+
attrs["workspace_id"] = attrs["workspace_id"].SetRequired()
83+
attrs["workspace_id"] = attrs["workspace_id"].(StringAttributeBuilder).AddValidator(stringvalidator.LengthAtLeast(1))
84+
return attrs
85+
}
86+
87+
// GetComplexFieldTypes returns a map of the types of elements in complex fields in ProviderConfigData.
88+
func (r ProviderConfigData) GetComplexFieldTypes(ctx context.Context) map[string]reflect.Type {
89+
return map[string]reflect.Type{}
90+
}
91+
92+
// ToObjectValue returns the object value for the data source
93+
func (r ProviderConfigData) ToObjectValue(ctx context.Context) basetypes.ObjectValue {
94+
return types.ObjectValueMust(
95+
r.Type(ctx).(basetypes.ObjectType).AttrTypes,
96+
map[string]attr.Value{
97+
"workspace_id": r.WorkspaceID,
98+
},
99+
)
100+
}
101+
102+
// Type returns the object type for the ProviderConfigData type.
103+
func (r ProviderConfigData) Type(ctx context.Context) attr.Type {
104+
return types.ObjectType{
105+
AttrTypes: map[string]attr.Type{
106+
"workspace_id": types.StringType,
107+
},
108+
}
109+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package tfschema
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
8+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
9+
"github.com/hashicorp/terraform-plugin-framework/types"
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func TestWorkspaceIDPlanModifier(t *testing.T) {
14+
tests := []struct {
15+
name string
16+
stateValue string
17+
planValue string
18+
expectedRequiresReplace bool
19+
}{
20+
{
21+
name: "both non-empty and different - requires replace",
22+
stateValue: "workspace-123",
23+
planValue: "workspace-456",
24+
expectedRequiresReplace: true,
25+
},
26+
{
27+
name: "both non-empty and same - no replace",
28+
stateValue: "workspace-123",
29+
planValue: "workspace-123",
30+
expectedRequiresReplace: false,
31+
},
32+
{
33+
name: "old empty, new non-empty - no replace",
34+
stateValue: "",
35+
planValue: "workspace-123",
36+
expectedRequiresReplace: false,
37+
},
38+
{
39+
name: "old non-empty, new empty - no replace",
40+
stateValue: "workspace-123",
41+
planValue: "",
42+
expectedRequiresReplace: false,
43+
},
44+
{
45+
name: "both empty - no replace",
46+
stateValue: "",
47+
planValue: "",
48+
expectedRequiresReplace: false,
49+
},
50+
}
51+
52+
for _, tt := range tests {
53+
t.Run(tt.name, func(t *testing.T) {
54+
req := planmodifier.StringRequest{
55+
StateValue: types.StringValue(tt.stateValue),
56+
PlanValue: types.StringValue(tt.planValue),
57+
}
58+
resp := &stringplanmodifier.RequiresReplaceIfFuncResponse{}
59+
60+
workspaceIDPlanModifier(context.Background(), req, resp)
61+
62+
assert.Equal(t, tt.expectedRequiresReplace, resp.RequiresReplace,
63+
"RequiresReplace mismatch for state '%s' -> plan '%s'",
64+
tt.stateValue, tt.planValue)
65+
})
66+
}
67+
}

0 commit comments

Comments
 (0)