diff --git a/go.mod b/go.mod index 2c3b556f8..7dcdb7fc2 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/go-openapi/loads v0.0.0-20171207192234-2a2b323bab96 github.com/go-openapi/spec v0.19.0 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 + github.com/hashicorp/terraform-plugin-sdk v1.1.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.1 github.com/iancoleman/strcase v0.0.0-20180726023541-3605ed457bf7 github.com/mitchellh/go-homedir v1.1.0 diff --git a/go.sum b/go.sum index c0de5a099..818df06ab 100644 --- a/go.sum +++ b/go.sum @@ -419,7 +419,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.33/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea/go.mod h1:eNr558nEUjP8acGw8FFjTeWvSgU1stO7FAO6eknhHe4= github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= @@ -482,9 +482,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -521,10 +520,7 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 h1:EN5+DfgmRMvRUrMGERW2gQl3Vc+Z7ZMnI/xdEpPSf0c= -golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -541,7 +537,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -581,10 +577,7 @@ golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f h1:8w7RhxzTVgUzw/AH/9mUV5q0vMgy40SQRursCcfmkCw= -golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -640,8 +633,6 @@ golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/openapi/common.go b/openapi/common.go index ec70936df..c935a6370 100644 --- a/openapi/common.go +++ b/openapi/common.go @@ -1,14 +1,17 @@ package openapi import ( + "bytes" "context" "errors" "fmt" + "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "io/ioutil" "log" "net/http" "reflect" + "sort" "strconv" "github.com/dikhan/terraform-provider-openapi/v3/openapi/openapierr" @@ -136,7 +139,8 @@ func updateStateWithPayloadDataAndOptions(openAPIResource SpecResource, remoteDa propValue = processIgnoreOrderIfEnabled(*property, propertyLocalStateValue, propertyRemoteValue) } - value, err := convertPayloadToLocalStateDataValue(property, propValue, propertyLocalStateValue) + value, err := convertPayloadToLocalStateDataValue(property, propValue, propertyLocalStateValue, true) + if err != nil { return err } @@ -193,12 +197,148 @@ func processIgnoreOrderIfEnabled(property SpecSchemaDefinitionProperty, inputPro } return remoteValue } +func hashByName(v interface{}) int { + m, ok := v.(map[string]interface{}) + if !ok { + // Handle error: v is not a map[string]interface{} + } + + name, ok := m["name"].(string) + if !ok { + // Handle error: name field is not a string or does not exist + } + + return hashcode.String(name) +} + +func hashComplexObject(v interface{}) int { + var buffer bytes.Buffer + + switch v := v.(type) { + case map[string]interface{}: + // Sort the keys so that the order is consistent + var keys []string + for k := range v { + keys = append(keys, k) + } + sort.Strings(keys) + + // Hash each key-value pair + for _, k := range keys { + buffer.WriteString(k) + buffer.WriteString(fmt.Sprintf("%v", hashComplexObject(v[k]))) + } + case []interface{}: + // Hash each element in the slice + for _, elem := range v { + buffer.WriteString(fmt.Sprintf("%v", hashComplexObject(elem))) + } + default: + // For primitive types, just write the value to the buffer + buffer.WriteString(fmt.Sprintf("%v", v)) + } + + // Compute and return the hash of the concatenated string + return hashcode.String(buffer.String()) +} + +//func deepConvertArrayToSet(property *SpecSchemaDefinitionProperty, v interface{}) (interface{}, error) { +// switch v := v.(type) { +// case []interface{}: +// // For slices, create a new set and add each element to the set +// if property.IgnoreItemsOrder { +// set := schema.NewSet(hashComplexObject, []interface{}{}) +// for k, elem := range v { +// convertedElem, err := deepConvertArrayToSet(property.SpecSchemaDefinition.Properties, elem) +// if err != nil { +// return nil, err +// } +// set.Add(convertedElem) +// } +// return set, nil +// } +// case map[string]interface{}: +// // For maps, create a new map and convert each value in the map +// newMap := make(map[string]interface{}) +// for key, value := range v { +// convertedValue, err := deepConvertArrayToSet(property.SpecSchemaDefinition.Properties[key], value) +// if err != nil { +// return nil, err +// } +// newMap[key] = convertedValue +// } +// return newMap, nil +// default: +// // For other types, return the value as is +// return v, nil +// } +//} + +func deepConvertArrayToSet(property *SpecSchemaDefinitionProperty, v interface{}) (interface{}, error) { + //log.Printf("[INFO] input of deep copy %s %s", property.String(), v) + switch v := v.(type) { + case []interface{}: + // For slices, create a new set and add each element to the set + if property.isSetProperty() { + set := schema.NewSet(hashComplexObject, []interface{}{}) + for _, elem := range v { + if property.isSetOfObjectsProperty() { + convertedElem, err := deepConvertArrayToSetMapNew(property.SpecSchemaDefinition.Properties, elem) + if err != nil { + return nil, err + } + set.Add(convertedElem) + } else { + set.Add(elem) + } + } + //log.Printf("[INFO] output of deep copy %s %s %s", property.String(), v, set) + return set, nil + } + return v, nil + default: + // For other types, return the value as is + return v, nil + } +} + +func deepConvertArrayToSetMapNew(properties []*SpecSchemaDefinitionProperty, object interface{}) (interface{}, error) { + //log.Printf("[INFO] input of deep copy map %s %s", properties, object) + inputMap, ok := object.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("object is not a map") + } + + // Create a new map and convert each value in the map + newMap := make(map[string]interface{}) + for key, value := range inputMap { + //log.Printf("[INFO] key,value %s %s", key, value) + for _, property := range properties { + if key == property.Name { + //log.Printf("[INFO] key,value %s %s", key, value) + if property.isSetOfObjectsProperty() { + //log.Printf("[INFO] key,value %s %s", key, value) + convertedValue, err := deepConvertArrayToSet(property, value) + if err != nil { + return nil, err + } + newMap[key] = convertedValue.(*schema.Set) + } else { + newMap[key] = value + } + } + } + } + + return newMap, nil +} -func convertPayloadToLocalStateDataValue(property *SpecSchemaDefinitionProperty, propertyValue interface{}, propertyLocalStateValue interface{}) (interface{}, error) { +func convertPayloadToLocalStateDataValue(property *SpecSchemaDefinitionProperty, propertyValue interface{}, propertyLocalStateValue interface{}, isFromAPI bool) (interface{}, error) { if property.WriteOnly { return propertyLocalStateValue, nil } - + //log.Printf("[INFO] propertyValue: %s %s %s", reflect.TypeOf(propertyValue), reflect.TypeOf(propertyValue).Kind(), propertyValue) + //log.Printf("[INFO] propertyLocalStateValue: %s %s %s", reflect.TypeOf(propertyLocalStateValue), reflect.TypeOf(propertyLocalStateValue).Kind(), propertyLocalStateValue) switch property.Type { case TypeObject: return convertObjectToLocalStateData(property, propertyValue, propertyLocalStateValue) @@ -237,6 +377,109 @@ func convertPayloadToLocalStateDataValue(property *SpecSchemaDefinitionProperty, return arrayInput, nil } return nil, fmt.Errorf("property '%s' is supposed to be an array objects", property.Name) + case TypeSet: + log.Printf("[INFO] ofTypeSet") + if isSetOfPrimitives, _ := property.isTerraformSetOfSimpleValues(); isSetOfPrimitives { + return propertyValue, nil + } + if property.isSetOfObjectsProperty() { + arrayInput := []interface{}{} + + arrayValue := make([]interface{}, 0) + if propertyValue != nil { + arrayValue = propertyValue.([]interface{}) + } + + var setLocalValue *schema.Set + + if propertyLocalStateValue == nil { + setLocalValue = schema.NewSet(schema.HashString, []interface{}{}) + } else { + setLocalValue = propertyLocalStateValue.(*schema.Set) + } + for _, remoteVal := range arrayValue { + hashCodeRemote := hashComplexObject(remoteVal) + matched := false + for _, localVal := range setLocalValue.List() { + hashCodeLocal := hashComplexObject(localVal) + if hashCodeLocal == hashCodeRemote { + objectValue, err := convertObjectToLocalStateData(property, remoteVal, localVal) + if err != nil { + return err, nil + } + matched = true + arrayInput = append(arrayInput, objectValue) + } + } + if matched == false { + objectValue, err := convertObjectToLocalStateData(property, remoteVal, nil) + if err != nil { + return err, nil + } + arrayInput = append(arrayInput, objectValue) + } + } + return arrayInput, nil + //if property.isSetOfObjectsProperty() { + // setInput := schema.NewSet(hashComplexObject, []interface{}{}) + // var setValue interface{} + // var err error + // if isFromAPI { + // arrayValue := make([]interface{}, 0) + // if propertyValue != nil { + // arrayValue = propertyValue.([]interface{}) + // } + // setValue, err = deepConvertArrayToSet(property, arrayValue) + // } else { + // setValue = propertyValue + // } + // //log.Printf("[INFO] arrayValue: %s", arrayValue) + // var setLocalValue *schema.Set + // + // if propertyLocalStateValue == nil { + // setLocalValue = schema.NewSet(schema.HashString, []interface{}{}) + // } else { + // setLocalValue = propertyLocalStateValue.(*schema.Set) + // } + // if err != nil { + // return err, nil + // } + // log.Printf("[INFO] setValue: %s", setValue) + // for _, v1 := range setValue.(*schema.Set).List() { + // // Do something with v + // hashCodeRemote := hashComplexObject(v1) + // matched := false + // for _, v2 := range setLocalValue.List() { + // hashCodeLocal := hashComplexObject(v2) + // //log.Printf("[INFO] properties: %s", property.String()) + // //log.Printf("[INFO] remote: %s %d", v1, hashCodeRemote) + // //log.Printf("[INFO] local: %s %d", v2, hashCodeLocal) + // if hashCodeLocal == hashCodeRemote { + // objectValue, err := convertObjectToLocalStateData(property, v1, v2) + // matched = true + // if err != nil { + // return err, nil + // } + // setInput.Add(objectValue) + // } + // } + // if matched == false { + // //log.Printf("[INFO] properties: %s", property.String()) + // //log.Printf("[INFO] remote: %s %d", v1, hashCodeRemote) + // objectValue, err := convertObjectToLocalStateData(property, v1, nil) + // //log.Printf("[INFO] object Value: %s", objectValue) + // matched = true + // if err != nil { + // return err, nil + // } + // setInput.Add(objectValue) + // } + // } + //log.Printf("[INFO] setInput: %s", setInput) + // + //return setInput, nil + } + return nil, fmt.Errorf("property '%s' is supposed to be an set objects", property.Name) case TypeString: if propertyValue == nil { return nil, nil @@ -273,6 +516,7 @@ func convertObjectToLocalStateData(property *SpecSchemaDefinitionProperty, prope if propertyValue != nil { mapValue = propertyValue.(map[string]interface{}) } + //log.Printf("[INFO] mapValue: %s", mapValue) localStateMapValue := make(map[string]interface{}) if propertyLocalStateValue != nil { @@ -286,10 +530,11 @@ func convertObjectToLocalStateData(property *SpecSchemaDefinitionProperty, prope for _, schemaDefinitionProperty := range property.SpecSchemaDefinition.Properties { propertyName := schemaDefinitionProperty.Name propertyValue := mapValue[propertyName] - + //log.Printf("[INFO] property name and remoteValue: %s %s %s", propertyName, propertyValue, localStateMapValue[propertyName]) // Here we are processing the items of the list which are objects. In this case we need to keep the original // types as Terraform honors property types for resource schemas attached to TypeList properties - propValue, err := convertPayloadToLocalStateDataValue(schemaDefinitionProperty, propertyValue, localStateMapValue[propertyName]) + propValue, err := convertPayloadToLocalStateDataValue(schemaDefinitionProperty, propertyValue, localStateMapValue[propertyName], false) + if err != nil { return nil, err } @@ -309,6 +554,8 @@ func convertObjectToLocalStateData(property *SpecSchemaDefinitionProperty, prope // setResourceDataProperty sets the expectedValue for the given schemaDefinitionPropertyName using the terraform compliant property name func setResourceDataProperty(schemaDefinitionProperty SpecSchemaDefinitionProperty, value interface{}, resourceLocalData *schema.ResourceData) error { + //log.Printf("[INFO] lastValue: %s", value) + //log.Printf("[INFO] lastLocalValue: %s", value) return resourceLocalData.Set(schemaDefinitionProperty.GetTerraformCompliantPropertyName(), value) } diff --git a/openapi/common_test.go b/openapi/common_test.go index cb7da3f62..ee47fbb7f 100644 --- a/openapi/common_test.go +++ b/openapi/common_test.go @@ -642,331 +642,720 @@ func TestConvertPayloadToLocalStateDataValue(t *testing.T) { Convey("Given a resource factory", t, func() { - Convey("When convertPayloadToLocalStateDataValue is called with ", func() { - property := newStringSchemaDefinitionPropertyWithDefaults("string_property", "", false, false, nil) - dataValue := "someValue" - resultValue, err := convertPayloadToLocalStateDataValue(property, dataValue, nil) - Convey("Then the error should be nil and the result value should be the expected value with the right type string", func() { - So(err, ShouldBeNil) - So(resultValue, ShouldEqual, dataValue) - }) - }) - - Convey("When convertPayloadToLocalStateDataValue is called with a bool property and a bool value", func() { - property := newBoolSchemaDefinitionPropertyWithDefaults("bool_property", "", false, false, nil) - dataValue := true - resultValue, err := convertPayloadToLocalStateDataValue(property, dataValue, nil) - Convey("Then the error should be nil and the result value should be the expected value with the right type boolean", func() { - So(err, ShouldBeNil) - So(resultValue, ShouldEqual, dataValue) - }) - }) - - Convey("When convertPayloadToLocalStateDataValue is called with an int property and a int value", func() { - property := newIntSchemaDefinitionPropertyWithDefaults("int_property", "", false, false, nil) - dataValue := 10 - resultValue, err := convertPayloadToLocalStateDataValue(property, dataValue, nil) - Convey("Then the error should be nil and the result value should be the expected value with the right type int", func() { - So(err, ShouldBeNil) - So(resultValue, ShouldEqual, dataValue) - }) - }) - Convey("When convertPayloadToLocalStateDataValue is called with an float property and a float value", func() { - property := newNumberSchemaDefinitionPropertyWithDefaults("float_property", "", false, false, nil) - dataValue := 45.23 - resultValue, err := convertPayloadToLocalStateDataValue(property, dataValue, nil) - Convey("Then error should be nil and the result value should be the expected value formatted string with the right type float", func() { - So(err, ShouldBeNil) - So(resultValue, ShouldEqual, dataValue) - }) - }) - Convey("When convertPayloadToLocalStateDataValue is called with an float property and a float value but the swagger property is an integer", func() { - property := newIntSchemaDefinitionPropertyWithDefaults("int_property", "", false, false, nil) - dataValue := 45 - resultValue, err := convertPayloadToLocalStateDataValue(property, dataValue, nil) - Convey("Then the error should be nil and the result value should be the expected value formatted string with the right type integer", func() { - So(err, ShouldBeNil) - So(resultValue, ShouldEqual, dataValue) - So(resultValue, ShouldHaveSameTypeAs, int(dataValue)) - }) - }) - Convey("When convertPayloadToLocalStateDataValue is called with a list property and with items object", func() { - objectSchemaDefinition := &SpecSchemaDefinition{ - Properties: SpecSchemaDefinitionProperties{ - newIntSchemaDefinitionPropertyWithDefaults("example_int", "", true, false, nil), - newStringSchemaDefinitionPropertyWithDefaults("example_string", "", true, false, nil), - newBoolSchemaDefinitionPropertyWithDefaults("example_bool", "", true, false, nil), - newNumberSchemaDefinitionPropertyWithDefaults("example_float", "", true, false, nil), - }, - } - objectDefault := map[string]interface{}{ - "example_int": 80, - "example_string": "http", - "example_bool": true, - "example_float": 10.45, - } - property := newListSchemaDefinitionPropertyWithDefaults("slice_object_property", "", true, false, false, nil, TypeObject, objectSchemaDefinition) - dataValue := []interface{}{objectDefault} - resultValue, err := convertPayloadToLocalStateDataValue(property, dataValue, nil) - Convey("Then the error should be nil and the result value should be the list containing the object items with the expected types (int, string, bool and float)", func() { - So(err, ShouldBeNil) - So(resultValue.([]interface{})[0].(map[string]interface{}), ShouldContainKey, "example_int") - So(resultValue.([]interface{})[0].(map[string]interface{})["example_int"].(int), ShouldEqual, objectDefault["example_int"]) - So(resultValue.([]interface{})[0].(map[string]interface{}), ShouldContainKey, "example_string") - So(resultValue.([]interface{})[0].(map[string]interface{})["example_string"].(string), ShouldEqual, objectDefault["example_string"]) - So(resultValue.([]interface{})[0].(map[string]interface{}), ShouldContainKey, "example_bool") - So(resultValue.([]interface{})[0].(map[string]interface{})["example_bool"].(bool), ShouldEqual, objectDefault["example_bool"]) - So(resultValue.([]interface{})[0].(map[string]interface{}), ShouldContainKey, "example_float") - So(resultValue.([]interface{})[0].(map[string]interface{})["example_float"].(float64), ShouldEqual, objectDefault["example_float"]) - }) - }) - Convey("When convertPayloadToLocalStateDataValue is called with a list property and an array with items string value", func() { - property := newListSchemaDefinitionPropertyWithDefaults("slice_object_property", "", true, false, false, nil, TypeString, nil) - dataValue := []interface{}{"value1"} - resultValue, err := convertPayloadToLocalStateDataValue(property, dataValue, nil) - Convey("Then the error should be nil and the result value should be the expected value with the right type array", func() { - So(err, ShouldBeNil) - So(resultValue.([]interface{}), ShouldContain, dataValue[0]) - }) - }) - - Convey("When convertPayloadToLocalStateDataValue is called with simple object property and an empty map as value", func() { - property := &SpecSchemaDefinitionProperty{ - Name: "some_object", - Type: TypeObject, - Required: true, - SpecSchemaDefinition: &SpecSchemaDefinition{}, - } - resultValue, err := convertPayloadToLocalStateDataValue(property, map[string]interface{}{}, nil) - Convey("Then the error should be nil and the result value should be the expected value with the right type array", func() { - So(err, ShouldBeNil) - So(resultValue.([]interface{}), ShouldNotBeEmpty) // By default objects' internal terraform schema is Type List with Max 1 elem *Resource - So(resultValue.([]interface{})[0].(map[string]interface{}), ShouldBeEmpty) - }) - }) - - // Edge case - Convey("When convertPayloadToLocalStateDataValue is called with a slice of map interfaces", func() { - property := newListSchemaDefinitionPropertyWithDefaults("slice_object_property", "", true, false, false, nil, TypeString, nil) - _, err := convertPayloadToLocalStateDataValue(property, []map[string]interface{}{}, nil) - Convey("Then the error should be nil", func() { - So(err, ShouldBeNil) - }) - }) - - Convey("When convertPayloadToLocalStateDataValue is called with a property list that the array items are of unknown type", func() { - property := &SpecSchemaDefinitionProperty{ - Name: "not_well_configured_property", - Type: TypeList, - ArrayItemsType: schemaDefinitionPropertyType("unknown"), - } - _, err := convertPayloadToLocalStateDataValue(property, []interface{}{}, nil) - Convey("Then the error should match the expected one", func() { - So(err.Error(), ShouldEqual, "property 'not_well_configured_property' is supposed to be an array objects") - }) - }) - - Convey("When convertPayloadToLocalStateDataValue is called with a simple object", func() { - // Simple objects are considered objects that all the properties are of the same type and are not computed - objectSchemaDefinition := &SpecSchemaDefinition{ - Properties: SpecSchemaDefinitionProperties{ - newStringSchemaDefinitionPropertyWithDefaults("example_string", "", true, false, nil), - newStringSchemaDefinitionPropertyWithDefaults("example_string_2", "", true, false, nil), - }, - } - dataValue := map[string]interface{}{ - "example_string": "http", - "example_string_2": "something", - } - property := newObjectSchemaDefinitionPropertyWithDefaults("object_property", "", true, false, false, nil, objectSchemaDefinition) - resultValue, err := convertPayloadToLocalStateDataValue(property, dataValue, nil) - Convey("Then the error should be nil and the result value should be the list containing the object items all being string type (as terraform only supports maps of strings, hence values need to be stored as strings)", func() { - So(err, ShouldBeNil) - So(resultValue.([]interface{})[0].(map[string]interface{})["example_string"].(string), ShouldEqual, "http") - So(resultValue.([]interface{})[0].(map[string]interface{})["example_string_2"].(string), ShouldEqual, "something") - }) - }) - - // Simple objects are considered objects that contain properties that are of different types and configuration (e,g: mix of required/optional/computed properties) - Convey("When convertPayloadToLocalStateDataValue is called with a complex object", func() { - objectSchemaDefinition := &SpecSchemaDefinition{ + //Convey("When convertPayloadToLocalStateDataValue is called with ", func() { + // property := newStringSchemaDefinitionPropertyWithDefaults("string_property", "", false, false, nil) + // dataValue := "someValue" + // resultValue, err := convertPayloadToLocalStateDataValue(property, dataValue, nil, true) + // Convey("Then the error should be nil and the result value should be the expected value with the right type string", func() { + // So(err, ShouldBeNil) + // So(resultValue, ShouldEqual, dataValue) + // }) + //}) + // + //Convey("When convertPayloadToLocalStateDataValue is called with a bool property and a bool value", func() { + // property := newBoolSchemaDefinitionPropertyWithDefaults("bool_property", "", false, false, nil) + // dataValue := true + // resultValue, err := convertPayloadToLocalStateDataValue(property, dataValue, nil, true) + // Convey("Then the error should be nil and the result value should be the expected value with the right type boolean", func() { + // So(err, ShouldBeNil) + // So(resultValue, ShouldEqual, dataValue) + // }) + //}) + // + //Convey("When convertPayloadToLocalStateDataValue is called with an int property and a int value", func() { + // property := newIntSchemaDefinitionPropertyWithDefaults("int_property", "", false, false, nil) + // dataValue := 10 + // resultValue, err := convertPayloadToLocalStateDataValue(property, dataValue, nil, true) + // Convey("Then the error should be nil and the result value should be the expected value with the right type int", func() { + // So(err, ShouldBeNil) + // So(resultValue, ShouldEqual, dataValue) + // }) + //}) + //Convey("When convertPayloadToLocalStateDataValue is called with an float property and a float value", func() { + // property := newNumberSchemaDefinitionPropertyWithDefaults("float_property", "", false, false, nil) + // dataValue := 45.23 + // resultValue, err := convertPayloadToLocalStateDataValue(property, dataValue, nil, true) + // Convey("Then error should be nil and the result value should be the expected value formatted string with the right type float", func() { + // So(err, ShouldBeNil) + // So(resultValue, ShouldEqual, dataValue) + // }) + //}) + //Convey("When convertPayloadToLocalStateDataValue is called with an float property and a float value but the swagger property is an integer", func() { + // property := newIntSchemaDefinitionPropertyWithDefaults("int_property", "", false, false, nil) + // dataValue := 45 + // resultValue, err := convertPayloadToLocalStateDataValue(property, dataValue, nil, true) + // Convey("Then the error should be nil and the result value should be the expected value formatted string with the right type integer", func() { + // So(err, ShouldBeNil) + // So(resultValue, ShouldEqual, dataValue) + // So(resultValue, ShouldHaveSameTypeAs, int(dataValue)) + // }) + //}) + //Convey("When convertPayloadToLocalStateDataValue is called with a list property and with items object", func() { + // objectSchemaDefinition := &SpecSchemaDefinition{ + // Properties: SpecSchemaDefinitionProperties{ + // newIntSchemaDefinitionPropertyWithDefaults("example_int", "", true, false, nil), + // newStringSchemaDefinitionPropertyWithDefaults("example_string", "", true, false, nil), + // newBoolSchemaDefinitionPropertyWithDefaults("example_bool", "", true, false, nil), + // newNumberSchemaDefinitionPropertyWithDefaults("example_float", "", true, false, nil), + // }, + // } + // objectDefault := map[string]interface{}{ + // "example_int": 80, + // "example_string": "http", + // "example_bool": true, + // "example_float": 10.45, + // } + // property := newListSchemaDefinitionPropertyWithDefaults("slice_object_property", "", true, false, false, nil, TypeObject, objectSchemaDefinition) + // dataValue := []interface{}{objectDefault} + // resultValue, err := convertPayloadToLocalStateDataValue(property, dataValue, nil, true) + // Convey("Then the error should be nil and the result value should be the list containing the object items with the expected types (int, string, bool and float)", func() { + // So(err, ShouldBeNil) + // So(resultValue.([]interface{})[0].(map[string]interface{}), ShouldContainKey, "example_int") + // So(resultValue.([]interface{})[0].(map[string]interface{})["example_int"].(int), ShouldEqual, objectDefault["example_int"]) + // So(resultValue.([]interface{})[0].(map[string]interface{}), ShouldContainKey, "example_string") + // So(resultValue.([]interface{})[0].(map[string]interface{})["example_string"].(string), ShouldEqual, objectDefault["example_string"]) + // So(resultValue.([]interface{})[0].(map[string]interface{}), ShouldContainKey, "example_bool") + // So(resultValue.([]interface{})[0].(map[string]interface{})["example_bool"].(bool), ShouldEqual, objectDefault["example_bool"]) + // So(resultValue.([]interface{})[0].(map[string]interface{}), ShouldContainKey, "example_float") + // So(resultValue.([]interface{})[0].(map[string]interface{})["example_float"].(float64), ShouldEqual, objectDefault["example_float"]) + // }) + //}) + //Convey("When convertPayloadToLocalStateDataValue is called with a set property and with items object", func() { + // objectSchemaDefinition := &SpecSchemaDefinition{ + // Properties: SpecSchemaDefinitionProperties{ + // newIntSchemaDefinitionPropertyWithDefaults("example_int", "", true, false, nil), + // newStringSchemaDefinitionPropertyWithDefaults("example_string", "", true, false, nil), + // newBoolSchemaDefinitionPropertyWithDefaults("example_bool", "", true, false, nil), + // newNumberSchemaDefinitionPropertyWithDefaults("example_float", "", true, false, nil), + // }, + // } + // objectDefault := map[string]interface{}{ + // "example_int": 80, + // "example_string": "http", + // "example_bool": true, + // "example_float": 10.45, + // } + // property := newSetSchemaDefinitionPropertyWithDefaults("slice_object_property", "", true, false, false, nil, TypeObject, objectSchemaDefinition) + // dataValue := []interface{}{objectDefault} + // resultValue, err := convertPayloadToLocalStateDataValue(property, dataValue, nil, true) + // Convey("Then the error should be nil and the result value should be the list containing the object items with the expected types (int, string, bool and float)", func() { + // So(err, ShouldBeNil) + // So(resultValue.(*schema.Set).List()[0].(map[string]interface{}), ShouldContainKey, "example_int") + // So(resultValue.(*schema.Set).List()[0].(map[string]interface{})["example_int"].(int), ShouldEqual, objectDefault["example_int"]) + // So(resultValue.(*schema.Set).List()[0].(map[string]interface{}), ShouldContainKey, "example_string") + // So(resultValue.(*schema.Set).List()[0].(map[string]interface{})["example_string"].(string), ShouldEqual, objectDefault["example_string"]) + // So(resultValue.(*schema.Set).List()[0].(map[string]interface{}), ShouldContainKey, "example_bool") + // So(resultValue.(*schema.Set).List()[0].(map[string]interface{})["example_bool"].(bool), ShouldEqual, objectDefault["example_bool"]) + // So(resultValue.(*schema.Set).List()[0].(map[string]interface{}), ShouldContainKey, "example_float") + // So(resultValue.(*schema.Set).List()[0].(map[string]interface{})["example_float"].(float64), ShouldEqual, objectDefault["example_float"]) + // }) + //}) + //Convey("When convertPayloadToLocalStateDataValue is called with a list property and an array with items string value", func() { + // property := newListSchemaDefinitionPropertyWithDefaults("slice_object_property", "", true, false, false, nil, TypeString, nil) + // dataValue := []interface{}{"value2", "value1"} + // resultValue, err := convertPayloadToLocalStateDataValue(property, dataValue, nil, true) + // Convey("Then the error should be nil and the result value should be the expected value with the right type array", func() { + // So(err, ShouldBeNil) + // So(resultValue.([]interface{}), ShouldContain, dataValue[0]) + // So(resultValue.([]interface{}), ShouldContain, dataValue[1]) + // }) + //}) + //Convey("When convertPayloadToLocalStateDataValue is called with a set property and an set with items string value", func() { + // property := newSetSchemaDefinitionPropertyWithDefaults("set_object_property", "", true, false, false, nil, TypeString, nil) + // dataValue := []interface{}{"value2", "value1"} + // resultValue, err := convertPayloadToLocalStateDataValue(property, dataValue, nil, true) + // Convey("Then the error should be nil and the result value should be the expected value with the right type array", func() { + // So(err, ShouldBeNil) + // So(resultValue.([]interface{}), ShouldContain, dataValue[0]) + // So(resultValue.([]interface{}), ShouldContain, dataValue[1]) + // }) + //}) + //Convey("When convertPayloadToLocalStateDataValue is called with a set property and an set with items string remote value and a set of items string local value", func() { + // property := newSetSchemaDefinitionPropertyWithDefaults("set_object_property", "", true, false, false, nil, TypeString, nil) + // dataValue := []interface{}{"value2", "value1", "value4"} + // localDataValue := schema.NewSet(schema.HashString, []interface{}{"value1", "value2", "value3"}) + // resultValue, err := convertPayloadToLocalStateDataValue(property, dataValue, localDataValue, true) + // Convey("Then the error should be nil and the result value should be the expected value with the right type array", func() { + // So(err, ShouldBeNil) + // So(resultValue.([]interface{}), ShouldContain, dataValue[0]) + // So(resultValue.([]interface{}), ShouldContain, dataValue[1]) + // So(resultValue.([]interface{}), ShouldContain, dataValue[2]) + // }) + //}) + // + //Convey("When convertPayloadToLocalStateDataValue is called with simple object property and an empty map as value", func() { + // property := &SpecSchemaDefinitionProperty{ + // Name: "some_object", + // Type: TypeObject, + // Required: true, + // SpecSchemaDefinition: &SpecSchemaDefinition{}, + // } + // resultValue, err := convertPayloadToLocalStateDataValue(property, map[string]interface{}{}, nil, true) + // Convey("Then the error should be nil and the result value should be the expected value with the right type array", func() { + // So(err, ShouldBeNil) + // So(resultValue.([]interface{}), ShouldNotBeEmpty) // By default objects' internal terraform schema is Type List with Max 1 elem *Resource + // So(resultValue.([]interface{})[0].(map[string]interface{}), ShouldBeEmpty) + // }) + //}) + // + //// Edge case + //Convey("When convertPayloadToLocalStateDataValue is called with a slice of map interfaces", func() { + // property := newListSchemaDefinitionPropertyWithDefaults("slice_object_property", "", true, false, false, nil, TypeString, nil) + // _, err := convertPayloadToLocalStateDataValue(property, []map[string]interface{}{}, nil, true) + // Convey("Then the error should be nil", func() { + // So(err, ShouldBeNil) + // }) + //}) + // + //Convey("When convertPayloadToLocalStateDataValue is called with a property list that the array items are of unknown type", func() { + // property := &SpecSchemaDefinitionProperty{ + // Name: "not_well_configured_property", + // Type: TypeList, + // ArrayItemsType: schemaDefinitionPropertyType("unknown"), + // } + // _, err := convertPayloadToLocalStateDataValue(property, []interface{}{}, nil, true) + // Convey("Then the error should match the expected one", func() { + // So(err.Error(), ShouldEqual, "property 'not_well_configured_property' is supposed to be an array objects") + // }) + //}) + // + //Convey("When convertPayloadToLocalStateDataValue is called with a simple object", func() { + // // Simple objects are considered objects that all the properties are of the same type and are not computed + // objectSchemaDefinition := &SpecSchemaDefinition{ + // Properties: SpecSchemaDefinitionProperties{ + // newStringSchemaDefinitionPropertyWithDefaults("example_string", "", true, false, nil), + // newStringSchemaDefinitionPropertyWithDefaults("example_string_2", "", true, false, nil), + // }, + // } + // dataValue := map[string]interface{}{ + // "example_string": "http", + // "example_string_2": "something", + // } + // property := newObjectSchemaDefinitionPropertyWithDefaults("object_property", "", true, false, false, nil, objectSchemaDefinition) + // resultValue, err := convertPayloadToLocalStateDataValue(property, dataValue, nil, true) + // Convey("Then the error should be nil and the result value should be the list containing the object items all being string type (as terraform only supports maps of strings, hence values need to be stored as strings)", func() { + // So(err, ShouldBeNil) + // So(resultValue.([]interface{})[0].(map[string]interface{})["example_string"].(string), ShouldEqual, "http") + // So(resultValue.([]interface{})[0].(map[string]interface{})["example_string_2"].(string), ShouldEqual, "something") + // }) + //}) + // + //// Simple objects are considered objects that contain properties that are of different types and configuration (e,g: mix of required/optional/computed properties) + //Convey("When convertPayloadToLocalStateDataValue is called with a complex object", func() { + // objectSchemaDefinition := &SpecSchemaDefinition{ + // Properties: SpecSchemaDefinitionProperties{ + // newIntSchemaDefinitionPropertyWithDefaults("example_int", "", true, false, nil), + // newStringSchemaDefinitionPropertyWithDefaults("example_string", "", true, false, nil), + // newBoolSchemaDefinitionPropertyWithDefaults("example_bool", "", true, true, nil), + // newNumberSchemaDefinitionPropertyWithDefaults("example_float", "", true, false, nil), + // }, + // } + // dataValue := map[string]interface{}{ + // "example_int": 80, + // "example_string": "http", + // "example_bool": true, + // "example_float": 10.45, + // } + // property := newObjectSchemaDefinitionPropertyWithDefaults("object_property", "", true, false, false, nil, objectSchemaDefinition) + // resultValue, err := convertPayloadToLocalStateDataValue(property, dataValue, nil, true) + // Convey("Then the error should be nil and the result value should be the list containing the object items all being string type (as terraform only supports maps of strings, hence values need to be stored as strings)", func() { + // So(err, ShouldBeNil) + // So(resultValue.([]interface{})[0], ShouldContainKey, "example_int") + // So(resultValue.([]interface{})[0].(map[string]interface{})["example_int"], ShouldEqual, 80) + // So(resultValue.([]interface{})[0].(map[string]interface{}), ShouldContainKey, "example_string") + // So(resultValue.([]interface{})[0].(map[string]interface{})["example_string"], ShouldEqual, "http") + // So(resultValue.([]interface{})[0].(map[string]interface{}), ShouldContainKey, "example_bool") + // So(resultValue.([]interface{})[0].(map[string]interface{})["example_bool"], ShouldEqual, true) + // So(resultValue.([]interface{})[0].(map[string]interface{}), ShouldContainKey, "example_float") + // So(resultValue.([]interface{})[0].(map[string]interface{})["example_float"], ShouldEqual, 10.45) + // }) + //}) + // + //Convey("When convertPayloadToLocalStateDataValue is called with an object containing objects", func() { + // nestedObjectSchemaDefinition := &SpecSchemaDefinition{ + // Properties: SpecSchemaDefinitionProperties{ + // newIntSchemaDefinitionPropertyWithDefaults("origin_port", "", true, false, 80), + // newStringSchemaDefinitionPropertyWithDefaults("protocol", "", true, false, "http"), + // }, + // } + // nestedObjectDefault := map[string]interface{}{ + // "origin_port": nestedObjectSchemaDefinition.Properties[0].Default, + // "protocol": nestedObjectSchemaDefinition.Properties[1].Default, + // } + // nestedObject := newObjectSchemaDefinitionPropertyWithDefaults("nested_object", "", true, false, false, nestedObjectDefault, nestedObjectSchemaDefinition) + // propertyWithNestedObjectSchemaDefinition := &SpecSchemaDefinition{ + // Properties: SpecSchemaDefinitionProperties{ + // idProperty, + // nestedObject, + // }, + // } + // // The below represents the JSON representation of the response payload received by the API + // dataValue := map[string]interface{}{ + // "id": propertyWithNestedObjectSchemaDefinition.Properties[0].Default, + // "nested_object": propertyWithNestedObjectSchemaDefinition.Properties[1].Default, + // } + // + // expectedPropertyWithNestedObjectName := "property_with_nested_object" + // propertyWithNestedObject := newObjectSchemaDefinitionPropertyWithDefaults(expectedPropertyWithNestedObjectName, "", true, false, false, dataValue, propertyWithNestedObjectSchemaDefinition) + // resultValue, err := convertPayloadToLocalStateDataValue(propertyWithNestedObject, dataValue, nil, true) + // Convey("Then the result returned should be the expected one", func() { + // So(err, ShouldBeNil) + // + // // The result value should be the list containing just one element (as per the nested struct workaround) + // // Tag(NestedStructsWorkaround) + // // Note: This is the workaround needed to support properties with nested structs. The current Terraform sdk version + // // does not support this now, hence the suggestion from the Terraform maintainer was to use a list of map[string]interface{} + // // with the list containing just one element. The below represents the internal representation of the terraform state + // // for an object property that contains other objects + // So(resultValue.([]interface{}), ShouldNotBeEmpty) + // So(len(resultValue.([]interface{})), ShouldEqual, 1) + // + // // AND the object should have the expected properties including the nested object + // So(resultValue.([]interface{})[0], ShouldContainKey, propertyWithNestedObjectSchemaDefinition.Properties[0].Name) + // So(resultValue.([]interface{})[0], ShouldContainKey, propertyWithNestedObjectSchemaDefinition.Properties[1].Name) + // + // // AND the object property with nested object should have the expected configuration + // nestedObject := propertyWithNestedObjectSchemaDefinition.Properties[1] + // So(resultValue.([]interface{})[0].(map[string]interface{})[nestedObject.Name].([]interface{})[0].(map[string]interface{}), ShouldContainKey, nestedObjectSchemaDefinition.Properties[0].Name) + // So(resultValue.([]interface{})[0].(map[string]interface{})[nestedObject.Name].([]interface{})[0].(map[string]interface{})[nestedObjectSchemaDefinition.Properties[0].Name], ShouldEqual, nestedObjectSchemaDefinition.Properties[0].Default.(int)) + // So(resultValue.([]interface{})[0].(map[string]interface{})[nestedObject.Name].([]interface{})[0].(map[string]interface{}), ShouldContainKey, nestedObjectSchemaDefinition.Properties[1].Name) + // So(resultValue.([]interface{})[0].(map[string]interface{})[nestedObject.Name].([]interface{})[0].(map[string]interface{})[nestedObjectSchemaDefinition.Properties[1].Name], ShouldEqual, nestedObjectSchemaDefinition.Properties[1].Default) + // }) + //}) + // + //Convey("When convertPayloadToLocalStateDataValue is called with complex objects with write-only properties", func() { + // nestedArrayItemSchemaDefinition := &SpecSchemaDefinition{ + // Properties: SpecSchemaDefinitionProperties{ + // newIntSchemaDefinitionPropertyWithDefaults("nested_list_property", "", true, false, 456), + // setSchemaDefinitionPropertyWriteOnly(newIntSchemaDefinitionPropertyWithDefaults("nested_list_property_password", "", true, false, nil)), + // }, + // } + // + // nestedObjectSchemaDefinition := &SpecSchemaDefinition{ + // Properties: SpecSchemaDefinitionProperties{ + // setSchemaDefinitionPropertyWriteOnly(newIntSchemaDefinitionPropertyWithDefaults("origin_port", "", true, false, 80)), + // newStringSchemaDefinitionPropertyWithDefaults("protocol", "", true, false, "http"), + // setSchemaDefinitionPropertyWriteOnly(newStringSchemaDefinitionPropertyWithDefaults("password", "", true, false, nil)), + // setSchemaDefinitionPropertyWriteOnly(newListSchemaDefinitionPropertyWithDefaults("password_array", "", true, false, false, nil, TypeString, nil)), + // newListSchemaDefinitionPropertyWithDefaults("nested_list", "", true, false, false, nil, TypeObject, nestedArrayItemSchemaDefinition), + // }, + // } + // nestedObject := newObjectSchemaDefinitionPropertyWithDefaults("nested_object", "", true, false, false, nil, nestedObjectSchemaDefinition) + // propertyWithNestedObjectSchemaDefinition := &SpecSchemaDefinition{ + // Properties: SpecSchemaDefinitionProperties{ + // idProperty, + // nestedObject, + // }, + // } + // // The below represents the JSON representation of the response payload received by the API + // dataValue := map[string]interface{}{ + // "id": propertyWithNestedObjectSchemaDefinition.Properties[0].Default, + // "nested_object": map[string]interface{}{ + // "origin_port": 12345, + // "protocol": "tcp", + // "nested_list": []interface{}{ + // map[string]interface{}{ + // "nested_list_property": 123, + // "nested_list_property_password": "some changed value", + // }, + // }, + // }, + // } + // + // expectedPropertyWithNestedObjectName := "property_with_nested_object" + // propertyWithNestedObject := newObjectSchemaDefinitionPropertyWithDefaults(expectedPropertyWithNestedObjectName, "", true, false, false, dataValue, propertyWithNestedObjectSchemaDefinition) + // + // Convey("When the local state is empty", func() { + // resultValue, err := convertPayloadToLocalStateDataValue(propertyWithNestedObject, dataValue, nil, true) + // + // Convey("Then the result returned should be the expected one", func() { + // So(err, ShouldBeNil) + // + // nestedObject := resultValue.([]interface{})[0].(map[string]interface{})["nested_object"].([]interface{})[0].(map[string]interface{}) + // So(nestedObject["origin_port"], ShouldBeNil) + // So(nestedObject["protocol"], ShouldEqual, "tcp") + // So(nestedObject["password"], ShouldBeNil) + // So(nestedObject["password_array"], ShouldBeNil) + // + // firstNestedListItem := nestedObject["nested_list"].([]interface{})[0].(map[string]interface{}) + // So(firstNestedListItem["nested_list_property"], ShouldEqual, 123) + // So(firstNestedListItem["nested_list_property_password"], ShouldBeNil) + // }) + // }) + // + // Convey("When the local state is populated", func() { + // localStateValue := map[string]interface{}{ + // "id": propertyWithNestedObjectSchemaDefinition.Properties[0].Default, + // "nested_object": map[string]interface{}{ + // "origin_port": 1111, + // "protocol": "tcp", + // "password": "secret", + // "password_array": []interface{}{"secret1", "secret2"}, + // "nested_list": []interface{}{ + // map[string]interface{}{ + // "nested_list_property": 555, + // "nested_list_property_password": "local secret value", + // }, + // }, + // }, + // } + // resultValue, err := convertPayloadToLocalStateDataValue(propertyWithNestedObject, dataValue, localStateValue, true) + // + // Convey("Then the result returned should be the expected one", func() { + // So(err, ShouldBeNil) + // + // nestedObject := resultValue.([]interface{})[0].(map[string]interface{})["nested_object"].([]interface{})[0].(map[string]interface{}) + // So(nestedObject["origin_port"], ShouldEqual, 1111) + // So(nestedObject["protocol"], ShouldEqual, "tcp") + // So(nestedObject["password"], ShouldEqual, "secret") + // So(len(nestedObject["password_array"].([]interface{})), ShouldEqual, 2) + // + // passwordArray := nestedObject["password_array"].([]interface{}) + // So(passwordArray[0], ShouldEqual, "secret1") + // So(passwordArray[1], ShouldEqual, "secret2") + // + // So(len(nestedObject["nested_list"].([]interface{})), ShouldEqual, 1) + // nestedListItem := nestedObject["nested_list"].([]interface{})[0].(map[string]interface{}) + // So(nestedListItem["nested_list_property"], ShouldEqual, 123) + // So(nestedListItem["nested_list_property_password"], ShouldEqual, "local secret value") + // }) + // }) + //}) + Convey("When convertPayloadToLocalStateDataValue is called with set of complex objects with write-only properties", func() { + nestedSetItemSchemaDefinition := &SpecSchemaDefinition{ Properties: SpecSchemaDefinitionProperties{ - newIntSchemaDefinitionPropertyWithDefaults("example_int", "", true, false, nil), - newStringSchemaDefinitionPropertyWithDefaults("example_string", "", true, false, nil), - newBoolSchemaDefinitionPropertyWithDefaults("example_bool", "", true, true, nil), - newNumberSchemaDefinitionPropertyWithDefaults("example_float", "", true, false, nil), + newIntSchemaDefinitionPropertyWithDefaults("nested_set_property", "", true, false, nil), + setSchemaDefinitionPropertyWriteOnly(newIntSchemaDefinitionPropertyWithDefaults("nested_set_property_password", "", true, false, nil)), }, } - dataValue := map[string]interface{}{ - "example_int": 80, - "example_string": "http", - "example_bool": true, - "example_float": 10.45, - } - property := newObjectSchemaDefinitionPropertyWithDefaults("object_property", "", true, false, false, nil, objectSchemaDefinition) - resultValue, err := convertPayloadToLocalStateDataValue(property, dataValue, nil) - Convey("Then the error should be nil and the result value should be the list containing the object items all being string type (as terraform only supports maps of strings, hence values need to be stored as strings)", func() { - So(err, ShouldBeNil) - So(resultValue.([]interface{})[0], ShouldContainKey, "example_int") - So(resultValue.([]interface{})[0].(map[string]interface{})["example_int"], ShouldEqual, 80) - So(resultValue.([]interface{})[0].(map[string]interface{}), ShouldContainKey, "example_string") - So(resultValue.([]interface{})[0].(map[string]interface{})["example_string"], ShouldEqual, "http") - So(resultValue.([]interface{})[0].(map[string]interface{}), ShouldContainKey, "example_bool") - So(resultValue.([]interface{})[0].(map[string]interface{})["example_bool"], ShouldEqual, true) - So(resultValue.([]interface{})[0].(map[string]interface{}), ShouldContainKey, "example_float") - So(resultValue.([]interface{})[0].(map[string]interface{})["example_float"], ShouldEqual, 10.45) - }) - }) - - Convey("When convertPayloadToLocalStateDataValue is called with an object containing objects", func() { nestedObjectSchemaDefinition := &SpecSchemaDefinition{ Properties: SpecSchemaDefinitionProperties{ - newIntSchemaDefinitionPropertyWithDefaults("origin_port", "", true, false, 80), + setSchemaDefinitionPropertyWriteOnly(newIntSchemaDefinitionPropertyWithDefaults("origin_port", "", true, false, 80)), newStringSchemaDefinitionPropertyWithDefaults("protocol", "", true, false, "http"), + setSchemaDefinitionPropertyWriteOnly(newStringSchemaDefinitionPropertyWithDefaults("password", "", true, false, nil)), + setSchemaDefinitionPropertyWriteOnly(newListSchemaDefinitionPropertyWithDefaults("password_array", "", true, false, false, nil, TypeString, nil)), + newSetSchemaDefinitionPropertyWithDefaults("nested_set", "", true, false, false, nil, TypeObject, nestedSetItemSchemaDefinition), }, } - nestedObjectDefault := map[string]interface{}{ - "origin_port": nestedObjectSchemaDefinition.Properties[0].Default, - "protocol": nestedObjectSchemaDefinition.Properties[1].Default, - } - nestedObject := newObjectSchemaDefinitionPropertyWithDefaults("nested_object", "", true, false, false, nestedObjectDefault, nestedObjectSchemaDefinition) - propertyWithNestedObjectSchemaDefinition := &SpecSchemaDefinition{ - Properties: SpecSchemaDefinitionProperties{ - idProperty, - nestedObject, + // The below represents the JSON representation of the response payload received by the API + dataValue1 := map[string]interface{}{ + "origin_port": 12345, + "protocol": "tcp", + "nested_set": []interface{}{ + map[string]interface{}{ + "nested_set_property": 123, + "nested_set_property_password": "some changed value123", + }, + map[string]interface{}{ + "nested_set_property": 321, + "nested_set_property_password": "some changed value321", + }, }, } - // The below represents the JSON representation of the response payload received by the API - dataValue := map[string]interface{}{ - "id": propertyWithNestedObjectSchemaDefinition.Properties[0].Default, - "nested_object": propertyWithNestedObjectSchemaDefinition.Properties[1].Default, + dataValue2 := map[string]interface{}{ + "origin_port": 12345, + "protocol": "tcp", + "nested_set": []interface{}{ + map[string]interface{}{ + "nested_set_property": 1233, + "nested_set_property_password": "some changed value1233", + }, + map[string]interface{}{ + "nested_set_property": 3211, + "nested_set_property_password": "some changed value3211", + }, + }, } + dataValues := []interface{}{dataValue1, dataValue2} + //expectedPropertyWithNestedObjectName := "property_with_nested_object" + //propertyWithNestedObject := newObjectSchemaDefinitionPropertyWithDefaults(expectedPropertyWithNestedObjectName, "", true, false, false, dataValue1, propertyWithNestedObjectSchemaDefinition) + expectedPropertyWithSetWithNestedObjectName := "property_with_set_with_nested_object" + setPropertyWithNestedObject := newSetSchemaDefinitionPropertyWithDefaults(expectedPropertyWithSetWithNestedObjectName, "", true, false, false, nil, TypeObject, nestedObjectSchemaDefinition) + Convey("When the local state is empty", func() { + resultValue, err := convertPayloadToLocalStateDataValue(setPropertyWithNestedObject, dataValues, nil, true) - expectedPropertyWithNestedObjectName := "property_with_nested_object" - propertyWithNestedObject := newObjectSchemaDefinitionPropertyWithDefaults(expectedPropertyWithNestedObjectName, "", true, false, false, dataValue, propertyWithNestedObjectSchemaDefinition) - resultValue, err := convertPayloadToLocalStateDataValue(propertyWithNestedObject, dataValue, nil) - Convey("Then the result returned should be the expected one", func() { - So(err, ShouldBeNil) - - // The result value should be the list containing just one element (as per the nested struct workaround) - // Tag(NestedStructsWorkaround) - // Note: This is the workaround needed to support properties with nested structs. The current Terraform sdk version - // does not support this now, hence the suggestion from the Terraform maintainer was to use a list of map[string]interface{} - // with the list containing just one element. The below represents the internal representation of the terraform state - // for an object property that contains other objects - So(resultValue.([]interface{}), ShouldNotBeEmpty) - So(len(resultValue.([]interface{})), ShouldEqual, 1) + Convey("Then the result returned should be the expected one", func() { + So(err, ShouldBeNil) + So(resultValue, ShouldHaveSameTypeAs, &schema.Set{}) + nestedObject := resultValue.(*schema.Set) + for _, v := range nestedObject.List() { + nesteSet := v.(map[string]interface{})["nested_set"].(*schema.Set) + So(nesteSet, ShouldHaveSameTypeAs, &schema.Set{}) + So(nesteSet.Len(), ShouldEqual, 2) + } + //So(nestedObject["protocol"], ShouldEqual, "tcp") + //So(nestedObject["password"], ShouldBeNil) + //So(nestedObject["password_array"], ShouldBeNil) + // + //firstNestedListItem := nestedObject["nested_list"].([]interface{})[0].(map[string]interface{}) + //secondNestedListItem := nestedObject["nested_list"].([]interface{})[1].(map[string]interface{}) + //So(firstNestedListItem["nested_list_property"], ShouldEqual, 123) + //So(secondNestedListItem["nested_list_property"], ShouldEqual, 321) + ////So(firstNestedListItem["nested_list_property_password"], ShouldBeNil) + //So(len(nestedObject["nested_list"].([]interface{})), ShouldEqual, 2) + }) + }) - // AND the object should have the expected properties including the nested object - So(resultValue.([]interface{})[0], ShouldContainKey, propertyWithNestedObjectSchemaDefinition.Properties[0].Name) - So(resultValue.([]interface{})[0], ShouldContainKey, propertyWithNestedObjectSchemaDefinition.Properties[1].Name) + //Convey("When the local state is populated", func() { + // localStateValue := map[string]interface{}{ + // "id": propertyWithNestedObjectSchemaDefinition.Properties[0].Default, + // "nested_object": map[string]interface{}{ + // "origin_port": 1111, + // "protocol": "tcp", + // "password": "secret", + // "password_array": []interface{}{"secret1", "secret2"}, + // "nested_list": []interface{}{ + // map[string]interface{}{ + // "nested_list_property": 555, + // "nested_list_property_password": "local secret value", + // }, + // }, + // }, + // } + // resultValue, err := convertPayloadToLocalStateDataValue(propertyWithNestedObject, dataValues, localStateValue, true) + // + // Convey("Then the result returned should be the expected one", func() { + // So(err, ShouldBeNil) + // + // nestedObject := resultValue.([]interface{})[0].(map[string]interface{})["nested_object"].([]interface{})[0].(map[string]interface{}) + // So(nestedObject["origin_port"], ShouldEqual, 1111) + // So(nestedObject["protocol"], ShouldEqual, "tcp") + // So(nestedObject["password"], ShouldEqual, "secret") + // So(len(nestedObject["password_array"].([]interface{})), ShouldEqual, 2) + // + // passwordArray := nestedObject["password_array"].([]interface{}) + // So(passwordArray[0], ShouldEqual, "secret1") + // So(passwordArray[1], ShouldEqual, "secret2") + // + // So(len(nestedObject["nested_list"].([]interface{})), ShouldEqual, 1) + // nestedListItem := nestedObject["nested_list"].([]interface{})[0].(map[string]interface{}) + // So(nestedListItem["nested_list_property"], ShouldEqual, 123) + // So(nestedListItem["nested_list_property_password"], ShouldEqual, "local secret value") + // }) + //}) + }) + }) +} - // AND the object property with nested object should have the expected configuration - nestedObject := propertyWithNestedObjectSchemaDefinition.Properties[1] - So(resultValue.([]interface{})[0].(map[string]interface{})[nestedObject.Name].([]interface{})[0].(map[string]interface{}), ShouldContainKey, nestedObjectSchemaDefinition.Properties[0].Name) - So(resultValue.([]interface{})[0].(map[string]interface{})[nestedObject.Name].([]interface{})[0].(map[string]interface{})[nestedObjectSchemaDefinition.Properties[0].Name], ShouldEqual, nestedObjectSchemaDefinition.Properties[0].Default.(int)) - So(resultValue.([]interface{})[0].(map[string]interface{})[nestedObject.Name].([]interface{})[0].(map[string]interface{}), ShouldContainKey, nestedObjectSchemaDefinition.Properties[1].Name) - So(resultValue.([]interface{})[0].(map[string]interface{})[nestedObject.Name].([]interface{})[0].(map[string]interface{})[nestedObjectSchemaDefinition.Properties[1].Name], ShouldEqual, nestedObjectSchemaDefinition.Properties[1].Default) +func TestDeepConvertArrayToSet(t *testing.T) { + Convey("Given a resource data (state) loaded with couple properties", t, func() { + Convey("When DeepConvertArrayToSet is called with a array of primitive element", func() { + inputArray := []interface{}{"value2", "value1", "value4"} + property := newSetSchemaDefinitionPropertyWithDefaults("set_object_property", "", true, false, false, nil, TypeString, nil) + resultSet, err := deepConvertArrayToSet(property, inputArray) + Convey("Then the error should be nil and the result value should be the expected value with the right type array", func() { + So(err, ShouldBeNil) + So(resultSet, ShouldHaveSameTypeAs, &schema.Set{}) + So(resultSet.(*schema.Set).Len(), ShouldEqual, 3) + So(resultSet.(*schema.Set).Contains("value2"), ShouldBeTrue) + So(resultSet.(*schema.Set).Contains("value1"), ShouldBeTrue) + So(resultSet.(*schema.Set).Contains("value4"), ShouldBeTrue) + So(resultSet.(*schema.Set).Contains("va"), ShouldBeFalse) }) }) - - Convey("When convertPayloadToLocalStateDataValue is called with complex objects with write-only properties", func() { + Convey("When DeepConvertArrayToSet is called with a array of complex object", func() { + nestedSetItemSchemaDefinition := &SpecSchemaDefinition{ + Properties: SpecSchemaDefinitionProperties{ + newIntSchemaDefinitionPropertyWithDefaults("nested_set_property", "", true, false, nil), + setSchemaDefinitionPropertyWriteOnly(newIntSchemaDefinitionPropertyWithDefaults("nested_set_property_password", "", true, false, nil)), + }, + } nestedArrayItemSchemaDefinition := &SpecSchemaDefinition{ Properties: SpecSchemaDefinitionProperties{ - newIntSchemaDefinitionPropertyWithDefaults("nested_list_property", "", true, false, 456), - setSchemaDefinitionPropertyWriteOnly(newIntSchemaDefinitionPropertyWithDefaults("nested_list_property_password", "", true, false, nil)), + newIntSchemaDefinitionPropertyWithDefaults("nested_array_property", "", true, false, nil), + setSchemaDefinitionPropertyWriteOnly(newIntSchemaDefinitionPropertyWithDefaults("nested_array_property_password", "", true, false, nil)), }, } - nestedObjectSchemaDefinition := &SpecSchemaDefinition{ Properties: SpecSchemaDefinitionProperties{ setSchemaDefinitionPropertyWriteOnly(newIntSchemaDefinitionPropertyWithDefaults("origin_port", "", true, false, 80)), newStringSchemaDefinitionPropertyWithDefaults("protocol", "", true, false, "http"), setSchemaDefinitionPropertyWriteOnly(newStringSchemaDefinitionPropertyWithDefaults("password", "", true, false, nil)), setSchemaDefinitionPropertyWriteOnly(newListSchemaDefinitionPropertyWithDefaults("password_array", "", true, false, false, nil, TypeString, nil)), + newSetSchemaDefinitionPropertyWithDefaults("nested_set", "", true, false, false, nil, TypeObject, nestedSetItemSchemaDefinition), newListSchemaDefinitionPropertyWithDefaults("nested_list", "", true, false, false, nil, TypeObject, nestedArrayItemSchemaDefinition), }, } - nestedObject := newObjectSchemaDefinitionPropertyWithDefaults("nested_object", "", true, false, false, nil, nestedObjectSchemaDefinition) - propertyWithNestedObjectSchemaDefinition := &SpecSchemaDefinition{ - Properties: SpecSchemaDefinitionProperties{ - idProperty, - nestedObject, + //nestedObject := newObjectSchemaDefinitionPropertyWithDefaults("nested_object", "", true, false, false, nil, nestedObjectSchemaDefinition) + //propertyWithNestedObjectSchemaDefinition := &SpecSchemaDefinition{ + // Properties: SpecSchemaDefinitionProperties{ + // idProperty, + // nestedObject, + // }, + //} + object1 := map[string]interface{}{ + "origin_port": 12345, + "protocol": "tcp", + "nested_set": []interface{}{ + map[string]interface{}{ + "nested_set_property": 123, + "nested_set_property_password": "setpassword123", + }, + map[string]interface{}{ + "nested_set_property": 456, + "nested_set_property_password": "setpassword456", + }, + }, + "nested_list": []interface{}{ + map[string]interface{}{ + "nested_list_property": 123, + "nested_list_property_password": "listpassword123", + }, + map[string]interface{}{ + "nested_list_property": 456, + "nested_list_property_password": "listpassword456", + }, }, } - // The below represents the JSON representation of the response payload received by the API - dataValue := map[string]interface{}{ - "id": propertyWithNestedObjectSchemaDefinition.Properties[0].Default, - "nested_object": map[string]interface{}{ - "origin_port": 12345, - "protocol": "tcp", - "nested_list": []interface{}{ - map[string]interface{}{ - "nested_list_property": 123, - "nested_list_property_password": "some changed value", - }, + object2 := map[string]interface{}{ + "origin_port": 54321, + "protocol": "tcp", + "nested_set": []interface{}{ + map[string]interface{}{ + "nested_set_property": 789, + "nested_set_property_password": "setpassword789", + }, + map[string]interface{}{ + "nested_set_property": 987, + "nested_set_property_password": "setpassword987", + }, + }, + "nested_list": []interface{}{ + map[string]interface{}{ + "nested_list_property": 789, + "nested_list_property_password": "listpassword789", + }, + map[string]interface{}{ + "nested_list_property": 987, + "nested_list_property_password": "listpassword987", }, }, } - - expectedPropertyWithNestedObjectName := "property_with_nested_object" - propertyWithNestedObject := newObjectSchemaDefinitionPropertyWithDefaults(expectedPropertyWithNestedObjectName, "", true, false, false, dataValue, propertyWithNestedObjectSchemaDefinition) - - Convey("When the local state is empty", func() { - resultValue, err := convertPayloadToLocalStateDataValue(propertyWithNestedObject, dataValue, nil) - - Convey("Then the result returned should be the expected one", func() { - So(err, ShouldBeNil) - - nestedObject := resultValue.([]interface{})[0].(map[string]interface{})["nested_object"].([]interface{})[0].(map[string]interface{}) - So(nestedObject["origin_port"], ShouldBeNil) - So(nestedObject["protocol"], ShouldEqual, "tcp") - So(nestedObject["password"], ShouldBeNil) - So(nestedObject["password_array"], ShouldBeNil) - - firstNestedListItem := nestedObject["nested_list"].([]interface{})[0].(map[string]interface{}) - So(firstNestedListItem["nested_list_property"], ShouldEqual, 123) - So(firstNestedListItem["nested_list_property_password"], ShouldBeNil) - }) - }) - - Convey("When the local state is populated", func() { - localStateValue := map[string]interface{}{ - "id": propertyWithNestedObjectSchemaDefinition.Properties[0].Default, - "nested_object": map[string]interface{}{ - "origin_port": 1111, - "protocol": "tcp", - "password": "secret", - "password_array": []interface{}{"secret1", "secret2"}, - "nested_list": []interface{}{ + object3 := map[string]interface{}{ + "origin_port": 11111, + "protocol": "udp", + "nested_set": []interface{}{ + map[string]interface{}{ + "nested_set_property": 789, + "nested_set_property_password": "setpassword789", + }, + map[string]interface{}{ + "nested_set_property": 987, + "nested_set_property_password": "setpassword987", + }, + }, + "nested_list": []interface{}{ + map[string]interface{}{ + "nested_list_property": 789, + "nested_list_property_password": "listpassword789", + }, + map[string]interface{}{ + "nested_list_property": 987, + "nested_list_property_password": "listpassword987", + }, + }, + } + object1NestedSetElement1 := map[string]interface{}{ + "nested_set_property": 123, + "nested_set_property_password": "setpassword123", + } + object1NestedSetElement2 := map[string]interface{}{ + "nested_set_property": 456, + "nested_set_property_password": "setpassword456", + } + object2NestedSetElement1 := map[string]interface{}{ + "nested_set_property": 789, + "nested_set_property_password": "setpassword789", + } + object2NestedSetElement2 := map[string]interface{}{ + "nested_set_property": 987, + "nested_set_property_password": "setpassword987", + } + object1NestedListElement1 := map[string]interface{}{ + "nested_set_property": 123, + "nested_set_property_password": "listpassword123", + } + object1NestedListElement2 := map[string]interface{}{ + "nested_set_property": 456, + "nested_set_property_password": "listpassword456", + } + object2NestedListElement1 := map[string]interface{}{ + "nested_set_property": 789, + "nested_set_property_password": "listpassword789", + } + object2NestedListElement2 := map[string]interface{}{ + "nested_set_property": 987, + "nested_set_property_password": "listpassword987", + } + inputArray := []interface{}{object1, object2, object3} + //expectedPropertyWithNestedObjectName := "property_with_nested_object" + //propertyWithNestedObject := newObjectSchemaDefinitionPropertyWithDefaults(expectedPropertyWithNestedObjectName, "", true, false, false, nil, propertyWithNestedObjectSchemaDefinition) + expectedPropertyWithSetWithNestedObjectName := "property_with_set_with_nested_object" + setPropertyWithNestedObject := newSetSchemaDefinitionPropertyWithDefaults(expectedPropertyWithSetWithNestedObjectName, "", true, false, false, nil, TypeObject, nestedObjectSchemaDefinition) + resultSet, err := deepConvertArrayToSet(setPropertyWithNestedObject, inputArray) + //resultSet = resultSet.(*schema.Set) + Convey("Then the error should be nil and the result value should be the expected value with the right type array", func() { + So(err, ShouldBeNil) + So(resultSet.(*schema.Set).Len(), ShouldEqual, 3) + for _, v := range resultSet.(*schema.Set).List() { + obj, ok := v.(map[string]interface{}) + So(ok, ShouldBeTrue) + + nestedList, ok := obj["nested_list"] + So(ok, ShouldBeTrue) + So(nestedList, ShouldHaveSameTypeAs, []interface{}{}) + + nestedSet, ok := obj["nested_set"].(*schema.Set) + So(ok, ShouldBeTrue) + So(nestedSet, ShouldHaveSameTypeAs, &schema.Set{}) + So(nestedSet.Len(), ShouldEqual, 2) + + protocol, ok := obj["origin_port"].(int) + So(ok, ShouldBeTrue) + So(protocol == 12345 || protocol == 54321 || protocol == 11111, ShouldBeTrue) + if obj["origin_port"] == 12345 { + expected := []interface{}{ map[string]interface{}{ - "nested_list_property": 555, - "nested_list_property_password": "local secret value", + "nested_list_property": 123, + "nested_list_property_password": "listpassword123", }, - }, - }, + map[string]interface{}{ + "nested_list_property": 456, + "nested_list_property_password": "listpassword456", + }, + } + So(nestedList, ShouldResemble, expected) + + So(nestedSet.Contains(object1NestedSetElement1), ShouldBeTrue) + So(nestedSet.Contains(object1NestedSetElement2), ShouldBeTrue) + So(nestedSet.Contains(object2NestedSetElement1), ShouldBeFalse) + So(nestedSet.Contains(object2NestedSetElement2), ShouldBeFalse) + So(nestedSet.Contains(object1NestedListElement1), ShouldBeFalse) + So(nestedSet.Contains(object1NestedListElement2), ShouldBeFalse) + } else if obj["origin_port"] == 54321 || obj["origin_port"] == 11111 { + expected := []interface{}{ + map[string]interface{}{ + "nested_list_property": 789, + "nested_list_property_password": "listpassword789", + }, + map[string]interface{}{ + "nested_list_property": 987, + "nested_list_property_password": "listpassword987", + }, + } + So(nestedList, ShouldResemble, expected) + So(nestedSet.Contains(object2NestedSetElement1), ShouldBeTrue) + So(nestedSet.Contains(object2NestedSetElement2), ShouldBeTrue) + So(nestedSet.Contains(object1NestedSetElement1), ShouldBeFalse) + So(nestedSet.Contains(object1NestedSetElement2), ShouldBeFalse) + So(nestedSet.Contains(object2NestedListElement1), ShouldBeFalse) + So(nestedSet.Contains(object2NestedListElement2), ShouldBeFalse) + } else { + So(false, ShouldBeFalse) + } } - resultValue, err := convertPayloadToLocalStateDataValue(propertyWithNestedObject, dataValue, localStateValue) - - Convey("Then the result returned should be the expected one", func() { - So(err, ShouldBeNil) - - nestedObject := resultValue.([]interface{})[0].(map[string]interface{})["nested_object"].([]interface{})[0].(map[string]interface{}) - So(nestedObject["origin_port"], ShouldEqual, 1111) - So(nestedObject["protocol"], ShouldEqual, "tcp") - So(nestedObject["password"], ShouldEqual, "secret") - So(len(nestedObject["password_array"].([]interface{})), ShouldEqual, 2) - - passwordArray := nestedObject["password_array"].([]interface{}) - So(passwordArray[0], ShouldEqual, "secret1") - So(passwordArray[1], ShouldEqual, "secret2") - - So(len(nestedObject["nested_list"].([]interface{})), ShouldEqual, 1) - nestedListItem := nestedObject["nested_list"].([]interface{})[0].(map[string]interface{}) - So(nestedListItem["nested_list_property"], ShouldEqual, 123) - So(nestedListItem["nested_list_property_password"], ShouldEqual, "local secret value") - }) }) + }) }) } diff --git a/openapi/helperutils_test.go b/openapi/helperutils_test.go index 2992f20f5..56295f385 100644 --- a/openapi/helperutils_test.go +++ b/openapi/helperutils_test.go @@ -96,6 +96,10 @@ func newListSchemaDefinitionPropertyWithDefaults(name, preferredName string, req return newListSchemaDefinitionProperty(name, preferredName, required, readOnly, computed, false, false, false, false, false, defaultValue, itemsType, objectSpecSchemaDefinition) } +func newSetSchemaDefinitionPropertyWithDefaults(name, preferredName string, required, readOnly, computed bool, defaultValue interface{}, itemsType schemaDefinitionPropertyType, objectSpecSchemaDefinition *SpecSchemaDefinition) *SpecSchemaDefinitionProperty { + return newSetSchemaDefinitionProperty(name, preferredName, required, readOnly, computed, false, false, false, false, false, defaultValue, itemsType, objectSpecSchemaDefinition) +} + func newListSchemaDefinitionProperty(name, preferredName string, required, readOnly, forceNew, computed, sensitive, immutable, isIdentifier, isStatusIdentifier bool, defaultValue interface{}, itemsType schemaDefinitionPropertyType, objectSpecSchemaDefinition *SpecSchemaDefinition) *SpecSchemaDefinitionProperty { schemaDefProperty := newSchemaDefinitionProperty(name, preferredName, TypeList, required, readOnly, computed, forceNew, sensitive, immutable, isIdentifier, isStatusIdentifier, defaultValue) schemaDefProperty.ArrayItemsType = itemsType @@ -103,6 +107,13 @@ func newListSchemaDefinitionProperty(name, preferredName string, required, readO return schemaDefProperty } +func newSetSchemaDefinitionProperty(name, preferredName string, required, readOnly, forceNew, computed, sensitive, immutable, isIdentifier, isStatusIdentifier bool, defaultValue interface{}, itemsType schemaDefinitionPropertyType, objectSpecSchemaDefinition *SpecSchemaDefinition) *SpecSchemaDefinitionProperty { + schemaDefProperty := newSchemaDefinitionProperty(name, preferredName, TypeSet, required, readOnly, computed, forceNew, sensitive, immutable, isIdentifier, isStatusIdentifier, defaultValue) + schemaDefProperty.SetItemsType = itemsType + schemaDefProperty.SpecSchemaDefinition = objectSpecSchemaDefinition + return schemaDefProperty +} + func newSchemaDefinitionProperty(name, preferredName string, propertyType schemaDefinitionPropertyType, required, readOnly, computed, forceNew, sensitive, immutable, isIdentifier, isStatusIdentifier bool, defaultValue interface{}) *SpecSchemaDefinitionProperty { return &SpecSchemaDefinitionProperty{ Name: name, diff --git a/openapi/openapi_client.go b/openapi/openapi_client.go index 052f45068..63e3adbff 100644 --- a/openapi/openapi_client.go +++ b/openapi/openapi_client.go @@ -49,6 +49,7 @@ func (o *ProviderClient) Post(resource SpecResource, requestPayload interface{}, return nil, err } operation := resource.getResourceOperations().Post + log.Printf("[INFO] requestPayload: %s", requestPayload) return o.performRequest(httpPost, resourceURL, operation, requestPayload, responsePayload) } @@ -59,6 +60,7 @@ func (o *ProviderClient) Put(resource SpecResource, id string, requestPayload in return nil, err } operation := resource.getResourceOperations().Put + log.Printf("[INFO] requestPayload: %s", requestPayload) return o.performRequest(httpPut, resourceURL, operation, requestPayload, responsePayload) } diff --git a/openapi/openapi_spec_resource_schema_definition_property.go b/openapi/openapi_spec_resource_schema_definition_property.go index aa67d13d7..2c1e2d70a 100644 --- a/openapi/openapi_spec_resource_schema_definition_property.go +++ b/openapi/openapi_spec_resource_schema_definition_property.go @@ -1,10 +1,14 @@ package openapi import ( + "bytes" "fmt" "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "log" "reflect" + "sort" "github.com/dikhan/terraform-provider-openapi/v3/openapi/terraformutils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -26,6 +30,7 @@ const ( TypeList schemaDefinitionPropertyType = "list" // TypeObject defines a schema definition property of type object TypeObject schemaDefinitionPropertyType = "object" + TypeSet schemaDefinitionPropertyType = "set" ) const idDefaultPropertyName = "id" @@ -37,6 +42,7 @@ type SpecSchemaDefinitionProperty struct { PreferredName string Type schemaDefinitionPropertyType ArrayItemsType schemaDefinitionPropertyType + SetItemsType schemaDefinitionPropertyType Description string // IgnoreItemsOrder if set to true means that the array items order should be ignored @@ -106,6 +112,10 @@ func (s *SpecSchemaDefinitionProperty) isArrayProperty() bool { return s.Type == TypeList } +func (s *SpecSchemaDefinitionProperty) isSetProperty() bool { + return s.Type == TypeSet +} + func (s *SpecSchemaDefinitionProperty) shouldIgnoreOrder() bool { return s.Type == TypeList && s.IgnoreItemsOrder } @@ -114,6 +124,10 @@ func (s *SpecSchemaDefinitionProperty) isArrayOfObjectsProperty() bool { return s.Type == TypeList && s.ArrayItemsType == TypeObject } +func (s *SpecSchemaDefinitionProperty) isSetOfObjectsProperty() bool { + return s.Type == TypeSet && s.SetItemsType == TypeObject +} + func (s *SpecSchemaDefinitionProperty) isReadOnly() bool { return s.ReadOnly } @@ -132,10 +146,10 @@ func (s *SpecSchemaDefinitionProperty) shouldIgnoreArrayItemsOrder() bool { } // isComputed returns true if one of the following cases is met: -//- The property is optional (marked as required=false), in which case there few use cases: -// - readOnly properties (marked as readOnly=true): -// - optional-computed (marked as readOnly=false, computed=true): -// - with no default (default=nil) +// - The property is optional (marked as required=false), in which case there few use cases: +// - readOnly properties (marked as readOnly=true): +// - optional-computed (marked as readOnly=false, computed=true): +// - with no default (default=nil) func (s *SpecSchemaDefinitionProperty) isComputed() bool { return s.isOptional() && (s.isReadOnly() || s.IsOptionalComputed()) } @@ -169,6 +183,8 @@ func (s *SpecSchemaDefinitionProperty) terraformType() (schema.ValueType, error) return schema.TypeBool, nil case TypeObject, TypeList: return schema.TypeList, nil + case TypeSet: + return schema.TypeSet, nil } return schema.TypeInvalid, fmt.Errorf("non supported type %s", s.Type) } @@ -187,8 +203,22 @@ func (s *SpecSchemaDefinitionProperty) isTerraformListOfSimpleValues() (bool, *s return false, nil } +func (s *SpecSchemaDefinitionProperty) isTerraformSetOfSimpleValues() (bool, *schema.Schema) { + switch s.SetItemsType { + case TypeString: + return true, &schema.Schema{Type: schema.TypeString} + case TypeInt: + return true, &schema.Schema{Type: schema.TypeInt} + case TypeFloat: + return true, &schema.Schema{Type: schema.TypeFloat} + case TypeBool: + return true, &schema.Schema{Type: schema.TypeBool} + } + return false, nil +} + func (s *SpecSchemaDefinitionProperty) terraformObjectSchema() (*schema.Resource, error) { - if s.Type == TypeObject || (s.Type == TypeList && s.ArrayItemsType == TypeObject) { + if s.Type == TypeObject || (s.Type == TypeList && s.ArrayItemsType == TypeObject) || (s.Type == TypeSet && s.SetItemsType == TypeObject) { if s.SpecSchemaDefinition == nil { return nil, fmt.Errorf("missing spec schema definition for property '%s' of type '%s'", s.Name, s.Type) } @@ -259,6 +289,17 @@ func (s *SpecSchemaDefinitionProperty) terraformSchema() (*schema.Schema, error) } terraformSchema.Elem = objectSchema } + case TypeSet: + if isSetOfPrimitives, elemSchema := s.isTerraformSetOfSimpleValues(); isSetOfPrimitives { + terraformSchema.Elem = elemSchema + } else { + objectSchema, err := s.terraformObjectSchema() + if err != nil { + return nil, err + } + terraformSchema.Elem = objectSchema + terraformSchema.Set = hashExampleWithSchema(objectSchema) + } } // A computed property could be one of: @@ -282,7 +323,7 @@ func (s *SpecSchemaDefinitionProperty) terraformSchema() (*schema.Schema, error) } // ValidateFunc is not yet supported on lists or sets - if !s.isArrayProperty() && !s.isObjectProperty() { + if !s.isArrayProperty() && !s.isObjectProperty() && !s.isSetProperty() { terraformSchema.ValidateDiagFunc = s.validateDiagFunc() } @@ -294,11 +335,93 @@ func (s *SpecSchemaDefinitionProperty) terraformSchema() (*schema.Schema, error) if !s.isArrayProperty() { terraformSchema.Default = s.Default } + if !s.isSetProperty() { + terraformSchema.Default = s.Default + } } return terraformSchema, nil } +func hashExampleWithSchema(objectSchema *schema.Resource) schema.SchemaSetFunc { + return func(v interface{}) int { + // You can access the schema here... + //log.Printf("[INFO] schema: %s", schema.Schema) + //log.Printf("[INFO] set: %s", v) + valueMap := v.(map[string]interface{}) + objectSchema := objectSchema.Schema + filteredSchema := map[string]*schema.Schema{} + //filteredSchemaWrong := map[string]*schema.Schema{} + filterValueMap := make(map[string]interface{}) + for key, value := range objectSchema { + //fmt.Printf("Key: %s, Value: %v\n", key, value) + //value := value.(*SpecSchemaDefinitionProperty) + if value.Computed == false { + filteredSchema[key] = value + } + //fmt.Printf("Key: %s Type: %v Computed: %s Value: %s \n ", key, value.Computed, value.Type, valueMap[key]) + } + //for key, value := range valueMap { + // //fmt.Printf("Key: %s, Value: %v\n", key, value) + // if value != nil { + // filterValueMap[key] = value + // } + // //fmt.Printf("Key: %s Type: %v Computed: %s Value: %s \n ", key, value.Computed, value.Type, valueMap[key]) + //} + for key, _ := range filteredSchema { + //fmt.Printf("Key: %s, Value: %v\n", key, value) + filterValueMap[key] = valueMap[key] + //fmt.Printf("Key: %s Value %s", key, valueMap[key]) + //fmt.Printf("Key: %s Type: %v Computed: %s Value: %s \n ", key, value.Type, value.Computed, valueMap[key]) + } + //fmt.Printf("-------------------") + //for key, value := range filterValueMap { + // //fmt.Printf("Key: %s, Value: %v\n", key, value) + // filterValueMap[key] = valueMap[key] + // fmt.Printf("Key: %s Value %s", key, value) + //} + //for key, value := range filteredSchemaWrong { + // //fmt.Printf("Key: %s, Value: %v\n", key, value) + // fmt.Printf("Key: %s Type: %v Computed: %s Value: %s \n ", key, value.Type, value.Computed, valueMap[key]) + //} + //var buf bytes.Buffer + //m := v.(map[string]interface{}) + // buf.WriteString(fmt.Sprintf("%s-", m["attribute1"].(string))) + return hashObject(filterValueMap) + } +} +func hashObject(v interface{}) int { + var buffer bytes.Buffer + log.Printf("[INFO] hashInput: %s", v) + switch v := v.(type) { + case map[string]interface{}: + // Sort the keys so that the order is consistent + var keys []string + for k := range v { + keys = append(keys, k) + } + sort.Strings(keys) + + // Hash each key-value pair + for _, k := range keys { + buffer.WriteString(k) + buffer.WriteString(fmt.Sprintf("%v", hashComplexObject(v[k]))) + } + case []interface{}: + // Hash each element in the slice + for _, elem := range v { + buffer.WriteString(fmt.Sprintf("%v", hashComplexObject(elem))) + } + default: + // For primitive types, just write the value to the buffer + buffer.WriteString(fmt.Sprintf("%v", v)) + } + + // Compute and return the hash of the concatenated string + log.Printf("[INFO] hashOutput: %s", buffer.String()) + + return hashcode.String(buffer.String()) +} func (s *SpecSchemaDefinitionProperty) validateDiagFunc() schema.SchemaValidateDiagFunc { return func(v interface{}, p cty.Path) diag.Diagnostics { _, errs := s.validateFunc()(v, "") // it's not clear what would be the value of k with the new schema.SchemaValidateDiagFunc and whether it can be extracted from the cty.Path diff --git a/openapi/openapi_v2_resource.go b/openapi/openapi_v2_resource.go index caec42aeb..39edcbb04 100644 --- a/openapi/openapi_v2_resource.go +++ b/openapi/openapi_v2_resource.go @@ -57,6 +57,7 @@ const extTfFieldStatus = "x-terraform-field-status" const extTfID = "x-terraform-id" const extTfComputed = "x-terraform-computed" const extTfIgnoreOrder = "x-terraform-ignore-order" +const extTfSet = "x-terraform-set" const extIgnoreOrder = "x-ignore-order" const extTfWriteOnly = "x-terraform-write-only" @@ -435,6 +436,31 @@ func (o *SpecV2Resource) createSchemaDefinitionProperty(propertyName string, pro } log.Printf("[DEBUG] found array type property '%s' with items of type '%s'", propertyName, itemsType) + } else if isSet, itemsType, itemsSchema, err := o.isSetProperty(property); isSet || err != nil { + if err != nil { + return nil, fmt.Errorf("failed to process set type property '%s': %s", propertyName, err) + } + + // Edge case where the description of the property is set under the items property instead of the root level + // array_property: + // items: + // description: Groups allowed to manage this identity + // type: string + // type: array + if schemaDefinitionProperty.Description == "" { + if property.Items != nil && property.Items.Schema != nil { + schemaDefinitionProperty.Description = property.Items.Schema.Description + } + } + + schemaDefinitionProperty.SetItemsType = itemsType + schemaDefinitionProperty.SpecSchemaDefinition = itemsSchema // only diff than nil if type is object + + if o.isBoolExtensionEnabled(property.Extensions, extTfIgnoreOrder) || o.isBoolExtensionEnabled(property.Extensions, extIgnoreOrder) { + schemaDefinitionProperty.IgnoreItemsOrder = true + } + + log.Printf("[DEBUG] found set type property '%s' with items of type '%s'", propertyName, itemsType) } if preferredPropertyName, exists := property.Extensions.GetString(extTfFieldName); exists { @@ -547,8 +573,9 @@ func (o *SpecV2Resource) isOptionalComputedProperty(propertyName string, propert // by specifying the default attribute. Example: // // optional_computed_with_default: # optional property that the default value is known at runtime, hence service provider documents it -// type: "string" -// default: “some known default value” +// +// type: "string" +// default: “some known default value” func (o *SpecV2Resource) isOptionalComputedWithDefault(propertyName string, property spec.Schema) (bool, error) { if !property.ReadOnly && property.Default != nil { if o.isBoolExtensionEnabled(property.Extensions, extTfComputed) { @@ -563,8 +590,9 @@ func (o *SpecV2Resource) isOptionalComputedWithDefault(propertyName string, prop // This covers the use case where a property is not marked as readOnly but still is optional value that can come from the user or if not provided will be computed by the API. Example // // optional_computed: # optional property that the default value is NOT known at runtime -// type: "string" -// x-terraform-computed: true +// +// type: "string" +// x-terraform-computed: true func (o *SpecV2Resource) isOptionalComputed(propertyName string, property spec.Schema) (bool, error) { if o.isBoolExtensionEnabled(property.Extensions, extTfComputed) { if property.ReadOnly { @@ -582,6 +610,10 @@ func (o *SpecV2Resource) isArrayItemPrimitiveType(propertyType schemaDefinitionP return propertyType == TypeString || propertyType == TypeInt || propertyType == TypeFloat || propertyType == TypeBool } +func (o *SpecV2Resource) isSetItemPrimitiveType(propertyType schemaDefinitionPropertyType) bool { + return propertyType == TypeString || propertyType == TypeInt || propertyType == TypeFloat || propertyType == TypeBool +} + func (o *SpecV2Resource) validateArrayItems(property spec.Schema) (schemaDefinitionPropertyType, error) { if property.Items == nil || property.Items.Schema == nil { return "", fmt.Errorf("array property is missing items schema definition") @@ -599,9 +631,28 @@ func (o *SpecV2Resource) validateArrayItems(property spec.Schema) (schemaDefinit return itemsType, nil } +func (o *SpecV2Resource) validateSetItems(property spec.Schema) (schemaDefinitionPropertyType, error) { + if property.Items == nil || property.Items.Schema == nil { + return "", fmt.Errorf("set property is missing items schema definition") + } + if o.isSetTypeProperty(*property.Items.Schema) { + return "", fmt.Errorf("set property can not have items of type 'set'") + } + itemsType, err := o.getPropertyType(*property.Items.Schema) + if err != nil { + return "", err + } + if !o.isSetItemPrimitiveType(itemsType) && !(itemsType == TypeObject) { + return "", fmt.Errorf("set item type '%s' not supported", itemsType) + } + return itemsType, nil +} + func (o *SpecV2Resource) getPropertyType(property spec.Schema) (schemaDefinitionPropertyType, error) { if o.isArrayTypeProperty(property) { return TypeList, nil + } else if o.isSetTypeProperty(property) { + return TypeSet, nil } else if isObject, _, err := o.isObjectProperty(property); isObject || err != nil { return TypeObject, err } else if property.Type.Contains("string") { @@ -659,8 +710,48 @@ func (o *SpecV2Resource) isArrayProperty(property spec.Schema) (bool, schemaDefi return false, "", nil, nil } +func (o *SpecV2Resource) isSetProperty(property spec.Schema) (bool, schemaDefinitionPropertyType, *SpecSchemaDefinition, error) { + if o.isSetTypeProperty(property) { + itemsType, err := o.validateSetItems(property) + if err != nil { + return false, "", nil, err + } + if o.isSetItemPrimitiveType(itemsType) { + return true, itemsType, nil, nil + } + // This is the case where items must be object + if isObject, schemaDefinition, err := o.isObjectProperty(*property.Items.Schema); isObject || err != nil { + if err != nil { + return true, itemsType, nil, err + } + objectSchemaDefinition, err := o.getSchemaDefinition(schemaDefinition) + if err != nil { + return true, itemsType, nil, err + } + return true, itemsType, objectSchemaDefinition, nil + } + } + return false, "", nil, nil +} + func (o *SpecV2Resource) isArrayTypeProperty(property spec.Schema) bool { - return o.isOfType(property, "array") + if o.isOfType(property, "array") { + if !o.isBoolExtensionEnabled(property.Extensions, extTfSet) { + log.Printf("[INFO] ofTypeArrayHaha") + return true + } + } + return false +} + +func (o *SpecV2Resource) isSetTypeProperty(property spec.Schema) bool { + if o.isOfType(property, "array") { + if o.isBoolExtensionEnabled(property.Extensions, extTfSet) { + log.Printf("[INFO] ofTypeSetHaha") + return true + } + } + return false } func (o *SpecV2Resource) isObjectTypeProperty(property spec.Schema) bool { diff --git a/openapi/openapi_v2_resource_test.go b/openapi/openapi_v2_resource_test.go index 23e58f25b..2614b30a0 100644 --- a/openapi/openapi_v2_resource_test.go +++ b/openapi/openapi_v2_resource_test.go @@ -2865,6 +2865,42 @@ func TestGetPropertyType(t *testing.T) { So(itemsPropType, ShouldEqual, TypeList) }) }) + Convey("When getPropertyType method is called with a property of type array and not x-terraform-ignore-order or x-ignore-order", func() { + expectedIgnoreOrder := false + property := spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: spec.StringOrArray{"array"}, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + extTfIgnoreOrder: expectedIgnoreOrder, + }, + }, + } + itemsPropType, err := r.getPropertyType(property) + Convey("Then the type of the items should match the expected set", func() { + So(err, ShouldBeNil) + So(itemsPropType, ShouldEqual, TypeList) + }) + }) + Convey("When getPropertyType method is called with a property of type array and x-terraform-ignore-order or x-ignore-order", func() { + expectedIgnoreOrder := true + property := spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: spec.StringOrArray{"array"}, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + extTfIgnoreOrder: expectedIgnoreOrder, + }, + }, + } + itemsPropType, err := r.getPropertyType(property) + Convey("Then the type of the items should match the expected set", func() { + So(err, ShouldBeNil) + So(itemsPropType, ShouldEqual, TypeSet) + }) + }) Convey("When getPropertyType method is called with a property of type object", func() { property := spec.Schema{ @@ -3177,6 +3213,43 @@ func TestResourceIsArrayProperty(t *testing.T) { So(exists, ShouldBeTrue) }) }) + Convey("When isArrayProperty is called with an array property type with terraform-ignore-order set", func() { + propertySchema := spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: spec.StringOrArray{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: spec.StringOrArray{"object"}, + Properties: map[string]spec.Schema{ + "prop1": { + SchemaProps: spec.SchemaProps{ + Type: spec.StringOrArray{"string"}, + }, + }, + "prop2": { + SchemaProps: spec.SchemaProps{ + Type: spec.StringOrArray{"integer"}, + }, + }, + }, + }, + }, + }, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + extTfIgnoreOrder: true, + }, + }, + } + isArray, _, objectItemSchema, err := r.isArrayProperty(propertySchema) + Convey("Then the result returned should be the expected one", func() { + So(err, ShouldBeNil) + So(isArray, ShouldBeFalse) + So(objectItemSchema, ShouldBeNil) + }) + }) Convey("When isArrayProperty is called with a NON array property type", func() { propertySchema := spec.Schema{ SchemaProps: spec.SchemaProps{ diff --git a/openapi/provider_factory.go b/openapi/provider_factory.go index ae348ebaa..2ed8a091d 100644 --- a/openapi/provider_factory.go +++ b/openapi/provider_factory.go @@ -207,9 +207,9 @@ func (p providerFactory) createTerraformProviderDataSourceMap() (map[string]*sch } // createTerraformProviderResourceMapAndDataSourceInstanceMap is responsible for building the following: -// - a map containing the resources that are terraform compatible -// - a map containing the data sources from the resources that are terraform compatible. This data sources enable data -// source configuration on the resource instance GET operation. +// - a map containing the resources that are terraform compatible +// - a map containing the data sources from the resources that are terraform compatible. This data sources enable data +// source configuration on the resource instance GET operation. func (p providerFactory) createTerraformProviderResourceMapAndDataSourceInstanceMap() (resourceMap, dataSourceInstanceMap map[string]*schema.Resource, err error) { resourceMap = map[string]*schema.Resource{} dataSourceInstanceMap = map[string]*schema.Resource{} @@ -281,6 +281,7 @@ func (p providerFactory) configureProvider(openAPIBackendConfiguration SpecBacke } return openAPIClient, nil } + } // GetTelemetryHandler returns a handler containing validated telemetry providers diff --git a/openapi/resource_factory.go b/openapi/resource_factory.go index 5cdad9b4a..f9bf59ae3 100644 --- a/openapi/resource_factory.go +++ b/openapi/resource_factory.go @@ -128,6 +128,9 @@ func (r resourceFactory) create(data *schema.ResourceData, i interface{}) error return updateStateWithPayloadData(r.openAPIResource, responsePayload, data) } +// dc.length=5 +// dc.length=6 +// remote state, terraform.state, terraform configuraion func (r resourceFactory) readWithOptions(data *schema.ResourceData, i interface{}, handleNotFoundErr bool) error { openAPIClient := i.(ClientOpenAPI) @@ -551,6 +554,7 @@ func (r resourceFactory) populatePayload(input map[string]interface{}, property return nil } dataValueKind := reflect.TypeOf(dataValue).Kind() + log.Printf("[INFO] dataValueKind %s", dataValueKind) switch dataValueKind { case reflect.Map: objectInput := map[string]interface{}{} @@ -596,6 +600,37 @@ func (r resourceFactory) populatePayload(input map[string]interface{}, property input[property.Name] = arrayInput } } + case reflect.Ptr: + if isSetOfPrimitives, _ := property.isTerraformSetOfSimpleValues(); isSetOfPrimitives { + input[property.Name] = dataValue.(*schema.Set).List() + } else { + // This is the work around put in place to have support for complex objects. In this case, because the + // state representation of nested objects is an array, we need to make sure we don't end up constructing an + // array but rather just a json object + if property.shouldUseLegacyTerraformSDKBlockApproachForComplexObjects() { + arrayValue := dataValue.(*schema.Set).List() + if len(arrayValue) != 1 { + return fmt.Errorf("something is really wrong here...an object property with nested objects should have exactly one elem in the terraform state list") + } + if err := r.populatePayload(input, property, arrayValue[0]); err != nil { + return err + } + } else { + arrayInput := []interface{}{} + arrayValue := dataValue.(*schema.Set).List() + for _, arrayItem := range arrayValue { + objectInput := map[string]interface{}{} + if err := r.populatePayload(objectInput, property, arrayItem); err != nil { + return err + } + // Only assign the value of the object, otherwise a dup key will be assigned which will cause problems. Example + // [propertyName: listeners; propertyValue: [map[options:[] origin_ingress_port:80 protocol:http shield_ingress_port:80]]] + // Here we just want to assign as value: map[options:[] origin_ingress_port:80 protocol:http shield_ingress_port:80] + arrayInput = append(arrayInput, objectInput[property.Name]) + } + input[property.Name] = arrayInput + } + } case reflect.String: input[property.Name] = dataValue.(string) case reflect.Int: @@ -605,7 +640,7 @@ func (r resourceFactory) populatePayload(input map[string]interface{}, property case reflect.Bool: input[property.Name] = dataValue.(bool) default: - return fmt.Errorf("'%s' type not supported", property.Type) + return fmt.Errorf("'%s' type not supported in factory", property.Type) } return nil }