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_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 new file mode 100644 index 0000000000..31fd6e2877 --- /dev/null +++ b/common/databricks_schema_attribute.go @@ -0,0 +1,571 @@ +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" + "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 + SetOptional() Attribute + SetRequired() Attribute + SetSensitive() Attribute + SetComputed() Attribute + SetReadOnly() Attribute + SetDeprecated(string) Attribute + AddValidators(any) Attribute +} + +type StringAttribute struct { + 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, Sensitive: a.Sensitive, DeprecationMessage: a.DeprecationMessage, Computed: a.Computed, Validators: a.Validators} +} + +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 + Sensitive bool + Computed bool + DeprecationMessage string + Validators []validator.Float64 +} + +func (a Float64Attribute) ToDataSourceAttribute() dataschema.Attribute { + 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) 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 + 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 + 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 + 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 + 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 { + 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 + 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 + 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 0dc80550bd..5ad8c9d5be 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" ) @@ -126,7 +128,7 @@ func tfSdkToGoSdkSingleField(srcField reflect.Value, destField reflect.Value, sr panic("types.Map should never be used, use go native maps instead") default: if srcField.IsZero() { - // Skip zeros + // Skip nils return nil } // If it is a real stuct instead of a tfsdk type, recursively resolve it. @@ -311,7 +313,7 @@ func goSdkToTfSdkSingleField(srcField reflect.Value, destField reflect.Value, sr } case reflect.Struct: if srcField.IsZero() { - // Skip zeros + // Skip nils return nil } // resolve the nested struct by recursively calling the function @@ -383,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() @@ -406,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 { @@ -418,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() @@ -440,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: @@ -470,12 +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} + scm[fieldName] = StringAttribute{Optional: isOptional, Required: !isOptional} } else { panic(fmt.Errorf("unknown type for field: %s", typeField.Name)) } @@ -493,13 +495,29 @@ func PluginFrameworkResourceStructToSchema(v any, customizeSchema func(Customiza return schema.Schema{Attributes: attributes} } +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 ToResourceAttributeMap(cs.ToAttributeMap()) + } else { + return ToResourceAttributeMap(attributes) + } +} + +func PluginFrameworkDataSourceStructToSchemaMap(v any, customizeSchema func(CustomizableSchemaPluginFramework) CustomizableSchemaPluginFramework) map[string]dataschema.Attribute { + attributes := pluginFrameworkTypeToSchema(reflect.ValueOf(v)) if customizeSchema != nil { cs := customizeSchema(*ConstructCustomizableSchema(attributes)) - return cs.ToAttributeMap() + return ToDataSourceAttributeMap(cs.ToAttributeMap()) } else { - return attributes + return ToDataSourceAttributeMap(attributes) } } diff --git a/pluginframework/data_volumes.go b/pluginframework/data_volumes.go index bbbb579ebd..714ece2ffc 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.CustomizableSchemaPluginFramework) common.CustomizableSchemaPluginFramework { + c.SetComputed("ids") + return c + }) } func (d *VolumesDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {