From 0e9f500de8177bcc691b5a03a6034bdb55db7eeb Mon Sep 17 00:00:00 2001 From: Edward Feng <67326663+edwardfeng-db@users.noreply.github.com> Date: Wed, 21 Aug 2024 19:34:29 +0200 Subject: [PATCH] [Internal] Add CustomizableSchema for Plugin Framework (#3927) ## Changes - Addressed comments in https://github.com/databricks/terraform-provider-databricks/pull/3880 - Add CustomizableSchema for PluginFramework ## Tests - [x] `make test` run locally - [x] relevant change in `docs/` folder - [x] covered with integration tests in `internal/acceptance` - [x] relevant acceptance tests are passing - [x] using Go SDK --- .../tfschema/customizable_schema.go | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 internal/pluginframework/tfschema/customizable_schema.go diff --git a/internal/pluginframework/tfschema/customizable_schema.go b/internal/pluginframework/tfschema/customizable_schema.go new file mode 100644 index 0000000000..129d8b161a --- /dev/null +++ b/internal/pluginframework/tfschema/customizable_schema.go @@ -0,0 +1,154 @@ +package tfschema + +import ( + "fmt" + "reflect" + + "github.com/databricks/terraform-provider-databricks/common" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +// CustomizableSchema is a wrapper struct on top of AttributeBuilder that can be used to navigate through nested schema add customizations. +type CustomizableSchema struct { + attr AttributeBuilder +} + +// ConstructCustomizableSchema constructs a CustomizableSchema given a map from string to AttributeBuilder. +func ConstructCustomizableSchema(attributes map[string]AttributeBuilder) *CustomizableSchema { + attr := AttributeBuilder(SingleNestedAttributeBuilder{Attributes: attributes}) + return &CustomizableSchema{attr: attr} +} + +// ToAttributeMap converts CustomizableSchema into a map from string to Attribute. +func (s *CustomizableSchema) ToAttributeMap() map[string]AttributeBuilder { + return attributeToMap(&s.attr) +} + +// attributeToMap converts AttributeBuilder into a map from string to AttributeBuilder. +func attributeToMap(attr *AttributeBuilder) map[string]AttributeBuilder { + var m map[string]AttributeBuilder + switch attr := (*attr).(type) { + case SingleNestedAttributeBuilder: + m = attr.Attributes + case ListNestedAttributeBuilder: + m = attr.NestedObject.Attributes + case MapNestedAttributeBuilder: + m = attr.NestedObject.Attributes + default: + panic(fmt.Errorf("cannot convert to map, attribute is not nested")) + } + + return m +} + +func (s *CustomizableSchema) AddValidator(v any, path ...string) *CustomizableSchema { + cb := func(attr AttributeBuilder) AttributeBuilder { + switch a := attr.(type) { + case BoolAttributeBuilder: + return a.AddValidator(v.(validator.Bool)) + case Float64AttributeBuilder: + return a.AddValidator(v.(validator.Float64)) + case Int64AttributeBuilder: + return a.AddValidator(v.(validator.Int64)) + case ListAttributeBuilder: + return a.AddValidator(v.(validator.List)) + case ListNestedAttributeBuilder: + return a.AddValidator(v.(validator.List)) + case MapAttributeBuilder: + return a.AddValidator(v.(validator.Map)) + case MapNestedAttributeBuilder: + return a.AddValidator(v.(validator.Map)) + case SingleNestedAttributeBuilder: + return a.AddValidator(v.(validator.Object)) + case StringAttributeBuilder: + return a.AddValidator(v.(validator.String)) + default: + panic(fmt.Errorf("cannot add validator, attribute builder type is invalid: %s. %s", reflect.TypeOf(attr).String(), common.TerraformBugErrorMessage)) + } + } + + navigateSchemaWithCallback(&s.attr, cb, path...) + + return s +} + +func (s *CustomizableSchema) SetOptional(path ...string) *CustomizableSchema { + cb := func(attr AttributeBuilder) AttributeBuilder { + return attr.SetOptional() + } + + navigateSchemaWithCallback(&s.attr, cb, path...) + + return s +} + +func (s *CustomizableSchema) SetRequired(path ...string) *CustomizableSchema { + cb := func(attr AttributeBuilder) AttributeBuilder { + return attr.SetRequired() + } + + navigateSchemaWithCallback(&s.attr, cb, path...) + + return s +} + +func (s *CustomizableSchema) SetSensitive(path ...string) *CustomizableSchema { + cb := func(attr AttributeBuilder) AttributeBuilder { + return attr.SetSensitive() + } + + navigateSchemaWithCallback(&s.attr, cb, path...) + return s +} + +func (s *CustomizableSchema) SetDeprecated(msg string, path ...string) *CustomizableSchema { + cb := func(attr AttributeBuilder) AttributeBuilder { + return attr.SetDeprecated(msg) + } + + navigateSchemaWithCallback(&s.attr, cb, path...) + + return s +} + +func (s *CustomizableSchema) SetComputed(path ...string) *CustomizableSchema { + cb := func(attr AttributeBuilder) AttributeBuilder { + return attr.SetComputed() + } + + navigateSchemaWithCallback(&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 *CustomizableSchema) SetReadOnly(path ...string) *CustomizableSchema { + cb := func(attr AttributeBuilder) AttributeBuilder { + return attr.SetReadOnly() + } + + navigateSchemaWithCallback(&s.attr, cb, path...) + + return s +} + +// navigateSchemaWithCallback navigates through schema attributes and executes callback on the target, panics if path does not exist or invalid. +func navigateSchemaWithCallback(s *AttributeBuilder, cb func(AttributeBuilder) AttributeBuilder, path ...string) (AttributeBuilder, 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 { + m[p] = cb(v) + return m[p], nil + } + current_scm = &v + } + return nil, fmt.Errorf("path %v is incomplete", path) +}