Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for evaluating static module outputs #2076

Open
wata727 opened this issue Jun 29, 2024 · 0 comments
Open

Add support for evaluating static module outputs #2076

wata727 opened this issue Jun 29, 2024 · 0 comments

Comments

@wata727
Copy link
Member

wata727 commented Jun 29, 2024

Introduction

TFLint cannot statically evaluate dynamic values ​​like aws_instance.test.id, so these values ​​are ignored. Similarly, module outputs (e.g. module.aws_instance.id) are also marked as dynamic values.

resource "aws_lb_target_group_attachment" "test" {
  target_id = module.aws_instance.id # => ignored
}

However, module outputs are not always dynamic values. Modules that are used to share variables across multiple modules may have outputs that are statically determinable.

resource "aws_instance" "test" {
  tags = module.shared.tags # => This is a static value, but it cannot be evaluated by TFLint.
}

Proposal

Add support for evaluating static module outputs.

Currently, all module.* named values ​​resolve to unknown values.

vals["module"] = cty.UnknownVal(cty.DynamicPseudoType)

If call_module_type is other than none and the child module's sources are accessible, then the output of the child module that corresponds to the address reference will be evaluated when generating the evaluation context. This might be similar to evaluating local values.

func (d *evaluationData) GetLocalValue(addr addrs.LocalValue, rng hcl.Range) (cty.Value, hcl.Diagnostics) {
var diags hcl.Diagnostics
// First we'll make sure the requested value is declared in configuration,
// so we can produce a nice message if not.
moduleConfig := d.Evaluator.Config.DescendentForInstance(d.ModulePath)
if moduleConfig == nil {
// should never happen, since we can't be evaluating in a module
// that wasn't mentioned in configuration.
panic(fmt.Sprintf("local value read from %s, which has no configuration", d.ModulePath))
}
config := moduleConfig.Module.Locals[addr.Name]
if config == nil {
var suggestions []string
for k := range moduleConfig.Module.Locals {
suggestions = append(suggestions, k)
}
suggestion := nameSuggestion(addr.Name, suggestions)
if suggestion != "" {
suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
}
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: `Reference to undeclared local value`,
Detail: fmt.Sprintf(`A local value with the name %q has not been declared.%s`, addr.Name, suggestion),
Subject: rng.Ptr(),
})
return cty.DynamicVal, diags
}
// Build a call stack for circular reference detection only when getting a local value.
if diags := d.Evaluator.CallStack.Push(addrs.Reference{Subject: addr, SourceRange: rng}); diags.HasErrors() {
return cty.UnknownVal(cty.DynamicPseudoType), diags
}
val, diags := d.Evaluator.EvaluateExpr(config.Expr, cty.DynamicPseudoType)
d.Evaluator.CallStack.Pop()
return val, diags
}

If the value cannot be evaluated at any point, for example because the source of the grandchild module is inaccessible, an unknown value will be returned as before.

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

1 participant