Skip to content

Commit

Permalink
resolve complex types and unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
patilpankaj212 committed Apr 13, 2021
1 parent 5abf9af commit 9bc3c4c
Show file tree
Hide file tree
Showing 8 changed files with 551 additions and 51 deletions.
51 changes: 37 additions & 14 deletions pkg/iac-providers/terraform/commons/cty-converters.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@ import (
"github.com/zclconf/go-cty/cty/gocty"
)

// list of available cty to golang type converters
// list of available primitive cty to golang type converters
var (
ctyConverterFuncs = []func(cty.Value) (interface{}, error){ctyToStr, ctyToInt, ctyToFloat, ctyToBool, ctyToSlice, ctyToMap}
ctyNativeConverterFuncs = []func(cty.Value) (interface{}, error){ctyToStr, ctyToInt, ctyToFloat, ctyToBool}
)

Expand Down Expand Up @@ -66,14 +65,12 @@ func ctyToSlice(ctyVal cty.Value) (interface{}, error) {

if ctyVal.Type().IsListType() || ctyVal.Type().IsTupleType() || ctyVal.Type().IsSetType() {
for _, v := range ctyVal.AsValueSlice() {
for _, converter := range ctyNativeConverterFuncs {
resolved, err := converter(v)
if err == nil {
val = append(val, resolved)
break
}
nativeVal, err := convertCtyToGoNative(v)
if err != nil {
allErrs = errors.Wrap(allErrs, err.Error())
continue
}
val = append(val, nativeVal)
}
if allErrs != nil {
return nil, allErrs
Expand Down Expand Up @@ -107,14 +104,12 @@ func ctyToMap(ctyVal cty.Value) (interface{}, error) {
// golang value
for k, v := range ctyValMap {
// convert cty.Value to native golang type based on cty.Type
for _, converter := range ctyNativeConverterFuncs {
resolved, err := converter(v)
if err == nil {
val[k] = resolved
break
}
nativeVal, err := convertCtyToGoNative(v)
if err != nil {
allErrs = errors.Wrap(allErrs, err.Error())
continue
}
val[k] = nativeVal
}
if allErrs != nil {
return nil, allErrs
Expand All @@ -123,3 +118,31 @@ func ctyToMap(ctyVal cty.Value) (interface{}, error) {
// hopefully successful!
return val, nil
}

// convertCtyToGoNative converts a cty.Value to its go native type
func convertCtyToGoNative(ctyVal cty.Value) (interface{}, error) {
if ctyVal.Type().IsPrimitiveType() {
return convertPrimitiveType(ctyVal)
} else {
return convertComplexType(ctyVal)
}
}

// convertPrimitiveType converts a primitive cty.Value to its go native type
func convertPrimitiveType(ctyVal cty.Value) (interface{}, error) {
for _, converter := range ctyNativeConverterFuncs {
if val, err := converter(ctyVal); err == nil {
return val, err
}
}
return nil, fmt.Errorf("ctyVal could not be resolved to native go type")
}

// convertPrimitiveType converts a complex cty.Value (list, tuple, set, map, object) to its go native type
func convertComplexType(ctyVal cty.Value) (interface{}, error) {
if ctyVal.Type().IsListType() || ctyVal.Type().IsTupleType() || ctyVal.Type().IsSetType() {
return ctyToSlice(ctyVal)
} else {
return ctyToMap(ctyVal)
}
}
203 changes: 203 additions & 0 deletions pkg/iac-providers/terraform/commons/cty-converters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -503,3 +503,206 @@ func TestCtyToMap(t *testing.T) {
})
}
}

func TestConvertPrimitiveType(t *testing.T) {
intCtyVal := cty.NumberIntVal(-10)
floatCtyVal := cty.NumberFloatVal(3.141)
unsignedIntCtyVal := cty.NumberUIntVal(44)
stringCtyVal := cty.StringVal("Peter Parker")
boolCtyVal := cty.BoolVal(true)
nonPrimitiveCtyVal := cty.ListVal([]cty.Value{cty.StringVal("test")})

type args struct {
ctyVal cty.Value
}
tests := []struct {
name string
args args
want interface{}
wantErr bool
}{
{
name: "signed int input",
args: args{
ctyVal: intCtyVal,
},
want: -10,
},
{
name: "float input",
args: args{
ctyVal: floatCtyVal,
},
want: 3.141,
},
{
name: "unsigned int input",
args: args{
ctyVal: unsignedIntCtyVal,
},
want: 44,
},
{
name: "string input",
args: args{
ctyVal: stringCtyVal,
},
want: "Peter Parker",
},
{
name: "boolean input",
args: args{
ctyVal: boolCtyVal,
},
want: true,
},
{
name: "non primitive input",
args: args{
ctyVal: nonPrimitiveCtyVal,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := convertPrimitiveType(tt.args.ctyVal)
if (err != nil) != tt.wantErr {
t.Errorf("convertPrimitiveType() got error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("convertPrimitiveType() got = %v, want %v", got, tt.want)
}
})
}
}

func TestConvertComplexType(t *testing.T) {

nonComplexCtyVal := cty.NumberIntVal(5)
complexListCtyVal := cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"application": cty.StringVal("app1"),
"port": cty.NumberIntVal(9010),
}),
cty.ObjectVal(map[string]cty.Value{
"application": cty.StringVal("app2"),
"port": cty.NumberIntVal(8000),
}),
})
complexMapCtyVal := cty.MapVal(map[string]cty.Value{
"first": cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("Thor"),
"ID": cty.NumberIntVal(1),
}),
"second": cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("Spiderman"),
"ID": cty.NumberIntVal(2),
}),
})

type args struct {
ctyVal cty.Value
}
tests := []struct {
name string
args args
want interface{}
wantErr bool
}{
{
name: "non complex input",
args: args{
ctyVal: nonComplexCtyVal,
},
wantErr: true,
},
{
name: "list of objects input",
args: args{
ctyVal: complexListCtyVal,
},
want: []interface{}{
map[string]interface{}{
"application": "app1",
"port": 9010,
},
map[string]interface{}{
"application": "app2",
"port": 8000,
},
},
},
{
name: "map of objects input",
args: args{
ctyVal: complexMapCtyVal,
},
want: map[string]interface{}{
"first": map[string]interface{}{
"name": "Thor",
"ID": 1,
},
"second": map[string]interface{}{
"name": "Spiderman",
"ID": 2,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := convertComplexType(tt.args.ctyVal)
if (err != nil) != tt.wantErr {
t.Errorf("convertComplexType() got error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("convertComplexType() got = %v, want %v", got, tt.want)
}
})
}
}

func TestConvertCtyToGoNative(t *testing.T) {
primitiveCtyVal := cty.StringVal("primitive")
complexCtyVal := cty.ListVal([]cty.Value{cty.BoolVal(false)})

type args struct {
ctyVal cty.Value
}
tests := []struct {
name string
args args
want interface{}
wantErr bool
}{
{
name: "primitive cty input",
args: args{
ctyVal: primitiveCtyVal,
},
want: "primitive",
},
{
name: "complex cty input",
args: args{
ctyVal: complexCtyVal,
},
want: []interface{}{false},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := convertCtyToGoNative(tt.args.ctyVal)
if (err != nil) != tt.wantErr {
t.Errorf("convertCtyToGoNative() got error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("convertCtyToGoNative() want = %v, want %v", got, tt.want)
}
})
}
}
33 changes: 17 additions & 16 deletions pkg/iac-providers/terraform/commons/module-references.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,22 +93,23 @@ func (r *RefResolver) ResolveModuleRef(moduleRef string,

// default value is of cty.Value type, convert it to native golang type
// based on cty.Type, determine golang type
for _, converter := range ctyConverterFuncs {
if val, err := converter(hclVar.Default); err == nil {
zap.S().Debugf("resolved module variable ref: '%v', value: '%v'", moduleRef, val)

// replace the variable reference string with actual value
if reflect.TypeOf(val).Kind() == reflect.String {
valStr := val.(string)
resolvedVal := strings.Replace(moduleRef, moduleExpr, valStr, 1)
zap.S().Debugf("resolved str module value ref: '%v', value: '%v'", moduleRef, resolvedVal)
return r.ResolveStrRef(resolvedVal)
}
zap.S().Debugf("resolved module value ref: '%v', value: '%v'", moduleRef, val)
return val
val, err := convertCtyToGoNative(hclVar.Default)
if err != nil {
zap.S().Debugf(err.Error())
zap.S().Debugf("failed to convert cty.Value '%v' to golang native value", hclVar.Default.GoString())
return moduleRef
}
zap.S().Debugf("resolved variable ref '%v', value: '%v'", moduleRef, val)

if reflect.TypeOf(val).Kind() == reflect.String {
valStr := val.(string)
resolvedVal := strings.Replace(moduleRef, moduleExpr, valStr, 1)
if moduleRef == resolvedVal {
zap.S().Debugf("resolved str variable ref refers to self: '%v'", moduleRef)
return moduleRef
}
zap.S().Debugf("resolved str variable ref: '%v', value: '%v'", moduleRef, resolvedVal)
return r.ResolveStrRef(resolvedVal)
}
zap.S().Debugf("failed to convert cty.Value '%v' to golang native value", hclVar.Default.GoString())

return moduleRef
return val
}
38 changes: 17 additions & 21 deletions pkg/iac-providers/terraform/commons/variable-references.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,29 +79,25 @@ func (r *RefResolver) ResolveVarRef(varRef string) interface{} {

// default value is of cty.Value type, convert it to native golang type
// based on cty.Type, determine golang type
for _, converter := range ctyConverterFuncs {
if val, err := converter(hclVar.Default); err == nil {
zap.S().Debugf("resolved variable ref '%v', value: '%v'", varRef, val)

// replace the variable reference string with actual value
if reflect.TypeOf(val).Kind() == reflect.String {
valStr := val.(string)
resolvedVal := strings.Replace(varRef, varExpr, valStr, 1)
if varRef == resolvedVal {
zap.S().Debugf("resolved str variable ref refers to self: '%v'", varRef)
return varRef
}
zap.S().Debugf("resolved str variable ref: '%v', value: '%v'", varRef, resolvedVal)
return r.ResolveStrRef(resolvedVal)
}
zap.S().Debugf("resolved variable ref: '%v', value: '%v'", varRef, val)
return val
}
val, err := convertCtyToGoNative(hclVar.Default)
if err != nil {
zap.S().Debugf(err.Error())
zap.S().Debugf("failed to convert cty.Value '%v' to golang native value", hclVar.Default.GoString())
return varRef
}
zap.S().Debugf("failed to convert cty.Value '%v' to golang native value", hclVar.Default.GoString())
zap.S().Debugf("resolved variable ref '%v', value: '%v'", varRef, val)

// return original reference if cty conversion attempts fail
return varRef
if reflect.TypeOf(val).Kind() == reflect.String {
valStr := val.(string)
resolvedVal := strings.Replace(varRef, varExpr, valStr, 1)
if varRef == resolvedVal {
zap.S().Debugf("resolved str variable ref refers to self: '%v'", varRef)
return varRef
}
zap.S().Debugf("resolved str variable ref: '%v', value: '%v'", varRef, resolvedVal)
return r.ResolveStrRef(resolvedVal)
}
return val
}

// ResolveVarRefFromParentModuleCall returns the variable value as configured in
Expand Down
7 changes: 7 additions & 0 deletions pkg/iac-providers/terraform/v14/load-dir_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,13 @@ func TestLoadIacDir(t *testing.T) {
tfv14: TfV14{},
wantErr: nil,
},
{
name: "complex variables",
tfConfigDir: filepath.Join(testDataDir, "complex-variables"),
tfJSONFile: filepath.Join(tfJSONDir, "complex-variables.json"),
tfv14: TfV14{},
wantErr: nil,
},
}

for _, tt := range table2 {
Expand Down
Loading

0 comments on commit 9bc3c4c

Please sign in to comment.