diff --git a/clusters/resource_library.go b/clusters/resource_library.go index d3b2d0fcbc..4863a63393 100644 --- a/clusters/resource_library.go +++ b/clusters/resource_library.go @@ -50,7 +50,15 @@ func ResourceLibrary() common.Resource { return err } var lib compute.Library + + var libTfSdk tfsdk.Library common.DataToStructPointer(d, libraySdkSchema, &lib) + + // d.get(&libTfSdk) + // libTfSdk --> gosdkLib + // libTfSdk.toGoSdk(goSdk) + // libTfSdk.fromGoSdk() + err = w.Libraries.Install(ctx, compute.InstallLibraries{ ClusterId: clusterID, Libraries: []compute.Library{lib}, diff --git a/common/reflect_resource_test.go b/common/reflect_resource_test.go index 593bc8c735..35facc36f7 100644 --- a/common/reflect_resource_test.go +++ b/common/reflect_resource_test.go @@ -5,8 +5,14 @@ import ( "fmt" "reflect" "testing" + "time" "github.com/databricks/databricks-sdk-go/service/sql" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + newschema "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/stretchr/testify/assert" ) @@ -973,3 +979,377 @@ func TestStructToData_IndirectString(t *testing.T) { }, scm, d) assert.NoError(t, err) } + +type DummyNewTfSdk struct { + Enabled types.Bool `tfsdk:"enabled"` + Workers types.Int64 `tfsdk:"workers"` + Floats types.Float64 `tfsdk:"floats"` + Description types.String `tfsdk:"description"` + Tasks types.String `tfsdk:"task"` + Nested *DummyNestedTfSdk `tfsdk:"nested"` + NoPointerNested DummyNestedTfSdk `tfsdk:"no_pointer_nested"` + NestedList types.List `tfsdk:"nested_list"` + NestedPointerList []*DummyNestedTfSdk `tfsdk:"nested_pointer_list"` + Repeated types.List `tfsdk:"repeated"` + Attributes types.Map `tfsdk:"attributes"` + NestedMap map[string]DummyNestedTfSdk `tfsdk:"nested_map"` + Irrelevant types.String `tfsdk:"-"` +} + +type DummyNestedTfSdk struct { + Name types.String `tfsdk:"name"` + Enabled types.Bool `tfsdk:"enabled"` +} + +type DummyNewGoSdk struct { + Enabled bool `json:"enabled"` + Workers int `json:"workers"` + Floats float64 `json:"floats"` + Description string `json:"description"` + Tasks string `json:"tasks"` + Nested *DummyNestedGoSdk `json:"nested"` + NoPointerNested DummyNestedGoSdk `json:"no_pointer_nested"` + NestedList []DummyNestedGoSdk `json:"nested_list"` + NestedPointerList []*DummyNestedGoSdk `json:"nested_pointer_list"` + Repeated []int64 `json:"repeated"` + Attributes map[string]string `json:"attributes"` + NestedMap map[string]DummyNestedTfSdk `json:"nested_map"` + ForceSendFields []string `json:"-"` +} + +type DummyNestedGoSdk struct { + Name string `json:"name"` + Enabled bool `json:"enabled"` + 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 +} + +func TfSdkToGoSdkStruct(tfsdk interface{}, gosdk interface{}, ctx context.Context) error { + srcVal := reflect.ValueOf(tfsdk) + destVal := reflect.ValueOf(gosdk) + + if srcVal.Kind() == reflect.Ptr { + srcVal = srcVal.Elem() + } + + if destVal.Kind() != reflect.Ptr { + panic("Please provide a pointer for the gosdk struct") + } + destVal = destVal.Elem() + + if srcVal.Kind() != reflect.Struct || destVal.Kind() != reflect.Struct { + panic("input should be structs") + } + + forceSendFieldsField := destVal.FieldByName("ForceSendFields") + + srcType := srcVal.Type() + for i := 0; i < srcVal.NumField(); i++ { + srcField := srcVal.Field(i) + srcFieldName := srcType.Field(i).Name + + destField := destVal.FieldByName(srcFieldName) + + srcFieldTag := srcType.Field(i).Tag.Get("tfsdk") + if srcFieldTag == "-" { + continue + } + + populateSingleField(srcField, destField, &forceSendFieldsField, ctx) + } + + return nil +} + +func populateSingleField(srcField reflect.Value, destField reflect.Value, forceSendFieldsField *reflect.Value, ctx context.Context) error { + + if !destField.IsValid() { + panic(fmt.Errorf("destination field is not valid: %s", destField.Type().Name())) + } + + if !destField.CanSet() { + panic(fmt.Errorf("destination field can not be set: %s", destField.Type().Name())) + } + + srcFieldName := srcField.Type().Name() + + srcFieldValue := srcField.Interface() + + if srcFieldValue == nil { + return nil + } else if srcField.Kind() == reflect.Ptr { + // Allocate new memory for the destination field + destField.Set(reflect.New(destField.Type().Elem())) + + // Recursively populate the nested struct. + if err := TfSdkToGoSdkStruct(srcFieldValue, destField.Interface(), ctx); err != nil { + return err + } + } else if srcField.Kind() == reflect.Struct { + switch srcFieldValue.(type) { + case types.Bool: + boolVal := srcFieldValue.(types.Bool) + destField.SetBool(boolVal.ValueBool()) + if !boolVal.IsNull() { + addToForceSendFields(srcFieldName, forceSendFieldsField) + } + case types.Int64: + intVal := srcFieldValue.(types.Int64) + destField.SetInt(intVal.ValueInt64()) + if !intVal.IsNull() { + addToForceSendFields(srcFieldName, forceSendFieldsField) + } + case types.Float64: + floatVal := srcFieldValue.(types.Float64) + destField.SetFloat(floatVal.ValueFloat64()) + if !floatVal.IsNull() { + addToForceSendFields(srcFieldName, forceSendFieldsField) + } + case types.String: + destField.SetString(srcFieldValue.(types.String).ValueString()) + case types.List: + diag := srcFieldValue.(types.List).ElementsAs(ctx, destField.Addr().Interface(), false) + if len(diag) != 0 { + panic("Error") + } + case types.Map: + srcFieldValue.(types.Map).ElementsAs(ctx, destField.Addr().Interface(), false) + default: + // If it is a real stuct instead of a tfsdk type, recursively resolve it. + if err := TfSdkToGoSdkStruct(srcFieldValue, destField.Addr().Interface(), ctx); err != nil { + return err + } + } + } else if srcField.Kind() == reflect.Slice { + println("slice!") + destSlice := reflect.MakeSlice(destField.Type(), srcField.Len(), srcField.Cap()) + for j := 0; j < srcField.Len(); j++ { + nestedSrcField := srcField.Index(j) + nestedSrcField.Kind() + + srcElem := srcField.Index(j) + + destElem := destSlice.Index(j) + if err := populateSingleField(srcElem, destElem, nil, ctx); err != nil { + return err + } + } + destField.Set(destSlice) + } else if srcField.Kind() == reflect.Map { + // println("map!") + // destMap := reflect.MakeMap(destField.Type()) + // for _, key := range srcField.MapKeys() { + // srcMapValue := srcField.MapIndex(key).Interface() + // destMapKey := reflect.New(destField.Type().Key()).Elem() + // populateSingleField(key, destMapKey, nil, ctx) + + // destMapValue := reflect.New(destField.Type().Elem()).Elem() + // if err := PopulateStruct(srcMapValue, destMapValue.Addr().Interface()); err != nil { + // return err + // } + // destMap.SetMapIndex(destMapKey, destMapValue) + // } + // destField.Set(destMap) + } else { + panic("Unknown type for field") + } + return nil +} + +func addToForceSendFields(fieldName string, forceSendFieldsField *reflect.Value) { + if forceSendFieldsField == nil { + return + } + forceSendFields := forceSendFieldsField.Interface().([]string) + forceSendFields = append(forceSendFields, fieldName) + forceSendFieldsField.Set(reflect.ValueOf(forceSendFields)) +} + +func TestGetAndSetPluginFramework(t *testing.T) { + ctx := emptyCtx{} + scm := newschema.Schema{ + Attributes: map[string]newschema.Attribute{ + "enabled": newschema.BoolAttribute{ + Required: true, + }, + "workers": newschema.Int64Attribute{ + Optional: true, + }, + "description": newschema.StringAttribute{ + Optional: true, + }, + "task": newschema.StringAttribute{ + Optional: true, + }, + "repeated": newschema.ListAttribute{ + ElementType: types.Int64Type, + Optional: true, + }, + "floats": newschema.Float64Attribute{ + Optional: true, + }, + "nested": newschema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]newschema.Attribute{ + "name": newschema.StringAttribute{ + Optional: true, + }, + "enabled": newschema.BoolAttribute{ + Optional: true, + }, + }, + }, + "no_pointer_nested": newschema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]newschema.Attribute{ + "name": newschema.StringAttribute{ + Optional: true, + }, + "enabled": newschema.BoolAttribute{ + Optional: true, + }, + }, + }, + "nested_list": newschema.ListNestedAttribute{ + NestedObject: newschema.NestedAttributeObject{ + Attributes: map[string]newschema.Attribute{ + "name": newschema.StringAttribute{ + Optional: true, + }, + "enabled": newschema.BoolAttribute{ + Optional: true, + }, + }, + }, + Optional: true, + }, + "nested_pointer_list": newschema.ListNestedAttribute{ + NestedObject: newschema.NestedAttributeObject{ + Attributes: map[string]newschema.Attribute{ + "name": newschema.StringAttribute{ + Optional: true, + }, + "enabled": newschema.BoolAttribute{ + Optional: true, + }, + }, + }, + Optional: true, + }, + "attributes": newschema.MapAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "nested_map": newschema.MapNestedAttribute{ + NestedObject: newschema.NestedAttributeObject{ + Attributes: map[string]newschema.Attribute{ + "name": newschema.StringAttribute{ + Optional: true, + }, + "enabled": newschema.BoolAttribute{ + Optional: true, + }, + }, + }, + Optional: true, + }, + }, + } + state := tfsdk.State{ + Schema: scm, + } + + intValues := []int64{12, 34, 56} + var attrValues []attr.Value + + for _, v := range intValues { + attrValues = append(attrValues, types.Int64Value(v)) + } + + listValue, _ := types.ListValue(types.Int64Type, attrValues) + + mapValues := map[string]string{"key": "value"} + + attrMap := make(map[string]attr.Value) + + for k, v := range mapValues { + attrMap[k] = types.StringValue(v) + } + + mapValue, _ := types.MapValue(types.StringType, attrMap) + + goVal := DummyNewTfSdk{ + 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), + // }, + // }, + NestedPointerList: []*DummyNestedTfSdk{ + &DummyNestedTfSdk{ + Name: types.StringValue("def"), + Enabled: types.BoolValue(true), + }, + &DummyNestedTfSdk{ + Name: types.StringValue("def"), + Enabled: types.BoolValue(true), + }, + }, + Attributes: mapValue, + Repeated: listValue, + } + + diags := state.Set(ctx, goVal) + assert.Len(t, diags, 0) + + getterStruct := DummyNewTfSdk{} + diags = state.Get(ctx, &getterStruct) + assert.Len(t, diags, 0) + println("!") + var enabled types.Bool + state.GetAttribute(ctx, path.Root("enabled"), &enabled) + assert.True(t, !enabled.IsNull()) + assert.True(t, !enabled.IsUnknown()) + assert.True(t, !enabled.ValueBool()) + + testGoSdk := DummyNewGoSdk{} + TfSdkToGoSdkStruct(goVal, &testGoSdk, ctx) + assert.True(t, testGoSdk.Enabled == false) + assert.True(t, testGoSdk.Description == "abc") + assert.True(t, testGoSdk.Workers == 12) + println("!") + assert.True(t, len(testGoSdk.ForceSendFields) == 2) +} diff --git a/go.mod b/go.mod index 5a56fb699e..4a7870876e 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/hcl v1.0.0 github.com/hashicorp/hcl/v2 v2.20.1 + github.com/hashicorp/terraform-plugin-framework v1.9.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 github.com/stretchr/testify v1.9.0 diff --git a/go.sum b/go.sum index d2e63b3423..a3cec01721 100644 --- a/go.sum +++ b/go.sum @@ -130,6 +130,8 @@ github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVW github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= +github.com/hashicorp/terraform-plugin-framework v1.9.0 h1:caLcDoxiRucNi2hk8+j3kJwkKfvHznubyFsJMWfZqKU= +github.com/hashicorp/terraform-plugin-framework v1.9.0/go.mod h1:qBXLDn69kM97NNVi/MQ9qgd1uWWsVftGSnygYG1tImM= github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/125t+AAurhqphJ2Co= github.com/hashicorp/terraform-plugin-go v0.23.0/go.mod h1:1E3Cr9h2vMlahWMbsSEcNrOCxovCZhOOIXjFHbjc/lQ= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=