diff --git a/pluginframework/common/converters_test.go b/pluginframework/common/converters_test.go index 4b8732924f..263f19eb17 100644 --- a/pluginframework/common/converters_test.go +++ b/pluginframework/common/converters_test.go @@ -1,12 +1,11 @@ package pluginframework import ( + "context" "fmt" "reflect" "testing" - "time" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stretchr/testify/assert" ) @@ -89,128 +88,15 @@ type DummyNestedGoSdk struct { ForceSendFields []string `json:"-"` } -type emptyCtx struct{} - -func (emptyCtx) Deadline() (deadline time.Time, ok bool) { - return -} - -func (emptyCtx) Done() <-chan struct{} { - return nil -} - -func (emptyCtx) Err() error { - return nil -} - -func (emptyCtx) Value(key any) any { - return nil -} - -var ctx = emptyCtx{} - -// Constructing a dummy tfsdk struct. -var tfSdkStructComplex = DummyTfSdk{ - Enabled: types.BoolValue(false), - Workers: types.Int64Value(12), - Description: types.StringValue("abc"), - Tasks: types.StringNull(), - Nested: &DummyNestedTfSdk{ - Name: types.StringValue("def"), - Enabled: types.BoolValue(true), - }, - NoPointerNested: DummyNestedTfSdk{ - Name: types.StringValue("def"), - Enabled: types.BoolValue(true), - }, - NestedList: []DummyNestedTfSdk{ - { - Name: types.StringValue("def"), - Enabled: types.BoolValue(true), - }, - { - Name: types.StringValue("def"), - Enabled: types.BoolValue(true), - }, - }, - Map: map[string]types.String{ - "key1": types.StringValue("value1"), - "key2": types.StringValue("value2"), - }, - NestedMap: map[string]DummyNestedTfSdk{ - "key1": { - Name: types.StringValue("abc"), - Enabled: types.BoolValue(false), - }, - "key2": { - Name: types.StringValue("def"), - Enabled: types.BoolValue(true), - }, - }, - NestedPointerList: []*DummyNestedTfSdk{ - { - Name: types.StringValue("def"), - Enabled: types.BoolValue(true), - }, - { - Name: types.StringValue("def"), - Enabled: types.BoolValue(true), - }, - }, - Attributes: map[string]types.String{"key": types.StringValue("value")}, - EnumField: types.StringValue("TEST_ENUM_A"), - Repeated: []types.Int64{types.Int64Value(12), types.Int64Value(34)}, -} - -func TestGetAndSetPluginFramework(t *testing.T) { - // Also test StructToSchema. - scm := PluginFrameworkResourceStructToSchema(DummyTfSdk{}, nil) - state := tfsdk.State{ - Schema: scm, - } - - // Test optional is being handled properly. - assert.True(t, scm.Attributes["enabled"].IsOptional()) - assert.True(t, scm.Attributes["workers"].IsRequired()) - assert.True(t, scm.Attributes["floats"].IsRequired()) - - // Assert that we can set state from the tfsdk struct. - diags := state.Set(ctx, tfSdkStructComplex) - assert.Len(t, diags, 0) - - // Assert that we can get a struct from the state. - getterStruct := DummyTfSdk{} - diags = state.Get(ctx, &getterStruct) - assert.Len(t, diags, 0) - - // Assert the struct populated from .Get is exactly the same as the original tfsdk struct. - assert.True(t, reflect.DeepEqual(getterStruct, tfSdkStructComplex)) -} - -func TestStructConversion(t *testing.T) { - // Convert from tfsdk to gosdk struct using the converter function - convertedGoSdkStruct := DummyGoSdk{} - diags := TfSdkToGoSdkStruct(tfSdkStructComplex, &convertedGoSdkStruct, ctx) - assert.True(t, !diags.HasError()) - - // Convert the gosdk struct back to tfsdk struct - convertedTfSdkStruct := DummyTfSdk{} - diags = GoSdkToTfSdkStruct(convertedGoSdkStruct, &convertedTfSdkStruct, ctx) - assert.True(t, !diags.HasError()) - - // Assert that the struct is exactly the same after tfsdk --> gosdk --> tfsdk - assert.True(t, reflect.DeepEqual(tfSdkStructComplex, convertedTfSdkStruct)) -} - // Function to construct individual test case with a pair of matching tfSdkStruct and gosdkStruct. // Verifies that the conversion both ways are working as expected. func ConverterTestCase(t *testing.T, description string, tfSdkStruct DummyTfSdk, goSdkStruct DummyGoSdk) { convertedGoSdkStruct := DummyGoSdk{} - assert.True(t, !TfSdkToGoSdkStruct(tfSdkStruct, &convertedGoSdkStruct, ctx).HasError()) + assert.True(t, !TfSdkToGoSdkStruct(tfSdkStruct, &convertedGoSdkStruct, context.Background()).HasError()) assert.True(t, reflect.DeepEqual(convertedGoSdkStruct, goSdkStruct), fmt.Sprintf("tfsdk to gosdk conversion - %s", description)) convertedTfSdkStruct := DummyTfSdk{} - assert.True(t, !GoSdkToTfSdkStruct(goSdkStruct, &convertedTfSdkStruct, ctx).HasError()) + assert.True(t, !GoSdkToTfSdkStruct(goSdkStruct, &convertedTfSdkStruct, context.Background()).HasError()) assert.True(t, reflect.DeepEqual(convertedTfSdkStruct, tfSdkStruct), fmt.Sprintf("gosdk to tfsdk conversion - %s", description)) } diff --git a/pluginframework/common/customizable_schema.go b/pluginframework/common/customizable_schema.go deleted file mode 100644 index dbaf2f0755..0000000000 --- a/pluginframework/common/customizable_schema.go +++ /dev/null @@ -1,126 +0,0 @@ -package pluginframework - -import ( - "fmt" -) - -type CustomizableSchemaPluginFramework struct { - attr Attribute -} - -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]Attribute { - return attributeToMap(&s.attr) -} - -func attributeToMap(attr *Attribute) map[string]Attribute { - var m map[string]Attribute - switch attr := (*attr).(type) { - case SingleNestedAttribute: - m = attr.Attributes - case ListNestedAttribute: - m = attr.NestedObject.Attributes - case MapNestedAttribute: - m = attr.NestedObject.Attributes - default: - panic(fmt.Errorf("cannot convert to map, attribute is not nested")) - } - - return m -} - -func (s *CustomizableSchemaPluginFramework) AddValidator(v any, path ...string) *CustomizableSchemaPluginFramework { - cb := func(attr Attribute) Attribute { - return attr.AddValidators(v) - } - - navigateSchemaWithCallback(&s.attr, cb, path...) - - return s -} - -func (s *CustomizableSchemaPluginFramework) SetOptional(path ...string) *CustomizableSchemaPluginFramework { - cb := func(attr Attribute) Attribute { - return attr.SetOptional() - } - - navigateSchemaWithCallback(&s.attr, cb, path...) - - return s -} - -func (s *CustomizableSchemaPluginFramework) SetRequired(path ...string) *CustomizableSchemaPluginFramework { - cb := func(attr Attribute) Attribute { - return attr.SetRequired() - } - - navigateSchemaWithCallback(&s.attr, cb, path...) - - return s -} - -func (s *CustomizableSchemaPluginFramework) SetSensitive(path ...string) *CustomizableSchemaPluginFramework { - cb := func(attr Attribute) Attribute { - return attr.SetSensitive() - } - - navigateSchemaWithCallback(&s.attr, cb, path...) - return s -} - -func (s *CustomizableSchemaPluginFramework) SetDeprecated(msg string, path ...string) *CustomizableSchemaPluginFramework { - cb := func(attr Attribute) Attribute { - return attr.SetDeprecated(msg) - } - - navigateSchemaWithCallback(&s.attr, cb, path...) - - return s -} - -func (s *CustomizableSchemaPluginFramework) SetComputed(path ...string) *CustomizableSchemaPluginFramework { - cb := func(attr Attribute) Attribute { - 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 *CustomizableSchemaPluginFramework) SetReadOnly(path ...string) *CustomizableSchemaPluginFramework { - cb := func(attr Attribute) Attribute { - return attr.SetReadOnly() - } - - navigateSchemaWithCallback(&s.attr, cb, path...) - - return s -} - -// Helper function for navigating through schema attributes, panics if path does not exist or invalid. -func navigateSchemaWithCallback(s *Attribute, cb func(Attribute) Attribute, path ...string) (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 { - m[p] = cb(v) - return m[p], nil - } - current_scm = &v - } - return nil, fmt.Errorf("path %v is incomplete", path) -} diff --git a/pluginframework/common/databricks_attribute.go b/pluginframework/common/databricks_attribute.go deleted file mode 100644 index dde24a1c33..0000000000 --- a/pluginframework/common/databricks_attribute.go +++ /dev/null @@ -1,571 +0,0 @@ -package pluginframework - -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/pluginframework/common/go_to_tf.go b/pluginframework/common/go_to_tf.go index 9f6ce66932..0501aa71f2 100644 --- a/pluginframework/common/go_to_tf.go +++ b/pluginframework/common/go_to_tf.go @@ -11,7 +11,21 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -// Converts a go-sdk struct into a tfsdk struct. +// Converts a gosdk struct into a tfsdk struct, with the folowing rules. +// +// string -> types.String +// bool -> types.Bool +// int64 -> types.Int64 +// float64 -> types.Float64 +// string -> types.String +// +// NOTE: +// +// If field name doesn't show up in ForceSendFields and the field is zero value, we set the null value on the tfsdk. +// types.list and types.map are not supported +// map keys should always be a string +// tfsdk structs use types.String for all enum values +// non-json fields will be omitted func GoSdkToTfSdkStruct(gosdk interface{}, tfsdk interface{}, ctx context.Context) diag.Diagnostics { srcVal := reflect.ValueOf(gosdk) @@ -37,11 +51,6 @@ func GoSdkToTfSdkStruct(gosdk interface{}, tfsdk interface{}, ctx context.Contex // If no forceSendField, just use an empty list. forceSendFieldVal = []string{} } else { - switch forceSendField.Interface().(type) { - case []string: - default: - panic(fmt.Errorf("ForceSendField is not of type []string")) - } forceSendFieldVal = forceSendField.Interface().([]string) } @@ -49,12 +58,18 @@ func GoSdkToTfSdkStruct(gosdk interface{}, tfsdk interface{}, ctx context.Contex srcField := field.V srcFieldName := field.Sf.Name - destField := destVal.FieldByName(srcFieldName) srcFieldTag := field.Sf.Tag.Get("json") if srcFieldTag == "-" { continue } - err := goSdkToTfSdkSingleField(srcField, destField, srcFieldName, forceSendFieldVal, false, ctx) + destField := destVal.FieldByName(srcFieldName) + + if !destField.IsValid() { + logger.Tracef(ctx, fmt.Sprintf("field skipped in gosdk to tfsdk conversion: destination struct does not have field %s", srcFieldName)) + continue + } + + err := goSdkToTfSdkSingleField(srcField, destField, fieldInForceSendFields(srcFieldName, forceSendFieldVal), ctx) if err != nil { return diag.Diagnostics{diag.NewErrorDiagnostic(err.Error(), "gosdk to tfsdk field conversion failure")} } @@ -62,13 +77,7 @@ func GoSdkToTfSdkStruct(gosdk interface{}, tfsdk interface{}, ctx context.Contex return nil } -func goSdkToTfSdkSingleField(srcField reflect.Value, destField reflect.Value, srcFieldName string, forceSendField []string, alwaysAdd bool, ctx context.Context) error { - - if !destField.IsValid() { - // Skip field that destination struct does not have. - logger.Tracef(ctx, fmt.Sprintf("field skipped in gosdk to tfsdk conversion: destination struct does not have field %s", srcFieldName)) - return nil - } +func goSdkToTfSdkSingleField(srcField reflect.Value, destField reflect.Value, forceSendField bool, ctx context.Context) error { if !destField.CanSet() { panic(fmt.Errorf("destination field can not be set: %s", destField.Type().Name())) @@ -94,26 +103,26 @@ func goSdkToTfSdkSingleField(srcField reflect.Value, destField reflect.Value, sr } case reflect.Bool: boolVal := srcFieldValue.(bool) - // check if alwaysAdd is false or the value is zero or if the field is in the forceSendFields list - if alwaysAdd || !(!boolVal && !checkTheStringInForceSendFields(srcFieldName, forceSendField)) { + // check if the value is non-zero or if the field is in the forceSendFields list + if boolVal || forceSendField { destField.Set(reflect.ValueOf(types.BoolValue(boolVal))) } else { destField.Set(reflect.ValueOf(types.BoolNull())) } - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + case reflect.Int64: // convert any kind of integer to int64 intVal := srcField.Convert(reflect.TypeOf(int64(0))).Interface().(int64) - // check if alwaysAdd is true or the value is zero or if the field is in the forceSendFields list - if alwaysAdd || !(intVal == 0 && !checkTheStringInForceSendFields(srcFieldName, forceSendField)) { + // check if the value is non-zero or if the field is in the forceSendFields list + if intVal != 0 || forceSendField { destField.Set(reflect.ValueOf(types.Int64Value(int64(intVal)))) } else { destField.Set(reflect.ValueOf(types.Int64Null())) } - case reflect.Float32, reflect.Float64: + case reflect.Float64: // convert any kind of float to float64 float64Val := srcField.Convert(reflect.TypeOf(float64(0))).Interface().(float64) - // check if alwaysAdd is true or the value is zero or if the field is in the forceSendFields list - if alwaysAdd || !(float64Val == 0 && !checkTheStringInForceSendFields(srcFieldName, forceSendField)) { + // check if the value is non-zero or if the field is in the forceSendFields list + if float64Val != 0 || forceSendField { destField.Set(reflect.ValueOf(types.Float64Value(float64Val))) } else { destField.Set(reflect.ValueOf(types.Float64Null())) @@ -144,8 +153,8 @@ func goSdkToTfSdkSingleField(srcField reflect.Value, destField reflect.Value, sr } else { strVal = srcFieldValue.(string) } - // check if alwaysAdd is false or the value is zero or if the field is in the forceSendFields list - if alwaysAdd || !(strVal == "" && !checkTheStringInForceSendFields(srcFieldName, forceSendField)) { + // check if the value is non-zero or if the field is in the forceSendFields list + if strVal != "" || forceSendField { destField.Set(reflect.ValueOf(types.StringValue(strVal))) } else { destField.Set(reflect.ValueOf(types.StringNull())) @@ -170,7 +179,7 @@ func goSdkToTfSdkSingleField(srcField reflect.Value, destField reflect.Value, sr srcElem := srcField.Index(j) destElem := destSlice.Index(j) - if err := goSdkToTfSdkSingleField(srcElem, destElem, "", nil, true, ctx); err != nil { + if err := goSdkToTfSdkSingleField(srcElem, destElem, true, ctx); err != nil { return err } } @@ -185,7 +194,7 @@ func goSdkToTfSdkSingleField(srcField reflect.Value, destField reflect.Value, sr srcMapValue := srcField.MapIndex(key) destMapValue := reflect.New(destField.Type().Elem()).Elem() destMapKey := reflect.ValueOf(key.Interface()) - if err := goSdkToTfSdkSingleField(srcMapValue, destMapValue, "", nil, true, ctx); err != nil { + if err := goSdkToTfSdkSingleField(srcMapValue, destMapValue, true, ctx); err != nil { return err } destMap.SetMapIndex(destMapKey, destMapValue) @@ -197,7 +206,7 @@ func goSdkToTfSdkSingleField(srcField reflect.Value, destField reflect.Value, sr return nil } -func checkTheStringInForceSendFields(fieldName string, forceSendFields []string) bool { +func fieldInForceSendFields(fieldName string, forceSendFields []string) bool { for _, field := range forceSendFields { if field == fieldName { return true diff --git a/pluginframework/common/reflect_resources.go b/pluginframework/common/reflect_resources.go deleted file mode 100644 index 2db3fbd0ef..0000000000 --- a/pluginframework/common/reflect_resources.go +++ /dev/null @@ -1,149 +0,0 @@ -package pluginframework - -import ( - "fmt" - "reflect" - "strings" - - "github.com/databricks/terraform-provider-databricks/common" - dataschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/types" -) - -func pluginFrameworkTypeToSchema(v reflect.Value) map[string]Attribute { - scm := map[string]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", rk.String(), v)) - } - fields := common.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 := 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 { - elem = elem.Elem() - } - if elem.Kind() != reflect.Struct { - panic(fmt.Errorf("unsupported slice value for %s: %s", fieldName, elem.Kind().String())) - } - switch elem { - case reflect.TypeOf(types.Bool{}): - scm[fieldName] = ListAttribute{ElementType: types.BoolType, Optional: isOptional, Required: !isOptional} - case reflect.TypeOf(types.Int64{}): - scm[fieldName] = ListAttribute{ElementType: types.Int64Type, Optional: isOptional, Required: !isOptional} - case reflect.TypeOf(types.Float64{}): - scm[fieldName] = ListAttribute{ElementType: types.Float64Type, Optional: isOptional, Required: !isOptional} - case reflect.TypeOf(types.String{}): - scm[fieldName] = ListAttribute{ElementType: types.StringType, Optional: isOptional, Required: !isOptional} - default: - // Nested struct - 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() - if elem.Kind() == reflect.Ptr { - elem = elem.Elem() - } - if elem.Kind() != reflect.Struct { - panic(fmt.Errorf("unsupported map value for %s: %s", fieldName, elem.Kind().String())) - } - switch elem { - case reflect.TypeOf(types.Bool{}): - scm[fieldName] = MapAttribute{ElementType: types.BoolType, Optional: isOptional, Required: !isOptional} - case reflect.TypeOf(types.Int64{}): - scm[fieldName] = MapAttribute{ElementType: types.Int64Type, Optional: isOptional, Required: !isOptional} - case reflect.TypeOf(types.Float64{}): - scm[fieldName] = MapAttribute{ElementType: types.Float64Type, Optional: isOptional, Required: !isOptional} - case reflect.TypeOf(types.String{}): - scm[fieldName] = MapAttribute{ElementType: types.StringType, Optional: isOptional, Required: !isOptional} - default: - // Nested struct - 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] = BoolAttribute{Optional: isOptional, Required: !isOptional} - case types.Int64: - scm[fieldName] = Int64Attribute{Optional: isOptional, Required: !isOptional} - case types.Float64: - scm[fieldName] = Float64Attribute{Optional: isOptional, Required: !isOptional} - case types.String: - scm[fieldName] = 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 := pluginFrameworkTypeToSchema(sv) - scm[fieldName] = SingleNestedAttribute{Attributes: nestedScm, Optional: isOptional, Required: !isOptional} - } - } else if kind == reflect.String { - // This case is for Enum types. - scm[fieldName] = 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") -} - -func PluginFrameworkResourceStructToSchema(v any, customizeSchema func(CustomizableSchemaPluginFramework) CustomizableSchemaPluginFramework) schema.Schema { - attributes := PluginFrameworkResourceStructToSchemaMap(v, customizeSchema) - 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 := 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 ToDataSourceAttributeMap(cs.ToAttributeMap()) - } else { - return ToDataSourceAttributeMap(attributes) - } -} diff --git a/pluginframework/common/tf_to_go.go b/pluginframework/common/tf_to_go.go index 420d956808..111fe32e84 100644 --- a/pluginframework/common/tf_to_go.go +++ b/pluginframework/common/tf_to_go.go @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -// Converts a tfsdk struct into a go-sdk struct, with the folowing rules. +// Converts a tfsdk struct into a gosdk struct, with the folowing rules. // // types.String -> string // types.Bool -> bool