lang/globalref: Global reference analysis utilities
Our existing functionality for dealing with references generally only has
to concern itself with one level of references at a time, and only within
one module, because we use it to draw a dependency graph which then ends
up reflecting the broader context.
However, there are some situations where it's handy to be able to ask
questions about the indirect contributions to a particular expression in
the configuration, particularly for additional hints in the user interface
where we're just providing some extra context rather than changing
behavior.
This new "globalref" package therefore aims to be the home for algorithms
for use-cases like this. It introduces its own special "Reference" type
that wraps addrs.Reference to annotate it also with the usually-implied
context about where the references would be evaluated.
With that building block we can therefore ask questions whose answers
might involve discussing references in multiple packages at once, such as
"which resources directly or indirectly contribute to this expression?",
including indirect hops through input variables or output values which
would therefore change the evaluation context.
The current implementations of this are around mapping references onto the
static configuration expressions that they refer to, which is a pretty
broad and conservative approach that unfortunately therefore loses
accuracy when confronted with complex expressions that might take dynamic
actions on the contents of an object. My hunch is that this'll be good
enough to get some initial small use-cases solved, though there's plenty
room for improvement in accuracy.
It's somewhat ironic that this sort of "what is this value built from?"
question is the use-case I had in mind when I designed the "marks" feature
in cty, yet we've ended up putting it to an unexpected but still valid
use in Terraform for sensitivity analysis and our currently handling of
that isn't really tight enough to permit other concurrent uses of marks
for other use-cases. I expect we can address that later and so maybe we'll
try for a more accurate version of these analyses at a later date, but my
hunch is that this'll be good enough for us to still get some good use out
of it in the near future, particular related to helping understand where
unknown values came from and in tailoring our refresh results in plan
output to deemphasize detected changes that couldn't possibly have
contributed to the proposed plan.
2021-06-09 21:11:44 +02:00
|
|
|
package globalref
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
|
|
"github.com/hashicorp/terraform/internal/lang"
|
|
|
|
)
|
|
|
|
|
|
|
|
// ReferencesFromOutputValue returns all of the direct references from the
|
|
|
|
// value expression of the given output value. It doesn't include any indirect
|
|
|
|
// references.
|
|
|
|
func (a *Analyzer) ReferencesFromOutputValue(addr addrs.AbsOutputValue) []Reference {
|
|
|
|
mc := a.ModuleConfig(addr.Module)
|
|
|
|
if mc == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
oc := mc.Outputs[addr.OutputValue.Name]
|
|
|
|
if oc == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
refs, _ := lang.ReferencesInExpr(oc.Expr)
|
|
|
|
return absoluteRefs(addr.Module, refs)
|
|
|
|
}
|
|
|
|
|
2022-02-03 16:50:39 +01:00
|
|
|
// ReferencesFromResourceInstance returns all of the direct references from the
|
|
|
|
// definition of the resource instance at the given address. It doesn't include
|
|
|
|
// any indirect references.
|
lang/globalref: Global reference analysis utilities
Our existing functionality for dealing with references generally only has
to concern itself with one level of references at a time, and only within
one module, because we use it to draw a dependency graph which then ends
up reflecting the broader context.
However, there are some situations where it's handy to be able to ask
questions about the indirect contributions to a particular expression in
the configuration, particularly for additional hints in the user interface
where we're just providing some extra context rather than changing
behavior.
This new "globalref" package therefore aims to be the home for algorithms
for use-cases like this. It introduces its own special "Reference" type
that wraps addrs.Reference to annotate it also with the usually-implied
context about where the references would be evaluated.
With that building block we can therefore ask questions whose answers
might involve discussing references in multiple packages at once, such as
"which resources directly or indirectly contribute to this expression?",
including indirect hops through input variables or output values which
would therefore change the evaluation context.
The current implementations of this are around mapping references onto the
static configuration expressions that they refer to, which is a pretty
broad and conservative approach that unfortunately therefore loses
accuracy when confronted with complex expressions that might take dynamic
actions on the contents of an object. My hunch is that this'll be good
enough to get some initial small use-cases solved, though there's plenty
room for improvement in accuracy.
It's somewhat ironic that this sort of "what is this value built from?"
question is the use-case I had in mind when I designed the "marks" feature
in cty, yet we've ended up putting it to an unexpected but still valid
use in Terraform for sensitivity analysis and our currently handling of
that isn't really tight enough to permit other concurrent uses of marks
for other use-cases. I expect we can address that later and so maybe we'll
try for a more accurate version of these analyses at a later date, but my
hunch is that this'll be good enough for us to still get some good use out
of it in the near future, particular related to helping understand where
unknown values came from and in tailoring our refresh results in plan
output to deemphasize detected changes that couldn't possibly have
contributed to the proposed plan.
2021-06-09 21:11:44 +02:00
|
|
|
//
|
|
|
|
// The result doesn't directly include references from a "count" or "for_each"
|
|
|
|
// expression belonging to the associated resource, but it will include any
|
|
|
|
// references to count.index, each.key, or each.value that appear in the
|
|
|
|
// expressions which you can then, if you wish, resolve indirectly using
|
|
|
|
// Analyzer.MetaReferences. Alternatively, you can use
|
|
|
|
// Analyzer.ReferencesFromResourceRepetition to get that same result directly.
|
|
|
|
func (a *Analyzer) ReferencesFromResourceInstance(addr addrs.AbsResourceInstance) []Reference {
|
|
|
|
// Using MetaReferences for this is kinda overkill, since
|
|
|
|
// lang.ReferencesInBlock would be sufficient really, but
|
|
|
|
// this ensures we keep consistent in how we build the
|
|
|
|
// resulting absolute references and otherwise aside from
|
|
|
|
// some extra overhead this call boils down to a call to
|
|
|
|
// lang.ReferencesInBlock anyway.
|
|
|
|
fakeRef := Reference{
|
|
|
|
ContainerAddr: addr.Module,
|
|
|
|
LocalRef: &addrs.Reference{
|
|
|
|
Subject: addr.Resource,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return a.MetaReferences(fakeRef)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReferencesFromResourceRepetition returns the references from the given
|
|
|
|
// resource's for_each or count expression, or an empty set if the resource
|
|
|
|
// doesn't use repetition.
|
|
|
|
//
|
|
|
|
// This is a special-case sort of helper for use in situations where an
|
|
|
|
// expression might refer to count.index, each.key, or each.value, and thus
|
|
|
|
// we say that it depends indirectly on the repetition expression.
|
|
|
|
func (a *Analyzer) ReferencesFromResourceRepetition(addr addrs.AbsResource) []Reference {
|
|
|
|
modCfg := a.ModuleConfig(addr.Module)
|
|
|
|
if modCfg == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
rc := modCfg.ResourceByAddr(addr.Resource)
|
|
|
|
if rc == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// We're assuming here that resources can either have count or for_each,
|
|
|
|
// but never both, because that's a requirement enforced by the language
|
|
|
|
// decoder. But we'll assert it just to make sure we catch it if that
|
|
|
|
// changes for some reason.
|
|
|
|
if rc.ForEach != nil && rc.Count != nil {
|
|
|
|
panic(fmt.Sprintf("%s has both for_each and count", addr))
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case rc.ForEach != nil:
|
|
|
|
refs, _ := lang.ReferencesInExpr(rc.ForEach)
|
|
|
|
return absoluteRefs(addr.Module, refs)
|
|
|
|
case rc.Count != nil:
|
|
|
|
refs, _ := lang.ReferencesInExpr(rc.Count)
|
|
|
|
return absoluteRefs(addr.Module, refs)
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|