Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
edwardfeng-db committed Jul 17, 2024
1 parent 81dd4d3 commit cd99b67
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 135 deletions.
101 changes: 65 additions & 36 deletions common/reflect_resource_plugin_framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ func checkTheStringInForceSendFields(fieldName string, forceSendFields []string)
}

func pluginFrameworkTypeToSchema(v reflect.Value) map[string]schema.Attribute {
scm := map[string]schema.Attribute{}
rk := v.Kind()
if rk == reflect.Ptr {
v = v.Elem()
Expand All @@ -307,59 +308,87 @@ func pluginFrameworkTypeToSchema(v reflect.Value) map[string]schema.Attribute {
if fieldName == "-" {
continue
}
// TODO: handle optional and all kinds of stuff
// For now everything is marked as optional. TODO: add tf annotations for computed, optional, etc.
kind := typeField.Type.Kind()
if kind == reflect.Ptr {
// In the schema ptr and non ptr are treated the same way
// Seems like in the original typeToSchema implementation we assumed that the ptr can only be a ptr to a struct
// If so, we just need to recursively call this function with its elem
// Otherwise we need more helper functions

elem := typeField.Type.Elem()
sv := reflect.New(elem).Elem()
nestedScm := pluginFrameworkTypeToSchema(sv)
scm[fieldName] = schema.SingleNestedAttribute{Attributes: nestedScm, Optional: true}
} else if kind == reflect.Slice {
// If it's a slice, we need to check what type it is
// If it's a slice of primitive types (tfsdk primiritive types), it's a schema.ListAttribute
// If it's a slice of nested types (struct), it's a schema.ListNestedAttribute
// If it's a list of struct, we can create a schema.NestedAttributeObject and call the function recursively
// It seems like the two scenarios below are not covered in the existing structToSchema
// If it's a list of list, what to do?
// If it's a list of map, what to do?

elem := typeField.Type.Elem()
if elem.Kind() == reflect.Ptr {
elem = elem.Elem()
}
if elem.Kind() != reflect.Struct {
panic(fmt.Errorf("unsupported slice value for %s: %s", fieldName, reflectKind(elem.Kind())))
}
switch elem {
case reflect.TypeOf(types.Bool{}):
scm[fieldName] = schema.ListAttribute{ElementType: types.BoolType, Optional: true}
case reflect.TypeOf(types.Int64{}):
scm[fieldName] = schema.ListAttribute{ElementType: types.Int64Type, Optional: true}
case reflect.TypeOf(types.Float64{}):
scm[fieldName] = schema.ListAttribute{ElementType: types.Float64Type, Optional: true}
case reflect.TypeOf(types.String{}):
scm[fieldName] = schema.ListAttribute{ElementType: types.StringType, Optional: true}
default:
// Nested struct
nestedScm := pluginFrameworkTypeToSchema(reflect.New(elem).Elem())
scm[fieldName] = schema.ListNestedAttribute{NestedObject: schema.NestedAttributeObject{Attributes: nestedScm}, Optional: true}
}
} else if kind == reflect.Map {
// If it's a slice, we need to check what type it is
// If it's a map with values of primitive types (tfsdk primiritive types), it's a schema.MapAttribute
// If it's a map with values of nested types (struct), it's a schema.MapNestedAttribute
// Complicated nested maps are not covered for structToSchema's existing implementation

elem := typeField.Type.Elem()
if elem.Kind() == reflect.Ptr {
elem = elem.Elem()
}
if elem.Kind() != reflect.Struct {
panic(fmt.Errorf("unsupported map value for %s: %s", fieldName, reflectKind(elem.Kind())))
}
switch elem {
case reflect.TypeOf(types.Bool{}):
scm[fieldName] = schema.MapAttribute{ElementType: types.BoolType, Optional: true}
case reflect.TypeOf(types.Int64{}):
scm[fieldName] = schema.MapAttribute{ElementType: types.Int64Type, Optional: true}
case reflect.TypeOf(types.Float64{}):
scm[fieldName] = schema.MapAttribute{ElementType: types.Float64Type, Optional: true}
case reflect.TypeOf(types.String{}):
scm[fieldName] = schema.MapAttribute{ElementType: types.StringType, Optional: true}
default:
// Nested struct
nestedScm := pluginFrameworkTypeToSchema(reflect.New(elem).Elem())
scm[fieldName] = schema.MapNestedAttribute{NestedObject: schema.NestedAttributeObject{Attributes: nestedScm}, Optional: true}
}
} else if kind == reflect.Struct {
switch field.v.Interface().(type) {
case types.Bool:
println("bool!")
println(field.sf.Name)
scm[fieldName] = schema.BoolAttribute{Optional: true}
case types.Int64:
println("int64!")
println(field.sf.Name)
scm[fieldName] = schema.Int64Attribute{Optional: true}
case types.Float64:
println("float64!")
println(field.sf.Name)
scm[fieldName] = schema.Float64Attribute{Optional: true}
case types.String:
println("string!")
println(field.sf.Name)
scm[fieldName] = schema.StringAttribute{Optional: true}
case types.List:
println("list!")
println(field.sf.Name)
panic("types.List should never be used in tfsdk structs")
case types.Map:
println("map!")
println(field.sf.Name)
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.
println("real struct")
println(field.sf.Name)
return nil
elem := typeField.Type
sv := reflect.New(elem)
nestedScm := pluginFrameworkTypeToSchema(sv)
scm[fieldName] = schema.SingleNestedAttribute{Attributes: nestedScm, Optional: true}
}

} else {
panic(fmt.Errorf("unknown type for field: %s", typeField.Name))
}
}
return nil
return scm
}

func pluginFrameworkStructToSchema(v any) schema.Schema {
return schema.Schema{
Attributes: pluginFrameworkTypeToSchema(reflect.ValueOf(v)),
}
}
101 changes: 2 additions & 99 deletions common/reflect_resource_plugin_framework_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"testing"
"time"

"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -129,99 +128,8 @@ var tfSdkStruct = DummyTfSdk{
}

func TestGetAndSetPluginFramework(t *testing.T) {
// Creating schema.
scm := schema.Schema{
Attributes: map[string]schema.Attribute{
"enabled": schema.BoolAttribute{
Required: true,
},
"workers": schema.Int64Attribute{
Optional: true,
},
"description": schema.StringAttribute{
Optional: true,
},
"task": schema.StringAttribute{
Optional: true,
},
"repeated": schema.ListAttribute{
ElementType: types.Int64Type,
Optional: true,
},
"floats": schema.Float64Attribute{
Optional: true,
},
"nested": schema.SingleNestedAttribute{
Optional: true,
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Optional: true,
},
"enabled": schema.BoolAttribute{
Optional: true,
},
},
},
"no_pointer_nested": schema.SingleNestedAttribute{
Optional: true,
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Optional: true,
},
"enabled": schema.BoolAttribute{
Optional: true,
},
},
},
"nested_list": schema.ListNestedAttribute{
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Optional: true,
},
"enabled": schema.BoolAttribute{
Optional: true,
},
},
},
Optional: true,
},
"map": schema.MapAttribute{
ElementType: types.StringType,
Optional: true,
},
"nested_pointer_list": schema.ListNestedAttribute{
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Optional: true,
},
"enabled": schema.BoolAttribute{
Optional: true,
},
},
},
Optional: true,
},
"attributes": schema.MapAttribute{
ElementType: types.StringType,
Optional: true,
},
"nested_map": schema.MapNestedAttribute{
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Optional: true,
},
"enabled": schema.BoolAttribute{
Optional: true,
},
},
},
Optional: true,
},
},
}
// Also test StructToSchema.
scm := pluginFrameworkStructToSchema(DummyTfSdk{})
state := tfsdk.State{
Schema: scm,
}
Expand All @@ -237,7 +145,6 @@ func TestGetAndSetPluginFramework(t *testing.T) {

// Assert the struct populated from .Get is exactly the same as the original tfsdk struct.
assert.True(t, reflect.DeepEqual(getterStruct, tfSdkStruct))

}

func TestStructConversion(t *testing.T) {
Expand All @@ -254,7 +161,3 @@ func TestStructConversion(t *testing.T) {
// Assert that the struct is exactly the same after tfsdk --> gosdk --> tfsdk
assert.True(t, reflect.DeepEqual(tfSdkStruct, convertedTfSdkStruct))
}

func TestStructToSchema(t *testing.T) {
pluginFrameworkTypeToSchema(reflect.ValueOf(DummyTfSdk{}))
}

0 comments on commit cd99b67

Please sign in to comment.