diff --git a/pkg/iac-providers/terraform/commons/local-references.go b/pkg/iac-providers/terraform/commons/local-references.go index 1c006c984..b28583a04 100644 --- a/pkg/iac-providers/terraform/commons/local-references.go +++ b/pkg/iac-providers/terraform/commons/local-references.go @@ -58,7 +58,7 @@ func getLocalName(localRef string) (string, string) { } // ResolveLocalRef returns the local value as configured in IaC config in module -func (r *RefResolver) ResolveLocalRef(localRef string) interface{} { +func (r *RefResolver) ResolveLocalRef(localRef, callerRef string) interface{} { // get local name from localRef localName, localExpr := getLocalName(localRef) @@ -89,8 +89,11 @@ func (r *RefResolver) ResolveLocalRef(localRef string) interface{} { if reflect.TypeOf(val).Kind() == reflect.String { valStr := val.(string) resolvedVal := strings.Replace(localRef, localExpr, valStr, 1) - zap.S().Debugf("resolved str local value ref: '%v', value: '%v'", localRef, resolvedVal) - return r.ResolveStrRef(resolvedVal) + if callerRef != "" && strings.Contains(resolvedVal, callerRef) { + zap.S().Debugf("resolved str local value ref: '%v', value: '%v'", localRef, resolvedVal) + return resolvedVal + } + return r.ResolveStrRef(resolvedVal, localRef) } // return extracted value diff --git a/pkg/iac-providers/terraform/commons/lookup-references.go b/pkg/iac-providers/terraform/commons/lookup-references.go index 66d1f9d2f..671807836 100644 --- a/pkg/iac-providers/terraform/commons/lookup-references.go +++ b/pkg/iac-providers/terraform/commons/lookup-references.go @@ -56,13 +56,13 @@ func getLookupName(lookupRef string) (string, string, string) { } // ResolveLookupRef returns the lookup value as configured in IaC config in module -func (r *RefResolver) ResolveLookupRef(lookupRef string) interface{} { +func (r *RefResolver) ResolveLookupRef(lookupRef, callerRef string) interface{} { // get lookup name from lookupRef table, key, _ := getLookupName(lookupRef) // resolve key, if it is a reference - resolvedKey := r.ResolveStrRef(key) + resolvedKey := r.ResolveStrRef(key, callerRef) // check if key is still an unresolved reference if reflect.TypeOf(resolvedKey).Kind() == reflect.String && isRef(resolvedKey.(string)) { @@ -71,7 +71,7 @@ func (r *RefResolver) ResolveLookupRef(lookupRef string) interface{} { } // resolve table, if it is a ref - lookup := r.ResolveStrRef(table) + lookup := r.ResolveStrRef(table, callerRef) // check if lookup is a map if reflect.TypeOf(lookup).String() != "map[string]interface {}" { diff --git a/pkg/iac-providers/terraform/commons/module-references.go b/pkg/iac-providers/terraform/commons/module-references.go index 3c519140d..3bacf3d74 100644 --- a/pkg/iac-providers/terraform/commons/module-references.go +++ b/pkg/iac-providers/terraform/commons/module-references.go @@ -59,7 +59,7 @@ func getModuleVarName(moduleRef string) (string, string, string) { } // ResolveModuleRef tries to resolve cross module references -func (r *RefResolver) ResolveModuleRef(moduleRef string, +func (r *RefResolver) ResolveModuleRef(moduleRef, callerRef string, children map[string]*hclConfigs.Config) interface{} { // get module and variable name @@ -108,8 +108,11 @@ func (r *RefResolver) ResolveModuleRef(moduleRef string, 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) + if callerRef != "" && strings.Contains(resolvedVal, callerRef) { + zap.S().Debugf("resolved str variable ref: '%v', value: '%v'", moduleRef, resolvedVal) + return resolvedVal + } + return r.ResolveStrRef(resolvedVal, moduleRef) } return val } diff --git a/pkg/iac-providers/terraform/commons/references.go b/pkg/iac-providers/terraform/commons/references.go index 3ed0ad7d3..2ddc7fcf2 100644 --- a/pkg/iac-providers/terraform/commons/references.go +++ b/pkg/iac-providers/terraform/commons/references.go @@ -67,7 +67,7 @@ func (r *RefResolver) ResolveRefs(config jsonObj) jsonObj { // case 1: config value is a string; in resource config, refs // are of the type string - config[k] = r.ResolveStrRef(v.(string)) + config[k] = r.ResolveStrRef(v.(string), "") case vType == "tfv12.jsonObj" && vKind == reflect.Map: @@ -86,7 +86,7 @@ func (r *RefResolver) ResolveRefs(config jsonObj) jsonObj { // references if len(sConfig) > 0 && reflect.TypeOf(sConfig[0]).Kind() == reflect.String { for i, c := range sConfig { - sConfig[i] = r.ResolveStrRef(c.(string)) + sConfig[i] = r.ResolveStrRef(c.(string), "") } config[k] = sConfig } @@ -115,23 +115,23 @@ func (r *RefResolver) ResolveRefs(config jsonObj) jsonObj { // ResolveStrRef tries to resolve a string reference. Reference can be a // variable "${var.foo}", cross module variable "${module.foo.bar}", local // value "${local.foo}" -func (r *RefResolver) ResolveStrRef(ref string) interface{} { +func (r *RefResolver) ResolveStrRef(ref, callerRef string) interface{} { switch { case isModuleRef(ref): // resolve cross module references - return r.ResolveModuleRef(ref, r.Config.Children) + return r.ResolveModuleRef(ref, callerRef, r.Config.Children) case isLocalRef(ref): // resolve local value references - return r.ResolveLocalRef(ref) + return r.ResolveLocalRef(ref, callerRef) case isLookupRef(ref): // resolve lookup references - return r.ResolveLookupRef(ref) + return r.ResolveLookupRef(ref, callerRef) case isVarRef(ref): @@ -144,7 +144,7 @@ func (r *RefResolver) ResolveStrRef(ref string) interface{} { */ // 1. resolve variables initialized in parent module call - val := r.ResolveVarRefFromParentModuleCall(ref) + val := r.ResolveVarRefFromParentModuleCall(ref, callerRef) if reflect.TypeOf(val).Kind() == reflect.String { @@ -163,17 +163,17 @@ func (r *RefResolver) ResolveStrRef(ref string) interface{} { case isVarRef(valStr): // resolve variable reference - return r.ResolveVarRef(valStr) + return r.ResolveVarRef(valStr, ref) case isModuleRef(valStr): // resolve cross module reference in parent module - return r.ResolveModuleRef(valStr, r.Config.Parent.Children) + return r.ResolveModuleRef(valStr, ref, r.Config.Parent.Children) case isRef(valStr): // some other reference - return r.ResolveStrRef(valStr) + return r.ResolveStrRef(valStr, ref) } } diff --git a/pkg/iac-providers/terraform/commons/variable-references.go b/pkg/iac-providers/terraform/commons/variable-references.go index c77023631..4d4b066f5 100644 --- a/pkg/iac-providers/terraform/commons/variable-references.go +++ b/pkg/iac-providers/terraform/commons/variable-references.go @@ -59,7 +59,7 @@ func getVarName(varRef string) (string, string) { } // ResolveVarRef returns the variable value as configured in IaC config in module -func (r *RefResolver) ResolveVarRef(varRef string) interface{} { +func (r *RefResolver) ResolveVarRef(varRef, callerRef string) interface{} { // get variable name from varRef varName, varExpr := getVarName(varRef) @@ -94,8 +94,11 @@ func (r *RefResolver) ResolveVarRef(varRef string) interface{} { 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) + if callerRef != "" && strings.Contains(resolvedVal, callerRef) { + zap.S().Debugf("resolved str variable ref: '%v', value: '%v'", varRef, string(resolvedVal)) + return resolvedVal + } + return r.ResolveStrRef(resolvedVal, varRef) } return val } @@ -104,7 +107,7 @@ func (r *RefResolver) ResolveVarRef(varRef string) interface{} { // ModuleCall from parent module. The resolved value can be an absolute value // (string, int, bool etc.) or it can also be another reference, which may // need further resolution -func (r *RefResolver) ResolveVarRefFromParentModuleCall(varRef string) interface{} { +func (r *RefResolver) ResolveVarRefFromParentModuleCall(varRef, callerRef string) interface{} { zap.S().Debugf("resolving variable ref %q in parent module call", varRef) @@ -152,8 +155,11 @@ func (r *RefResolver) ResolveVarRefFromParentModuleCall(varRef string) interface zap.S().Debugf("resolved str variable ref refers to self: '%v'", varRef) return resolvedVal } - zap.S().Debugf("resolved str variable ref: '%v', value: '%v'", varRef, string(resolvedVal)) - return r.ResolveStrRef(resolvedVal) + if callerRef != "" && strings.Contains(resolvedVal, callerRef) { + zap.S().Debugf("resolved str variable ref: '%v', value: '%v'", varRef, string(resolvedVal)) + return resolvedVal + } + return r.ResolveStrRef(resolvedVal, varRef) } // return extracted value diff --git a/pkg/iac-providers/terraform/v14/load-dir_test.go b/pkg/iac-providers/terraform/v14/load-dir_test.go index 5b9b81eec..1584d824a 100644 --- a/pkg/iac-providers/terraform/v14/load-dir_test.go +++ b/pkg/iac-providers/terraform/v14/load-dir_test.go @@ -121,8 +121,15 @@ func TestLoadIacDir(t *testing.T) { }, { name: "recursive loop while resolving variables", - tfConfigDir: filepath.Join(testDataDir, "recursive-loop"), - tfJSONFile: filepath.Join(tfJSONDir, "recursive-loop.json"), + tfConfigDir: filepath.Join(testDataDir, "recursive-loop-variables"), + tfJSONFile: filepath.Join(tfJSONDir, "recursive-loop-variables.json"), + tfv14: TfV14{}, + wantErr: nil, + }, + { + name: "recursive loop while resolving locals", + tfConfigDir: filepath.Join(testDataDir, "recursive-loop-locals"), + tfJSONFile: filepath.Join(tfJSONDir, "recursive-loop-locals.json"), tfv14: TfV14{}, wantErr: nil, }, diff --git a/pkg/iac-providers/terraform/v14/testdata/recursive-loop-locals/bug.tf b/pkg/iac-providers/terraform/v14/testdata/recursive-loop-locals/bug.tf new file mode 100644 index 000000000..6b51f2f6b --- /dev/null +++ b/pkg/iac-providers/terraform/v14/testdata/recursive-loop-locals/bug.tf @@ -0,0 +1,9 @@ +locals { + foo = "bar" +} + +module "dummy" { + source = "./dummy" + bar = local.foo + foo = "bar" +} \ No newline at end of file diff --git a/pkg/iac-providers/terraform/v14/testdata/recursive-loop-locals/dummy/main.tf b/pkg/iac-providers/terraform/v14/testdata/recursive-loop-locals/dummy/main.tf new file mode 100644 index 000000000..160b13cce --- /dev/null +++ b/pkg/iac-providers/terraform/v14/testdata/recursive-loop-locals/dummy/main.tf @@ -0,0 +1,15 @@ +variable bar { + type = string +} + +variable foo { + type = string +} + +locals { + foo = lower(var.bar != null ? var.bar : var.foo) +} + +resource "aws_iam_user" "lb" { + name = local.foo +} \ No newline at end of file diff --git a/pkg/iac-providers/terraform/v14/testdata/recursive-loop/bug.tf b/pkg/iac-providers/terraform/v14/testdata/recursive-loop-variables/bug.tf similarity index 100% rename from pkg/iac-providers/terraform/v14/testdata/recursive-loop/bug.tf rename to pkg/iac-providers/terraform/v14/testdata/recursive-loop-variables/bug.tf diff --git a/pkg/iac-providers/terraform/v14/testdata/recursive-loop/dummy/main.tf b/pkg/iac-providers/terraform/v14/testdata/recursive-loop-variables/dummy/main.tf similarity index 100% rename from pkg/iac-providers/terraform/v14/testdata/recursive-loop/dummy/main.tf rename to pkg/iac-providers/terraform/v14/testdata/recursive-loop-variables/dummy/main.tf diff --git a/pkg/iac-providers/terraform/v14/testdata/tfjson/recursive-loop-locals.json b/pkg/iac-providers/terraform/v14/testdata/tfjson/recursive-loop-locals.json new file mode 100644 index 000000000..3e539d6c3 --- /dev/null +++ b/pkg/iac-providers/terraform/v14/testdata/tfjson/recursive-loop-locals.json @@ -0,0 +1,15 @@ +{ + "aws_iam_user": [ + { + "id": "aws_iam_user.lb", + "name": "lb", + "source": "dummy/main.tf", + "line": 13, + "type": "aws_iam_user", + "config": { + "name": "${lower(${local.foo} != null ? var.bar : var.foo)}" + }, + "skip_rules": null + } + ] +} \ No newline at end of file diff --git a/pkg/iac-providers/terraform/v14/testdata/tfjson/recursive-loop.json b/pkg/iac-providers/terraform/v14/testdata/tfjson/recursive-loop-variables.json similarity index 100% rename from pkg/iac-providers/terraform/v14/testdata/tfjson/recursive-loop.json rename to pkg/iac-providers/terraform/v14/testdata/tfjson/recursive-loop-variables.json