diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7ead3fe7f3..54f584a1e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -135,10 +135,18 @@ We are migrating the resource from SDKv2 to Plugin Framework provider and hence 6. Create a PR and send it for review. ### Migrating resource to plugin framework -Ideally there shouldn't be any behaviour change when migrating a resource or data source to either Go SDk or Plugin Framework. +There must not be any behaviour change or schema change when migrating a resource or data source to either Go SDK or Plugin Framework. - Please make sure there are no breaking differences due to changes in schema by running: `make diff-schema`. - Integration tests shouldn't require any major changes. +By default, `ResourceStructToSchema` will convert a `types.List` field to a `ListAttribute` or `ListNestedAttribute`. For resources or data sources migrated from the SDKv2, `ListNestedBlock` must be used for such fields. To do this, call `cs.ConfigureAsSdkV2Compatible()` in the `ResourceStructToSchema` callback: +```go +resp.Schema = tfschema.ResourceStructToSchema(ctx, Resource{}, func(c tfschema.CustomizableSchema) tfschema.CustomizableSchema { + cs.ConfigureAsSdkV2Compatible() + // Add any additional configuration here + return cs +}) +``` ### Code Organization Each resource and data source should be defined in package `internal/providers/plugnifw/products/`, e.g.: `internal/providers/plugnifw/products/volume` package will contain both resource, data sources and other utils specific to volumes. Tests (both unit and integration tests) will also remain in this package. diff --git a/internal/providers/pluginfw/products/library/resource_library.go b/internal/providers/pluginfw/products/library/resource_library.go index 178687495d..f7b3854a5c 100644 --- a/internal/providers/pluginfw/products/library/resource_library.go +++ b/internal/providers/pluginfw/products/library/resource_library.go @@ -88,6 +88,7 @@ func (r *LibraryResource) Metadata(ctx context.Context, req resource.MetadataReq func (r *LibraryResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { attrs, blocks := tfschema.ResourceStructToSchemaMap(ctx, LibraryExtended{}, func(c tfschema.CustomizableSchema) tfschema.CustomizableSchema { + c.ConfigureAsSdkV2Compatible() for field, attribute := range c.ToNestedBlockObject().Attributes { switch attribute.(type) { case tfschema.StringAttributeBuilder: diff --git a/internal/providers/pluginfw/products/qualitymonitor/resource_quality_monitor.go b/internal/providers/pluginfw/products/qualitymonitor/resource_quality_monitor.go index 00e04a3bd8..574bb72e3d 100644 --- a/internal/providers/pluginfw/products/qualitymonitor/resource_quality_monitor.go +++ b/internal/providers/pluginfw/products/qualitymonitor/resource_quality_monitor.go @@ -78,6 +78,7 @@ func (r *QualityMonitorResource) Metadata(ctx context.Context, req resource.Meta func (r *QualityMonitorResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { attrs, blocks := tfschema.ResourceStructToSchemaMap(ctx, MonitorInfoExtended{}, func(c tfschema.CustomizableSchema) tfschema.CustomizableSchema { + c.ConfigureAsSdkV2Compatible() c.SetRequired("assets_dir") c.SetRequired("output_schema_name") c.SetReadOnly("monitor_version") diff --git a/internal/providers/pluginfw/products/sharing/resource_share.go b/internal/providers/pluginfw/products/sharing/resource_share.go index 39d1cf2cb4..17c10137a1 100644 --- a/internal/providers/pluginfw/products/sharing/resource_share.go +++ b/internal/providers/pluginfw/products/sharing/resource_share.go @@ -145,6 +145,7 @@ func (r *ShareResource) Metadata(ctx context.Context, req resource.MetadataReque func (r *ShareResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { attrs, blocks := tfschema.ResourceStructToSchemaMap(ctx, ShareInfoExtended{}, func(c tfschema.CustomizableSchema) tfschema.CustomizableSchema { + c.ConfigureAsSdkV2Compatible() c.SetRequired("name") c.AddPlanModifier(stringplanmodifier.RequiresReplace(), "name") // ForceNew diff --git a/internal/providers/pluginfw/tfschema/attribute_converter.go b/internal/providers/pluginfw/tfschema/attribute_converter.go index db2b1bda5c..8611509c08 100644 --- a/internal/providers/pluginfw/tfschema/attribute_converter.go +++ b/internal/providers/pluginfw/tfschema/attribute_converter.go @@ -1,6 +1,34 @@ package tfschema -type BlockToAttributeConverter interface { - // ConvertBlockToAttribute converts a contained block to its corresponding attribute type. - ConvertBlockToAttribute(string) BaseSchemaBuilder +// Blockable is an interface that can be implemented by an AttributeBuilder to convert it to a BlockBuilder. +type Blockable interface { + // ToBlock converts the AttributeBuilder to a BlockBuilder. + ToBlock() BlockBuilder +} + +// convertAttributesToBlocks converts all attributes implementing the Blockable interface to blocks, returning +// a new NestedBlockObject with the converted attributes and the original blocks. +func convertAttributesToBlocks(attributes map[string]AttributeBuilder, blocks map[string]BlockBuilder) NestedBlockObject { + newAttributes := make(map[string]AttributeBuilder) + newBlocks := make(map[string]BlockBuilder) + for name, attr := range attributes { + if lnab, ok := attr.(Blockable); ok { + newBlocks[name] = lnab.ToBlock() + } else { + newAttributes[name] = attr + } + } + for name, block := range blocks { + newBlocks[name] = block + } + if len(newAttributes) == 0 { + newAttributes = nil + } + if len(newBlocks) == 0 { + newBlocks = nil + } + return NestedBlockObject{ + Attributes: newAttributes, + Blocks: newBlocks, + } } diff --git a/internal/providers/pluginfw/tfschema/customizable_schema.go b/internal/providers/pluginfw/tfschema/customizable_schema.go index b2837371a5..217bfa888e 100644 --- a/internal/providers/pluginfw/tfschema/customizable_schema.go +++ b/internal/providers/pluginfw/tfschema/customizable_schema.go @@ -202,32 +202,12 @@ func (s *CustomizableSchema) SetReadOnly(path ...string) *CustomizableSchema { return s } -// ConvertToAttribute converts the last element of the path from a block to an attribute. -// It panics if the path is empty, if the path does not exist in the schema, or if the path -// points to an attribute, not a block. -func (s *CustomizableSchema) ConvertToAttribute(path ...string) *CustomizableSchema { - if len(path) == 0 { - panic(fmt.Errorf("ConvertToAttribute called on root schema. %s", common.TerraformBugErrorMessage)) - } - field := path[len(path)-1] - - cb := func(attr BaseSchemaBuilder) BaseSchemaBuilder { - switch a := attr.(type) { - case BlockToAttributeConverter: - return a.ConvertBlockToAttribute(field) - default: - panic(fmt.Errorf("ConvertToAttribute called on invalid attribute type: %s. %s", reflect.TypeOf(attr).String(), common.TerraformBugErrorMessage)) - } - } - - // We have to go only as far as the second-to-last entry, since we need to change the parent schema - // by moving the last entry from a block to an attribute. - if len(path) == 1 { - s.attr = cb(s.attr) - } else { - navigateSchemaWithCallback(&s.attr, cb, path[0:len(path)-1]...) - } - +// ConfigureAsSdkV2Compatible modifies the underlying schema to be compatible with SDKv2. This method must +// be called on all resources that were originally implemented using the SDKv2 and are migrated to the plugin +// framework. +func (s *CustomizableSchema) ConfigureAsSdkV2Compatible() *CustomizableSchema { + nbo := s.attr.(SingleNestedBlockBuilder).NestedObject + s.attr = SingleNestedBlockBuilder{NestedObject: convertAttributesToBlocks(nbo.Attributes, nbo.Blocks)} return s } diff --git a/internal/providers/pluginfw/tfschema/customizable_schema_test.go b/internal/providers/pluginfw/tfschema/customizable_schema_test.go index 0b6fc49ebd..c2d691da44 100644 --- a/internal/providers/pluginfw/tfschema/customizable_schema_test.go +++ b/internal/providers/pluginfw/tfschema/customizable_schema_test.go @@ -75,7 +75,7 @@ func TestCustomizeSchemaSetRequired(t *testing.T) { return c }) - assert.True(t, scm.Blocks["nested"].(schema.ListNestedBlock).NestedObject.Attributes["enabled"].IsRequired()) + assert.True(t, scm.Attributes["nested"].(schema.ListNestedAttribute).NestedObject.Attributes["enabled"].IsRequired()) } func TestCustomizeSchemaSetOptional(t *testing.T) { @@ -93,7 +93,7 @@ func TestCustomizeSchemaSetSensitive(t *testing.T) { return c }) - assert.True(t, scm.Blocks["nested"].(schema.ListNestedBlock).NestedObject.Attributes["name"].IsSensitive()) + assert.True(t, scm.Attributes["nested"].(schema.ListNestedAttribute).NestedObject.Attributes["name"].IsSensitive()) } func TestCustomizeSchemaSetDeprecated(t *testing.T) { @@ -127,7 +127,7 @@ func (testTfSdkListNestedAttribute) GetComplexFieldTypes(context.Context) map[st func TestCustomizeSchemaSetReadOnly_RecursivelySetsFieldsOfListNestedAttributes(t *testing.T) { scm := ResourceStructToSchema(context.Background(), testTfSdkListNestedAttribute{}, func(c CustomizableSchema) CustomizableSchema { - c.ConvertToAttribute("list").SetReadOnly("list") + c.SetReadOnly("list") return c }) for _, field := range []string{"name", "enabled"} { @@ -160,12 +160,13 @@ func TestCustomizeSchemaObjectTypeValidatorAdded(t *testing.T) { return c }) - assert.True(t, len(scm.Blocks["nested_slice_object"].(schema.ListNestedBlock).Validators) == 1) + assert.True(t, len(scm.Attributes["nested_slice_object"].(schema.ListNestedAttribute).Validators) == 1) } func TestCustomizeSchema_SetRequired_PanicOnBlock(t *testing.T) { assert.Panics(t, func() { _ = ResourceStructToSchema(context.Background(), TestTfSdk{}, func(c CustomizableSchema) CustomizableSchema { + c.ConfigureAsSdkV2Compatible() c.SetRequired("nested") return c }) @@ -175,6 +176,7 @@ func TestCustomizeSchema_SetRequired_PanicOnBlock(t *testing.T) { func TestCustomizeSchema_SetOptional_PanicOnBlock(t *testing.T) { assert.Panics(t, func() { _ = ResourceStructToSchema(context.Background(), TestTfSdk{}, func(c CustomizableSchema) CustomizableSchema { + c.ConfigureAsSdkV2Compatible() c.SetOptional("nested") return c }) @@ -184,6 +186,7 @@ func TestCustomizeSchema_SetOptional_PanicOnBlock(t *testing.T) { func TestCustomizeSchema_SetSensitive_PanicOnBlock(t *testing.T) { assert.Panics(t, func() { _ = ResourceStructToSchema(context.Background(), TestTfSdk{}, func(c CustomizableSchema) CustomizableSchema { + c.ConfigureAsSdkV2Compatible() c.SetSensitive("nested") return c }) @@ -193,6 +196,7 @@ func TestCustomizeSchema_SetSensitive_PanicOnBlock(t *testing.T) { func TestCustomizeSchema_SetReadOnly_PanicOnBlock(t *testing.T) { assert.Panics(t, func() { _ = ResourceStructToSchema(context.Background(), TestTfSdk{}, func(c CustomizableSchema) CustomizableSchema { + c.ConfigureAsSdkV2Compatible() c.SetReadOnly("nested") return c }) @@ -202,6 +206,7 @@ func TestCustomizeSchema_SetReadOnly_PanicOnBlock(t *testing.T) { func TestCustomizeSchema_SetComputed_PanicOnBlock(t *testing.T) { assert.Panics(t, func() { _ = ResourceStructToSchema(context.Background(), TestTfSdk{}, func(c CustomizableSchema) CustomizableSchema { + c.ConfigureAsSdkV2Compatible() c.SetComputed("nested") return c }) @@ -258,22 +263,21 @@ func (m mockValidator) ValidateObject(context.Context, validator.ObjectRequest, var _ validator.List = mockValidator{} var _ validator.Object = mockValidator{} -func TestCustomizeSchema_ConvertToAttribute(t *testing.T) { +func TestCustomizeSchema_ConfigureAsSdkV2Compatible(t *testing.T) { v := mockValidator{} pm := mockPlanModifier{} testCases := []struct { name string baseSchema NestedBlockObject - path []string want NestedBlockObject expectPanic bool }{ { - name: "ListNestedBlock", + name: "ListNestedAttribute", baseSchema: NestedBlockObject{ - Blocks: map[string]BlockBuilder{ - "list": ListNestedBlockBuilder{ - NestedObject: NestedBlockObject{ + Attributes: map[string]AttributeBuilder{ + "list": ListNestedAttributeBuilder{ + NestedObject: NestedAttributeObject{ Attributes: map[string]AttributeBuilder{ "attr": StringAttributeBuilder{}, }, @@ -284,11 +288,10 @@ func TestCustomizeSchema_ConvertToAttribute(t *testing.T) { }, }, }, - path: []string{"list"}, want: NestedBlockObject{ - Attributes: map[string]AttributeBuilder{ - "list": ListNestedAttributeBuilder{ - NestedObject: NestedAttributeObject{ + Blocks: map[string]BlockBuilder{ + "list": ListNestedBlockBuilder{ + NestedObject: NestedBlockObject{ Attributes: map[string]AttributeBuilder{ "attr": StringAttributeBuilder{}, }, @@ -301,14 +304,14 @@ func TestCustomizeSchema_ConvertToAttribute(t *testing.T) { }, }, { - name: "ListNestedBlock/CalledOnInnerBlock", + name: "ListNestedAttribute/RecursiveBlocks", baseSchema: NestedBlockObject{ - Blocks: map[string]BlockBuilder{ - "list": ListNestedBlockBuilder{ - NestedObject: NestedBlockObject{ - Blocks: map[string]BlockBuilder{ - "nested_block": ListNestedBlockBuilder{ - NestedObject: NestedBlockObject{ + Attributes: map[string]AttributeBuilder{ + "list": ListNestedAttributeBuilder{ + NestedObject: NestedAttributeObject{ + Attributes: map[string]AttributeBuilder{ + "nested_block": ListNestedAttributeBuilder{ + NestedObject: NestedAttributeObject{ Attributes: map[string]AttributeBuilder{ "attr": StringAttributeBuilder{}, }, @@ -319,14 +322,13 @@ func TestCustomizeSchema_ConvertToAttribute(t *testing.T) { }, }, }, - path: []string{"list", "nested_block"}, want: NestedBlockObject{ Blocks: map[string]BlockBuilder{ "list": ListNestedBlockBuilder{ NestedObject: NestedBlockObject{ - Attributes: map[string]AttributeBuilder{ - "nested_block": ListNestedAttributeBuilder{ - NestedObject: NestedAttributeObject{ + Blocks: map[string]BlockBuilder{ + "nested_block": ListNestedBlockBuilder{ + NestedObject: NestedBlockObject{ Attributes: map[string]AttributeBuilder{ "attr": StringAttributeBuilder{}, }, @@ -339,23 +341,8 @@ func TestCustomizeSchema_ConvertToAttribute(t *testing.T) { }, }, { - name: "SingleNestedBlock", + name: "SingleNestedBlock/Panics", baseSchema: NestedBlockObject{ - Blocks: map[string]BlockBuilder{ - "single": SingleNestedBlockBuilder{ - NestedObject: NestedBlockObject{ - Attributes: map[string]AttributeBuilder{ - "attr": StringAttributeBuilder{}, - }, - }, - DeprecationMessage: "deprecated", - Validators: []validator.Object{v}, - PlanModifiers: []planmodifier.Object{pm}, - }, - }, - }, - path: []string{"single"}, - want: NestedBlockObject{ Attributes: map[string]AttributeBuilder{ "single": SingleNestedAttributeBuilder{ Attributes: map[string]AttributeBuilder{ @@ -367,46 +354,6 @@ func TestCustomizeSchema_ConvertToAttribute(t *testing.T) { }, }, }, - }, - { - name: "SingleNestedBlock/RecursiveBlocks", - baseSchema: NestedBlockObject{ - Blocks: map[string]BlockBuilder{ - "single": SingleNestedBlockBuilder{ - NestedObject: NestedBlockObject{ - Blocks: map[string]BlockBuilder{ - "nested_block": ListNestedBlockBuilder{ - NestedObject: NestedBlockObject{ - Attributes: map[string]AttributeBuilder{ - "attr": StringAttributeBuilder{}, - }, - }, - }, - }, - }, - }, - }, - }, - path: []string{"single"}, - want: NestedBlockObject{ - Attributes: map[string]AttributeBuilder{ - "single": SingleNestedAttributeBuilder{ - Attributes: map[string]AttributeBuilder{ - "nested_block": ListNestedAttributeBuilder{ - NestedObject: NestedAttributeObject{ - Attributes: map[string]AttributeBuilder{ - "attr": StringAttributeBuilder{}, - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "PanicOnEmptyPath", - path: nil, expectPanic: true, }, } @@ -414,10 +361,10 @@ func TestCustomizeSchema_ConvertToAttribute(t *testing.T) { t.Run(c.name, func(t *testing.T) { if c.expectPanic { assert.Panics(t, func() { - ConstructCustomizableSchema(c.baseSchema).ConvertToAttribute(c.path...) + ConstructCustomizableSchema(c.baseSchema).ConfigureAsSdkV2Compatible() }) } else { - got := ConstructCustomizableSchema(c.baseSchema).ConvertToAttribute(c.path...) + got := ConstructCustomizableSchema(c.baseSchema).ConfigureAsSdkV2Compatible() assert.Equal(t, c.want, got.attr.(SingleNestedBlockBuilder).NestedObject) } }) diff --git a/internal/providers/pluginfw/tfschema/list_nested_attribute.go b/internal/providers/pluginfw/tfschema/list_nested_attribute.go index adc97a7ed8..fee5da83c8 100644 --- a/internal/providers/pluginfw/tfschema/list_nested_attribute.go +++ b/internal/providers/pluginfw/tfschema/list_nested_attribute.go @@ -103,3 +103,12 @@ func (a ListNestedAttributeBuilder) AddPlanModifier(v planmodifier.List) BaseSch a.PlanModifiers = append(a.PlanModifiers, v) return a } + +func (a ListNestedAttributeBuilder) ToBlock() BlockBuilder { + return ListNestedBlockBuilder{ + NestedObject: convertAttributesToBlocks(a.NestedObject.Attributes, nil), + DeprecationMessage: a.DeprecationMessage, + Validators: a.Validators, + PlanModifiers: a.PlanModifiers, + } +} diff --git a/internal/providers/pluginfw/tfschema/single_nested_attribute.go b/internal/providers/pluginfw/tfschema/single_nested_attribute.go index 10885e98e7..0321d0c37f 100644 --- a/internal/providers/pluginfw/tfschema/single_nested_attribute.go +++ b/internal/providers/pluginfw/tfschema/single_nested_attribute.go @@ -1,6 +1,9 @@ package tfschema import ( + "fmt" + + "github.com/databricks/terraform-provider-databricks/common" dataschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" @@ -102,3 +105,7 @@ func (a SingleNestedAttributeBuilder) AddPlanModifier(v planmodifier.Object) Att a.PlanModifiers = append(a.PlanModifiers, v) return a } + +func (a SingleNestedAttributeBuilder) ToBlock() BlockBuilder { + panic(fmt.Errorf("ToBlock() called on SingleNestedAttributeBuilder. This means that the corresponding field is a types.Object, which should never happen for legacy resources. %s", common.TerraformBugErrorMessage)) +} diff --git a/internal/providers/pluginfw/tfschema/single_nested_block.go b/internal/providers/pluginfw/tfschema/single_nested_block.go index 59508235b6..1b88c9ae01 100644 --- a/internal/providers/pluginfw/tfschema/single_nested_block.go +++ b/internal/providers/pluginfw/tfschema/single_nested_block.go @@ -68,19 +68,3 @@ func (a SingleNestedBlockBuilder) AddPlanModifier(v planmodifier.Object) BaseSch a.PlanModifiers = append(a.PlanModifiers, v) return a } - -func (a SingleNestedBlockBuilder) ConvertBlockToAttribute(field string) BaseSchemaBuilder { - elem, ok := a.NestedObject.Blocks[field] - if !ok { - panic(fmt.Errorf("field %s does not exist in nested block", field)) - } - if a.NestedObject.Attributes == nil { - a.NestedObject.Attributes = make(map[string]AttributeBuilder) - } - a.NestedObject.Attributes[field] = elem.ToAttribute() - delete(a.NestedObject.Blocks, field) - if len(a.NestedObject.Blocks) == 0 { - a.NestedObject.Blocks = nil - } - return a -} diff --git a/internal/providers/pluginfw/tfschema/struct_to_schema.go b/internal/providers/pluginfw/tfschema/struct_to_schema.go index 318029a34e..4612b63eef 100644 --- a/internal/providers/pluginfw/tfschema/struct_to_schema.go +++ b/internal/providers/pluginfw/tfschema/struct_to_schema.go @@ -25,7 +25,6 @@ type structTag struct { func typeToSchema(ctx context.Context, v reflect.Value) NestedBlockObject { scmAttr := map[string]AttributeBuilder{} - scmBlock := map[string]BlockBuilder{} rk := v.Kind() if rk == reflect.Ptr { v = v.Elem() @@ -88,9 +87,8 @@ func typeToSchema(ctx context.Context, v reflect.Value) NestedBlockObject { scmAttr[fieldName] = MapAttributeBuilder{ElementType: containerType.ElementType()} } default: - // The element type is a TFSDK type. Map fields are treated as MapNestedAttributes. For compatibility, - // list fields are treated as ListNestedBlocks. - // TODO: Change the default for lists to ListNestedAttribute. + // The element type is a TFSDK type. Map fields are treated as MapNestedAttributes, and list + // fields are treated as ListNestedAttributes. Object fields are treated as SingleNestedAttributes. fieldValue := reflect.New(fieldType).Elem() // Generate the nested block schema @@ -101,9 +99,8 @@ func typeToSchema(ctx context.Context, v reflect.Value) NestedBlockObject { if structTag.singleObject { validators = append(validators, listvalidator.SizeAtMost(1)) } - // Note that this is being added to the block map, not the attribute map. - scmBlock[fieldName] = ListNestedBlockBuilder{ - NestedObject: nestedSchema, + scmAttr[fieldName] = ListNestedAttributeBuilder{ + NestedObject: nestedSchema.ToNestedAttributeObject(), Validators: validators, } case types.Map: @@ -142,22 +139,24 @@ func typeToSchema(ctx context.Context, v reflect.Value) NestedBlockObject { } panic(fmt.Errorf("unexpected type %T in tfsdk structs, expected a plugin framework value type. %s", value, common.TerraformBugErrorMessage)) } - // types.List fields of complex types correspond to ListNestedBlock, which don't have optional/required/computed flags. - // When these fields are later changed to use ListNestedAttribute, we can inline the if statement below, as all fields - // will be attributes. - if attr, ok := scmAttr[fieldName]; ok { + attr := scmAttr[fieldName] + if structTag.computed { + // Computed attributes are always computed and may be optional. + attr = attr.SetComputed() + if structTag.optional { + attr = attr.SetOptional() + } + } else { + // Non-computed attributes must be either optional or required. if structTag.optional { attr = attr.SetOptional() } else { attr = attr.SetRequired() } - if structTag.computed { - attr = attr.SetComputed() - } - scmAttr[fieldName] = attr } + scmAttr[fieldName] = attr } - return NestedBlockObject{Attributes: scmAttr, Blocks: scmBlock} + return NestedBlockObject{Attributes: scmAttr} } func getStructTag(field reflect.StructField) structTag { diff --git a/internal/providers/pluginfw/tfschema/struct_to_schema_test.go b/internal/providers/pluginfw/tfschema/struct_to_schema_test.go index 6a410ffced..be22464d61 100644 --- a/internal/providers/pluginfw/tfschema/struct_to_schema_test.go +++ b/internal/providers/pluginfw/tfschema/struct_to_schema_test.go @@ -265,10 +265,11 @@ func TestStructToSchemaExpectedError(t *testing.T) { } func TestComputedField(t *testing.T) { - // Test that ComputedTag field is computed and required + // Test that ComputedTag field is computed scm := ResourceStructToSchema(context.Background(), TestComputedTfSdk{}, nil) assert.True(t, scm.Attributes["computedtag"].IsComputed()) - assert.True(t, scm.Attributes["computedtag"].IsRequired()) + // Computed fields can never be required + assert.False(t, scm.Attributes["computedtag"].IsRequired()) // Test that MultipleTags field is computed and optional assert.True(t, scm.Attributes["multipletags"].IsComputed()) diff --git a/internal/service/apps_tf/model.go b/internal/service/apps_tf/model.go index ec2b5a415d..e570ec50e4 100755 --- a/internal/service/apps_tf/model.go +++ b/internal/service/apps_tf/model.go @@ -24,11 +24,11 @@ import ( type App struct { // The active deployment of the app. A deployment is considered active when // it has been deployed to the app compute. - ActiveDeployment types.List `tfsdk:"active_deployment" tf:"optional,object"` + ActiveDeployment types.Object `tfsdk:"active_deployment" tf:"optional,object"` - AppStatus types.List `tfsdk:"app_status" tf:"optional,object"` + AppStatus types.Object `tfsdk:"app_status" tf:"optional,object"` - ComputeStatus types.List `tfsdk:"compute_status" tf:"optional,object"` + ComputeStatus types.Object `tfsdk:"compute_status" tf:"optional,object"` // The creation time of the app. Formatted timestamp in ISO 6801. CreateTime types.String `tfsdk:"create_time" tf:"computed,optional"` // The email of the user that created the app. @@ -44,7 +44,7 @@ type App struct { Name types.String `tfsdk:"name" tf:""` // The pending deployment of the app. A deployment is considered pending // when it is being prepared for deployment to the app compute. - PendingDeployment types.List `tfsdk:"pending_deployment" tf:"optional,object"` + PendingDeployment types.Object `tfsdk:"pending_deployment" tf:"optional,object"` // Resources for the app. Resources types.List `tfsdk:"resources" tf:"optional"` @@ -114,23 +114,15 @@ func (o App) ToObjectValue(ctx context.Context) basetypes.ObjectValue { func (o App) Type(ctx context.Context) attr.Type { return types.ObjectType{ AttrTypes: map[string]attr.Type{ - "active_deployment": basetypes.ListType{ - ElemType: AppDeployment{}.Type(ctx), - }, - "app_status": basetypes.ListType{ - ElemType: ApplicationStatus{}.Type(ctx), - }, - "compute_status": basetypes.ListType{ - ElemType: ComputeStatus{}.Type(ctx), - }, + "active_deployment": AppDeployment{}.Type(ctx), + "app_status": ApplicationStatus{}.Type(ctx), + "compute_status": ComputeStatus{}.Type(ctx), "create_time": types.StringType, "creator": types.StringType, "default_source_code_path": types.StringType, "description": types.StringType, "name": types.StringType, - "pending_deployment": basetypes.ListType{ - ElemType: AppDeployment{}.Type(ctx), - }, + "pending_deployment": AppDeployment{}.Type(ctx), "resources": basetypes.ListType{ ElemType: AppResource{}.Type(ctx), }, @@ -153,7 +145,10 @@ func (o *App) GetActiveDeployment(ctx context.Context) (AppDeployment, bool) { return e, false } var v []AppDeployment - d := o.ActiveDeployment.ElementsAs(ctx, &v, true) + d := o.ActiveDeployment.As(ctx, &v, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) if d.HasError() { panic(pluginfwcommon.DiagToString(d)) } @@ -165,9 +160,8 @@ func (o *App) GetActiveDeployment(ctx context.Context) (AppDeployment, bool) { // SetActiveDeployment sets the value of the ActiveDeployment field in App. func (o *App) SetActiveDeployment(ctx context.Context, v AppDeployment) { - vs := []attr.Value{v.ToObjectValue(ctx)} - t := o.Type(ctx).(basetypes.ObjectType).AttrTypes["active_deployment"] - o.ActiveDeployment = types.ListValueMust(t, vs) + vs := v.ToObjectValue(ctx) + o.ActiveDeployment = vs } // GetAppStatus returns the value of the AppStatus field in App as @@ -179,7 +173,10 @@ func (o *App) GetAppStatus(ctx context.Context) (ApplicationStatus, bool) { return e, false } var v []ApplicationStatus - d := o.AppStatus.ElementsAs(ctx, &v, true) + d := o.AppStatus.As(ctx, &v, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) if d.HasError() { panic(pluginfwcommon.DiagToString(d)) } @@ -191,9 +188,8 @@ func (o *App) GetAppStatus(ctx context.Context) (ApplicationStatus, bool) { // SetAppStatus sets the value of the AppStatus field in App. func (o *App) SetAppStatus(ctx context.Context, v ApplicationStatus) { - vs := []attr.Value{v.ToObjectValue(ctx)} - t := o.Type(ctx).(basetypes.ObjectType).AttrTypes["app_status"] - o.AppStatus = types.ListValueMust(t, vs) + vs := v.ToObjectValue(ctx) + o.AppStatus = vs } // GetComputeStatus returns the value of the ComputeStatus field in App as @@ -205,7 +201,10 @@ func (o *App) GetComputeStatus(ctx context.Context) (ComputeStatus, bool) { return e, false } var v []ComputeStatus - d := o.ComputeStatus.ElementsAs(ctx, &v, true) + d := o.ComputeStatus.As(ctx, &v, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) if d.HasError() { panic(pluginfwcommon.DiagToString(d)) } @@ -217,9 +216,8 @@ func (o *App) GetComputeStatus(ctx context.Context) (ComputeStatus, bool) { // SetComputeStatus sets the value of the ComputeStatus field in App. func (o *App) SetComputeStatus(ctx context.Context, v ComputeStatus) { - vs := []attr.Value{v.ToObjectValue(ctx)} - t := o.Type(ctx).(basetypes.ObjectType).AttrTypes["compute_status"] - o.ComputeStatus = types.ListValueMust(t, vs) + vs := v.ToObjectValue(ctx) + o.ComputeStatus = vs } // GetPendingDeployment returns the value of the PendingDeployment field in App as @@ -231,7 +229,10 @@ func (o *App) GetPendingDeployment(ctx context.Context) (AppDeployment, bool) { return e, false } var v []AppDeployment - d := o.PendingDeployment.ElementsAs(ctx, &v, true) + d := o.PendingDeployment.As(ctx, &v, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) if d.HasError() { panic(pluginfwcommon.DiagToString(d)) } @@ -243,9 +244,8 @@ func (o *App) GetPendingDeployment(ctx context.Context) (AppDeployment, bool) { // SetPendingDeployment sets the value of the PendingDeployment field in App. func (o *App) SetPendingDeployment(ctx context.Context, v AppDeployment) { - vs := []attr.Value{v.ToObjectValue(ctx)} - t := o.Type(ctx).(basetypes.ObjectType).AttrTypes["pending_deployment"] - o.PendingDeployment = types.ListValueMust(t, vs) + vs := v.ToObjectValue(ctx) + o.PendingDeployment = vs } // GetResources returns the value of the Resources field in App as @@ -422,7 +422,7 @@ type AppDeployment struct { // The email of the user creates the deployment. Creator types.String `tfsdk:"creator" tf:"computed,optional"` // The deployment artifacts for an app. - DeploymentArtifacts types.List `tfsdk:"deployment_artifacts" tf:"optional,object"` + DeploymentArtifacts types.Object `tfsdk:"deployment_artifacts" tf:"optional,object"` // The unique id of the deployment. DeploymentId types.String `tfsdk:"deployment_id" tf:"optional"` // The mode of which the deployment will manage the source code. @@ -436,7 +436,7 @@ type AppDeployment struct { // the deployment. SourceCodePath types.String `tfsdk:"source_code_path" tf:"optional"` // Status and status message of the deployment - Status types.List `tfsdk:"status" tf:"optional,object"` + Status types.Object `tfsdk:"status" tf:"optional,object"` // The update time of the deployment. Formatted timestamp in ISO 6801. UpdateTime types.String `tfsdk:"update_time" tf:"computed,optional"` } @@ -483,18 +483,14 @@ func (o AppDeployment) ToObjectValue(ctx context.Context) basetypes.ObjectValue func (o AppDeployment) Type(ctx context.Context) attr.Type { return types.ObjectType{ AttrTypes: map[string]attr.Type{ - "create_time": types.StringType, - "creator": types.StringType, - "deployment_artifacts": basetypes.ListType{ - ElemType: AppDeploymentArtifacts{}.Type(ctx), - }, - "deployment_id": types.StringType, - "mode": types.StringType, - "source_code_path": types.StringType, - "status": basetypes.ListType{ - ElemType: AppDeploymentStatus{}.Type(ctx), - }, - "update_time": types.StringType, + "create_time": types.StringType, + "creator": types.StringType, + "deployment_artifacts": AppDeploymentArtifacts{}.Type(ctx), + "deployment_id": types.StringType, + "mode": types.StringType, + "source_code_path": types.StringType, + "status": AppDeploymentStatus{}.Type(ctx), + "update_time": types.StringType, }, } } @@ -508,7 +504,10 @@ func (o *AppDeployment) GetDeploymentArtifacts(ctx context.Context) (AppDeployme return e, false } var v []AppDeploymentArtifacts - d := o.DeploymentArtifacts.ElementsAs(ctx, &v, true) + d := o.DeploymentArtifacts.As(ctx, &v, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) if d.HasError() { panic(pluginfwcommon.DiagToString(d)) } @@ -520,9 +519,8 @@ func (o *AppDeployment) GetDeploymentArtifacts(ctx context.Context) (AppDeployme // SetDeploymentArtifacts sets the value of the DeploymentArtifacts field in AppDeployment. func (o *AppDeployment) SetDeploymentArtifacts(ctx context.Context, v AppDeploymentArtifacts) { - vs := []attr.Value{v.ToObjectValue(ctx)} - t := o.Type(ctx).(basetypes.ObjectType).AttrTypes["deployment_artifacts"] - o.DeploymentArtifacts = types.ListValueMust(t, vs) + vs := v.ToObjectValue(ctx) + o.DeploymentArtifacts = vs } // GetStatus returns the value of the Status field in AppDeployment as @@ -534,7 +532,10 @@ func (o *AppDeployment) GetStatus(ctx context.Context) (AppDeploymentStatus, boo return e, false } var v []AppDeploymentStatus - d := o.Status.ElementsAs(ctx, &v, true) + d := o.Status.As(ctx, &v, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) if d.HasError() { panic(pluginfwcommon.DiagToString(d)) } @@ -546,9 +547,8 @@ func (o *AppDeployment) GetStatus(ctx context.Context) (AppDeploymentStatus, boo // SetStatus sets the value of the Status field in AppDeployment. func (o *AppDeployment) SetStatus(ctx context.Context, v AppDeploymentStatus) { - vs := []attr.Value{v.ToObjectValue(ctx)} - t := o.Type(ctx).(basetypes.ObjectType).AttrTypes["status"] - o.Status = types.ListValueMust(t, vs) + vs := v.ToObjectValue(ctx) + o.Status = vs } type AppDeploymentArtifacts struct { @@ -922,15 +922,15 @@ type AppResource struct { // Description of the App Resource. Description types.String `tfsdk:"description" tf:"optional"` - Job types.List `tfsdk:"job" tf:"optional,object"` + Job types.Object `tfsdk:"job" tf:"optional,object"` // Name of the App Resource. Name types.String `tfsdk:"name" tf:""` - Secret types.List `tfsdk:"secret" tf:"optional,object"` + Secret types.Object `tfsdk:"secret" tf:"optional,object"` - ServingEndpoint types.List `tfsdk:"serving_endpoint" tf:"optional,object"` + ServingEndpoint types.Object `tfsdk:"serving_endpoint" tf:"optional,object"` - SqlWarehouse types.List `tfsdk:"sql_warehouse" tf:"optional,object"` + SqlWarehouse types.Object `tfsdk:"sql_warehouse" tf:"optional,object"` } func (newState *AppResource) SyncEffectiveFieldsDuringCreateOrUpdate(plan AppResource) { @@ -975,20 +975,12 @@ func (o AppResource) ToObjectValue(ctx context.Context) basetypes.ObjectValue { func (o AppResource) Type(ctx context.Context) attr.Type { return types.ObjectType{ AttrTypes: map[string]attr.Type{ - "description": types.StringType, - "job": basetypes.ListType{ - ElemType: AppResourceJob{}.Type(ctx), - }, - "name": types.StringType, - "secret": basetypes.ListType{ - ElemType: AppResourceSecret{}.Type(ctx), - }, - "serving_endpoint": basetypes.ListType{ - ElemType: AppResourceServingEndpoint{}.Type(ctx), - }, - "sql_warehouse": basetypes.ListType{ - ElemType: AppResourceSqlWarehouse{}.Type(ctx), - }, + "description": types.StringType, + "job": AppResourceJob{}.Type(ctx), + "name": types.StringType, + "secret": AppResourceSecret{}.Type(ctx), + "serving_endpoint": AppResourceServingEndpoint{}.Type(ctx), + "sql_warehouse": AppResourceSqlWarehouse{}.Type(ctx), }, } } @@ -1002,7 +994,10 @@ func (o *AppResource) GetJob(ctx context.Context) (AppResourceJob, bool) { return e, false } var v []AppResourceJob - d := o.Job.ElementsAs(ctx, &v, true) + d := o.Job.As(ctx, &v, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) if d.HasError() { panic(pluginfwcommon.DiagToString(d)) } @@ -1014,9 +1009,8 @@ func (o *AppResource) GetJob(ctx context.Context) (AppResourceJob, bool) { // SetJob sets the value of the Job field in AppResource. func (o *AppResource) SetJob(ctx context.Context, v AppResourceJob) { - vs := []attr.Value{v.ToObjectValue(ctx)} - t := o.Type(ctx).(basetypes.ObjectType).AttrTypes["job"] - o.Job = types.ListValueMust(t, vs) + vs := v.ToObjectValue(ctx) + o.Job = vs } // GetSecret returns the value of the Secret field in AppResource as @@ -1028,7 +1022,10 @@ func (o *AppResource) GetSecret(ctx context.Context) (AppResourceSecret, bool) { return e, false } var v []AppResourceSecret - d := o.Secret.ElementsAs(ctx, &v, true) + d := o.Secret.As(ctx, &v, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) if d.HasError() { panic(pluginfwcommon.DiagToString(d)) } @@ -1040,9 +1037,8 @@ func (o *AppResource) GetSecret(ctx context.Context) (AppResourceSecret, bool) { // SetSecret sets the value of the Secret field in AppResource. func (o *AppResource) SetSecret(ctx context.Context, v AppResourceSecret) { - vs := []attr.Value{v.ToObjectValue(ctx)} - t := o.Type(ctx).(basetypes.ObjectType).AttrTypes["secret"] - o.Secret = types.ListValueMust(t, vs) + vs := v.ToObjectValue(ctx) + o.Secret = vs } // GetServingEndpoint returns the value of the ServingEndpoint field in AppResource as @@ -1054,7 +1050,10 @@ func (o *AppResource) GetServingEndpoint(ctx context.Context) (AppResourceServin return e, false } var v []AppResourceServingEndpoint - d := o.ServingEndpoint.ElementsAs(ctx, &v, true) + d := o.ServingEndpoint.As(ctx, &v, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) if d.HasError() { panic(pluginfwcommon.DiagToString(d)) } @@ -1066,9 +1065,8 @@ func (o *AppResource) GetServingEndpoint(ctx context.Context) (AppResourceServin // SetServingEndpoint sets the value of the ServingEndpoint field in AppResource. func (o *AppResource) SetServingEndpoint(ctx context.Context, v AppResourceServingEndpoint) { - vs := []attr.Value{v.ToObjectValue(ctx)} - t := o.Type(ctx).(basetypes.ObjectType).AttrTypes["serving_endpoint"] - o.ServingEndpoint = types.ListValueMust(t, vs) + vs := v.ToObjectValue(ctx) + o.ServingEndpoint = vs } // GetSqlWarehouse returns the value of the SqlWarehouse field in AppResource as @@ -1080,7 +1078,10 @@ func (o *AppResource) GetSqlWarehouse(ctx context.Context) (AppResourceSqlWareho return e, false } var v []AppResourceSqlWarehouse - d := o.SqlWarehouse.ElementsAs(ctx, &v, true) + d := o.SqlWarehouse.As(ctx, &v, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) if d.HasError() { panic(pluginfwcommon.DiagToString(d)) } @@ -1092,9 +1093,8 @@ func (o *AppResource) GetSqlWarehouse(ctx context.Context) (AppResourceSqlWareho // SetSqlWarehouse sets the value of the SqlWarehouse field in AppResource. func (o *AppResource) SetSqlWarehouse(ctx context.Context, v AppResourceSqlWarehouse) { - vs := []attr.Value{v.ToObjectValue(ctx)} - t := o.Type(ctx).(basetypes.ObjectType).AttrTypes["sql_warehouse"] - o.SqlWarehouse = types.ListValueMust(t, vs) + vs := v.ToObjectValue(ctx) + o.SqlWarehouse = vs } type AppResourceJob struct { @@ -1383,7 +1383,7 @@ func (o ComputeStatus) Type(ctx context.Context) attr.Type { // Create an app deployment type CreateAppDeploymentRequest struct { - AppDeployment types.List `tfsdk:"app_deployment" tf:"optional,object"` + AppDeployment types.Object `tfsdk:"app_deployment" tf:"optional,object"` // The name of the app. AppName types.String `tfsdk:"-"` } @@ -1423,10 +1423,8 @@ func (o CreateAppDeploymentRequest) ToObjectValue(ctx context.Context) basetypes func (o CreateAppDeploymentRequest) Type(ctx context.Context) attr.Type { return types.ObjectType{ AttrTypes: map[string]attr.Type{ - "app_deployment": basetypes.ListType{ - ElemType: AppDeployment{}.Type(ctx), - }, - "app_name": types.StringType, + "app_deployment": AppDeployment{}.Type(ctx), + "app_name": types.StringType, }, } } @@ -1440,7 +1438,10 @@ func (o *CreateAppDeploymentRequest) GetAppDeployment(ctx context.Context) (AppD return e, false } var v []AppDeployment - d := o.AppDeployment.ElementsAs(ctx, &v, true) + d := o.AppDeployment.As(ctx, &v, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) if d.HasError() { panic(pluginfwcommon.DiagToString(d)) } @@ -1452,14 +1453,13 @@ func (o *CreateAppDeploymentRequest) GetAppDeployment(ctx context.Context) (AppD // SetAppDeployment sets the value of the AppDeployment field in CreateAppDeploymentRequest. func (o *CreateAppDeploymentRequest) SetAppDeployment(ctx context.Context, v AppDeployment) { - vs := []attr.Value{v.ToObjectValue(ctx)} - t := o.Type(ctx).(basetypes.ObjectType).AttrTypes["app_deployment"] - o.AppDeployment = types.ListValueMust(t, vs) + vs := v.ToObjectValue(ctx) + o.AppDeployment = vs } // Create an app type CreateAppRequest struct { - App types.List `tfsdk:"app" tf:"optional,object"` + App types.Object `tfsdk:"app" tf:"optional,object"` } func (newState *CreateAppRequest) SyncEffectiveFieldsDuringCreateOrUpdate(plan CreateAppRequest) { @@ -1496,9 +1496,7 @@ func (o CreateAppRequest) ToObjectValue(ctx context.Context) basetypes.ObjectVal func (o CreateAppRequest) Type(ctx context.Context) attr.Type { return types.ObjectType{ AttrTypes: map[string]attr.Type{ - "app": basetypes.ListType{ - ElemType: App{}.Type(ctx), - }, + "app": App{}.Type(ctx), }, } } @@ -1512,7 +1510,10 @@ func (o *CreateAppRequest) GetApp(ctx context.Context) (App, bool) { return e, false } var v []App - d := o.App.ElementsAs(ctx, &v, true) + d := o.App.As(ctx, &v, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) if d.HasError() { panic(pluginfwcommon.DiagToString(d)) } @@ -1524,9 +1525,8 @@ func (o *CreateAppRequest) GetApp(ctx context.Context) (App, bool) { // SetApp sets the value of the App field in CreateAppRequest. func (o *CreateAppRequest) SetApp(ctx context.Context, v App) { - vs := []attr.Value{v.ToObjectValue(ctx)} - t := o.Type(ctx).(basetypes.ObjectType).AttrTypes["app"] - o.App = types.ListValueMust(t, vs) + vs := v.ToObjectValue(ctx) + o.App = vs } // Delete an app @@ -2157,7 +2157,7 @@ func (o StopAppRequest) Type(ctx context.Context) attr.Type { // Update an app type UpdateAppRequest struct { - App types.List `tfsdk:"app" tf:"optional,object"` + App types.Object `tfsdk:"app" tf:"optional,object"` // The name of the app. The name must contain only lowercase alphanumeric // characters and hyphens. It must be unique within the workspace. Name types.String `tfsdk:"-"` @@ -2198,9 +2198,7 @@ func (o UpdateAppRequest) ToObjectValue(ctx context.Context) basetypes.ObjectVal func (o UpdateAppRequest) Type(ctx context.Context) attr.Type { return types.ObjectType{ AttrTypes: map[string]attr.Type{ - "app": basetypes.ListType{ - ElemType: App{}.Type(ctx), - }, + "app": App{}.Type(ctx), "name": types.StringType, }, } @@ -2215,7 +2213,10 @@ func (o *UpdateAppRequest) GetApp(ctx context.Context) (App, bool) { return e, false } var v []App - d := o.App.ElementsAs(ctx, &v, true) + d := o.App.As(ctx, &v, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) if d.HasError() { panic(pluginfwcommon.DiagToString(d)) } @@ -2227,7 +2228,6 @@ func (o *UpdateAppRequest) GetApp(ctx context.Context) (App, bool) { // SetApp sets the value of the App field in UpdateAppRequest. func (o *UpdateAppRequest) SetApp(ctx context.Context, v App) { - vs := []attr.Value{v.ToObjectValue(ctx)} - t := o.Type(ctx).(basetypes.ObjectType).AttrTypes["app"] - o.App = types.ListValueMust(t, vs) + vs := v.ToObjectValue(ctx) + o.App = vs } diff --git a/internal/service/cleanrooms_tf/model.go b/internal/service/cleanrooms_tf/model.go index 8ff8e18842..71f1a62633 100755 --- a/internal/service/cleanrooms_tf/model.go +++ b/internal/service/cleanrooms_tf/model.go @@ -1534,7 +1534,7 @@ func (o ComplianceSecurityProfile) Type(ctx context.Context) attr.Type { return types.ObjectType{ AttrTypes: map[string]attr.Type{ "compliance_standards": basetypes.ListType{ - ElemType: types.String{}.Type(ctx), + ElemType: types.StringType, }, "is_enabled": types.BoolType, },