cai2hcl/common/utils.go (100 lines of code) (raw):
package common
import (
"encoding/json"
"fmt"
"strings"
hashicorpcty "github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport"
"github.com/zclconf/go-cty/cty"
ctyjson "github.com/zclconf/go-cty/cty/json"
)
// ParseFieldValue extracts named part from resource url.
func ParseFieldValue(url string, name string) string {
fragments := strings.Split(url, "/")
for ix, item := range fragments {
if item == name && ix+1 < len(fragments) {
return fragments[ix+1]
}
}
return ""
}
// DecodeJSON decodes the map object into the target struct.
func DecodeJSON(data map[string]interface{}, v interface{}) error {
b, err := json.Marshal(data)
if err != nil {
return err
}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
return nil
}
// MapToCtyValWithSchema normalizes and converts resource from untyped map format to TF JSON.
//
// Normalization is a post-processing of the output map, which does the following:
// * Converts unmarshallable "schema.Set" to marshallable counterpart.
// * Strips out properties, which are not part ofthe resource TF schema.
func MapToCtyValWithSchema(m map[string]interface{}, s map[string]*schema.Schema) (cty.Value, error) {
m = normalizeFlattenedObj(m, s).(map[string]interface{})
b, err := json.Marshal(&m)
if err != nil {
return cty.NilVal, fmt.Errorf("error marshaling map as JSON: %v", err)
}
ty, err := hashicorpCtyTypeToZclconfCtyType(schema.InternalMap(s).CoreConfigSchema().ImpliedType())
if err != nil {
return cty.NilVal, fmt.Errorf("error casting type: %v", err)
}
ret, err := ctyjson.Unmarshal(b, ty)
if err != nil {
return cty.NilVal, fmt.Errorf("error unmarshaling JSON as cty.Value: %v", err)
}
return ret, nil
}
func hashicorpCtyTypeToZclconfCtyType(t hashicorpcty.Type) (cty.Type, error) {
b, err := json.Marshal(t)
if err != nil {
return cty.NilType, err
}
var ret cty.Type
if err := json.Unmarshal(b, &ret); err != nil {
return cty.NilType, err
}
return ret, nil
}
func NewConfig() *transport_tpg.Config {
return &transport_tpg.Config{}
}
// normalizeFlattenedObj traverses the output map recursively, removes fields which are
// not a part of TF schema and converts unmarshallable "schema.Set" objects to arrays.
func normalizeFlattenedObj(obj interface{}, schemaPerProp map[string]*schema.Schema) interface{} {
obj = convertToMarshallableObj(obj)
if schemaPerProp == nil {
// Schema for leaf nodes was already checked.
return obj
}
switch obj.(type) {
case map[string]interface{}:
objMap := obj.(map[string]interface{})
objMapNew := map[string]interface{}{}
for property, propertySchema := range schemaPerProp {
propertyValue := objMap[property]
switch propertySchema.Elem.(type) {
case *schema.Resource:
objMapNew[property] = normalizeFlattenedObj(propertyValue, propertySchema.Elem.(*schema.Resource).Schema)
case *schema.ValueType:
default:
objMapNew[property] = normalizeFlattenedObj(propertyValue, nil)
}
}
return objMapNew
case []interface{}:
arr := obj.([]interface{})
arrNew := make([]interface{}, len(arr))
for i := range arr {
arrNew[i] = normalizeFlattenedObj(arr[i], schemaPerProp)
}
return arrNew
default:
return obj
}
}
func convertToMarshallableObj(node interface{}) interface{} {
switch node.(type) {
case *schema.Set:
nodeSet := node.(*schema.Set)
return nodeSet.List()
default:
return node
}
}