diff --git a/go.mod b/go.mod index cfe8f48f3..3a73c7c1b 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/google/uuid v1.3.0 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.29.0 + github.com/stretchr/testify v1.7.2 github.com/vmware/go-vmware-nsxt v0.0.0-20220328155605-f49a14c1ef5f github.com/vmware/vsphere-automation-sdk-go/lib v0.7.0 github.com/vmware/vsphere-automation-sdk-go/runtime v0.7.0 @@ -22,6 +23,7 @@ require ( github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/beevik/etree v1.1.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.13.0 // indirect github.com/gibson042/canonicaljson-go v1.0.3 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 // indirect @@ -53,6 +55,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/oklog/run v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect @@ -67,4 +70,5 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect google.golang.org/grpc v1.57.1 // indirect google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/nsxt/metadata/metadata.go b/nsxt/metadata/metadata.go index 4479da801..8aa4ab5d0 100644 --- a/nsxt/metadata/metadata.go +++ b/nsxt/metadata/metadata.go @@ -89,6 +89,10 @@ func StructToSchema(elem reflect.Value, d *schema.ResourceData, metadata map[str if len(parent) > 0 { log.Printf("[INFO] parent %s key %s", parent, key) } + if elem.FieldByName(item.Metadata.SdkFieldName).IsNil() { + log.Printf("[INFO] skip key %s with nil value", key) + continue + } if item.Metadata.SchemaType == "struct" { nestedObj := elem.FieldByName(item.Metadata.SdkFieldName) nestedSchema := make(map[string]interface{}) @@ -105,10 +109,10 @@ func StructToSchema(elem reflect.Value, d *schema.ResourceData, metadata map[str } } else { if len(parent) > 0 { - log.Printf("[INFO] assigning nested value %v to %s", elem.FieldByName(item.Metadata.SdkFieldName).Interface(), key) + log.Printf("[INFO] assigning nested value %+v to %s", elem.FieldByName(item.Metadata.SdkFieldName).Interface(), key) parentMap[key] = elem.FieldByName(item.Metadata.SdkFieldName).Interface() } else { - log.Printf("[INFO] assigning value %v to %s", elem.FieldByName(item.Metadata.SdkFieldName).Interface(), key) + log.Printf("[INFO] assigning value %+v to %s", elem.FieldByName(item.Metadata.SdkFieldName).Interface(), key) d.Set(key, elem.FieldByName(item.Metadata.SdkFieldName).Interface()) } } @@ -127,7 +131,7 @@ func SchemaToStruct(elem reflect.Value, d *schema.ResourceData, metadata map[str continue } - log.Printf("[INFO] inspecting key %s", key) + log.Printf("[INFO] inspecting key %s with type %s", key, item.Metadata.SchemaType) if len(parent) > 0 { log.Printf("[INFO] parent %s key %s", parent, key) } @@ -174,6 +178,7 @@ func SchemaToStruct(elem reflect.Value, d *schema.ResourceData, metadata map[str nestedSchema := nestedSchemaList[0].(map[string]interface{}) childElem := item.Schema.Elem.(*ExtendedResource) + log.Printf("[INFO] calling recur %s", key) SchemaToStruct(nestedObj.Elem(), d, childElem.Schema, key, nestedSchema) log.Printf("[INFO] assigning struct %v to %s", nestedObj, key) elem.FieldByName(item.Metadata.SdkFieldName).Set(nestedObj) diff --git a/nsxt/metadata/metadata_test.go b/nsxt/metadata/metadata_test.go new file mode 100644 index 000000000..8c6bba125 --- /dev/null +++ b/nsxt/metadata/metadata_test.go @@ -0,0 +1,165 @@ +package metadata + +import ( + "reflect" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/stretchr/testify/assert" +) + +type testStruct struct { + StringField *string + BoolField *bool + IntField *int64 + StructField *testNestedStruct +} + +type testNestedStruct struct { + StringField *string + BoolField *bool + IntField *int64 +} + +var testSchema = map[string]*schema.Schema{ + "string_field": { + Type: schema.TypeString, + }, + "bool_field": { + Type: schema.TypeBool, + }, + "int_field": { + Type: schema.TypeInt, + }, + "struct_field": { + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "string_field": { + Type: schema.TypeString, + }, + "bool_field": { + Type: schema.TypeBool, + }, + "int_field": { + Type: schema.TypeInt, + }, + }, + }, + }, +} + +func basicStringSchema() *ExtendedSchema { + return &ExtendedSchema{ + Schema: schema.Schema{ + Type: schema.TypeString, + }, + Metadata: Metadata{ + SchemaType: "string", + SdkFieldName: "StringField", + }, + } +} + +func basicBoolSchema() *ExtendedSchema { + return &ExtendedSchema{ + Schema: schema.Schema{ + Type: schema.TypeBool, + }, + Metadata: Metadata{ + SchemaType: "bool", + SdkFieldName: "BoolField", + }, + } +} + +func basicIntSchema() *ExtendedSchema { + return &ExtendedSchema{ + Schema: schema.Schema{ + Type: schema.TypeInt, + }, + Metadata: Metadata{ + SchemaType: "int", + SdkFieldName: "IntField", + }, + } +} + +var testExtendedSchema = map[string]*ExtendedSchema{ + "string_field": basicStringSchema(), + "bool_field": basicBoolSchema(), + "int_field": basicIntSchema(), + "struct_field": { + Schema: schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Elem: &ExtendedResource{ + Schema: map[string]*ExtendedSchema{ + "string_field": basicStringSchema(), + "bool_field": basicBoolSchema(), + "int_field": basicIntSchema(), + }, + }, + }, + Metadata: Metadata{ + SchemaType: "struct", + SdkFieldName: "StructField", + ReflectType: reflect.TypeOf(testNestedStruct{}), + }, + }, +} + +func TestStructToSchema(t *testing.T) { + testStr := "test_string" + testBool := true + testInt := int64(123) + obj := testStruct{ + StringField: &testStr, + BoolField: &testBool, + IntField: &testInt, + StructField: &testNestedStruct{ + StringField: &testStr, + BoolField: &testBool, + IntField: &testInt, + }, + } + d := schema.TestResourceDataRaw( + t, testSchema, map[string]interface{}{}) + + elem := reflect.ValueOf(&obj).Elem() + StructToSchema(elem, d, testExtendedSchema, "", nil) + assert.Equal(t, "test_string", d.Get("string_field").(string)) + assert.Equal(t, true, d.Get("bool_field").(bool)) + assert.Equal(t, 123, d.Get("int_field").(int)) + nestedObj := d.Get("struct_field").([]interface{})[0].(map[string]interface{}) + assert.Equal(t, "test_string", nestedObj["string_field"].(string)) + assert.Equal(t, true, nestedObj["bool_field"].(bool)) + assert.Equal(t, 123, nestedObj["int_field"].(int)) +} + +func TestSchemaToStruct(t *testing.T) { + d := schema.TestResourceDataRaw( + t, testSchema, map[string]interface{}{ + "string_field": "test_string", + "bool_field": true, + "int_field": 100, + "struct_field": []interface{}{ + map[string]interface{}{ + "string_field": "nested_string", + "bool_field": true, + "int_field": 1, + }, + }, + }) + + obj := testStruct{} + elem := reflect.ValueOf(&obj).Elem() + SchemaToStruct(elem, d, testExtendedSchema, "", nil) + assert.Equal(t, "test_string", *obj.StringField) + assert.Equal(t, true, *obj.BoolField) + assert.Equal(t, int64(100), *obj.IntField) + assert.Equal(t, "nested_string", *obj.StructField.StringField) + assert.Equal(t, true, *obj.StructField.BoolField) + assert.Equal(t, int64(1), *obj.StructField.IntField) +}