From c361ae62af4735ca1156dc6e31de24645a5c73d6 Mon Sep 17 00:00:00 2001 From: edwardfeng-db Date: Fri, 9 Aug 2024 17:04:57 +0200 Subject: [PATCH 1/5] update --- ...ble_schema_plugin_framework_data_source.go | 421 ++++++++++++++++++ common/reflect_resource_plugin_framework.go | 126 +++++- pluginframework/data_volumes.go | 21 +- 3 files changed, 547 insertions(+), 21 deletions(-) create mode 100644 common/customizable_schema_plugin_framework_data_source.go diff --git a/common/customizable_schema_plugin_framework_data_source.go b/common/customizable_schema_plugin_framework_data_source.go new file mode 100644 index 0000000000..f73f27410b --- /dev/null +++ b/common/customizable_schema_plugin_framework_data_source.go @@ -0,0 +1,421 @@ +package common + +import ( + "fmt" + "reflect" + + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" +) + +type CustomizableSchemaPluginFrameworkDataSource struct { + attr schema.Attribute +} + +func ConstructCustomizableSchemaDataSource(attributes map[string]schema.Attribute) *CustomizableSchemaPluginFrameworkDataSource { + attr := schema.Attribute(schema.SingleNestedAttribute{Attributes: attributes}) + return &CustomizableSchemaPluginFrameworkDataSource{attr: attr} +} + +// Converts CustomizableSchema into a map from string to Attribute. +func (s *CustomizableSchemaPluginFrameworkDataSource) ToAttributeMap() map[string]schema.Attribute { + return attributeToMapDataSource(&s.attr) +} + +func attributeToMapDataSource(attr *schema.Attribute) map[string]schema.Attribute { + var m map[string]schema.Attribute + switch attr := (*attr).(type) { + case schema.SingleNestedAttribute: + m = attr.Attributes + case schema.ListNestedAttribute: + m = attr.NestedObject.Attributes + case schema.MapNestedAttribute: + m = attr.NestedObject.Attributes + default: + panic(fmt.Errorf("cannot convert to map, attribute is not nested")) + } + + return m +} + +func (s *CustomizableSchemaPluginFrameworkDataSource) AddNewField(key string, newField schema.Attribute, path ...string) *CustomizableSchemaPluginFrameworkDataSource { + cb := func(a schema.Attribute) schema.Attribute { + switch attr := a.(type) { + case schema.SingleNestedAttribute: + _, exists := attr.Attributes[key] + if exists { + panic("Cannot add new field, " + key + " already exists in the schema") + } + attr.Attributes[key] = newField + return attr + case schema.ListNestedAttribute: + _, exists := attr.NestedObject.Attributes[key] + if exists { + panic("Cannot add new field, " + key + " already exists in the schema") + } + attr.NestedObject.Attributes[key] = newField + return attr + case schema.MapNestedAttribute: + _, exists := attr.NestedObject.Attributes[key] + if exists { + panic("Cannot add new field, " + key + " already exists in the schema") + } + attr.NestedObject.Attributes[key] = newField + return attr + default: + panic("attribute is not nested, cannot add field") + } + } + + if len(path) == 0 { + s.attr = cb(s.attr) + } else { + navigateSchemaWithCallbackDataSource(&s.attr, cb, path...) + } + return s +} + +func (s *CustomizableSchemaPluginFrameworkDataSource) RemoveField(key string, path ...string) *CustomizableSchemaPluginFrameworkDataSource { + cb := func(a schema.Attribute) schema.Attribute { + switch attr := a.(type) { + case schema.SingleNestedAttribute: + _, exists := attr.Attributes[key] + if !exists { + panic("Cannot remove field, " + key + " does not exist in the schema") + } + delete(attr.Attributes, key) + return attr + case schema.ListNestedAttribute: + _, exists := attr.NestedObject.Attributes[key] + if !exists { + panic("Cannot remove field, " + key + " does not exist in the schema") + } + delete(attr.NestedObject.Attributes, key) + return attr + case schema.MapNestedAttribute: + _, exists := attr.NestedObject.Attributes[key] + if !exists { + panic("Cannot remove field, " + key + " does not exist in the schema") + } + delete(attr.NestedObject.Attributes, key) + return attr + default: + panic("attribute is not nested, cannot add field") + } + } + + if len(path) == 0 { + s.attr = cb(s.attr) + } else { + navigateSchemaWithCallbackDataSource(&s.attr, cb, path...) + } + return s +} + +func (s *CustomizableSchemaPluginFrameworkDataSource) AddValidator(v any, path ...string) *CustomizableSchemaPluginFrameworkDataSource { + cb := func(attr schema.Attribute) schema.Attribute { + val := reflect.ValueOf(attr) + + // Make a copy of the existing attr. + newAttr := reflect.New(val.Type()).Elem() + newAttr.Set(val) + val = newAttr + + field := val.FieldByName("Validators") + if !field.IsValid() { + panic(fmt.Sprintf("Validators field not found in %T", attr)) + } + if field.Kind() != reflect.Slice { + panic(fmt.Sprintf("Validators field is not a slice in %T", attr)) + } + if !field.CanSet() { + panic(fmt.Sprintf("Validators field cannot be set in %T", attr)) + } + + elemType := field.Type().Elem() + value := reflect.ValueOf(v) + + if !value.Type().AssignableTo(elemType) { + panic(fmt.Sprintf("Value of type %T is not assignable to slice of %s", v, elemType)) + } + + // Append the value + newSlice := reflect.Append(field, value) + field.Set(newSlice) + + return val.Interface().(schema.Attribute) + } + + navigateSchemaWithCallbackDataSource(&s.attr, cb, path...) + + return s +} + +func (s *CustomizableSchemaPluginFrameworkDataSource) SetOptional(path ...string) *CustomizableSchemaPluginFrameworkDataSource { + cb := func(attr schema.Attribute) schema.Attribute { + // Get the concrete value stored in the interface + v := reflect.ValueOf(attr) + + // Make a new addressable value and copy the original value into it + newAttr := reflect.New(v.Type()).Elem() + newAttr.Set(v) + v = newAttr + + field := v.FieldByName("Required") + if field.IsValid() && field.CanSet() { + if field.Kind() == reflect.Bool { + field.SetBool(false) + } else { + panic(fmt.Sprintf("Required is not a bool field in %T", attr)) + } + } else { + panic(fmt.Sprintf("Required field not found or cannot be set in %T", attr)) + } + + field = v.FieldByName("Optional") + if field.IsValid() && field.CanSet() { + if field.Kind() == reflect.Bool { + field.SetBool(true) + } else { + panic(fmt.Sprintf("Optional is not a bool field in %T", attr)) + } + } else { + panic(fmt.Sprintf("Optional field not found or cannot be set in %T", attr)) + } + + return v.Interface().(schema.Attribute) + } + + navigateSchemaWithCallbackDataSource(&s.attr, cb, path...) + + return s +} + +func (s *CustomizableSchemaPluginFrameworkDataSource) SetRequired(path ...string) *CustomizableSchemaPluginFrameworkDataSource { + cb := func(attr schema.Attribute) schema.Attribute { + // Get the concrete value stored in the interface + v := reflect.ValueOf(attr) + + // Make a new addressable value and copy the original value into it + newAttr := reflect.New(v.Type()).Elem() + newAttr.Set(v) + v = newAttr + + field := v.FieldByName("Required") + if field.IsValid() && field.CanSet() { + if field.Kind() == reflect.Bool { + field.SetBool(true) + } else { + panic(fmt.Sprintf("Required is not a bool field in %T", attr)) + } + } else { + panic(fmt.Sprintf("Required field not found or cannot be set in %T", attr)) + } + + field = v.FieldByName("Optional") + if field.IsValid() && field.CanSet() { + if field.Kind() == reflect.Bool { + field.SetBool(false) + } else { + panic(fmt.Sprintf("Optional is not a bool field in %T", attr)) + } + } else { + panic(fmt.Sprintf("Optional field not found or cannot be set in %T", attr)) + } + + return v.Interface().(schema.Attribute) + } + + navigateSchemaWithCallbackDataSource(&s.attr, cb, path...) + + return s +} + +func (s *CustomizableSchemaPluginFrameworkDataSource) SetSensitive(path ...string) *CustomizableSchemaPluginFrameworkDataSource { + cb := func(attr schema.Attribute) schema.Attribute { + // Get the concrete value stored in the interface + v := reflect.ValueOf(attr) + + // Make a new addressable value and copy the original value into it + newAttr := reflect.New(v.Type()).Elem() + newAttr.Set(v) + v = newAttr + + field := v.FieldByName("Sensitive") + if field.IsValid() && field.CanSet() { + if field.Kind() == reflect.Bool { + field.SetBool(true) + } else { + panic(fmt.Sprintf("Sensitive is not a bool field in %T", attr)) + } + } else { + panic(fmt.Sprintf("Sensitive field not found or cannot be set in %T", attr)) + } + + return v.Interface().(schema.Attribute) + } + + navigateSchemaWithCallbackDataSource(&s.attr, cb, path...) + return s +} + +func (s *CustomizableSchemaPluginFrameworkDataSource) SetDeprecated(msg string, path ...string) *CustomizableSchemaPluginFrameworkDataSource { + cb := func(attr schema.Attribute) schema.Attribute { + // Get the concrete value stored in the interface + v := reflect.ValueOf(attr) + + // Make a new addressable value and copy the original value into it + newAttr := reflect.New(v.Type()).Elem() + newAttr.Set(v) + v = newAttr + + field := v.FieldByName("DeprecationMessage") + if field.IsValid() && field.CanSet() { + if field.Kind() == reflect.String { + field.SetString(msg) + } else { + panic(fmt.Sprintf("DeprecationMessage is not a string field in %T", attr)) + } + } else { + panic(fmt.Sprintf("DeprecationMessage field not found or cannot be set in %T", attr)) + } + + return v.Interface().(schema.Attribute) + } + + navigateSchemaWithCallbackDataSource(&s.attr, cb, path...) + + return s +} + +func (s *CustomizableSchemaPluginFrameworkDataSource) SetComputed(path ...string) *CustomizableSchemaPluginFrameworkDataSource { + cb := func(attr schema.Attribute) schema.Attribute { + // Get the concrete value stored in the interface + v := reflect.ValueOf(attr) + + // Make a new addressable value and copy the original value into it + newAttr := reflect.New(v.Type()).Elem() + newAttr.Set(v) + v = newAttr + + field := v.FieldByName("Computed") + if field.IsValid() && field.CanSet() { + if field.Kind() == reflect.Bool { + field.SetBool(true) + } else { + panic(fmt.Sprintf("Computed is not a bool field in %T", attr)) + } + } else { + panic(fmt.Sprintf("Computed field not found or cannot be set in %T", attr)) + } + + return v.Interface().(schema.Attribute) + } + + navigateSchemaWithCallbackDataSource(&s.attr, cb, path...) + return s +} + +// SetReadOnly sets the schema to be read-only (i.e. computed, non-optional). +// This should be used for fields that are not user-configurable but are returned +// by the platform. +func (s *CustomizableSchemaPluginFrameworkDataSource) SetReadOnly(path ...string) *CustomizableSchemaPluginFrameworkDataSource { + cb := func(attr schema.Attribute) schema.Attribute { + // Get the concrete value stored in the interface + v := reflect.ValueOf(attr) + + // Make a new addressable value and copy the original value into it + newAttr := reflect.New(v.Type()).Elem() + newAttr.Set(v) + v = newAttr + + field := v.FieldByName("Computed") + if field.IsValid() && field.CanSet() { + if field.Kind() == reflect.Bool { + field.SetBool(true) + } else { + panic(fmt.Sprintf("Computed is not a bool field in %T", attr)) + } + } else { + panic(fmt.Sprintf("Computed field not found or cannot be set in %T", attr)) + } + + field = v.FieldByName("Optional") + if field.IsValid() && field.CanSet() { + if field.Kind() == reflect.Bool { + field.SetBool(false) + } else { + panic(fmt.Sprintf("Optional is not a bool field in %T", attr)) + } + } else { + panic(fmt.Sprintf("Optional field not found or cannot be set in %T", attr)) + } + + field = v.FieldByName("Required") + if field.IsValid() && field.CanSet() { + if field.Kind() == reflect.Bool { + field.SetBool(false) + } else { + panic(fmt.Sprintf("Required is not a bool field in %T", attr)) + } + } else { + panic(fmt.Sprintf("Required field not found or cannot be set in %T", attr)) + } + + return v.Interface().(schema.Attribute) + } + + navigateSchemaWithCallbackDataSource(&s.attr, cb, path...) + + return s +} + +// Given a attribute map, navigate through the given path, panics if the path is not valid. +func MustSchemaAttributePathDataSource(attrs map[string]schema.Attribute, path ...string) schema.Attribute { + attr := ConstructCustomizableSchemaDataSource(attrs).attr + + res, err := navigateSchemaDataSource(&attr, path...) + if err != nil { + panic(err) + } + + return res +} + +// Helper function for navigating through schema attributes, panics if path does not exist or invalid. +func navigateSchemaDataSource(s *schema.Attribute, path ...string) (schema.Attribute, error) { + current_scm := s + for i, p := range path { + m := attributeToMapDataSource(current_scm) + + v, ok := m[p] + if !ok { + return nil, fmt.Errorf("missing key %s", p) + } + + if i == len(path)-1 { + return v, nil + } + current_scm = &v + } + return nil, fmt.Errorf("path %v is incomplete", path) +} + +// Helper function for navigating through schema attributes, panics if path does not exist or invalid. +func navigateSchemaWithCallbackDataSource(s *schema.Attribute, cb func(schema.Attribute) schema.Attribute, path ...string) (schema.Attribute, error) { + current_scm := s + for i, p := range path { + m := attributeToMapDataSource(current_scm) + + v, ok := m[p] + if !ok { + return nil, fmt.Errorf("missing key %s", p) + } + + if i == len(path)-1 { + m[p] = cb(v) + return m[p], nil + } + current_scm = &v + } + return nil, fmt.Errorf("path %v is incomplete", path) +} diff --git a/common/reflect_resource_plugin_framework.go b/common/reflect_resource_plugin_framework.go index 0dc80550bd..f84bf99cc8 100644 --- a/common/reflect_resource_plugin_framework.go +++ b/common/reflect_resource_plugin_framework.go @@ -7,7 +7,9 @@ import ( "reflect" "strings" + dataschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -125,8 +127,8 @@ func tfSdkToGoSdkSingleField(srcField reflect.Value, destField reflect.Value, sr case types.Map: panic("types.Map should never be used, use go native maps instead") default: - if srcField.IsZero() { - // Skip zeros + if srcField.IsNil() { + // Skip nils return nil } // If it is a real stuct instead of a tfsdk type, recursively resolve it. @@ -310,8 +312,8 @@ func goSdkToTfSdkSingleField(srcField reflect.Value, destField reflect.Value, sr destField.Set(reflect.ValueOf(types.StringNull())) } case reflect.Struct: - if srcField.IsZero() { - // Skip zeros + if srcField.IsNil() { + // Skip nils return nil } // resolve the nested struct by recursively calling the function @@ -483,6 +485,106 @@ func pluginFrameworkResourceTypeToSchema(v reflect.Value) map[string]schema.Attr return scm } +func pluginFrameworkDataSourceTypeToSchema(v reflect.Value) map[string]dataschema.Attribute { + scm := map[string]dataschema.Attribute{} + rk := v.Kind() + if rk == reflect.Ptr { + v = v.Elem() + rk = v.Kind() + } + if rk != reflect.Struct { + panic(fmt.Errorf("Schema value of Struct is expected, but got %s: %#v", reflectKind(rk), v)) + } + fields := listAllFields(v) + for _, field := range fields { + typeField := field.sf + fieldName := typeField.Tag.Get("tfsdk") + if fieldName == "-" { + continue + } + isOptional := fieldIsOptional(typeField) + // For now everything is marked as optional. TODO: add tf annotations for computed, optional, etc. + kind := typeField.Type.Kind() + if kind == reflect.Ptr { + elem := typeField.Type.Elem() + sv := reflect.New(elem).Elem() + nestedScm := pluginFrameworkDataSourceTypeToSchema(sv) + scm[fieldName] = dataschema.SingleNestedAttribute{Attributes: nestedScm, Optional: isOptional, Required: !isOptional} + } else if kind == reflect.Slice { + elem := typeField.Type.Elem() + if elem.Kind() == reflect.Ptr { + elem = elem.Elem() + } + if elem.Kind() != reflect.Struct { + panic(fmt.Errorf("unsupported slice value for %s: %s", fieldName, reflectKind(elem.Kind()))) + } + switch elem { + case reflect.TypeOf(types.Bool{}): + scm[fieldName] = dataschema.ListAttribute{ElementType: types.BoolType, Optional: isOptional, Required: !isOptional} + case reflect.TypeOf(types.Int64{}): + scm[fieldName] = dataschema.ListAttribute{ElementType: types.Int64Type, Optional: isOptional, Required: !isOptional} + case reflect.TypeOf(types.Float64{}): + scm[fieldName] = dataschema.ListAttribute{ElementType: types.Float64Type, Optional: isOptional, Required: !isOptional} + case reflect.TypeOf(types.String{}): + scm[fieldName] = dataschema.ListAttribute{ElementType: types.StringType, Optional: isOptional, Required: !isOptional} + default: + // Nested struct + nestedScm := pluginFrameworkDataSourceTypeToSchema(reflect.New(elem).Elem()) + scm[fieldName] = dataschema.ListNestedAttribute{NestedObject: dataschema.NestedAttributeObject{Attributes: nestedScm}, Optional: isOptional, Required: !isOptional} + } + } else if kind == reflect.Map { + elem := typeField.Type.Elem() + if elem.Kind() == reflect.Ptr { + elem = elem.Elem() + } + if elem.Kind() != reflect.Struct { + panic(fmt.Errorf("unsupported map value for %s: %s", fieldName, reflectKind(elem.Kind()))) + } + switch elem { + case reflect.TypeOf(types.Bool{}): + scm[fieldName] = dataschema.MapAttribute{ElementType: types.BoolType, Optional: isOptional, Required: !isOptional} + case reflect.TypeOf(types.Int64{}): + scm[fieldName] = dataschema.MapAttribute{ElementType: types.Int64Type, Optional: isOptional, Required: !isOptional} + case reflect.TypeOf(types.Float64{}): + scm[fieldName] = dataschema.MapAttribute{ElementType: types.Float64Type, Optional: isOptional, Required: !isOptional} + case reflect.TypeOf(types.String{}): + scm[fieldName] = dataschema.MapAttribute{ElementType: types.StringType, Optional: isOptional, Required: !isOptional} + default: + // Nested struct + nestedScm := pluginFrameworkDataSourceTypeToSchema(reflect.New(elem).Elem()) + scm[fieldName] = dataschema.MapNestedAttribute{NestedObject: dataschema.NestedAttributeObject{Attributes: nestedScm}, Optional: isOptional, Required: !isOptional} + } + } else if kind == reflect.Struct { + switch field.v.Interface().(type) { + case types.Bool: + scm[fieldName] = dataschema.BoolAttribute{Optional: isOptional, Required: !isOptional} + case types.Int64: + scm[fieldName] = dataschema.Int64Attribute{Optional: isOptional, Required: !isOptional} + case types.Float64: + scm[fieldName] = dataschema.Float64Attribute{Optional: isOptional, Required: !isOptional} + case types.String: + scm[fieldName] = dataschema.StringAttribute{Optional: isOptional, Required: !isOptional} + case types.List: + panic("types.List should never be used in tfsdk structs") + case types.Map: + panic("types.Map should never be used in tfsdk structs") + default: + // If it is a real stuct instead of a tfsdk type, recursively resolve it. + elem := typeField.Type + sv := reflect.New(elem) + nestedScm := pluginFrameworkDataSourceTypeToSchema(sv) + scm[fieldName] = dataschema.SingleNestedAttribute{Attributes: nestedScm, Optional: isOptional, Required: !isOptional} + } + } else if kind == reflect.String { + // This case is for Enum types. + scm[fieldName] = dataschema.StringAttribute{Optional: isOptional, Required: !isOptional} + } else { + panic(fmt.Errorf("unknown type for field: %s", typeField.Name)) + } + } + return scm +} + func fieldIsOptional(field reflect.StructField) bool { tagValue := field.Tag.Get("tf") return strings.Contains(tagValue, "optional") @@ -493,6 +595,11 @@ func PluginFrameworkResourceStructToSchema(v any, customizeSchema func(Customiza return schema.Schema{Attributes: attributes} } +func PluginFrameworkDataSourceStructToSchema(v any, customizeSchema func(CustomizableSchemaPluginFrameworkDataSource) CustomizableSchemaPluginFrameworkDataSource) dataschema.Schema { + attributes := PluginFrameworkDataSourceStructToSchemaMap(v, customizeSchema) + return dataschema.Schema{Attributes: attributes} +} + func PluginFrameworkResourceStructToSchemaMap(v any, customizeSchema func(CustomizableSchemaPluginFramework) CustomizableSchemaPluginFramework) map[string]schema.Attribute { attributes := pluginFrameworkResourceTypeToSchema(reflect.ValueOf(v)) @@ -503,3 +610,14 @@ func PluginFrameworkResourceStructToSchemaMap(v any, customizeSchema func(Custom return attributes } } + +func PluginFrameworkDataSourceStructToSchemaMap(v any, customizeSchema func(CustomizableSchemaPluginFrameworkDataSource) CustomizableSchemaPluginFrameworkDataSource) map[string]dataschema.Attribute { + attributes := pluginFrameworkDataSourceTypeToSchema(reflect.ValueOf(v)) + + if customizeSchema != nil { + cs := customizeSchema(*ConstructCustomizableSchemaDataSource(attributes)) + return cs.ToAttributeMap() + } else { + return attributes + } +} diff --git a/pluginframework/data_volumes.go b/pluginframework/data_volumes.go index bbbb579ebd..824609b2a5 100644 --- a/pluginframework/data_volumes.go +++ b/pluginframework/data_volumes.go @@ -7,7 +7,6 @@ import ( "github.com/databricks/databricks-sdk-go/service/catalog" "github.com/databricks/terraform-provider-databricks/common" "github.com/hashicorp/terraform-plugin-framework/datasource" - "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -34,22 +33,10 @@ func (d *VolumesDataSource) Metadata(ctx context.Context, req datasource.Metadat } func (d *VolumesDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = schema.Schema{ - // TODO: Use StructToSchemaMap to generate the schema once it supports schema for data sources - Attributes: map[string]schema.Attribute{ - "catalog_name": schema.StringAttribute{ - Required: true, - }, - "schema_name": schema.StringAttribute{ - Required: true, - }, - "ids": schema.ListAttribute{ - ElementType: types.StringType, - Optional: true, - Computed: true, - }, - }, - } + resp.Schema = common.PluginFrameworkDataSourceStructToSchema(VolumesList{}, func(c common.CustomizableSchemaPluginFrameworkDataSource) common.CustomizableSchemaPluginFrameworkDataSource { + c.SetComputed("ids") + return c + }) } func (d *VolumesDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { From 3cd53ee851842f2f0e6c35207ac760cc035f27ed Mon Sep 17 00:00:00 2001 From: edwardfeng-db Date: Wed, 14 Aug 2024 12:53:40 +0200 Subject: [PATCH 2/5] update --- common/databricks_schema_attribute.go | 81 +++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 common/databricks_schema_attribute.go diff --git a/common/databricks_schema_attribute.go b/common/databricks_schema_attribute.go new file mode 100644 index 0000000000..4ebbe97044 --- /dev/null +++ b/common/databricks_schema_attribute.go @@ -0,0 +1,81 @@ +package common + +import ( + "github.com/hashicorp/terraform-plugin-framework/attr" + dataschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" +) + +type Attribute interface { + ToDataSourceAttribute() dataschema.Attribute + ToResourceAttribute() schema.Attribute +} + +type StringAttribute struct { + Optional bool + Required bool +} + +func (a StringAttribute) ToDataSourceAttribute() dataschema.Attribute { + return dataschema.StringAttribute{Optional: a.Optional, Required: a.Required} +} + +func (a StringAttribute) ToResourceAttribute() dataschema.Attribute { + return schema.StringAttribute{Optional: a.Optional, Required: a.Required} +} + +type Float64Attribute struct { + Optional bool + Required bool +} + +func (a Float64Attribute) ToDataSourceAttribute() dataschema.Attribute { + return dataschema.Float64Attribute{Optional: a.Optional, Required: a.Required} +} + +func (a Float64Attribute) ToResourceAttribute() dataschema.Attribute { + return schema.Float64Attribute{Optional: a.Optional, Required: a.Required} +} + +type Int64Attribute struct { + Optional bool + Required bool +} + +type BoolAttribute struct { + Optional bool + Required bool +} + +type MapAttribute struct { + ElementType attr.Type + Optional bool + Required bool +} + +type ListAttribute struct { + ElementType attr.Type + Optional bool + Required bool +} + +type SingleNestedAttribute struct { + Optional bool + Required bool +} + +type ListNestedAttribute struct { + NestedObject NestedAttributeObject + Optional bool + Required bool +} + +type NestedAttributeObject struct { + Attributes map[string]Attribute +} + +type MapNestedAttribute struct { + NestedObject NestedAttributeObject + Optional bool + Required bool +} From c0b13d196c77ae52296f863815d52dfbb635b19d Mon Sep 17 00:00:00 2001 From: edwardfeng-db Date: Wed, 14 Aug 2024 17:32:09 +0200 Subject: [PATCH 3/5] update --- .../customizable_schema_plugin_framework.go | 269 +-------- ...ble_schema_plugin_framework_data_source.go | 421 -------------- ...stomizable_schema_plugin_framework_test.go | 9 +- common/databricks_schema_attribute.go | 544 +++++++++++++++++- common/reflect_resource_plugin_framework.go | 168 ++---- 5 files changed, 579 insertions(+), 832 deletions(-) delete mode 100644 common/customizable_schema_plugin_framework_data_source.go diff --git a/common/customizable_schema_plugin_framework.go b/common/customizable_schema_plugin_framework.go index a486dc971d..f5defbc3c4 100644 --- a/common/customizable_schema_plugin_framework.go +++ b/common/customizable_schema_plugin_framework.go @@ -2,33 +2,30 @@ package common import ( "fmt" - "reflect" - - "github.com/hashicorp/terraform-plugin-framework/resource/schema" ) type CustomizableSchemaPluginFramework struct { - attr schema.Attribute + attr Attribute } -func ConstructCustomizableSchema(attributes map[string]schema.Attribute) *CustomizableSchemaPluginFramework { - attr := schema.Attribute(schema.SingleNestedAttribute{Attributes: attributes}) +func ConstructCustomizableSchema(attributes map[string]Attribute) *CustomizableSchemaPluginFramework { + attr := Attribute(SingleNestedAttribute{Attributes: attributes}) return &CustomizableSchemaPluginFramework{attr: attr} } // Converts CustomizableSchema into a map from string to Attribute. -func (s *CustomizableSchemaPluginFramework) ToAttributeMap() map[string]schema.Attribute { +func (s *CustomizableSchemaPluginFramework) ToAttributeMap() map[string]Attribute { return attributeToMap(&s.attr) } -func attributeToMap(attr *schema.Attribute) map[string]schema.Attribute { - var m map[string]schema.Attribute +func attributeToMap(attr *Attribute) map[string]Attribute { + var m map[string]Attribute switch attr := (*attr).(type) { - case schema.SingleNestedAttribute: + case SingleNestedAttribute: m = attr.Attributes - case schema.ListNestedAttribute: + case ListNestedAttribute: m = attr.NestedObject.Attributes - case schema.MapNestedAttribute: + case MapNestedAttribute: m = attr.NestedObject.Attributes default: panic(fmt.Errorf("cannot convert to map, attribute is not nested")) @@ -38,37 +35,8 @@ func attributeToMap(attr *schema.Attribute) map[string]schema.Attribute { } func (s *CustomizableSchemaPluginFramework) AddValidator(v any, path ...string) *CustomizableSchemaPluginFramework { - cb := func(attr schema.Attribute) schema.Attribute { - val := reflect.ValueOf(attr) - - // Make a copy of the existing attr. - newAttr := reflect.New(val.Type()).Elem() - newAttr.Set(val) - val = newAttr - - field := val.FieldByName("Validators") - if !field.IsValid() { - panic(fmt.Sprintf("Validators field not found in %T", attr)) - } - if field.Kind() != reflect.Slice { - panic(fmt.Sprintf("Validators field is not a slice in %T", attr)) - } - if !field.CanSet() { - panic(fmt.Sprintf("Validators field cannot be set in %T", attr)) - } - - elemType := field.Type().Elem() - value := reflect.ValueOf(v) - - if !value.Type().AssignableTo(elemType) { - panic(fmt.Sprintf("Value of type %T is not assignable to slice of %s", v, elemType)) - } - - // Append the value - newSlice := reflect.Append(field, value) - field.Set(newSlice) - - return val.Interface().(schema.Attribute) + cb := func(attr Attribute) Attribute { + return attr.AddValidators(v) } navigateSchemaWithCallback(&s.attr, cb, path...) @@ -77,38 +45,8 @@ func (s *CustomizableSchemaPluginFramework) AddValidator(v any, path ...string) } func (s *CustomizableSchemaPluginFramework) SetOptional(path ...string) *CustomizableSchemaPluginFramework { - cb := func(attr schema.Attribute) schema.Attribute { - // Get the concrete value stored in the interface - v := reflect.ValueOf(attr) - - // Make a new addressable value and copy the original value into it - newAttr := reflect.New(v.Type()).Elem() - newAttr.Set(v) - v = newAttr - - field := v.FieldByName("Required") - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.Bool { - field.SetBool(false) - } else { - panic(fmt.Sprintf("Required is not a bool field in %T", attr)) - } - } else { - panic(fmt.Sprintf("Required field not found or cannot be set in %T", attr)) - } - - field = v.FieldByName("Optional") - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.Bool { - field.SetBool(true) - } else { - panic(fmt.Sprintf("Optional is not a bool field in %T", attr)) - } - } else { - panic(fmt.Sprintf("Optional field not found or cannot be set in %T", attr)) - } - - return v.Interface().(schema.Attribute) + cb := func(attr Attribute) Attribute { + return attr.SetOptional() } navigateSchemaWithCallback(&s.attr, cb, path...) @@ -117,38 +55,8 @@ func (s *CustomizableSchemaPluginFramework) SetOptional(path ...string) *Customi } func (s *CustomizableSchemaPluginFramework) SetRequired(path ...string) *CustomizableSchemaPluginFramework { - cb := func(attr schema.Attribute) schema.Attribute { - // Get the concrete value stored in the interface - v := reflect.ValueOf(attr) - - // Make a new addressable value and copy the original value into it - newAttr := reflect.New(v.Type()).Elem() - newAttr.Set(v) - v = newAttr - - field := v.FieldByName("Required") - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.Bool { - field.SetBool(true) - } else { - panic(fmt.Sprintf("Required is not a bool field in %T", attr)) - } - } else { - panic(fmt.Sprintf("Required field not found or cannot be set in %T", attr)) - } - - field = v.FieldByName("Optional") - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.Bool { - field.SetBool(false) - } else { - panic(fmt.Sprintf("Optional is not a bool field in %T", attr)) - } - } else { - panic(fmt.Sprintf("Optional field not found or cannot be set in %T", attr)) - } - - return v.Interface().(schema.Attribute) + cb := func(attr Attribute) Attribute { + return attr.SetRequired() } navigateSchemaWithCallback(&s.attr, cb, path...) @@ -157,27 +65,8 @@ func (s *CustomizableSchemaPluginFramework) SetRequired(path ...string) *Customi } func (s *CustomizableSchemaPluginFramework) SetSensitive(path ...string) *CustomizableSchemaPluginFramework { - cb := func(attr schema.Attribute) schema.Attribute { - // Get the concrete value stored in the interface - v := reflect.ValueOf(attr) - - // Make a new addressable value and copy the original value into it - newAttr := reflect.New(v.Type()).Elem() - newAttr.Set(v) - v = newAttr - - field := v.FieldByName("Sensitive") - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.Bool { - field.SetBool(true) - } else { - panic(fmt.Sprintf("Sensitive is not a bool field in %T", attr)) - } - } else { - panic(fmt.Sprintf("Sensitive field not found or cannot be set in %T", attr)) - } - - return v.Interface().(schema.Attribute) + cb := func(attr Attribute) Attribute { + return attr.SetSensitive() } navigateSchemaWithCallback(&s.attr, cb, path...) @@ -185,27 +74,8 @@ func (s *CustomizableSchemaPluginFramework) SetSensitive(path ...string) *Custom } func (s *CustomizableSchemaPluginFramework) SetDeprecated(msg string, path ...string) *CustomizableSchemaPluginFramework { - cb := func(attr schema.Attribute) schema.Attribute { - // Get the concrete value stored in the interface - v := reflect.ValueOf(attr) - - // Make a new addressable value and copy the original value into it - newAttr := reflect.New(v.Type()).Elem() - newAttr.Set(v) - v = newAttr - - field := v.FieldByName("DeprecationMessage") - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.String { - field.SetString(msg) - } else { - panic(fmt.Sprintf("DeprecationMessage is not a string field in %T", attr)) - } - } else { - panic(fmt.Sprintf("DeprecationMessage field not found or cannot be set in %T", attr)) - } - - return v.Interface().(schema.Attribute) + cb := func(attr Attribute) Attribute { + return attr.SetDeprecated(msg) } navigateSchemaWithCallback(&s.attr, cb, path...) @@ -214,27 +84,8 @@ func (s *CustomizableSchemaPluginFramework) SetDeprecated(msg string, path ...st } func (s *CustomizableSchemaPluginFramework) SetComputed(path ...string) *CustomizableSchemaPluginFramework { - cb := func(attr schema.Attribute) schema.Attribute { - // Get the concrete value stored in the interface - v := reflect.ValueOf(attr) - - // Make a new addressable value and copy the original value into it - newAttr := reflect.New(v.Type()).Elem() - newAttr.Set(v) - v = newAttr - - field := v.FieldByName("Computed") - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.Bool { - field.SetBool(true) - } else { - panic(fmt.Sprintf("Computed is not a bool field in %T", attr)) - } - } else { - panic(fmt.Sprintf("Computed field not found or cannot be set in %T", attr)) - } - - return v.Interface().(schema.Attribute) + cb := func(attr Attribute) Attribute { + return attr.SetComputed() } navigateSchemaWithCallback(&s.attr, cb, path...) @@ -245,49 +96,8 @@ func (s *CustomizableSchemaPluginFramework) SetComputed(path ...string) *Customi // This should be used for fields that are not user-configurable but are returned // by the platform. func (s *CustomizableSchemaPluginFramework) SetReadOnly(path ...string) *CustomizableSchemaPluginFramework { - cb := func(attr schema.Attribute) schema.Attribute { - // Get the concrete value stored in the interface - v := reflect.ValueOf(attr) - - // Make a new addressable value and copy the original value into it - newAttr := reflect.New(v.Type()).Elem() - newAttr.Set(v) - v = newAttr - - field := v.FieldByName("Computed") - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.Bool { - field.SetBool(true) - } else { - panic(fmt.Sprintf("Computed is not a bool field in %T", attr)) - } - } else { - panic(fmt.Sprintf("Computed field not found or cannot be set in %T", attr)) - } - - field = v.FieldByName("Optional") - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.Bool { - field.SetBool(false) - } else { - panic(fmt.Sprintf("Optional is not a bool field in %T", attr)) - } - } else { - panic(fmt.Sprintf("Optional field not found or cannot be set in %T", attr)) - } - - field = v.FieldByName("Required") - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.Bool { - field.SetBool(false) - } else { - panic(fmt.Sprintf("Required is not a bool field in %T", attr)) - } - } else { - panic(fmt.Sprintf("Required field not found or cannot be set in %T", attr)) - } - - return v.Interface().(schema.Attribute) + cb := func(attr Attribute) Attribute { + return attr.SetReadOnly() } navigateSchemaWithCallback(&s.attr, cb, path...) @@ -295,39 +105,8 @@ func (s *CustomizableSchemaPluginFramework) SetReadOnly(path ...string) *Customi return s } -// Given a attribute map, navigate through the given path, panics if the path is not valid. -func MustSchemaAttributePath(attrs map[string]schema.Attribute, path ...string) schema.Attribute { - attr := ConstructCustomizableSchema(attrs).attr - - res, err := navigateSchema(&attr, path...) - if err != nil { - panic(err) - } - - return res -} - -// Helper function for navigating through schema attributes, panics if path does not exist or invalid. -func navigateSchema(s *schema.Attribute, path ...string) (schema.Attribute, error) { - current_scm := s - for i, p := range path { - m := attributeToMap(current_scm) - - v, ok := m[p] - if !ok { - return nil, fmt.Errorf("missing key %s", p) - } - - if i == len(path)-1 { - return v, nil - } - current_scm = &v - } - return nil, fmt.Errorf("path %v is incomplete", path) -} - // Helper function for navigating through schema attributes, panics if path does not exist or invalid. -func navigateSchemaWithCallback(s *schema.Attribute, cb func(schema.Attribute) schema.Attribute, path ...string) (schema.Attribute, error) { +func navigateSchemaWithCallback(s *Attribute, cb func(Attribute) Attribute, path ...string) (Attribute, error) { current_scm := s for i, p := range path { m := attributeToMap(current_scm) diff --git a/common/customizable_schema_plugin_framework_data_source.go b/common/customizable_schema_plugin_framework_data_source.go deleted file mode 100644 index f73f27410b..0000000000 --- a/common/customizable_schema_plugin_framework_data_source.go +++ /dev/null @@ -1,421 +0,0 @@ -package common - -import ( - "fmt" - "reflect" - - "github.com/hashicorp/terraform-plugin-framework/datasource/schema" -) - -type CustomizableSchemaPluginFrameworkDataSource struct { - attr schema.Attribute -} - -func ConstructCustomizableSchemaDataSource(attributes map[string]schema.Attribute) *CustomizableSchemaPluginFrameworkDataSource { - attr := schema.Attribute(schema.SingleNestedAttribute{Attributes: attributes}) - return &CustomizableSchemaPluginFrameworkDataSource{attr: attr} -} - -// Converts CustomizableSchema into a map from string to Attribute. -func (s *CustomizableSchemaPluginFrameworkDataSource) ToAttributeMap() map[string]schema.Attribute { - return attributeToMapDataSource(&s.attr) -} - -func attributeToMapDataSource(attr *schema.Attribute) map[string]schema.Attribute { - var m map[string]schema.Attribute - switch attr := (*attr).(type) { - case schema.SingleNestedAttribute: - m = attr.Attributes - case schema.ListNestedAttribute: - m = attr.NestedObject.Attributes - case schema.MapNestedAttribute: - m = attr.NestedObject.Attributes - default: - panic(fmt.Errorf("cannot convert to map, attribute is not nested")) - } - - return m -} - -func (s *CustomizableSchemaPluginFrameworkDataSource) AddNewField(key string, newField schema.Attribute, path ...string) *CustomizableSchemaPluginFrameworkDataSource { - cb := func(a schema.Attribute) schema.Attribute { - switch attr := a.(type) { - case schema.SingleNestedAttribute: - _, exists := attr.Attributes[key] - if exists { - panic("Cannot add new field, " + key + " already exists in the schema") - } - attr.Attributes[key] = newField - return attr - case schema.ListNestedAttribute: - _, exists := attr.NestedObject.Attributes[key] - if exists { - panic("Cannot add new field, " + key + " already exists in the schema") - } - attr.NestedObject.Attributes[key] = newField - return attr - case schema.MapNestedAttribute: - _, exists := attr.NestedObject.Attributes[key] - if exists { - panic("Cannot add new field, " + key + " already exists in the schema") - } - attr.NestedObject.Attributes[key] = newField - return attr - default: - panic("attribute is not nested, cannot add field") - } - } - - if len(path) == 0 { - s.attr = cb(s.attr) - } else { - navigateSchemaWithCallbackDataSource(&s.attr, cb, path...) - } - return s -} - -func (s *CustomizableSchemaPluginFrameworkDataSource) RemoveField(key string, path ...string) *CustomizableSchemaPluginFrameworkDataSource { - cb := func(a schema.Attribute) schema.Attribute { - switch attr := a.(type) { - case schema.SingleNestedAttribute: - _, exists := attr.Attributes[key] - if !exists { - panic("Cannot remove field, " + key + " does not exist in the schema") - } - delete(attr.Attributes, key) - return attr - case schema.ListNestedAttribute: - _, exists := attr.NestedObject.Attributes[key] - if !exists { - panic("Cannot remove field, " + key + " does not exist in the schema") - } - delete(attr.NestedObject.Attributes, key) - return attr - case schema.MapNestedAttribute: - _, exists := attr.NestedObject.Attributes[key] - if !exists { - panic("Cannot remove field, " + key + " does not exist in the schema") - } - delete(attr.NestedObject.Attributes, key) - return attr - default: - panic("attribute is not nested, cannot add field") - } - } - - if len(path) == 0 { - s.attr = cb(s.attr) - } else { - navigateSchemaWithCallbackDataSource(&s.attr, cb, path...) - } - return s -} - -func (s *CustomizableSchemaPluginFrameworkDataSource) AddValidator(v any, path ...string) *CustomizableSchemaPluginFrameworkDataSource { - cb := func(attr schema.Attribute) schema.Attribute { - val := reflect.ValueOf(attr) - - // Make a copy of the existing attr. - newAttr := reflect.New(val.Type()).Elem() - newAttr.Set(val) - val = newAttr - - field := val.FieldByName("Validators") - if !field.IsValid() { - panic(fmt.Sprintf("Validators field not found in %T", attr)) - } - if field.Kind() != reflect.Slice { - panic(fmt.Sprintf("Validators field is not a slice in %T", attr)) - } - if !field.CanSet() { - panic(fmt.Sprintf("Validators field cannot be set in %T", attr)) - } - - elemType := field.Type().Elem() - value := reflect.ValueOf(v) - - if !value.Type().AssignableTo(elemType) { - panic(fmt.Sprintf("Value of type %T is not assignable to slice of %s", v, elemType)) - } - - // Append the value - newSlice := reflect.Append(field, value) - field.Set(newSlice) - - return val.Interface().(schema.Attribute) - } - - navigateSchemaWithCallbackDataSource(&s.attr, cb, path...) - - return s -} - -func (s *CustomizableSchemaPluginFrameworkDataSource) SetOptional(path ...string) *CustomizableSchemaPluginFrameworkDataSource { - cb := func(attr schema.Attribute) schema.Attribute { - // Get the concrete value stored in the interface - v := reflect.ValueOf(attr) - - // Make a new addressable value and copy the original value into it - newAttr := reflect.New(v.Type()).Elem() - newAttr.Set(v) - v = newAttr - - field := v.FieldByName("Required") - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.Bool { - field.SetBool(false) - } else { - panic(fmt.Sprintf("Required is not a bool field in %T", attr)) - } - } else { - panic(fmt.Sprintf("Required field not found or cannot be set in %T", attr)) - } - - field = v.FieldByName("Optional") - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.Bool { - field.SetBool(true) - } else { - panic(fmt.Sprintf("Optional is not a bool field in %T", attr)) - } - } else { - panic(fmt.Sprintf("Optional field not found or cannot be set in %T", attr)) - } - - return v.Interface().(schema.Attribute) - } - - navigateSchemaWithCallbackDataSource(&s.attr, cb, path...) - - return s -} - -func (s *CustomizableSchemaPluginFrameworkDataSource) SetRequired(path ...string) *CustomizableSchemaPluginFrameworkDataSource { - cb := func(attr schema.Attribute) schema.Attribute { - // Get the concrete value stored in the interface - v := reflect.ValueOf(attr) - - // Make a new addressable value and copy the original value into it - newAttr := reflect.New(v.Type()).Elem() - newAttr.Set(v) - v = newAttr - - field := v.FieldByName("Required") - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.Bool { - field.SetBool(true) - } else { - panic(fmt.Sprintf("Required is not a bool field in %T", attr)) - } - } else { - panic(fmt.Sprintf("Required field not found or cannot be set in %T", attr)) - } - - field = v.FieldByName("Optional") - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.Bool { - field.SetBool(false) - } else { - panic(fmt.Sprintf("Optional is not a bool field in %T", attr)) - } - } else { - panic(fmt.Sprintf("Optional field not found or cannot be set in %T", attr)) - } - - return v.Interface().(schema.Attribute) - } - - navigateSchemaWithCallbackDataSource(&s.attr, cb, path...) - - return s -} - -func (s *CustomizableSchemaPluginFrameworkDataSource) SetSensitive(path ...string) *CustomizableSchemaPluginFrameworkDataSource { - cb := func(attr schema.Attribute) schema.Attribute { - // Get the concrete value stored in the interface - v := reflect.ValueOf(attr) - - // Make a new addressable value and copy the original value into it - newAttr := reflect.New(v.Type()).Elem() - newAttr.Set(v) - v = newAttr - - field := v.FieldByName("Sensitive") - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.Bool { - field.SetBool(true) - } else { - panic(fmt.Sprintf("Sensitive is not a bool field in %T", attr)) - } - } else { - panic(fmt.Sprintf("Sensitive field not found or cannot be set in %T", attr)) - } - - return v.Interface().(schema.Attribute) - } - - navigateSchemaWithCallbackDataSource(&s.attr, cb, path...) - return s -} - -func (s *CustomizableSchemaPluginFrameworkDataSource) SetDeprecated(msg string, path ...string) *CustomizableSchemaPluginFrameworkDataSource { - cb := func(attr schema.Attribute) schema.Attribute { - // Get the concrete value stored in the interface - v := reflect.ValueOf(attr) - - // Make a new addressable value and copy the original value into it - newAttr := reflect.New(v.Type()).Elem() - newAttr.Set(v) - v = newAttr - - field := v.FieldByName("DeprecationMessage") - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.String { - field.SetString(msg) - } else { - panic(fmt.Sprintf("DeprecationMessage is not a string field in %T", attr)) - } - } else { - panic(fmt.Sprintf("DeprecationMessage field not found or cannot be set in %T", attr)) - } - - return v.Interface().(schema.Attribute) - } - - navigateSchemaWithCallbackDataSource(&s.attr, cb, path...) - - return s -} - -func (s *CustomizableSchemaPluginFrameworkDataSource) SetComputed(path ...string) *CustomizableSchemaPluginFrameworkDataSource { - cb := func(attr schema.Attribute) schema.Attribute { - // Get the concrete value stored in the interface - v := reflect.ValueOf(attr) - - // Make a new addressable value and copy the original value into it - newAttr := reflect.New(v.Type()).Elem() - newAttr.Set(v) - v = newAttr - - field := v.FieldByName("Computed") - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.Bool { - field.SetBool(true) - } else { - panic(fmt.Sprintf("Computed is not a bool field in %T", attr)) - } - } else { - panic(fmt.Sprintf("Computed field not found or cannot be set in %T", attr)) - } - - return v.Interface().(schema.Attribute) - } - - navigateSchemaWithCallbackDataSource(&s.attr, cb, path...) - return s -} - -// SetReadOnly sets the schema to be read-only (i.e. computed, non-optional). -// This should be used for fields that are not user-configurable but are returned -// by the platform. -func (s *CustomizableSchemaPluginFrameworkDataSource) SetReadOnly(path ...string) *CustomizableSchemaPluginFrameworkDataSource { - cb := func(attr schema.Attribute) schema.Attribute { - // Get the concrete value stored in the interface - v := reflect.ValueOf(attr) - - // Make a new addressable value and copy the original value into it - newAttr := reflect.New(v.Type()).Elem() - newAttr.Set(v) - v = newAttr - - field := v.FieldByName("Computed") - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.Bool { - field.SetBool(true) - } else { - panic(fmt.Sprintf("Computed is not a bool field in %T", attr)) - } - } else { - panic(fmt.Sprintf("Computed field not found or cannot be set in %T", attr)) - } - - field = v.FieldByName("Optional") - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.Bool { - field.SetBool(false) - } else { - panic(fmt.Sprintf("Optional is not a bool field in %T", attr)) - } - } else { - panic(fmt.Sprintf("Optional field not found or cannot be set in %T", attr)) - } - - field = v.FieldByName("Required") - if field.IsValid() && field.CanSet() { - if field.Kind() == reflect.Bool { - field.SetBool(false) - } else { - panic(fmt.Sprintf("Required is not a bool field in %T", attr)) - } - } else { - panic(fmt.Sprintf("Required field not found or cannot be set in %T", attr)) - } - - return v.Interface().(schema.Attribute) - } - - navigateSchemaWithCallbackDataSource(&s.attr, cb, path...) - - return s -} - -// Given a attribute map, navigate through the given path, panics if the path is not valid. -func MustSchemaAttributePathDataSource(attrs map[string]schema.Attribute, path ...string) schema.Attribute { - attr := ConstructCustomizableSchemaDataSource(attrs).attr - - res, err := navigateSchemaDataSource(&attr, path...) - if err != nil { - panic(err) - } - - return res -} - -// Helper function for navigating through schema attributes, panics if path does not exist or invalid. -func navigateSchemaDataSource(s *schema.Attribute, path ...string) (schema.Attribute, error) { - current_scm := s - for i, p := range path { - m := attributeToMapDataSource(current_scm) - - v, ok := m[p] - if !ok { - return nil, fmt.Errorf("missing key %s", p) - } - - if i == len(path)-1 { - return v, nil - } - current_scm = &v - } - return nil, fmt.Errorf("path %v is incomplete", path) -} - -// Helper function for navigating through schema attributes, panics if path does not exist or invalid. -func navigateSchemaWithCallbackDataSource(s *schema.Attribute, cb func(schema.Attribute) schema.Attribute, path ...string) (schema.Attribute, error) { - current_scm := s - for i, p := range path { - m := attributeToMapDataSource(current_scm) - - v, ok := m[p] - if !ok { - return nil, fmt.Errorf("missing key %s", p) - } - - if i == len(path)-1 { - m[p] = cb(v) - return m[p], nil - } - current_scm = &v - } - return nil, fmt.Errorf("path %v is incomplete", path) -} diff --git a/common/customizable_schema_plugin_framework_test.go b/common/customizable_schema_plugin_framework_test.go index 7ca921b71d..c31c6dad59 100644 --- a/common/customizable_schema_plugin_framework_test.go +++ b/common/customizable_schema_plugin_framework_test.go @@ -55,12 +55,13 @@ func TestCustomizeSchema(t *testing.T) { c.AddValidator(stringLengthBetweenValidator{}, "description") return c }) - assert.True(t, MustSchemaAttributePath(scm.Attributes, "nested", "enabled").IsRequired()) - assert.True(t, MustSchemaAttributePath(scm.Attributes, "nested", "name").IsSensitive()) - assert.True(t, MustSchemaAttributePath(scm.Attributes, "map").GetDeprecationMessage() == "deprecated") + + assert.True(t, scm.Attributes["nested"].(schema.SingleNestedAttribute).Attributes["enabled"].IsRequired()) + assert.True(t, scm.Attributes["nested"].(schema.SingleNestedAttribute).Attributes["name"].IsSensitive()) + assert.True(t, scm.Attributes["map"].GetDeprecationMessage() == "deprecated") assert.True(t, scm.Attributes["description"].IsOptional()) assert.True(t, !scm.Attributes["map"].IsOptional()) assert.True(t, !scm.Attributes["map"].IsRequired()) assert.True(t, scm.Attributes["map"].IsComputed()) - assert.True(t, len(MustSchemaAttributePath(scm.Attributes, "description").(schema.StringAttribute).Validators) == 1) + assert.True(t, len(scm.Attributes["description"].(schema.StringAttribute).Validators) == 1) } diff --git a/common/databricks_schema_attribute.go b/common/databricks_schema_attribute.go index 4ebbe97044..dcb94b5781 100644 --- a/common/databricks_schema_attribute.go +++ b/common/databricks_schema_attribute.go @@ -4,78 +4,566 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" dataschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" ) type Attribute interface { ToDataSourceAttribute() dataschema.Attribute ToResourceAttribute() schema.Attribute + SetOptional() Attribute + SetRequired() Attribute + SetSensitive() Attribute + SetComputed() Attribute + SetReadOnly() Attribute + SetDeprecated(string) Attribute + AddValidators(any) Attribute } type StringAttribute struct { - Optional bool - Required bool + Optional bool + Required bool + Sensitive bool + Computed bool + DeprecationMessage string + Validators []validator.String } func (a StringAttribute) ToDataSourceAttribute() dataschema.Attribute { - return dataschema.StringAttribute{Optional: a.Optional, Required: a.Required} + return dataschema.StringAttribute{Optional: a.Optional, Required: a.Required, Sensitive: a.Sensitive, DeprecationMessage: a.DeprecationMessage, Computed: a.Computed, Validators: a.Validators} } -func (a StringAttribute) ToResourceAttribute() dataschema.Attribute { - return schema.StringAttribute{Optional: a.Optional, Required: a.Required} +func (a StringAttribute) ToResourceAttribute() schema.Attribute { + return schema.StringAttribute{Optional: a.Optional, Required: a.Required, Sensitive: a.Sensitive, DeprecationMessage: a.DeprecationMessage, Computed: a.Computed, Validators: a.Validators} +} + +func (a StringAttribute) SetOptional() Attribute { + a.Optional = true + a.Required = false + return a +} + +func (a StringAttribute) SetRequired() Attribute { + a.Optional = false + a.Required = true + return a +} + +func (a StringAttribute) SetSensitive() Attribute { + a.Sensitive = true + return a +} + +func (a StringAttribute) SetComputed() Attribute { + a.Computed = true + return a +} + +func (a StringAttribute) SetReadOnly() Attribute { + a.Computed = true + a.Optional = false + a.Required = false + return a +} + +func (a StringAttribute) SetDeprecated(msg string) Attribute { + a.DeprecationMessage = msg + return a +} + +func (a StringAttribute) AddValidators(v any) Attribute { + a.Validators = append(a.Validators, v.(validator.String)) + return a } type Float64Attribute struct { - Optional bool - Required bool + Optional bool + Required bool + Sensitive bool + Computed bool + DeprecationMessage string + Validators []validator.Float64 } func (a Float64Attribute) ToDataSourceAttribute() dataschema.Attribute { - return dataschema.Float64Attribute{Optional: a.Optional, Required: a.Required} + return dataschema.Float64Attribute{Optional: a.Optional, Required: a.Required, Sensitive: a.Sensitive, DeprecationMessage: a.DeprecationMessage, Computed: a.Computed, Validators: a.Validators} +} + +func (a Float64Attribute) ToResourceAttribute() schema.Attribute { + return schema.Float64Attribute{Optional: a.Optional, Required: a.Required, Sensitive: a.Sensitive, DeprecationMessage: a.DeprecationMessage, Computed: a.Computed, Validators: a.Validators} +} + +func (a Float64Attribute) SetOptional() Attribute { + a.Optional = true + a.Required = false + return a +} + +func (a Float64Attribute) SetRequired() Attribute { + a.Optional = false + a.Required = true + return a +} + +func (a Float64Attribute) SetSensitive() Attribute { + a.Sensitive = true + return a +} + +func (a Float64Attribute) SetComputed() Attribute { + a.Computed = true + return a } -func (a Float64Attribute) ToResourceAttribute() dataschema.Attribute { - return schema.Float64Attribute{Optional: a.Optional, Required: a.Required} +func (a Float64Attribute) SetReadOnly() Attribute { + a.Computed = true + a.Optional = false + a.Required = false + return a +} + +func (a Float64Attribute) SetDeprecated(msg string) Attribute { + a.DeprecationMessage = msg + return a +} + +func (a Float64Attribute) AddValidators(v any) Attribute { + a.Validators = append(a.Validators, v.(validator.Float64)) + return a } type Int64Attribute struct { - Optional bool - Required bool + Optional bool + Required bool + Sensitive bool + Computed bool + DeprecationMessage string + Validators []validator.Int64 +} + +func (a Int64Attribute) ToDataSourceAttribute() dataschema.Attribute { + return dataschema.Int64Attribute{Optional: a.Optional, Required: a.Required, Sensitive: a.Sensitive, DeprecationMessage: a.DeprecationMessage, Computed: a.Computed, Validators: a.Validators} +} + +func (a Int64Attribute) ToResourceAttribute() schema.Attribute { + return schema.Int64Attribute{Optional: a.Optional, Required: a.Required, Sensitive: a.Sensitive, DeprecationMessage: a.DeprecationMessage, Computed: a.Computed, Validators: a.Validators} +} + +func (a Int64Attribute) SetOptional() Attribute { + a.Optional = true + a.Required = false + return a +} + +func (a Int64Attribute) SetRequired() Attribute { + a.Optional = false + a.Required = true + return a +} + +func (a Int64Attribute) SetSensitive() Attribute { + a.Sensitive = true + return a +} + +func (a Int64Attribute) SetComputed() Attribute { + a.Computed = true + return a +} + +func (a Int64Attribute) SetReadOnly() Attribute { + a.Computed = true + a.Optional = false + a.Required = false + return a +} + +func (a Int64Attribute) SetDeprecated(msg string) Attribute { + a.DeprecationMessage = msg + return a +} + +func (a Int64Attribute) AddValidators(v any) Attribute { + a.Validators = append(a.Validators, v.(validator.Int64)) + return a } type BoolAttribute struct { - Optional bool - Required bool + Optional bool + Required bool + Sensitive bool + Computed bool + DeprecationMessage string + Validators []validator.Bool +} + +func (a BoolAttribute) ToDataSourceAttribute() dataschema.Attribute { + return dataschema.BoolAttribute{Optional: a.Optional, Required: a.Required, Sensitive: a.Sensitive, DeprecationMessage: a.DeprecationMessage, Computed: a.Computed, Validators: a.Validators} +} + +func (a BoolAttribute) ToResourceAttribute() schema.Attribute { + return schema.BoolAttribute{Optional: a.Optional, Required: a.Required, Sensitive: a.Sensitive, DeprecationMessage: a.DeprecationMessage, Computed: a.Computed, Validators: a.Validators} +} + +func (a BoolAttribute) SetOptional() Attribute { + a.Optional = true + a.Required = false + return a +} + +func (a BoolAttribute) SetRequired() Attribute { + a.Optional = false + a.Required = true + return a +} + +func (a BoolAttribute) SetSensitive() Attribute { + a.Sensitive = true + return a +} + +func (a BoolAttribute) SetComputed() Attribute { + a.Computed = true + return a +} + +func (a BoolAttribute) SetReadOnly() Attribute { + a.Computed = true + a.Optional = false + a.Required = false + return a +} + +func (a BoolAttribute) SetDeprecated(msg string) Attribute { + a.DeprecationMessage = msg + return a +} + +func (a BoolAttribute) AddValidators(v any) Attribute { + a.Validators = append(a.Validators, v.(validator.Bool)) + return a } type MapAttribute struct { - ElementType attr.Type - Optional bool - Required bool + ElementType attr.Type + Optional bool + Required bool + Sensitive bool + Computed bool + DeprecationMessage string + Validators []validator.Map +} + +func (a MapAttribute) ToDataSourceAttribute() dataschema.Attribute { + return dataschema.MapAttribute{ElementType: a.ElementType, Optional: a.Optional, Required: a.Required, Sensitive: a.Sensitive, DeprecationMessage: a.DeprecationMessage, Computed: a.Computed, Validators: a.Validators} +} + +func (a MapAttribute) ToResourceAttribute() schema.Attribute { + return schema.MapAttribute{ElementType: a.ElementType, Optional: a.Optional, Required: a.Required, Sensitive: a.Sensitive, DeprecationMessage: a.DeprecationMessage, Computed: a.Computed, Validators: a.Validators} +} + +func (a MapAttribute) SetOptional() Attribute { + a.Optional = true + a.Required = false + return a +} + +func (a MapAttribute) SetRequired() Attribute { + a.Optional = false + a.Required = true + return a +} + +func (a MapAttribute) SetSensitive() Attribute { + a.Sensitive = true + return a +} + +func (a MapAttribute) SetComputed() Attribute { + a.Computed = true + return a +} + +func (a MapAttribute) SetReadOnly() Attribute { + a.Computed = true + a.Optional = false + a.Required = false + return a +} + +func (a MapAttribute) SetDeprecated(msg string) Attribute { + a.DeprecationMessage = msg + return a +} + +func (a MapAttribute) AddValidators(v any) Attribute { + a.Validators = append(a.Validators, v.(validator.Map)) + return a } type ListAttribute struct { - ElementType attr.Type - Optional bool - Required bool + ElementType attr.Type + Optional bool + Required bool + Sensitive bool + Computed bool + DeprecationMessage string + Validators []validator.List +} + +func (a ListAttribute) ToDataSourceAttribute() dataschema.Attribute { + return dataschema.ListAttribute{ElementType: a.ElementType, Optional: a.Optional, Required: a.Required, Sensitive: a.Sensitive, DeprecationMessage: a.DeprecationMessage, Computed: a.Computed, Validators: a.Validators} +} + +func (a ListAttribute) ToResourceAttribute() schema.Attribute { + return schema.ListAttribute{ElementType: a.ElementType, Optional: a.Optional, Required: a.Required, Sensitive: a.Sensitive, DeprecationMessage: a.DeprecationMessage, Computed: a.Computed, Validators: a.Validators} +} + +func (a ListAttribute) SetOptional() Attribute { + a.Optional = true + a.Required = false + return a +} + +func (a ListAttribute) SetRequired() Attribute { + a.Optional = false + a.Required = true + return a +} + +func (a ListAttribute) SetSensitive() Attribute { + a.Sensitive = true + return a +} + +func (a ListAttribute) SetComputed() Attribute { + a.Computed = true + return a +} + +func (a ListAttribute) SetReadOnly() Attribute { + a.Computed = true + a.Optional = false + a.Required = false + return a +} + +func (a ListAttribute) SetDeprecated(msg string) Attribute { + a.DeprecationMessage = msg + return a +} + +func (a ListAttribute) AddValidators(v any) Attribute { + a.Validators = append(a.Validators, v.(validator.List)) + return a } type SingleNestedAttribute struct { - Optional bool - Required bool + Attributes map[string]Attribute + Optional bool + Required bool + Sensitive bool + Computed bool + DeprecationMessage string + Validators []validator.Object +} + +func (a SingleNestedAttribute) ToDataSourceAttribute() dataschema.Attribute { + return dataschema.SingleNestedAttribute{Attributes: ToDataSourceAttributeMap(a.Attributes), Optional: a.Optional, Required: a.Required, Sensitive: a.Sensitive, DeprecationMessage: a.DeprecationMessage, Computed: a.Computed, Validators: a.Validators} +} + +func (a SingleNestedAttribute) ToResourceAttribute() schema.Attribute { + return schema.SingleNestedAttribute{Attributes: ToResourceAttributeMap(a.Attributes), Optional: a.Optional, Required: a.Required, Sensitive: a.Sensitive, DeprecationMessage: a.DeprecationMessage, Computed: a.Computed, Validators: a.Validators} +} + +func (a SingleNestedAttribute) SetOptional() Attribute { + a.Optional = true + a.Required = false + return a +} + +func (a SingleNestedAttribute) SetRequired() Attribute { + a.Optional = false + a.Required = true + return a +} + +func (a SingleNestedAttribute) SetSensitive() Attribute { + a.Sensitive = true + return a +} + +func (a SingleNestedAttribute) SetComputed() Attribute { + a.Computed = true + return a +} + +func (a SingleNestedAttribute) SetReadOnly() Attribute { + a.Computed = true + a.Optional = false + a.Required = false + return a +} + +func (a SingleNestedAttribute) SetDeprecated(msg string) Attribute { + a.DeprecationMessage = msg + return a +} + +func (a SingleNestedAttribute) AddValidators(v any) Attribute { + a.Validators = append(a.Validators, v.(validator.Object)) + return a } type ListNestedAttribute struct { - NestedObject NestedAttributeObject - Optional bool - Required bool + NestedObject NestedAttributeObject + Optional bool + Required bool + Sensitive bool + Computed bool + DeprecationMessage string + Validators []validator.List +} + +func (a ListNestedAttribute) ToDataSourceAttribute() dataschema.Attribute { + return dataschema.ListNestedAttribute{NestedObject: a.NestedObject.ToDataSourceAttribute(), Optional: a.Optional, Required: a.Required, Sensitive: a.Sensitive, DeprecationMessage: a.DeprecationMessage, Computed: a.Computed, Validators: a.Validators} +} + +func (a ListNestedAttribute) ToResourceAttribute() schema.Attribute { + return schema.ListNestedAttribute{NestedObject: a.NestedObject.ToResourceAttribute(), Optional: a.Optional, Required: a.Required, Sensitive: a.Sensitive, DeprecationMessage: a.DeprecationMessage, Computed: a.Computed, Validators: a.Validators} +} + +func (a ListNestedAttribute) SetOptional() Attribute { + a.Optional = true + a.Required = false + return a +} + +func (a ListNestedAttribute) SetRequired() Attribute { + a.Optional = false + a.Required = true + return a +} + +func (a ListNestedAttribute) SetSensitive() Attribute { + a.Sensitive = true + return a +} + +func (a ListNestedAttribute) SetComputed() Attribute { + a.Computed = true + return a +} + +func (a ListNestedAttribute) SetReadOnly() Attribute { + a.Computed = true + a.Optional = false + a.Required = false + return a +} + +func (a ListNestedAttribute) SetDeprecated(msg string) Attribute { + a.DeprecationMessage = msg + return a +} + +func (a ListNestedAttribute) AddValidators(v any) Attribute { + a.Validators = append(a.Validators, v.(validator.List)) + return a } type NestedAttributeObject struct { Attributes map[string]Attribute } +func (a NestedAttributeObject) ToDataSourceAttribute() dataschema.NestedAttributeObject { + dataSourceAttributes := ToDataSourceAttributeMap(a.Attributes) + + return dataschema.NestedAttributeObject{ + Attributes: dataSourceAttributes, + } +} + +func (a NestedAttributeObject) ToResourceAttribute() schema.NestedAttributeObject { + resourceAttributes := ToResourceAttributeMap(a.Attributes) + + return schema.NestedAttributeObject{ + Attributes: resourceAttributes, + } +} + +func ToDataSourceAttributeMap(attributes map[string]Attribute) map[string]dataschema.Attribute { + dataSourceAttributes := make(map[string]dataschema.Attribute) + + for key, attribute := range attributes { + dataSourceAttributes[key] = attribute.ToDataSourceAttribute() + } + + return dataSourceAttributes +} + +func ToResourceAttributeMap(attributes map[string]Attribute) map[string]schema.Attribute { + resourceAttributes := make(map[string]schema.Attribute) + + for key, attribute := range attributes { + resourceAttributes[key] = attribute.ToResourceAttribute() + } + + return resourceAttributes +} + type MapNestedAttribute struct { - NestedObject NestedAttributeObject - Optional bool - Required bool + NestedObject NestedAttributeObject + Optional bool + Required bool + Sensitive bool + Computed bool + DeprecationMessage string + Validators []validator.Map +} + +func (a MapNestedAttribute) ToDataSourceAttribute() dataschema.Attribute { + return dataschema.MapNestedAttribute{NestedObject: a.NestedObject.ToDataSourceAttribute(), Optional: a.Optional, Required: a.Required, Sensitive: a.Sensitive, DeprecationMessage: a.DeprecationMessage, Computed: a.Computed, Validators: a.Validators} +} + +func (a MapNestedAttribute) ToResourceAttribute() schema.Attribute { + return schema.MapNestedAttribute{NestedObject: a.NestedObject.ToResourceAttribute(), Optional: a.Optional, Required: a.Required, Sensitive: a.Sensitive, DeprecationMessage: a.DeprecationMessage, Computed: a.Computed, Validators: a.Validators} +} + +func (a MapNestedAttribute) SetOptional() Attribute { + a.Optional = true + a.Required = false + return a +} + +func (a MapNestedAttribute) SetRequired() Attribute { + a.Optional = false + a.Required = true + return a +} + +func (a MapNestedAttribute) SetSensitive() Attribute { + a.Sensitive = true + return a +} + +func (a MapNestedAttribute) SetComputed() Attribute { + a.Computed = true + return a +} + +func (a MapNestedAttribute) SetReadOnly() Attribute { + a.Computed = true + a.Optional = false + a.Required = false + return a +} + +func (a MapNestedAttribute) SetDeprecated(msg string) Attribute { + a.DeprecationMessage = msg + return a +} + +func (a MapNestedAttribute) AddValidators(v any) Attribute { + a.Validators = append(a.Validators, v.(validator.Map)) + return a } diff --git a/common/reflect_resource_plugin_framework.go b/common/reflect_resource_plugin_framework.go index f84bf99cc8..5ad8c9d5be 100644 --- a/common/reflect_resource_plugin_framework.go +++ b/common/reflect_resource_plugin_framework.go @@ -127,7 +127,7 @@ func tfSdkToGoSdkSingleField(srcField reflect.Value, destField reflect.Value, sr case types.Map: panic("types.Map should never be used, use go native maps instead") default: - if srcField.IsNil() { + if srcField.IsZero() { // Skip nils return nil } @@ -312,7 +312,7 @@ func goSdkToTfSdkSingleField(srcField reflect.Value, destField reflect.Value, sr destField.Set(reflect.ValueOf(types.StringNull())) } case reflect.Struct: - if srcField.IsNil() { + if srcField.IsZero() { // Skip nils return nil } @@ -385,8 +385,8 @@ func checkTheStringInForceSendFields(fieldName string, forceSendFields []string) return false } -func pluginFrameworkResourceTypeToSchema(v reflect.Value) map[string]schema.Attribute { - scm := map[string]schema.Attribute{} +func pluginFrameworkTypeToSchema(v reflect.Value) map[string]Attribute { + scm := map[string]Attribute{} rk := v.Kind() if rk == reflect.Ptr { v = v.Elem() @@ -408,8 +408,8 @@ func pluginFrameworkResourceTypeToSchema(v reflect.Value) map[string]schema.Attr if kind == reflect.Ptr { elem := typeField.Type.Elem() sv := reflect.New(elem).Elem() - nestedScm := pluginFrameworkResourceTypeToSchema(sv) - scm[fieldName] = schema.SingleNestedAttribute{Attributes: nestedScm, Optional: isOptional, Required: !isOptional} + nestedScm := pluginFrameworkTypeToSchema(sv) + scm[fieldName] = SingleNestedAttribute{Attributes: nestedScm, Optional: isOptional, Required: !isOptional} } else if kind == reflect.Slice { elem := typeField.Type.Elem() if elem.Kind() == reflect.Ptr { @@ -420,17 +420,17 @@ func pluginFrameworkResourceTypeToSchema(v reflect.Value) map[string]schema.Attr } switch elem { case reflect.TypeOf(types.Bool{}): - scm[fieldName] = schema.ListAttribute{ElementType: types.BoolType, Optional: isOptional, Required: !isOptional} + scm[fieldName] = ListAttribute{ElementType: types.BoolType, Optional: isOptional, Required: !isOptional} case reflect.TypeOf(types.Int64{}): - scm[fieldName] = schema.ListAttribute{ElementType: types.Int64Type, Optional: isOptional, Required: !isOptional} + scm[fieldName] = ListAttribute{ElementType: types.Int64Type, Optional: isOptional, Required: !isOptional} case reflect.TypeOf(types.Float64{}): - scm[fieldName] = schema.ListAttribute{ElementType: types.Float64Type, Optional: isOptional, Required: !isOptional} + scm[fieldName] = ListAttribute{ElementType: types.Float64Type, Optional: isOptional, Required: !isOptional} case reflect.TypeOf(types.String{}): - scm[fieldName] = schema.ListAttribute{ElementType: types.StringType, Optional: isOptional, Required: !isOptional} + scm[fieldName] = ListAttribute{ElementType: types.StringType, Optional: isOptional, Required: !isOptional} default: // Nested struct - nestedScm := pluginFrameworkResourceTypeToSchema(reflect.New(elem).Elem()) - scm[fieldName] = schema.ListNestedAttribute{NestedObject: schema.NestedAttributeObject{Attributes: nestedScm}, Optional: isOptional, Required: !isOptional} + nestedScm := pluginFrameworkTypeToSchema(reflect.New(elem).Elem()) + scm[fieldName] = ListNestedAttribute{NestedObject: NestedAttributeObject{Attributes: nestedScm}, Optional: isOptional, Required: !isOptional} } } else if kind == reflect.Map { elem := typeField.Type.Elem() @@ -442,28 +442,28 @@ func pluginFrameworkResourceTypeToSchema(v reflect.Value) map[string]schema.Attr } switch elem { case reflect.TypeOf(types.Bool{}): - scm[fieldName] = schema.MapAttribute{ElementType: types.BoolType, Optional: isOptional, Required: !isOptional} + scm[fieldName] = MapAttribute{ElementType: types.BoolType, Optional: isOptional, Required: !isOptional} case reflect.TypeOf(types.Int64{}): - scm[fieldName] = schema.MapAttribute{ElementType: types.Int64Type, Optional: isOptional, Required: !isOptional} + scm[fieldName] = MapAttribute{ElementType: types.Int64Type, Optional: isOptional, Required: !isOptional} case reflect.TypeOf(types.Float64{}): - scm[fieldName] = schema.MapAttribute{ElementType: types.Float64Type, Optional: isOptional, Required: !isOptional} + scm[fieldName] = MapAttribute{ElementType: types.Float64Type, Optional: isOptional, Required: !isOptional} case reflect.TypeOf(types.String{}): - scm[fieldName] = schema.MapAttribute{ElementType: types.StringType, Optional: isOptional, Required: !isOptional} + scm[fieldName] = MapAttribute{ElementType: types.StringType, Optional: isOptional, Required: !isOptional} default: // Nested struct - nestedScm := pluginFrameworkResourceTypeToSchema(reflect.New(elem).Elem()) - scm[fieldName] = schema.MapNestedAttribute{NestedObject: schema.NestedAttributeObject{Attributes: nestedScm}, Optional: isOptional, Required: !isOptional} + nestedScm := pluginFrameworkTypeToSchema(reflect.New(elem).Elem()) + scm[fieldName] = MapNestedAttribute{NestedObject: NestedAttributeObject{Attributes: nestedScm}, Optional: isOptional, Required: !isOptional} } } else if kind == reflect.Struct { switch field.v.Interface().(type) { case types.Bool: - scm[fieldName] = schema.BoolAttribute{Optional: isOptional, Required: !isOptional} + scm[fieldName] = BoolAttribute{Optional: isOptional, Required: !isOptional} case types.Int64: - scm[fieldName] = schema.Int64Attribute{Optional: isOptional, Required: !isOptional} + scm[fieldName] = Int64Attribute{Optional: isOptional, Required: !isOptional} case types.Float64: - scm[fieldName] = schema.Float64Attribute{Optional: isOptional, Required: !isOptional} + scm[fieldName] = Float64Attribute{Optional: isOptional, Required: !isOptional} case types.String: - scm[fieldName] = schema.StringAttribute{Optional: isOptional, Required: !isOptional} + scm[fieldName] = StringAttribute{Optional: isOptional, Required: !isOptional} case types.List: panic("types.List should never be used in tfsdk structs") case types.Map: @@ -472,112 +472,12 @@ func pluginFrameworkResourceTypeToSchema(v reflect.Value) map[string]schema.Attr // If it is a real stuct instead of a tfsdk type, recursively resolve it. elem := typeField.Type sv := reflect.New(elem) - nestedScm := pluginFrameworkResourceTypeToSchema(sv) - scm[fieldName] = schema.SingleNestedAttribute{Attributes: nestedScm, Optional: isOptional, Required: !isOptional} + nestedScm := pluginFrameworkTypeToSchema(sv) + scm[fieldName] = SingleNestedAttribute{Attributes: nestedScm, Optional: isOptional, Required: !isOptional} } } else if kind == reflect.String { // This case is for Enum types. - scm[fieldName] = schema.StringAttribute{Optional: isOptional, Required: !isOptional} - } else { - panic(fmt.Errorf("unknown type for field: %s", typeField.Name)) - } - } - return scm -} - -func pluginFrameworkDataSourceTypeToSchema(v reflect.Value) map[string]dataschema.Attribute { - scm := map[string]dataschema.Attribute{} - rk := v.Kind() - if rk == reflect.Ptr { - v = v.Elem() - rk = v.Kind() - } - if rk != reflect.Struct { - panic(fmt.Errorf("Schema value of Struct is expected, but got %s: %#v", reflectKind(rk), v)) - } - fields := listAllFields(v) - for _, field := range fields { - typeField := field.sf - fieldName := typeField.Tag.Get("tfsdk") - if fieldName == "-" { - continue - } - isOptional := fieldIsOptional(typeField) - // For now everything is marked as optional. TODO: add tf annotations for computed, optional, etc. - kind := typeField.Type.Kind() - if kind == reflect.Ptr { - elem := typeField.Type.Elem() - sv := reflect.New(elem).Elem() - nestedScm := pluginFrameworkDataSourceTypeToSchema(sv) - scm[fieldName] = dataschema.SingleNestedAttribute{Attributes: nestedScm, Optional: isOptional, Required: !isOptional} - } else if kind == reflect.Slice { - elem := typeField.Type.Elem() - if elem.Kind() == reflect.Ptr { - elem = elem.Elem() - } - if elem.Kind() != reflect.Struct { - panic(fmt.Errorf("unsupported slice value for %s: %s", fieldName, reflectKind(elem.Kind()))) - } - switch elem { - case reflect.TypeOf(types.Bool{}): - scm[fieldName] = dataschema.ListAttribute{ElementType: types.BoolType, Optional: isOptional, Required: !isOptional} - case reflect.TypeOf(types.Int64{}): - scm[fieldName] = dataschema.ListAttribute{ElementType: types.Int64Type, Optional: isOptional, Required: !isOptional} - case reflect.TypeOf(types.Float64{}): - scm[fieldName] = dataschema.ListAttribute{ElementType: types.Float64Type, Optional: isOptional, Required: !isOptional} - case reflect.TypeOf(types.String{}): - scm[fieldName] = dataschema.ListAttribute{ElementType: types.StringType, Optional: isOptional, Required: !isOptional} - default: - // Nested struct - nestedScm := pluginFrameworkDataSourceTypeToSchema(reflect.New(elem).Elem()) - scm[fieldName] = dataschema.ListNestedAttribute{NestedObject: dataschema.NestedAttributeObject{Attributes: nestedScm}, Optional: isOptional, Required: !isOptional} - } - } else if kind == reflect.Map { - elem := typeField.Type.Elem() - if elem.Kind() == reflect.Ptr { - elem = elem.Elem() - } - if elem.Kind() != reflect.Struct { - panic(fmt.Errorf("unsupported map value for %s: %s", fieldName, reflectKind(elem.Kind()))) - } - switch elem { - case reflect.TypeOf(types.Bool{}): - scm[fieldName] = dataschema.MapAttribute{ElementType: types.BoolType, Optional: isOptional, Required: !isOptional} - case reflect.TypeOf(types.Int64{}): - scm[fieldName] = dataschema.MapAttribute{ElementType: types.Int64Type, Optional: isOptional, Required: !isOptional} - case reflect.TypeOf(types.Float64{}): - scm[fieldName] = dataschema.MapAttribute{ElementType: types.Float64Type, Optional: isOptional, Required: !isOptional} - case reflect.TypeOf(types.String{}): - scm[fieldName] = dataschema.MapAttribute{ElementType: types.StringType, Optional: isOptional, Required: !isOptional} - default: - // Nested struct - nestedScm := pluginFrameworkDataSourceTypeToSchema(reflect.New(elem).Elem()) - scm[fieldName] = dataschema.MapNestedAttribute{NestedObject: dataschema.NestedAttributeObject{Attributes: nestedScm}, Optional: isOptional, Required: !isOptional} - } - } else if kind == reflect.Struct { - switch field.v.Interface().(type) { - case types.Bool: - scm[fieldName] = dataschema.BoolAttribute{Optional: isOptional, Required: !isOptional} - case types.Int64: - scm[fieldName] = dataschema.Int64Attribute{Optional: isOptional, Required: !isOptional} - case types.Float64: - scm[fieldName] = dataschema.Float64Attribute{Optional: isOptional, Required: !isOptional} - case types.String: - scm[fieldName] = dataschema.StringAttribute{Optional: isOptional, Required: !isOptional} - case types.List: - panic("types.List should never be used in tfsdk structs") - case types.Map: - panic("types.Map should never be used in tfsdk structs") - default: - // If it is a real stuct instead of a tfsdk type, recursively resolve it. - elem := typeField.Type - sv := reflect.New(elem) - nestedScm := pluginFrameworkDataSourceTypeToSchema(sv) - scm[fieldName] = dataschema.SingleNestedAttribute{Attributes: nestedScm, Optional: isOptional, Required: !isOptional} - } - } else if kind == reflect.String { - // This case is for Enum types. - scm[fieldName] = dataschema.StringAttribute{Optional: isOptional, Required: !isOptional} + scm[fieldName] = StringAttribute{Optional: isOptional, Required: !isOptional} } else { panic(fmt.Errorf("unknown type for field: %s", typeField.Name)) } @@ -595,29 +495,29 @@ func PluginFrameworkResourceStructToSchema(v any, customizeSchema func(Customiza return schema.Schema{Attributes: attributes} } -func PluginFrameworkDataSourceStructToSchema(v any, customizeSchema func(CustomizableSchemaPluginFrameworkDataSource) CustomizableSchemaPluginFrameworkDataSource) dataschema.Schema { +func PluginFrameworkDataSourceStructToSchema(v any, customizeSchema func(CustomizableSchemaPluginFramework) CustomizableSchemaPluginFramework) dataschema.Schema { attributes := PluginFrameworkDataSourceStructToSchemaMap(v, customizeSchema) return dataschema.Schema{Attributes: attributes} } func PluginFrameworkResourceStructToSchemaMap(v any, customizeSchema func(CustomizableSchemaPluginFramework) CustomizableSchemaPluginFramework) map[string]schema.Attribute { - attributes := pluginFrameworkResourceTypeToSchema(reflect.ValueOf(v)) + attributes := pluginFrameworkTypeToSchema(reflect.ValueOf(v)) if customizeSchema != nil { cs := customizeSchema(*ConstructCustomizableSchema(attributes)) - return cs.ToAttributeMap() + return ToResourceAttributeMap(cs.ToAttributeMap()) } else { - return attributes + return ToResourceAttributeMap(attributes) } } -func PluginFrameworkDataSourceStructToSchemaMap(v any, customizeSchema func(CustomizableSchemaPluginFrameworkDataSource) CustomizableSchemaPluginFrameworkDataSource) map[string]dataschema.Attribute { - attributes := pluginFrameworkDataSourceTypeToSchema(reflect.ValueOf(v)) +func PluginFrameworkDataSourceStructToSchemaMap(v any, customizeSchema func(CustomizableSchemaPluginFramework) CustomizableSchemaPluginFramework) map[string]dataschema.Attribute { + attributes := pluginFrameworkTypeToSchema(reflect.ValueOf(v)) if customizeSchema != nil { - cs := customizeSchema(*ConstructCustomizableSchemaDataSource(attributes)) - return cs.ToAttributeMap() + cs := customizeSchema(*ConstructCustomizableSchema(attributes)) + return ToDataSourceAttributeMap(cs.ToAttributeMap()) } else { - return attributes + return ToDataSourceAttributeMap(attributes) } } From 715d086dd70c54d9f5d628e4dd1254556b67ea99 Mon Sep 17 00:00:00 2001 From: edwardfeng-db Date: Wed, 14 Aug 2024 19:19:14 +0200 Subject: [PATCH 4/5] update --- pluginframework/data_volumes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pluginframework/data_volumes.go b/pluginframework/data_volumes.go index 824609b2a5..714ece2ffc 100644 --- a/pluginframework/data_volumes.go +++ b/pluginframework/data_volumes.go @@ -33,7 +33,7 @@ func (d *VolumesDataSource) Metadata(ctx context.Context, req datasource.Metadat } func (d *VolumesDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = common.PluginFrameworkDataSourceStructToSchema(VolumesList{}, func(c common.CustomizableSchemaPluginFrameworkDataSource) common.CustomizableSchemaPluginFrameworkDataSource { + resp.Schema = common.PluginFrameworkDataSourceStructToSchema(VolumesList{}, func(c common.CustomizableSchemaPluginFramework) common.CustomizableSchemaPluginFramework { c.SetComputed("ids") return c }) From c6e00ed687403e9c955c515c40cee83662e6ab6e Mon Sep 17 00:00:00 2001 From: edwardfeng-db Date: Wed, 14 Aug 2024 19:21:40 +0200 Subject: [PATCH 5/5] update --- common/databricks_schema_attribute.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/databricks_schema_attribute.go b/common/databricks_schema_attribute.go index dcb94b5781..31fd6e2877 100644 --- a/common/databricks_schema_attribute.go +++ b/common/databricks_schema_attribute.go @@ -7,6 +7,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/schema/validator" ) +// Common interface for all attributes, we need this because in terraform plugin framework, the datasource schema and resource +// schema are in two separate packages. This common interface prevents us from keeping two copies of StructToSchema and CustomizableSchema. type Attribute interface { ToDataSourceAttribute() dataschema.Attribute ToResourceAttribute() schema.Attribute