terraform/internal/lang/globalref/analyzer.go

69 lines
2.9 KiB
Go
Raw Normal View History

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/configs"
"github.com/hashicorp/terraform/internal/providers"
)
// Analyzer is the main component of this package, serving as a container for
// various state that the analysis algorithms depend on either for their core
// functionality or for producing results more quickly.
//
// Global reference analysis is currently intended only for "best effort"
// use-cases related to giving hints to the user or tailoring UI output.
// Avoid using it for anything that would cause changes to the analyzer being
// considered a breaking change under the v1 compatibility promises, because
// we expect to continue to refine and evolve these rules over time in ways
// that may cause us to detect either more or fewer references than today.
// Typically we will conservatively return more references than would be
// necessary dynamically, but that isn't guaranteed for all situations.
//
// In particular, we currently typically don't distinguish between multiple
// instances of the same module, and so we overgeneralize references from
// one instance of a module as references from the same location in all
// instances of that module. We may make this more precise in future, which
// would then remove various detected references from the analysis results.
//
// Each Analyzer works with a particular configs.Config object which it assumes
// represents the root module of a configuration. Config objects are typically
// immutable by convention anyway, but it's particularly important not to
// modify a configuration while it's attached to a live Analyzer, because
// the Analyzer contains caches derived from data in the configuration tree.
type Analyzer struct {
cfg *configs.Config
providerSchemas map[addrs.Provider]*providers.Schemas
}
// NewAnalyzer constructs a new analyzer bound to the given configuration and
// provider schemas.
//
// The given object must represent a root module, or this function will panic.
//
// The given provider schemas must cover at least all of the providers used
// in the given configuration. If not then analysis results will be silently
// incomplete for any decision that requires checking schema.
func NewAnalyzer(cfg *configs.Config, providerSchemas map[addrs.Provider]*providers.Schemas) *Analyzer {
if !cfg.Path.IsRoot() {
panic(fmt.Sprintf("constructing an Analyzer with non-root module %s", cfg.Path))
}
ret := &Analyzer{
cfg: cfg,
providerSchemas: providerSchemas,
}
return ret
}
// ModuleConfig retrieves a module configuration from the configuration the
// analyzer belongs to, or nil if there is no module with the given address.
func (a *Analyzer) ModuleConfig(addr addrs.ModuleInstance) *configs.Module {
modCfg := a.cfg.DescendentForInstance(addr)
if modCfg == nil {
return nil
}
return modCfg.Module
}