Merge pull request #30486 from hashicorp/jbardin/drift

Only show external changes which contributed to the plan
This commit is contained in:
James Bardin 2022-03-18 14:19:46 -04:00 committed by GitHub
commit fef66f9a60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 2493 additions and 270 deletions

View File

@ -2,10 +2,12 @@ package addrs
import (
"fmt"
"strings"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
)
// Reference describes a reference to an address with source location
@ -16,6 +18,43 @@ type Reference struct {
Remaining hcl.Traversal
}
// DisplayString returns a string that approximates the subject and remaining
// traversal of the reciever in a way that resembles the Terraform language
// syntax that could've produced it.
//
// It's not guaranteed to actually be a valid Terraform language expression,
// since the intended use here is primarily for UI messages such as
// diagnostics.
func (r *Reference) DisplayString() string {
if len(r.Remaining) == 0 {
// Easy case: we can just return the subject's string.
return r.Subject.String()
}
var ret strings.Builder
ret.WriteString(r.Subject.String())
for _, step := range r.Remaining {
switch tStep := step.(type) {
case hcl.TraverseRoot:
ret.WriteString(tStep.Name)
case hcl.TraverseAttr:
ret.WriteByte('.')
ret.WriteString(tStep.Name)
case hcl.TraverseIndex:
ret.WriteByte('[')
switch tStep.Key.Type() {
case cty.String:
ret.WriteString(fmt.Sprintf("%q", tStep.Key.AsString()))
case cty.Number:
bf := tStep.Key.AsBigFloat()
ret.WriteString(bf.Text('g', 10))
}
ret.WriteByte(']')
}
}
return ret.String()
}
// ParseRef attempts to extract a referencable address from the prefix of the
// given traversal, which must be an absolute traversal or this function
// will panic.

View File

@ -45,7 +45,7 @@ const (
// If "color" is non-nil, it will be used to color the result. Otherwise,
// no color codes will be included.
func ResourceChange(
change *plans.ResourceInstanceChangeSrc,
change *plans.ResourceInstanceChange,
schema *configschema.Block,
color *colorstring.Colorize,
language DiffLanguage,
@ -187,24 +187,7 @@ func ResourceChange(
// structures.
path := make(cty.Path, 0, 3)
changeV, err := change.Decode(schema.ImpliedType())
if err != nil {
// Should never happen in here, since we've already been through
// loads of layers of encode/decode of the planned changes before now.
panic(fmt.Sprintf("failed to decode plan for %s while rendering diff: %s", addr, err))
}
// We currently have an opt-out that permits the legacy SDK to return values
// that defy our usual conventions around handling of nesting blocks. To
// avoid the rendering code from needing to handle all of these, we'll
// normalize first.
// (Ideally we'd do this as part of the SDK opt-out implementation in core,
// but we've added it here for now to reduce risk of unexpected impacts
// on other code in core.)
changeV.Change.Before = objchange.NormalizeObjectFromLegacySDK(changeV.Change.Before, schema)
changeV.Change.After = objchange.NormalizeObjectFromLegacySDK(changeV.Change.After, schema)
result := p.writeBlockBodyDiff(schema, changeV.Before, changeV.After, 6, path)
result := p.writeBlockBodyDiff(schema, change.Before, change.After, 6, path)
if result.bodyWritten {
buf.WriteString("\n")
buf.WriteString(strings.Repeat(" ", 4))

View File

@ -4857,10 +4857,6 @@ func runTestCases(t *testing.T, testCases map[string]testCase) {
case !beforeVal.IsKnown():
beforeVal = cty.UnknownVal(ty) // allow mistyped unknowns
}
before, err := plans.NewDynamicValue(beforeVal, ty)
if err != nil {
t.Fatal(err)
}
afterVal := tc.After
switch { // Some fixups to make the test cases a little easier to write
@ -4869,10 +4865,6 @@ func runTestCases(t *testing.T, testCases map[string]testCase) {
case !afterVal.IsKnown():
afterVal = cty.UnknownVal(ty) // allow mistyped unknowns
}
after, err := plans.NewDynamicValue(afterVal, ty)
if err != nil {
t.Fatal(err)
}
addr := addrs.Resource{
Mode: tc.Mode,
@ -4887,7 +4879,7 @@ func runTestCases(t *testing.T, testCases map[string]testCase) {
prevRunAddr = addr
}
change := &plans.ResourceInstanceChangeSrc{
change := &plans.ResourceInstanceChange{
Addr: addr,
PrevRunAddr: prevRunAddr,
DeposedKey: tc.DeposedKey,
@ -4895,12 +4887,10 @@ func runTestCases(t *testing.T, testCases map[string]testCase) {
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
ChangeSrc: plans.ChangeSrc{
Action: tc.Action,
Before: before,
After: after,
BeforeValMarks: tc.BeforeValMarks,
AfterValMarks: tc.AfterValMarks,
Change: plans.Change{
Action: tc.Action,
Before: beforeVal.MarkWithPaths(tc.BeforeValMarks),
After: afterVal.MarkWithPaths(tc.AfterValMarks),
},
ActionReason: tc.ActionReason,
RequiredReplace: tc.RequiredReplace,

View File

@ -33,11 +33,12 @@ type plan struct {
PlannedValues stateValues `json:"planned_values,omitempty"`
// ResourceDrift and ResourceChanges are sorted in a user-friendly order
// that is undefined at this time, but consistent.
ResourceDrift []resourceChange `json:"resource_drift,omitempty"`
ResourceChanges []resourceChange `json:"resource_changes,omitempty"`
OutputChanges map[string]change `json:"output_changes,omitempty"`
PriorState json.RawMessage `json:"prior_state,omitempty"`
Config json.RawMessage `json:"configuration,omitempty"`
ResourceDrift []resourceChange `json:"resource_drift,omitempty"`
ResourceChanges []resourceChange `json:"resource_changes,omitempty"`
OutputChanges map[string]change `json:"output_changes,omitempty"`
PriorState json.RawMessage `json:"prior_state,omitempty"`
Config json.RawMessage `json:"configuration,omitempty"`
RelevantAttributes []resourceAttr `json:"relevant_attributes,omitempty"`
}
func newPlan() *plan {
@ -46,6 +47,13 @@ func newPlan() *plan {
}
}
// resourceAttr contains the address and attribute of an external for the
// RelevantAttributes in the plan.
type resourceAttr struct {
Resource string `json:"resource"`
Attr json.RawMessage `json:"attribute"`
}
// Change is the representation of a proposed change for an object.
type change struct {
// Actions are the actions that will be taken on the object selected by the
@ -151,6 +159,10 @@ func Marshal(
}
}
if err := output.marshalRelevantAttrs(p); err != nil {
return nil, fmt.Errorf("error marshaling relevant attributes for external changes: %s", err)
}
// output.ResourceChanges
if p.Changes != nil {
output.ResourceChanges, err = output.marshalResourceChanges(p.Changes.Resources, schemas)
@ -482,6 +494,19 @@ func (p *plan) marshalPlannedValues(changes *plans.Changes, schemas *terraform.S
return nil
}
func (p *plan) marshalRelevantAttrs(plan *plans.Plan) error {
for _, ra := range plan.RelevantAttributes {
addr := ra.Resource.String()
path, err := encodePath(ra.Attr)
if err != nil {
return err
}
p.RelevantAttributes = append(p.RelevantAttributes, resourceAttr{addr, path})
}
return nil
}
// omitUnknowns recursively walks the src cty.Value and returns a new cty.Value,
// omitting any unknowns.
//
@ -655,26 +680,7 @@ func encodePaths(pathSet cty.PathSet) (json.RawMessage, error) {
jsonPaths := make([]json.RawMessage, 0, len(pathList))
for _, path := range pathList {
steps := make([]json.RawMessage, 0, len(path))
for _, step := range path {
switch s := step.(type) {
case cty.IndexStep:
key, err := ctyjson.Marshal(s.Key, s.Key.Type())
if err != nil {
return nil, fmt.Errorf("Failed to marshal index step key %#v: %s", s.Key, err)
}
steps = append(steps, key)
case cty.GetAttrStep:
name, err := json.Marshal(s.Name)
if err != nil {
return nil, fmt.Errorf("Failed to marshal get attr step name %#v: %s", s.Name, err)
}
steps = append(steps, name)
default:
return nil, fmt.Errorf("Unsupported path step %#v (%t)", step, step)
}
}
jsonPath, err := json.Marshal(steps)
jsonPath, err := encodePath(path)
if err != nil {
return nil, err
}
@ -683,3 +689,26 @@ func encodePaths(pathSet cty.PathSet) (json.RawMessage, error) {
return json.Marshal(jsonPaths)
}
func encodePath(path cty.Path) (json.RawMessage, error) {
steps := make([]json.RawMessage, 0, len(path))
for _, step := range path {
switch s := step.(type) {
case cty.IndexStep:
key, err := ctyjson.Marshal(s.Key, s.Key.Type())
if err != nil {
return nil, fmt.Errorf("Failed to marshal index step key %#v: %s", s.Key, err)
}
steps = append(steps, key)
case cty.GetAttrStep:
name, err := json.Marshal(s.Name)
if err != nil {
return nil, fmt.Errorf("Failed to marshal get attr step name %#v: %s", s.Name, err)
}
steps = append(steps, name)
default:
return nil, fmt.Errorf("Unsupported path step %#v (%t)", step, step)
}
}
return json.Marshal(steps)
}

View File

@ -8,6 +8,7 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/lang/globalref"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/states/statefile"
@ -107,7 +108,7 @@ func TestOperation_planNoChanges(t *testing.T) {
},
"No objects need to be destroyed.",
},
"drift detected in normal mode": {
"no drift detected in normal noop": {
func(schemas *terraform.Schemas) *plans.Plan {
addr := addrs.Resource{
Mode: addrs.ManagedResourceMode,
@ -146,7 +147,54 @@ func TestOperation_planNoChanges(t *testing.T) {
DriftedResources: drs,
}
},
"to update the Terraform state to match, create and apply a refresh-only plan",
"No changes",
},
"drift detected in normal mode": {
func(schemas *terraform.Schemas) *plans.Plan {
addr := addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_resource",
Name: "somewhere",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
schema, _ := schemas.ResourceTypeConfig(
addrs.NewDefaultProvider("test"),
addr.Resource.Resource.Mode,
addr.Resource.Resource.Type,
)
ty := schema.ImpliedType()
rc := &plans.ResourceInstanceChange{
Addr: addr,
PrevRunAddr: addr,
ProviderAddr: addrs.RootModuleInstance.ProviderConfigDefault(
addrs.NewDefaultProvider("test"),
),
Change: plans.Change{
Action: plans.Update,
Before: cty.NullVal(ty),
After: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("1234"),
"foo": cty.StringVal("bar"),
}),
},
}
rcs, err := rc.Encode(ty)
if err != nil {
panic(err)
}
drs := []*plans.ResourceInstanceChangeSrc{rcs}
changes := plans.NewChanges()
changes.Resources = drs
return &plans.Plan{
UIMode: plans.NormalMode,
Changes: changes,
DriftedResources: drs,
RelevantAttributes: []globalref.ResourceAttr{{
Resource: addr,
Attr: cty.GetAttrPath("id"),
}},
}
},
"Objects have changed outside of Terraform",
},
"drift detected in refresh-only mode": {
func(schemas *terraform.Schemas) *plans.Plan {

View File

@ -9,9 +9,13 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/command/format"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/lang/globalref"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/plans/objchange"
"github.com/hashicorp/terraform/internal/terraform"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
)
// The Plan view is used for the plan command.
@ -96,37 +100,7 @@ func (v *PlanJSON) HelpPrompt() {
// The plan renderer is used by the Operation view (for plan and apply
// commands) and the Show view (for the show command).
func renderPlan(plan *plans.Plan, schemas *terraform.Schemas, view *View) {
// In refresh-only mode, we show all resources marked as drifted,
// including those which have moved without other changes. In other plan
// modes, move-only changes will be rendered in the planned changes, so
// we skip them here.
var driftedResources []*plans.ResourceInstanceChangeSrc
if plan.UIMode == plans.RefreshOnlyMode {
driftedResources = plan.DriftedResources
} else {
for _, dr := range plan.DriftedResources {
if dr.Action != plans.NoOp {
driftedResources = append(driftedResources, dr)
}
}
}
haveRefreshChanges := len(driftedResources) > 0
if haveRefreshChanges {
renderChangesDetectedByRefresh(driftedResources, schemas, view)
switch plan.UIMode {
case plans.RefreshOnlyMode:
view.streams.Println(format.WordWrap(
"\nThis is a refresh-only plan, so Terraform will not take any actions to undo these. If you were expecting these changes then you can apply this plan to record the updated values in the Terraform state without changing any remote objects.",
view.outputColumns(),
))
default:
view.streams.Println(format.WordWrap(
"\nUnless you have made equivalent changes to your configuration, or ignored the relevant attributes using ignore_changes, the following plan may include actions to undo or respond to these changes.",
view.outputColumns(),
))
}
}
haveRefreshChanges := renderChangesDetectedByRefresh(plan, schemas, view)
counts := map[plans.Action]int{}
var rChanges []*plans.ResourceInstanceChangeSrc
@ -304,7 +278,7 @@ func renderPlan(plan *plans.Plan, schemas *terraform.Schemas, view *View) {
}
view.streams.Println(format.ResourceChange(
rcs,
decodeChange(rcs, rSchema),
rSchema,
view.colorize,
format.DiffLanguageProposedChange,
@ -360,12 +334,66 @@ func renderPlan(plan *plans.Plan, schemas *terraform.Schemas, view *View) {
// renderChangesDetectedByRefresh returns true if it produced at least one
// line of output, and guarantees to always produce whole lines terminated
// by newline characters.
func renderChangesDetectedByRefresh(drs []*plans.ResourceInstanceChangeSrc, schemas *terraform.Schemas, view *View) {
func renderChangesDetectedByRefresh(plan *plans.Plan, schemas *terraform.Schemas, view *View) (rendered bool) {
// If this is not a refresh-only plan, we will need to filter out any
// non-relevant changes to reduce plan output.
relevant := make(map[string]bool)
for _, r := range plan.RelevantAttributes {
relevant[r.Resource.String()] = true
}
var changes []*plans.ResourceInstanceChange
for _, rcs := range plan.DriftedResources {
providerSchema := schemas.ProviderSchema(rcs.ProviderAddr.Provider)
if providerSchema == nil {
// Should never happen
view.streams.Printf("(schema missing for %s)\n\n", rcs.ProviderAddr)
continue
}
rSchema, _ := providerSchema.SchemaForResourceAddr(rcs.Addr.Resource.Resource)
if rSchema == nil {
// Should never happen
view.streams.Printf("(schema missing for %s)\n\n", rcs.Addr)
continue
}
changes = append(changes, decodeChange(rcs, rSchema))
}
// In refresh-only mode, we show all resources marked as drifted,
// including those which have moved without other changes. In other plan
// modes, move-only changes will be rendered in the planned changes, so
// we skip them here.
var drs []*plans.ResourceInstanceChange
if plan.UIMode == plans.RefreshOnlyMode {
drs = changes
} else {
for _, dr := range changes {
change := filterRefreshChange(dr, plan.RelevantAttributes)
if change.Action != plans.NoOp {
dr.Change = change
drs = append(drs, dr)
}
}
}
if len(drs) == 0 {
return false
}
// In an empty plan, we don't show any outside changes, because nothing in
// the plan could have been affected by those changes. If a user wants to
// see all external changes, then a refresh-only plan should be executed
// instead.
if plan.Changes.Empty() && plan.UIMode != plans.RefreshOnlyMode {
return false
}
view.streams.Print(
view.colorize.Color("[reset]\n[bold][cyan]Note:[reset][bold] Objects have changed outside of Terraform[reset]\n\n"),
)
view.streams.Print(format.WordWrap(
"Terraform detected the following changes made outside of Terraform since the last \"terraform apply\":\n\n",
"Terraform detected the following changes made outside of Terraform since the last \"terraform apply\" which may have affected this plan:\n\n",
view.outputColumns(),
))
@ -403,6 +431,111 @@ func renderChangesDetectedByRefresh(drs []*plans.ResourceInstanceChangeSrc, sche
format.DiffLanguageDetectedDrift,
))
}
switch plan.UIMode {
case plans.RefreshOnlyMode:
view.streams.Println(format.WordWrap(
"\nThis is a refresh-only plan, so Terraform will not take any actions to undo these. If you were expecting these changes then you can apply this plan to record the updated values in the Terraform state without changing any remote objects.",
view.outputColumns(),
))
default:
view.streams.Println(format.WordWrap(
"\nUnless you have made equivalent changes to your configuration, or ignored the relevant attributes using ignore_changes, the following plan may include actions to undo or respond to these changes.",
view.outputColumns(),
))
}
return true
}
// Filter individual resource changes for display based on the attributes which
// may have contributed to the plan as a whole. In order to continue to use the
// existing diff renderer, we are going to create a fake change for display,
// only showing the attributes we're interested in.
// The resulting change will be a NoOp if it has nothing relevant to the plan.
func filterRefreshChange(change *plans.ResourceInstanceChange, contributing []globalref.ResourceAttr) plans.Change {
if change.Action == plans.NoOp {
return change.Change
}
var relevantAttrs []cty.Path
resAddr := change.Addr
for _, attr := range contributing {
if !resAddr.ContainingResource().Equal(attr.Resource.ContainingResource()) {
continue
}
// If the contributing address has no instance key, then the
// contributing reference applies to all instances.
if attr.Resource.Resource.Key == addrs.NoKey || resAddr.Equal(attr.Resource) {
relevantAttrs = append(relevantAttrs, attr.Attr)
}
}
// If no attributes are relevant in this resource, then we can turn this
// onto a NoOp change for display.
if len(relevantAttrs) == 0 {
return plans.Change{
Action: plans.NoOp,
Before: change.Before,
After: change.Before,
}
}
// We have some attributes in this change which were marked as relevant, so
// we are going to take the Before value and add in only those attributes
// from the After value which may have contributed to the plan.
before := change.Before
after, _ := cty.Transform(before, func(path cty.Path, v cty.Value) (cty.Value, error) {
for i, attrPath := range relevantAttrs {
// If the current value is null, but we are only a prefix of the
// affected path, we need to take the value from this point since
// we can't recurse any further into the object. This has the
// possibility of pulling in extra attribute changes we're not
// concerned with, but we can take this as "close enough" for now.
if (v.IsNull() && attrPath.HasPrefix(path)) || attrPath.Equals(path) {
// remove the path from further consideration
relevantAttrs = append(relevantAttrs[:i], relevantAttrs[i+1:]...)
v, err := path.Apply(change.After)
return v, err
}
}
return v, nil
})
action := change.Action
if before.RawEquals(after) {
action = plans.NoOp
}
return plans.Change{
Action: action,
Before: before,
After: after,
}
}
func decodeChange(change *plans.ResourceInstanceChangeSrc, schema *configschema.Block) *plans.ResourceInstanceChange {
changeV, err := change.Decode(schema.ImpliedType())
if err != nil {
// Should never happen in here, since we've already been through
// loads of layers of encode/decode of the planned changes before now.
panic(fmt.Sprintf("failed to decode plan for %s while rendering diff: %s", change.Addr, err))
}
// We currently have an opt-out that permits the legacy SDK to return values
// that defy our usual conventions around handling of nesting blocks. To
// avoid the rendering code from needing to handle all of these, we'll
// normalize first.
// (Ideally we'd do this as part of the SDK opt-out implementation in core,
// but we've added it here for now to reduce risk of unexpected impacts
// on other code in core.)
changeV.Change.Before = objchange.NormalizeObjectFromLegacySDK(changeV.Change.Before, schema)
changeV.Change.After = objchange.NormalizeObjectFromLegacySDK(changeV.Change.After, schema)
return changeV
}
const planHeaderIntro = `

View File

@ -0,0 +1,68 @@
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
}

View File

@ -0,0 +1,130 @@
package globalref
import (
"sort"
"github.com/hashicorp/terraform/internal/addrs"
)
// ContributingResources analyzes all of the given references and
// for each one tries to walk backwards through any named values to find all
// resources whose values contributed either directly or indirectly to any of
// them.
//
// This is a wrapper around ContributingResourceReferences which simplifies
// the result to only include distinct resource addresses, not full references.
// If the configuration includes several different references to different
// parts of a resource, ContributingResources will not preserve that detail.
func (a *Analyzer) ContributingResources(refs ...Reference) []addrs.AbsResource {
retRefs := a.ContributingResourceReferences(refs...)
if len(retRefs) == 0 {
return nil
}
uniq := make(map[string]addrs.AbsResource, len(refs))
for _, ref := range retRefs {
if addr, ok := resourceForAddr(ref.LocalRef.Subject); ok {
moduleAddr := ref.ModuleAddr()
absAddr := addr.Absolute(moduleAddr)
uniq[absAddr.String()] = absAddr
}
}
ret := make([]addrs.AbsResource, 0, len(uniq))
for _, addr := range uniq {
ret = append(ret, addr)
}
sort.Slice(ret, func(i, j int) bool {
// We only have a sorting function for resource _instances_, but
// it'll do well enough if we just pretend we have no-key instances.
return ret[i].Instance(addrs.NoKey).Less(ret[j].Instance(addrs.NoKey))
})
return ret
}
// ContributingResourceReferences analyzes all of the given references and
// for each one tries to walk backwards through any named values to find all
// references to resource attributes that contributed either directly or
// indirectly to any of them.
//
// This is a global operation that can be potentially quite expensive for
// complex configurations.
func (a *Analyzer) ContributingResourceReferences(refs ...Reference) []Reference {
// Our methodology here is to keep digging through MetaReferences
// until we've visited everything we encounter directly or indirectly,
// and keep track of any resources we find along the way.
// We'll aggregate our result here, using the string representations of
// the resources as keys to avoid returning the same one more than once.
found := make(map[referenceAddrKey]Reference)
// We might encounter the same object multiple times as we walk,
// but we won't learn anything more by traversing them again and so we'll
// just skip them instead.
visitedObjects := make(map[referenceAddrKey]struct{})
// A queue of objects we still need to visit.
// Note that if we find multiple references to the same object then we'll
// just arbitrary choose any one of them, because for our purposes here
// it's immaterial which reference we actually followed.
pendingObjects := make(map[referenceAddrKey]Reference)
// Initial state: identify any directly-mentioned resources and
// queue up any named values we refer to.
for _, ref := range refs {
if _, ok := resourceForAddr(ref.LocalRef.Subject); ok {
found[ref.addrKey()] = ref
}
pendingObjects[ref.addrKey()] = ref
}
for len(pendingObjects) > 0 {
// Note: we modify this map while we're iterating over it, which means
// that anything we add might be either visited within a later
// iteration of the inner loop or in a later iteration of the outer
// loop, but we get the correct result either way because we keep
// working until we've fully depleted the queue.
for key, ref := range pendingObjects {
delete(pendingObjects, key)
// We do this _before_ the visit below just in case this is an
// invalid config with a self-referential local value, in which
// case we'll just silently ignore the self reference for our
// purposes here, and thus still eventually converge (albeit
// with an incomplete answer).
visitedObjects[key] = struct{}{}
moreRefs := a.MetaReferences(ref)
for _, newRef := range moreRefs {
if _, ok := resourceForAddr(newRef.LocalRef.Subject); ok {
found[newRef.addrKey()] = newRef
}
newKey := newRef.addrKey()
if _, visited := visitedObjects[newKey]; !visited {
pendingObjects[newKey] = newRef
}
}
}
}
if len(found) == 0 {
return nil
}
ret := make([]Reference, 0, len(found))
for _, ref := range found {
ret = append(ret, ref)
}
return ret
}
func resourceForAddr(addr addrs.Referenceable) (addrs.Resource, bool) {
switch addr := addr.(type) {
case addrs.Resource:
return addr, true
case addrs.ResourceInstance:
return addr.Resource, true
default:
return addrs.Resource{}, false
}
}

View File

@ -0,0 +1,190 @@
package globalref
import (
"sort"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform/internal/addrs"
)
func TestAnalyzerContributingResources(t *testing.T) {
azr := testAnalyzer(t, "contributing-resources")
tests := map[string]struct {
StartRefs func() []Reference
WantAddrs []string
}{
"root output 'network'": {
func() []Reference {
return azr.ReferencesFromOutputValue(
addrs.OutputValue{Name: "network"}.Absolute(addrs.RootModuleInstance),
)
},
[]string{
`data.test_thing.environment`,
`module.network.test_thing.subnet`,
`module.network.test_thing.vpc`,
},
},
"root output 'c10s_url'": {
func() []Reference {
return azr.ReferencesFromOutputValue(
addrs.OutputValue{Name: "c10s_url"}.Absolute(addrs.RootModuleInstance),
)
},
[]string{
`data.test_thing.environment`,
`module.compute.test_thing.load_balancer`,
`module.network.test_thing.subnet`,
`module.network.test_thing.vpc`,
// NOTE: module.compute.test_thing.controller isn't here
// because we can see statically that the output value refers
// only to the "string" attribute of
// module.compute.test_thing.load_balancer , and so we
// don't consider references inside the "list" blocks.
},
},
"module.compute.test_thing.load_balancer": {
func() []Reference {
return azr.ReferencesFromResourceInstance(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "load_balancer",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("compute", addrs.NoKey)),
)
},
[]string{
`data.test_thing.environment`,
`module.compute.test_thing.controller`,
`module.network.test_thing.subnet`,
`module.network.test_thing.vpc`,
},
},
"data.test_thing.environment": {
func() []Reference {
return azr.ReferencesFromResourceInstance(
addrs.Resource{
Mode: addrs.DataResourceMode,
Type: "test_thing",
Name: "environment",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
)
},
[]string{
// Nothing! This one only refers to an input variable.
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
startRefs := test.StartRefs()
addrs := azr.ContributingResources(startRefs...)
want := test.WantAddrs
got := make([]string, len(addrs))
for i, addr := range addrs {
got[i] = addr.String()
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("wrong addresses\n%s", diff)
}
})
}
}
func TestAnalyzerContributingResourceAttrs(t *testing.T) {
azr := testAnalyzer(t, "contributing-resources")
tests := map[string]struct {
StartRefs func() []Reference
WantAttrs []string
}{
"root output 'network'": {
func() []Reference {
return azr.ReferencesFromOutputValue(
addrs.OutputValue{Name: "network"}.Absolute(addrs.RootModuleInstance),
)
},
[]string{
`data.test_thing.environment.any.base_cidr_block`,
`data.test_thing.environment.any.subnet_count`,
`module.network.test_thing.subnet`,
`module.network.test_thing.vpc.string`,
},
},
"root output 'c10s_url'": {
func() []Reference {
return azr.ReferencesFromOutputValue(
addrs.OutputValue{Name: "c10s_url"}.Absolute(addrs.RootModuleInstance),
)
},
[]string{
`data.test_thing.environment.any.base_cidr_block`,
`data.test_thing.environment.any.subnet_count`,
`module.compute.test_thing.load_balancer.string`,
`module.network.test_thing.subnet`,
`module.network.test_thing.vpc.string`,
},
},
"module.compute.test_thing.load_balancer": {
func() []Reference {
return azr.ReferencesFromResourceInstance(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "load_balancer",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("compute", addrs.NoKey)),
)
},
[]string{
`data.test_thing.environment.any.base_cidr_block`,
`data.test_thing.environment.any.subnet_count`,
`module.compute.test_thing.controller`,
`module.network.test_thing.subnet`,
`module.network.test_thing.vpc.string`,
},
},
"data.test_thing.environment": {
func() []Reference {
return azr.ReferencesFromResourceInstance(
addrs.Resource{
Mode: addrs.DataResourceMode,
Type: "test_thing",
Name: "environment",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
)
},
[]string{
// Nothing! This one only refers to an input variable.
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
startRefs := test.StartRefs()
refs := azr.ContributingResourceReferences(startRefs...)
want := test.WantAttrs
got := make([]string, len(refs))
for i, ref := range refs {
resAttr, ok := ref.ResourceAttr()
if !ok {
t.Errorf("%s is not a resource attr reference", resAttr.DebugString())
continue
}
got[i] = resAttr.DebugString()
}
sort.Strings(got)
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("wrong addresses\n%s", diff)
}
})
}
}

View File

@ -0,0 +1,606 @@
package globalref
import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/lang"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
"github.com/zclconf/go-cty/cty/gocty"
)
// MetaReferences inspects the configuration to find the references contained
// within the most specific object that the given address refers to.
//
// This finds only the direct references in that object, not any indirect
// references from those. This is a building block for some other Analyzer
// functions that can walk through multiple levels of reference.
//
// If the given reference refers to something that doesn't exist in the
// configuration we're analyzing then MetaReferences will return no
// meta-references at all, which is indistinguishable from an existing
// object that doesn't refer to anything.
func (a *Analyzer) MetaReferences(ref Reference) []Reference {
// This function is aiming to encapsulate the fact that a reference
// is actually quite a complex notion which includes both a specific
// object the reference is to, where each distinct object type has
// a very different representation in the configuration, and then
// also potentially an attribute or block within the definition of that
// object. Our goal is to make all of these different situations appear
// mostly the same to the caller, in that all of them can be reduced to
// a set of references regardless of which expression or expressions we
// derive those from.
moduleAddr := ref.ModuleAddr()
remaining := ref.LocalRef.Remaining
// Our first task then is to select an appropriate implementation based
// on which address type the reference refers to.
switch targetAddr := ref.LocalRef.Subject.(type) {
case addrs.InputVariable:
return a.metaReferencesInputVariable(moduleAddr, targetAddr, remaining)
case addrs.LocalValue:
return a.metaReferencesLocalValue(moduleAddr, targetAddr, remaining)
case addrs.ModuleCallInstanceOutput:
return a.metaReferencesOutputValue(moduleAddr, targetAddr, remaining)
case addrs.ModuleCallInstance:
return a.metaReferencesModuleCall(moduleAddr, targetAddr, remaining)
case addrs.ModuleCall:
// TODO: It isn't really correct to say that a reference to a module
// call is a reference to its no-key instance. Really what we want to
// say here is that it's a reference to _all_ instances, or to an
// instance with an unknown key, but we don't have any representation
// of that. For the moment it's pretty immaterial since most of our
// other analysis ignores instance keys anyway, but maybe we'll revisit
// this latter to distingish these two cases better.
return a.metaReferencesModuleCall(moduleAddr, targetAddr.Instance(addrs.NoKey), remaining)
case addrs.CountAttr, addrs.ForEachAttr:
if resourceAddr, ok := ref.ResourceInstance(); ok {
return a.metaReferencesCountOrEach(resourceAddr.ContainingResource())
}
return nil
case addrs.ResourceInstance:
return a.metaReferencesResourceInstance(moduleAddr, targetAddr, remaining)
case addrs.Resource:
// TODO: It isn't really correct to say that a reference to a resource
// is a reference to its no-key instance. Really what we want to say
// here is that it's a reference to _all_ instances, or to an instance
// with an unknown key, but we don't have any representation of that.
// For the moment it's pretty immaterial since most of our other
// analysis ignores instance keys anyway, but maybe we'll revisit this
// latter to distingish these two cases better.
return a.metaReferencesResourceInstance(moduleAddr, targetAddr.Instance(addrs.NoKey), remaining)
default:
// For anything we don't explicitly support we'll just return no
// references. This includes the reference types that don't really
// refer to configuration objects at all, like "path.module",
// and so which cannot possibly generate any references.
return nil
}
}
func (a *Analyzer) metaReferencesInputVariable(calleeAddr addrs.ModuleInstance, addr addrs.InputVariable, remain hcl.Traversal) []Reference {
if calleeAddr.IsRoot() {
// A root module variable definition can never refer to anything,
// because it conceptually exists outside of any module.
return nil
}
callerAddr, callAddr := calleeAddr.Call()
// We need to find the module call inside the caller module.
callerCfg := a.ModuleConfig(callerAddr)
if callerCfg == nil {
return nil
}
call := callerCfg.ModuleCalls[callAddr.Name]
if call == nil {
return nil
}
// Now we need to look for an attribute matching the variable name inside
// the module block body.
body := call.Config
schema := &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{Name: addr.Name},
},
}
// We don't check for errors here because we'll make a best effort to
// analyze whatever partial result HCL is able to extract.
content, _, _ := body.PartialContent(schema)
attr := content.Attributes[addr.Name]
if attr == nil {
return nil
}
refs, _ := lang.ReferencesInExpr(attr.Expr)
return absoluteRefs(callerAddr, refs)
}
func (a *Analyzer) metaReferencesOutputValue(callerAddr addrs.ModuleInstance, addr addrs.ModuleCallInstanceOutput, remain hcl.Traversal) []Reference {
calleeAddr := callerAddr.Child(addr.Call.Call.Name, addr.Call.Key)
// We need to find the output value declaration inside the callee module.
calleeCfg := a.ModuleConfig(calleeAddr)
if calleeCfg == nil {
return nil
}
oc := calleeCfg.Outputs[addr.Name]
if oc == nil {
return nil
}
// We don't check for errors here because we'll make a best effort to
// analyze whatever partial result HCL is able to extract.
refs, _ := lang.ReferencesInExpr(oc.Expr)
return absoluteRefs(calleeAddr, refs)
}
func (a *Analyzer) metaReferencesLocalValue(moduleAddr addrs.ModuleInstance, addr addrs.LocalValue, remain hcl.Traversal) []Reference {
modCfg := a.ModuleConfig(moduleAddr)
if modCfg == nil {
return nil
}
local := modCfg.Locals[addr.Name]
if local == nil {
return nil
}
// We don't check for errors here because we'll make a best effort to
// analyze whatever partial result HCL is able to extract.
refs, _ := lang.ReferencesInExpr(local.Expr)
return absoluteRefs(moduleAddr, refs)
}
func (a *Analyzer) metaReferencesModuleCall(callerAddr addrs.ModuleInstance, addr addrs.ModuleCallInstance, remain hcl.Traversal) []Reference {
calleeAddr := callerAddr.Child(addr.Call.Name, addr.Key)
// What we're really doing here is just rolling up all of the references
// from all of this module's output values.
calleeCfg := a.ModuleConfig(calleeAddr)
if calleeCfg == nil {
return nil
}
var ret []Reference
for name := range calleeCfg.Outputs {
outputAddr := addrs.ModuleCallInstanceOutput{
Call: addr,
Name: name,
}
moreRefs := a.metaReferencesOutputValue(callerAddr, outputAddr, nil)
ret = append(ret, moreRefs...)
}
return ret
}
func (a *Analyzer) metaReferencesCountOrEach(resourceAddr addrs.AbsResource) []Reference {
return a.ReferencesFromResourceRepetition(resourceAddr)
}
func (a *Analyzer) metaReferencesResourceInstance(moduleAddr addrs.ModuleInstance, addr addrs.ResourceInstance, remain hcl.Traversal) []Reference {
modCfg := a.ModuleConfig(moduleAddr)
if modCfg == nil {
return nil
}
rc := modCfg.ResourceByAddr(addr.Resource)
if rc == nil {
return nil
}
// In valid cases we should have the schema for this resource type
// available. In invalid cases we might be dealing with partial information,
// and so the schema might be nil so we won't be able to return reference
// information for this particular situation.
providerSchema := a.providerSchemas[rc.Provider]
if providerSchema == nil {
return nil
}
resourceTypeSchema, _ := providerSchema.SchemaForResourceAddr(addr.Resource)
if resourceTypeSchema == nil {
return nil
}
// When analyzing the resource configuration to look for references, we'll
// make a best effort to narrow down to only a particular sub-portion of
// the configuration by following the remaining traversal steps. In the
// ideal case this will lead us to a specific expression, but as a
// compromise it might lead us to some nested blocks where at least we
// can limit our searching only to those.
bodies := []hcl.Body{rc.Config}
var exprs []hcl.Expression
schema := resourceTypeSchema
var steppingThrough *configschema.NestedBlock
var steppingThroughType string
nextStep := func(newBodies []hcl.Body, newExprs []hcl.Expression) {
// We append exprs but replace bodies because exprs represent extra
// expressions we collected on the path, such as dynamic block for_each,
// which can potentially contribute to the final evalcontext, but
// bodies never contribute any values themselves, and instead just
// narrow down where we're searching.
bodies = newBodies
exprs = append(exprs, newExprs...)
steppingThrough = nil
steppingThroughType = ""
// Caller must also update "schema" if necessary.
}
traverseInBlock := func(name string) ([]hcl.Body, []hcl.Expression) {
if attr := schema.Attributes[name]; attr != nil {
// When we reach a specific attribute we can't traverse any deeper, because attributes are the leaves of the schema.
schema = nil
return traverseAttr(bodies, name)
} else if blockType := schema.BlockTypes[name]; blockType != nil {
// We need to take a different action here depending on
// the nesting mode of the block type. Some require us
// to traverse in two steps in order to select a specific
// child block, while others we can just step through
// directly.
switch blockType.Nesting {
case configschema.NestingSingle, configschema.NestingGroup:
// There should be only zero or one blocks of this
// type, so we can traverse in only one step.
schema = &blockType.Block
return traverseNestedBlockSingle(bodies, name)
case configschema.NestingMap, configschema.NestingList, configschema.NestingSet:
steppingThrough = blockType
return bodies, exprs // Preserve current selections for the second step
default:
// The above should be exhaustive, but just in case
// we add something new in future we'll bail out
// here and conservatively return everything under
// the current traversal point.
schema = nil
return nil, nil
}
}
// We'll get here if the given name isn't in the schema at all. If so,
// there's nothing else to be done here.
schema = nil
return nil, nil
}
Steps:
for _, step := range remain {
// If we filter out all of our bodies before we finish traversing then
// we know we won't find anything else, because all of our subsequent
// traversal steps won't have any bodies to search.
if len(bodies) == 0 {
return nil
}
// If we no longer have a schema then that suggests we've
// traversed as deep as what the schema covers (e.g. we reached
// a specific attribute) and so we'll stop early, assuming that
// any remaining steps are traversals into an attribute expression
// result.
if schema == nil {
break
}
switch step := step.(type) {
case hcl.TraverseAttr:
switch {
case steppingThrough != nil:
// If we're stepping through a NestingMap block then
// it's valid to use attribute syntax to select one of
// the blocks by its label. Other nesting types require
// TraverseIndex, so can never be valid.
if steppingThrough.Nesting != configschema.NestingMap {
nextStep(nil, nil) // bail out
continue
}
nextStep(traverseNestedBlockMap(bodies, steppingThroughType, step.Name))
schema = &steppingThrough.Block
default:
nextStep(traverseInBlock(step.Name))
if schema == nil {
// traverseInBlock determined that we've traversed as
// deep as we can with reference to schema, so we'll
// stop here and just process whatever's selected.
break Steps
}
}
case hcl.TraverseIndex:
switch {
case steppingThrough != nil:
switch steppingThrough.Nesting {
case configschema.NestingMap:
keyVal, err := convert.Convert(step.Key, cty.String)
if err != nil { // Invalid traversal, so can't have any refs
nextStep(nil, nil) // bail out
continue
}
nextStep(traverseNestedBlockMap(bodies, steppingThroughType, keyVal.AsString()))
schema = &steppingThrough.Block
case configschema.NestingList:
idxVal, err := convert.Convert(step.Key, cty.Number)
if err != nil { // Invalid traversal, so can't have any refs
nextStep(nil, nil) // bail out
continue
}
var idx int
err = gocty.FromCtyValue(idxVal, &idx)
if err != nil { // Invalid traversal, so can't have any refs
nextStep(nil, nil) // bail out
continue
}
nextStep(traverseNestedBlockList(bodies, steppingThroughType, idx))
schema = &steppingThrough.Block
default:
// Note that NestingSet ends up in here because we don't
// actually allow traversing into set-backed block types,
// and so such a reference would be invalid.
nextStep(nil, nil) // bail out
continue
}
default:
// When indexing the contents of a block directly we always
// interpret the key as a string representing an attribute
// name.
nameVal, err := convert.Convert(step.Key, cty.String)
if err != nil { // Invalid traversal, so can't have any refs
nextStep(nil, nil) // bail out
continue
}
nextStep(traverseInBlock(nameVal.AsString()))
if schema == nil {
// traverseInBlock determined that we've traversed as
// deep as we can with reference to schema, so we'll
// stop here and just process whatever's selected.
break Steps
}
}
default:
// We shouldn't get here, because the above cases are exhaustive
// for all of the relative traversal types, but we'll be robust in
// case HCL adds more in future and just pretend the traversal
// ended a bit early if so.
break Steps
}
}
if steppingThrough != nil {
// If we ended in the middle of "stepping through" then we'll conservatively
// use the bodies of _all_ nested blocks of the type we were stepping
// through, because the recipient of this value could refer to any
// of them dynamically.
var labelNames []string
if steppingThrough.Nesting == configschema.NestingMap {
labelNames = []string{"key"}
}
blocks := findBlocksInBodies(bodies, steppingThroughType, labelNames)
for _, block := range blocks {
bodies, exprs = blockParts(block)
}
}
if len(bodies) == 0 && len(exprs) == 0 {
return nil
}
var refs []*addrs.Reference
for _, expr := range exprs {
moreRefs, _ := lang.ReferencesInExpr(expr)
refs = append(refs, moreRefs...)
}
if schema != nil {
for _, body := range bodies {
moreRefs, _ := lang.ReferencesInBlock(body, schema)
refs = append(refs, moreRefs...)
}
}
return absoluteRefs(addr.Absolute(moduleAddr), refs)
}
func traverseAttr(bodies []hcl.Body, name string) ([]hcl.Body, []hcl.Expression) {
if len(bodies) == 0 {
return nil, nil
}
schema := &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{Name: name},
},
}
// We can find at most one expression per body, because attribute names
// are always unique within a body.
retExprs := make([]hcl.Expression, 0, len(bodies))
for _, body := range bodies {
content, _, _ := body.PartialContent(schema)
if attr := content.Attributes[name]; attr != nil && attr.Expr != nil {
retExprs = append(retExprs, attr.Expr)
}
}
return nil, retExprs
}
func traverseNestedBlockSingle(bodies []hcl.Body, typeName string) ([]hcl.Body, []hcl.Expression) {
if len(bodies) == 0 {
return nil, nil
}
blocks := findBlocksInBodies(bodies, typeName, nil)
var retBodies []hcl.Body
var retExprs []hcl.Expression
for _, block := range blocks {
moreBodies, moreExprs := blockParts(block)
retBodies = append(retBodies, moreBodies...)
retExprs = append(retExprs, moreExprs...)
}
return retBodies, retExprs
}
func traverseNestedBlockMap(bodies []hcl.Body, typeName string, key string) ([]hcl.Body, []hcl.Expression) {
if len(bodies) == 0 {
return nil, nil
}
blocks := findBlocksInBodies(bodies, typeName, []string{"key"})
var retBodies []hcl.Body
var retExprs []hcl.Expression
for _, block := range blocks {
switch block.Type {
case "dynamic":
// For dynamic blocks we allow the key to be chosen dynamically
// and so we'll just conservatively include all dynamic block
// bodies. However, we need to also look for references in some
// arguments of the dynamic block itself.
argExprs, contentBody := dynamicBlockParts(block.Body)
retExprs = append(retExprs, argExprs...)
if contentBody != nil {
retBodies = append(retBodies, contentBody)
}
case typeName:
if len(block.Labels) == 1 && block.Labels[0] == key && block.Body != nil {
retBodies = append(retBodies, block.Body)
}
}
}
return retBodies, retExprs
}
func traverseNestedBlockList(bodies []hcl.Body, typeName string, idx int) ([]hcl.Body, []hcl.Expression) {
if len(bodies) == 0 {
return nil, nil
}
schema := &hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{
{Type: typeName, LabelNames: nil},
{Type: "dynamic", LabelNames: []string{"type"}},
},
}
var retBodies []hcl.Body
var retExprs []hcl.Expression
for _, body := range bodies {
content, _, _ := body.PartialContent(schema)
blocks := content.Blocks
// A tricky aspect of this scenario is that if there are any "dynamic"
// blocks then we can't statically predict how many concrete blocks they
// will generate, and so consequently we can't predict the indices of
// any statically-defined blocks that might appear after them.
firstDynamic := -1 // -1 means "no dynamic blocks"
for i, block := range blocks {
if block.Type == "dynamic" {
firstDynamic = i
break
}
}
switch {
case firstDynamic >= 0 && idx >= firstDynamic:
// This is the unfortunate case where the selection could be
// any of the blocks from firstDynamic onwards, and so we
// need to conservatively include all of them in our result.
for _, block := range blocks[firstDynamic:] {
moreBodies, moreExprs := blockParts(block)
retBodies = append(retBodies, moreBodies...)
retExprs = append(retExprs, moreExprs...)
}
default:
// This is the happier case where we can select just a single
// static block based on idx. Note that this one is guaranteed
// to never be dynamic but we're using blockParts here just
// for consistency.
moreBodies, moreExprs := blockParts(blocks[idx])
retBodies = append(retBodies, moreBodies...)
retExprs = append(retExprs, moreExprs...)
}
}
return retBodies, retExprs
}
func findBlocksInBodies(bodies []hcl.Body, typeName string, labelNames []string) []*hcl.Block {
// We need to look for both static blocks of the given type, and any
// dynamic blocks whose label gives the expected type name.
schema := &hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{
{Type: typeName, LabelNames: labelNames},
{Type: "dynamic", LabelNames: []string{"type"}},
},
}
var blocks []*hcl.Block
for _, body := range bodies {
// We ignore errors here because we'll just make a best effort to analyze
// whatever partial result HCL returns in that case.
content, _, _ := body.PartialContent(schema)
for _, block := range content.Blocks {
switch block.Type {
case "dynamic":
if len(block.Labels) != 1 { // Invalid
continue
}
if block.Labels[0] == typeName {
blocks = append(blocks, block)
}
case typeName:
blocks = append(blocks, block)
}
}
}
// NOTE: The caller still needs to check for dynamic vs. static in order
// to do further processing. The callers above all aim to encapsulate
// that.
return blocks
}
func blockParts(block *hcl.Block) ([]hcl.Body, []hcl.Expression) {
switch block.Type {
case "dynamic":
exprs, contentBody := dynamicBlockParts(block.Body)
var bodies []hcl.Body
if contentBody != nil {
bodies = []hcl.Body{contentBody}
}
return bodies, exprs
default:
if block.Body == nil {
return nil, nil
}
return []hcl.Body{block.Body}, nil
}
}
func dynamicBlockParts(body hcl.Body) ([]hcl.Expression, hcl.Body) {
if body == nil {
return nil, nil
}
// This is a subset of the "dynamic" block schema defined by the HCL
// dynblock extension, covering only the two arguments that are allowed
// to be arbitrary expressions possibly referring elsewhere.
schema := &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{Name: "for_each"},
{Name: "labels"},
},
Blocks: []hcl.BlockHeaderSchema{
{Type: "content"},
},
}
content, _, _ := body.PartialContent(schema)
var exprs []hcl.Expression
if len(content.Attributes) != 0 {
exprs = make([]hcl.Expression, 0, len(content.Attributes))
}
for _, attr := range content.Attributes {
if attr.Expr != nil {
exprs = append(exprs, attr.Expr)
}
}
var contentBody hcl.Body
for _, block := range content.Blocks {
if block != nil && block.Type == "content" && block.Body != nil {
contentBody = block.Body
}
}
return exprs, contentBody
}

View File

@ -0,0 +1,87 @@
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)
}
// 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.
//
// 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
}
}

View File

@ -0,0 +1,170 @@
package globalref
import (
"sort"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform/internal/addrs"
)
func TestAnalyzerMetaReferences(t *testing.T) {
tests := []struct {
InputContainer string
InputRef string
WantRefs []string
}{
{
``,
`local.a`,
nil,
},
{
``,
`local.single`,
[]string{
"::test_thing.single.id",
},
},
{
``,
`test_thing.single`,
[]string{
"::local.a",
"::local.b",
},
},
{
``,
`test_thing.single.string`,
[]string{
"::local.a",
},
},
{
``,
`test_thing.for_each`,
[]string{
"::local.a",
"::test_thing.single.string",
},
},
{
``,
`test_thing.for_each["whatever"]`,
[]string{
"::local.a",
"::test_thing.single.string",
},
},
{
``,
`test_thing.for_each["whatever"].single`,
[]string{
"::test_thing.single.string",
},
},
{
``,
`test_thing.for_each["whatever"].single.z`,
[]string{
"::test_thing.single.string",
},
},
{
``,
`test_thing.count`,
[]string{
"::local.a",
},
},
{
``,
`test_thing.count[0]`,
[]string{
"::local.a",
},
},
{
``,
`module.single.a`,
[]string{
"module.single::test_thing.foo",
"module.single::var.a",
},
},
{
``,
`module.for_each["whatever"].a`,
[]string{
`module.for_each["whatever"]::test_thing.foo`,
`module.for_each["whatever"]::var.a`,
},
},
{
``,
`module.count[0].a`,
[]string{
`module.count[0]::test_thing.foo`,
`module.count[0]::var.a`,
},
},
{
`module.single`,
`var.a`,
[]string{
"::test_thing.single",
},
},
{
`module.single`,
`test_thing.foo`,
[]string{
"module.single::var.a",
},
},
}
azr := testAnalyzer(t, "assorted")
for _, test := range tests {
name := test.InputRef
if test.InputContainer != "" {
name = test.InputContainer + " " + test.InputRef
}
t.Run(name, func(t *testing.T) {
t.Logf("testing %s", name)
var containerAddr addrs.Targetable
containerAddr = addrs.RootModuleInstance
if test.InputContainer != "" {
moduleAddrTarget, diags := addrs.ParseTargetStr(test.InputContainer)
if diags.HasErrors() {
t.Fatalf("input module address is invalid: %s", diags.Err())
}
containerAddr = moduleAddrTarget.Subject
}
localRef, diags := addrs.ParseRefStr(test.InputRef)
if diags.HasErrors() {
t.Fatalf("input reference is invalid: %s", diags.Err())
}
ref := Reference{
ContainerAddr: containerAddr,
LocalRef: localRef,
}
refs := azr.MetaReferences(ref)
want := test.WantRefs
var got []string
for _, ref := range refs {
got = append(got, ref.DebugString())
}
sort.Strings(got)
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("wrong references\n%s", diff)
}
})
}
}

View File

@ -0,0 +1,98 @@
package globalref
import (
"context"
"path/filepath"
"testing"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configload"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/initwd"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/registry"
"github.com/zclconf/go-cty/cty"
)
func testAnalyzer(t *testing.T, fixtureName string) *Analyzer {
configDir := filepath.Join("testdata", fixtureName)
loader, cleanup := configload.NewLoaderForTests(t)
defer cleanup()
inst := initwd.NewModuleInstaller(loader.ModulesDir(), registry.NewClient(nil, nil))
_, instDiags := inst.InstallModules(context.Background(), configDir, true, initwd.ModuleInstallHooksImpl{})
if instDiags.HasErrors() {
t.Fatalf("unexpected module installation errors: %s", instDiags.Err().Error())
}
if err := loader.RefreshModules(); err != nil {
t.Fatalf("failed to refresh modules after install: %s", err)
}
cfg, loadDiags := loader.LoadConfig(configDir)
if loadDiags.HasErrors() {
t.Fatalf("unexpected configuration errors: %s", loadDiags.Error())
}
resourceTypeSchema := &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"string": {Type: cty.String, Optional: true},
"number": {Type: cty.Number, Optional: true},
"any": {Type: cty.DynamicPseudoType, Optional: true},
},
BlockTypes: map[string]*configschema.NestedBlock{
"single": {
Nesting: configschema.NestingSingle,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"z": {Type: cty.String, Optional: true},
},
},
},
"group": {
Nesting: configschema.NestingGroup,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"z": {Type: cty.String, Optional: true},
},
},
},
"list": {
Nesting: configschema.NestingList,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"z": {Type: cty.String, Optional: true},
},
},
},
"map": {
Nesting: configschema.NestingMap,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"z": {Type: cty.String, Optional: true},
},
},
},
"set": {
Nesting: configschema.NestingSet,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"z": {Type: cty.String, Optional: true},
},
},
},
},
}
schemas := map[addrs.Provider]*providers.Schemas{
addrs.MustParseProviderSourceString("hashicorp/test"): {
ResourceTypes: map[string]*configschema.Block{
"test_thing": resourceTypeSchema,
},
DataSources: map[string]*configschema.Block{
"test_thing": resourceTypeSchema,
},
},
}
return NewAnalyzer(cfg, schemas)
}

View File

@ -0,0 +1,9 @@
// Package globalref is home to some analysis algorithms that aim to answer
// questions about references between objects and object attributes across
// an entire configuration.
//
// This is a different problem than references within a single module, which
// we handle using some relatively simpler functions in the "lang" package
// in the parent directory. The globalref algorithms are often implemented
// in terms of those module-local reference-checking functions.
package globalref

View File

@ -0,0 +1,200 @@
package globalref
import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
)
// Reference combines an addrs.Reference with the address of the module
// instance or resource instance where it was found.
//
// Because of the design of the Terraform language, our main model of
// references only captures the module-local part of the reference and assumes
// that it's always clear from context which module a reference belongs to.
// That's not true for globalref because our whole purpose is to work across
// module boundaries, and so this package in particular has its own
// representation of references.
type Reference struct {
// ContainerAddr is always either addrs.ModuleInstance or
// addrs.AbsResourceInstance. The latter is required if LocalRef's
// subject is either an addrs.CountAddr or addrs.ForEachAddr, so
// we can know which resource's repetition expression it's
// referring to.
ContainerAddr addrs.Targetable
// LocalRef is a reference that would be resolved in the context
// of the module instance or resource instance given in ContainerAddr.
LocalRef *addrs.Reference
}
func absoluteRef(containerAddr addrs.Targetable, localRef *addrs.Reference) Reference {
ret := Reference{
ContainerAddr: containerAddr,
LocalRef: localRef,
}
// For simplicity's sake, we always reduce the ContainerAddr to be
// just the module address unless it's a count.index, each.key, or
// each.value reference, because for anything else it's immaterial
// which resource it belongs to.
switch localRef.Subject.(type) {
case addrs.CountAttr, addrs.ForEachAttr:
// nothing to do
default:
ret.ContainerAddr = ret.ModuleAddr()
}
return ret
}
func absoluteRefs(containerAddr addrs.Targetable, refs []*addrs.Reference) []Reference {
if len(refs) == 0 {
return nil
}
ret := make([]Reference, len(refs))
for i, ref := range refs {
ret[i] = absoluteRef(containerAddr, ref)
}
return ret
}
// ModuleAddr returns the address of the module where the reference would
// be resolved.
//
// This is either ContainerAddr directly if it's already just a module
// instance, or the module instance part of it if it's a resource instance.
func (r Reference) ModuleAddr() addrs.ModuleInstance {
switch addr := r.ContainerAddr.(type) {
case addrs.ModuleInstance:
return addr
case addrs.AbsResourceInstance:
return addr.Module
default:
// NOTE: We're intentionally using only a subset of possible
// addrs.Targetable implementations here, so anything else
// is invalid.
panic(fmt.Sprintf("reference has invalid container address type %T", addr))
}
}
// ResourceInstance returns the address of the resource where the reference
// would be resolved, if there is one.
//
// Because not all references belong to resources, the extra boolean return
// value indicates whether the returned address is valid.
func (r Reference) ResourceInstance() (addrs.AbsResourceInstance, bool) {
switch container := r.ContainerAddr.(type) {
case addrs.ModuleInstance:
moduleInstance := container
switch ref := r.LocalRef.Subject.(type) {
case addrs.Resource:
return ref.Instance(addrs.NoKey).Absolute(moduleInstance), true
case addrs.ResourceInstance:
return ref.Absolute(moduleInstance), true
}
return addrs.AbsResourceInstance{}, false
case addrs.AbsResourceInstance:
return container, true
default:
// NOTE: We're intentionally using only a subset of possible
// addrs.Targetable implementations here, so anything else
// is invalid.
panic(fmt.Sprintf("reference has invalid container address type %T", container))
}
}
// DebugString returns an internal (but still somewhat Terraform-language-like)
// compact string representation of the reciever, which isn't an address that
// any of our usual address parsers could accept but still captures the
// essence of what the reference represents.
//
// The DebugString result is not suitable for end-user-oriented messages.
//
// DebugString is also not suitable for use as a unique key for a reference,
// because it's ambiguous (between a no-key resource instance and a resource)
// and because it discards the source location information in the LocalRef.
func (r Reference) DebugString() string {
// As the doc comment insinuates, we don't have any real syntax for
// "absolute references": references are always local, and targets are
// always absolute but only include modules and resources.
return r.ContainerAddr.String() + "::" + r.LocalRef.DisplayString()
}
// ResourceAttr converts the Reference value to a more specific ResourceAttr
// value.
//
// Because not all references belong to resources, the extra boolean return
// value indicates whether the returned address is valid.
func (r Reference) ResourceAttr() (ResourceAttr, bool) {
res, ok := r.ResourceInstance()
if !ok {
return ResourceAttr{}, ok
}
traversal := r.LocalRef.Remaining
path := make(cty.Path, len(traversal))
for si, step := range traversal {
switch ts := step.(type) {
case hcl.TraverseRoot:
path[si] = cty.GetAttrStep{
Name: ts.Name,
}
case hcl.TraverseAttr:
path[si] = cty.GetAttrStep{
Name: ts.Name,
}
case hcl.TraverseIndex:
path[si] = cty.IndexStep{
Key: ts.Key,
}
default:
panic(fmt.Sprintf("unsupported traversal step %#v", step))
}
}
return ResourceAttr{
Resource: res,
Attr: path,
}, true
}
// addrKey returns the referenceAddrKey value for the item that
// this reference refers to, discarding any source location information.
//
// See the referenceAddrKey doc comment for more information on what this
// is suitable for.
func (r Reference) addrKey() referenceAddrKey {
// This is a pretty arbitrary bunch of stuff. We include the type here
// just to differentiate between no-key resource instances and resources.
return referenceAddrKey(fmt.Sprintf("%s(%T)%s", r.ContainerAddr.String(), r.LocalRef.Subject, r.LocalRef.DisplayString()))
}
// referenceAddrKey is a special string type which conventionally contains
// a unique string representation of the object that a reference refers to,
// although not of the reference itself because it ignores the information
// that would differentiate two different references to the same object.
//
// The actual content of a referenceAddrKey is arbitrary, for internal use
// only. and subject to change in future. We use a named type here only to
// make it easier to see when we're intentionally using strings to uniquely
// identify absolute reference addresses.
type referenceAddrKey string
// ResourceAttr represents a global resource and attribute reference.
// This is a more specific form of the Reference type since it can only refer
// to a specific AbsResource and one of its attributes.
type ResourceAttr struct {
Resource addrs.AbsResourceInstance
Attr cty.Path
}
func (r ResourceAttr) DebugString() string {
return r.Resource.String() + tfdiags.FormatCtyPath(r.Attr)
}

View File

@ -0,0 +1,47 @@
locals {
a = "hello world"
b = 2
single = test_thing.single.id
}
resource "test_thing" "single" {
string = local.a
number = local.b
}
resource "test_thing" "for_each" {
for_each = {"q": local.a}
string = local.a
single {
z = test_thing.single.string
}
}
resource "test_thing" "count" {
for_each = length(local.a)
string = local.a
}
module "single" {
source = "./child"
a = test_thing.single
}
module "for_each" {
source = "./child"
for_each = {"q": test_thing.single}
a = test_thing.single
}
module "count" {
source = "./child"
count = length(test_thing.single.string)
a = test_thing.single
}

View File

@ -0,0 +1,13 @@
variable "a" {
}
resource "test_thing" "foo" {
string = var.a
}
output "a" {
value = {
a = var.a
foo = test_thing.foo
}
}

View File

@ -0,0 +1,53 @@
variable "network" {
type = object({
vpc_id = string
subnet_ids = map(string)
})
}
resource "test_thing" "controller" {
for_each = var.network.subnet_ids
string = each.value
}
locals {
workers = flatten([
for k, id in var.network_subnet_ids : [
for n in range(3) : {
unique_key = "${k}:${n}"
subnet_id = n
}
]
])
controllers = test_thing.controller
}
resource "test_thing" "worker" {
for_each = { for o in local.workers : o.unique_key => o.subnet_id }
string = each.value
dynamic "list" {
for_each = test_thing.controller
content {
z = list.value.string
}
}
}
resource "test_thing" "load_balancer" {
string = var.network.vpc_id
dynamic "list" {
for_each = local.controllers
content {
z = list.value.string
}
}
}
output "compuneetees_api_url" {
value = test_thing.load_balancer.string
}

View File

@ -0,0 +1,28 @@
variable "environment" {
type = string
}
data "test_thing" "environment" {
string = var.environment
}
module "network" {
source = "./network"
base_cidr_block = data.test_thing.environment.any.base_cidr_block
subnet_count = data.test_thing.environment.any.subnet_count
}
module "compute" {
source = "./compute"
network = module.network
}
output "network" {
value = module.network
}
output "c10s_url" {
value = module.compute.compuneetees_api_url
}

View File

@ -0,0 +1,41 @@
variable "base_cidr_block" {
type = string
}
variable "subnet_count" {
type = number
}
locals {
subnet_newbits = log(var.subnet_count, 2)
subnet_cidr_blocks = toset([
for n in range(var.subnet_count) : cidrsubnet(var.base_cidr_block, local.subnet_newbits, n)
])
}
resource "test_thing" "vpc" {
string = var.base_cidr_block
}
resource "test_thing" "subnet" {
for_each = local.subnet_cidr_blocks
string = test_thing.vpc.string
single {
z = each.value
}
}
resource "test_thing" "route_table" {
for_each = local.subnet_cidr_blocks
string = each.value
}
output "vpc_id" {
value = test_thing.vpc.string
}
output "subnet_ids" {
value = { for k, sn in test_thing.subnet : k => sn.string }
}

View File

@ -234,6 +234,10 @@ func (rc *ResourceInstanceChange) Encode(ty cty.Type) (*ResourceInstanceChangeSr
}, err
}
func (rc *ResourceInstanceChange) Moved() bool {
return !rc.Addr.Equal(rc.PrevRunAddr)
}
// Simplify will, where possible, produce a change with a simpler action than
// the receiever given a flag indicating whether the caller is dealing with
// a normal apply or a destroy. This flag deals with the fact that Terraform

View File

@ -255,6 +255,9 @@ type Plan struct {
// Backend is a description of the backend configuration and other related
// settings at the time the plan was created.
Backend *Backend `protobuf:"bytes,13,opt,name=backend,proto3" json:"backend,omitempty"`
// RelevantAttributes lists individual resource attributes from
// ResourceDrift which may have contributed to the plan changes.
RelevantAttributes []*PlanResourceAttr `protobuf:"bytes,15,rep,name=relevant_attributes,json=relevantAttributes,proto3" json:"relevant_attributes,omitempty"`
}
func (x *Plan) Reset() {
@ -359,6 +362,13 @@ func (x *Plan) GetBackend() *Backend {
return nil
}
func (x *Plan) GetRelevantAttributes() []*PlanResourceAttr {
if x != nil {
return x.RelevantAttributes
}
return nil
}
// Backend is a description of backend configuration and other related settings.
type Backend struct {
state protoimpl.MessageState
@ -820,6 +830,61 @@ func (x *Path) GetSteps() []*Path_Step {
return nil
}
type PlanResourceAttr struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Resource string `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"`
Attr *Path `protobuf:"bytes,2,opt,name=attr,proto3" json:"attr,omitempty"`
}
func (x *PlanResourceAttr) Reset() {
*x = PlanResourceAttr{}
if protoimpl.UnsafeEnabled {
mi := &file_planfile_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PlanResourceAttr) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PlanResourceAttr) ProtoMessage() {}
func (x *PlanResourceAttr) ProtoReflect() protoreflect.Message {
mi := &file_planfile_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PlanResourceAttr.ProtoReflect.Descriptor instead.
func (*PlanResourceAttr) Descriptor() ([]byte, []int) {
return file_planfile_proto_rawDescGZIP(), []int{0, 1}
}
func (x *PlanResourceAttr) GetResource() string {
if x != nil {
return x.Resource
}
return ""
}
func (x *PlanResourceAttr) GetAttr() *Path {
if x != nil {
return x.Attr
}
return nil
}
type Path_Step struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -834,7 +899,7 @@ type Path_Step struct {
func (x *Path_Step) Reset() {
*x = Path_Step{}
if protoimpl.UnsafeEnabled {
mi := &file_planfile_proto_msgTypes[8]
mi := &file_planfile_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -847,7 +912,7 @@ func (x *Path_Step) String() string {
func (*Path_Step) ProtoMessage() {}
func (x *Path_Step) ProtoReflect() protoreflect.Message {
mi := &file_planfile_proto_msgTypes[8]
mi := &file_planfile_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -908,7 +973,7 @@ var File_planfile_proto protoreflect.FileDescriptor
var file_planfile_proto_rawDesc = []byte{
0x0a, 0x0e, 0x70, 0x6c, 0x61, 0x6e, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x12, 0x06, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x22, 0xd0, 0x04, 0x0a, 0x04, 0x50, 0x6c, 0x61,
0x12, 0x06, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x22, 0xec, 0x05, 0x0a, 0x04, 0x50, 0x6c, 0x61,
0x6e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01,
0x28, 0x04, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x07, 0x75,
0x69, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0c, 0x2e, 0x74,
@ -940,108 +1005,118 @@ var file_planfile_proto_rawDesc = []byte{
0x61, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x0a, 0x07,
0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e,
0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x52, 0x07,
0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x1a, 0x52, 0x0a, 0x0e, 0x56, 0x61, 0x72, 0x69, 0x61,
0x62, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66, 0x70,
0x6c, 0x61, 0x6e, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65,
0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x69, 0x0a, 0x07, 0x42,
0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x06, 0x63, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66, 0x70,
0x6c, 0x61, 0x6e, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65,
0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x77, 0x6f, 0x72, 0x6b,
0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x77, 0x6f, 0x72,
0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0xe4, 0x01, 0x0a, 0x06, 0x43, 0x68, 0x61, 0x6e, 0x67,
0x65, 0x12, 0x26, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0e, 0x32, 0x0e, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x06, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66, 0x70, 0x6c,
0x61, 0x6e, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52,
0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x42, 0x0a, 0x16, 0x62, 0x65, 0x66, 0x6f, 0x72,
0x65, 0x5f, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68,
0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e,
0x2e, 0x50, 0x61, 0x74, 0x68, 0x52, 0x14, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x53, 0x65, 0x6e,
0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x50, 0x61, 0x74, 0x68, 0x73, 0x12, 0x40, 0x0a, 0x15, 0x61,
0x66, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x70,
0x61, 0x74, 0x68, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x66, 0x70,
0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x52, 0x13, 0x61, 0x66, 0x74, 0x65, 0x72, 0x53,
0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x50, 0x61, 0x74, 0x68, 0x73, 0x22, 0xd3, 0x02,
0x0a, 0x16, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e,
0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72,
0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x22, 0x0a, 0x0d,
0x70, 0x72, 0x65, 0x76, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0e, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x65, 0x76, 0x52, 0x75, 0x6e, 0x41, 0x64, 0x64, 0x72,
0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18,
0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x4b, 0x65,
0x79, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x08, 0x20,
0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x26, 0x0a,
0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e,
0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x06, 0x63,
0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65,
0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12,
0x37, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x70, 0x6c,
0x61, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x66, 0x70, 0x6c,
0x61, 0x6e, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65,
0x64, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x12, 0x49, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x24, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52,
0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x0c, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x61,
0x73, 0x6f, 0x6e, 0x22, 0x68, 0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x43, 0x68, 0x61,
0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e,
0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12,
0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01,
0x28, 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x22, 0x28, 0x0a,
0x0c, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a,
0x07, 0x6d, 0x73, 0x67, 0x70, 0x61, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07,
0x6d, 0x73, 0x67, 0x70, 0x61, 0x63, 0x6b, 0x22, 0xa5, 0x01, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68,
0x12, 0x27, 0x0a, 0x05, 0x73, 0x74, 0x65, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x11, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x2e, 0x53, 0x74,
0x65, 0x70, 0x52, 0x05, 0x73, 0x74, 0x65, 0x70, 0x73, 0x1a, 0x74, 0x0a, 0x04, 0x53, 0x74, 0x65,
0x70, 0x12, 0x27, 0x0a, 0x0e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x6e,
0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x74, 0x74,
0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x0b, 0x65, 0x6c,
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x14, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63,
0x56, 0x61, 0x6c, 0x75, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74,
0x4b, 0x65, 0x79, 0x42, 0x0a, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2a,
0x31, 0x0a, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x4f, 0x52, 0x4d, 0x41,
0x4c, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x01,
0x12, 0x10, 0x0a, 0x0c, 0x52, 0x45, 0x46, 0x52, 0x45, 0x53, 0x48, 0x5f, 0x4f, 0x4e, 0x4c, 0x59,
0x10, 0x02, 0x2a, 0x70, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04,
0x4e, 0x4f, 0x4f, 0x50, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45,
0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x52, 0x45, 0x41, 0x44, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06,
0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x45, 0x4c, 0x45,
0x54, 0x45, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x54,
0x48, 0x45, 0x4e, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12,
0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4e, 0x5f, 0x44, 0x45, 0x4c, 0x45,
0x54, 0x45, 0x10, 0x07, 0x2a, 0xa7, 0x02, 0x0a, 0x1c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52,
0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12,
0x1b, 0x0a, 0x17, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55,
0x53, 0x45, 0x5f, 0x54, 0x41, 0x49, 0x4e, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12,
0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, 0x42, 0x59, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45,
0x53, 0x54, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f,
0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x43, 0x41, 0x4e, 0x4e, 0x4f, 0x54, 0x5f, 0x55,
0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x03, 0x12, 0x25, 0x0a, 0x21, 0x44, 0x45, 0x4c, 0x45, 0x54,
0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x45, 0x53,
0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x10, 0x04, 0x12, 0x23,
0x0a, 0x1f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45,
0x5f, 0x57, 0x52, 0x4f, 0x4e, 0x47, 0x5f, 0x52, 0x45, 0x50, 0x45, 0x54, 0x49, 0x54, 0x49, 0x4f,
0x4e, 0x10, 0x05, 0x12, 0x1e, 0x0a, 0x1a, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45,
0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x49, 0x4e, 0x44, 0x45,
0x58, 0x10, 0x06, 0x12, 0x1b, 0x0a, 0x17, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45,
0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x45, 0x41, 0x43, 0x48, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x07,
0x12, 0x1c, 0x0a, 0x18, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55,
0x53, 0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x4d, 0x4f, 0x44, 0x55, 0x4c, 0x45, 0x10, 0x08, 0x42, 0x42,
0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73,
0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x74, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d,
0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x2f,
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x4b, 0x0a, 0x13, 0x72, 0x65, 0x6c, 0x65, 0x76,
0x61, 0x6e, 0x74, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0f,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x6c,
0x61, 0x6e, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x72,
0x52, 0x12, 0x72, 0x65, 0x6c, 0x65, 0x76, 0x61, 0x6e, 0x74, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62,
0x75, 0x74, 0x65, 0x73, 0x1a, 0x52, 0x0a, 0x0e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65,
0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e,
0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4d, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x20, 0x0a, 0x04, 0x61, 0x74, 0x74, 0x72, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x61, 0x74,
0x68, 0x52, 0x04, 0x61, 0x74, 0x74, 0x72, 0x22, 0x69, 0x0a, 0x07, 0x42, 0x61, 0x63, 0x6b, 0x65,
0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e,
0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x63, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61,
0x63, 0x65, 0x22, 0xe4, 0x01, 0x0a, 0x06, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x26, 0x0a,
0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e,
0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61,
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18,
0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x44,
0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x73, 0x12, 0x42, 0x0a, 0x16, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x5f, 0x73, 0x65,
0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x03, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x61, 0x74,
0x68, 0x52, 0x14, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x53, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69,
0x76, 0x65, 0x50, 0x61, 0x74, 0x68, 0x73, 0x12, 0x40, 0x0a, 0x15, 0x61, 0x66, 0x74, 0x65, 0x72,
0x5f, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73,
0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e,
0x50, 0x61, 0x74, 0x68, 0x52, 0x13, 0x61, 0x66, 0x74, 0x65, 0x72, 0x53, 0x65, 0x6e, 0x73, 0x69,
0x74, 0x69, 0x76, 0x65, 0x50, 0x61, 0x74, 0x68, 0x73, 0x22, 0xd3, 0x02, 0x0a, 0x16, 0x52, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x68,
0x61, 0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0d, 0x20, 0x01,
0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x70, 0x72, 0x65, 0x76,
0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0b, 0x70, 0x72, 0x65, 0x76, 0x52, 0x75, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1f, 0x0a, 0x0b,
0x64, 0x65, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a,
0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52,
0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x06, 0x63, 0x68, 0x61,
0x6e, 0x67, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x66, 0x70, 0x6c,
0x61, 0x6e, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67,
0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x0a, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x37, 0x0a, 0x10, 0x72,
0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x18,
0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50,
0x61, 0x74, 0x68, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x70,
0x6c, 0x61, 0x63, 0x65, 0x12, 0x49, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72,
0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x74, 0x66,
0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73,
0x74, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x61, 0x73, 0x6f,
0x6e, 0x52, 0x0c, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22,
0x68, 0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12,
0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
0x61, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43, 0x68, 0x61,
0x6e, 0x67, 0x65, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73,
0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09,
0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x22, 0x28, 0x0a, 0x0c, 0x44, 0x79, 0x6e,
0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x73, 0x67,
0x70, 0x61, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x73, 0x67, 0x70,
0x61, 0x63, 0x6b, 0x22, 0xa5, 0x01, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x27, 0x0a, 0x05,
0x73, 0x74, 0x65, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x66,
0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x2e, 0x53, 0x74, 0x65, 0x70, 0x52, 0x05,
0x73, 0x74, 0x65, 0x70, 0x73, 0x1a, 0x74, 0x0a, 0x04, 0x53, 0x74, 0x65, 0x70, 0x12, 0x27, 0x0a,
0x0e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75,
0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x0b, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e,
0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66,
0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75,
0x65, 0x48, 0x00, 0x52, 0x0a, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x42,
0x0a, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2a, 0x31, 0x0a, 0x04, 0x4d,
0x6f, 0x64, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x00, 0x12,
0x0b, 0x0a, 0x07, 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c,
0x52, 0x45, 0x46, 0x52, 0x45, 0x53, 0x48, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x02, 0x2a, 0x70,
0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4f, 0x50,
0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x01, 0x12, 0x08,
0x0a, 0x04, 0x52, 0x45, 0x41, 0x44, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x50, 0x44, 0x41,
0x54, 0x45, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x05,
0x12, 0x16, 0x0a, 0x12, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4e, 0x5f,
0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x43, 0x52, 0x45, 0x41,
0x54, 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4e, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x07,
0x2a, 0xa7, 0x02, 0x0a, 0x1c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73,
0x74, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x61, 0x73, 0x6f,
0x6e, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x52,
0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x54,
0x41, 0x49, 0x4e, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x45, 0x50, 0x4c,
0x41, 0x43, 0x45, 0x5f, 0x42, 0x59, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x10, 0x02,
0x12, 0x21, 0x0a, 0x1d, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41,
0x55, 0x53, 0x45, 0x5f, 0x43, 0x41, 0x4e, 0x4e, 0x4f, 0x54, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54,
0x45, 0x10, 0x03, 0x12, 0x25, 0x0a, 0x21, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45,
0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43,
0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x10, 0x04, 0x12, 0x23, 0x0a, 0x1f, 0x44, 0x45,
0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x57, 0x52, 0x4f,
0x4e, 0x47, 0x5f, 0x52, 0x45, 0x50, 0x45, 0x54, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x05, 0x12,
0x1e, 0x0a, 0x1a, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53,
0x45, 0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x10, 0x06, 0x12,
0x1b, 0x0a, 0x17, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53,
0x45, 0x5f, 0x45, 0x41, 0x43, 0x48, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x07, 0x12, 0x1c, 0x0a, 0x18,
0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x4e,
0x4f, 0x5f, 0x4d, 0x4f, 0x44, 0x55, 0x4c, 0x45, 0x10, 0x08, 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69,
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f,
0x72, 0x70, 0x2f, 0x74, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x69, 0x6e, 0x74,
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x2f, 0x69, 0x6e, 0x74, 0x65,
0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -1057,7 +1132,7 @@ func file_planfile_proto_rawDescGZIP() []byte {
}
var file_planfile_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
var file_planfile_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
var file_planfile_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_planfile_proto_goTypes = []interface{}{
(Mode)(0), // 0: tfplan.Mode
(Action)(0), // 1: tfplan.Action
@ -1070,7 +1145,8 @@ var file_planfile_proto_goTypes = []interface{}{
(*DynamicValue)(nil), // 8: tfplan.DynamicValue
(*Path)(nil), // 9: tfplan.Path
nil, // 10: tfplan.Plan.VariablesEntry
(*Path_Step)(nil), // 11: tfplan.Path.Step
(*PlanResourceAttr)(nil), // 11: tfplan.Plan.resource_attr
(*Path_Step)(nil), // 12: tfplan.Path.Step
}
var file_planfile_proto_depIdxs = []int32{
0, // 0: tfplan.Plan.ui_mode:type_name -> tfplan.Mode
@ -1079,23 +1155,25 @@ var file_planfile_proto_depIdxs = []int32{
6, // 3: tfplan.Plan.resource_drift:type_name -> tfplan.ResourceInstanceChange
7, // 4: tfplan.Plan.output_changes:type_name -> tfplan.OutputChange
4, // 5: tfplan.Plan.backend:type_name -> tfplan.Backend
8, // 6: tfplan.Backend.config:type_name -> tfplan.DynamicValue
1, // 7: tfplan.Change.action:type_name -> tfplan.Action
8, // 8: tfplan.Change.values:type_name -> tfplan.DynamicValue
9, // 9: tfplan.Change.before_sensitive_paths:type_name -> tfplan.Path
9, // 10: tfplan.Change.after_sensitive_paths:type_name -> tfplan.Path
5, // 11: tfplan.ResourceInstanceChange.change:type_name -> tfplan.Change
9, // 12: tfplan.ResourceInstanceChange.required_replace:type_name -> tfplan.Path
2, // 13: tfplan.ResourceInstanceChange.action_reason:type_name -> tfplan.ResourceInstanceActionReason
5, // 14: tfplan.OutputChange.change:type_name -> tfplan.Change
11, // 15: tfplan.Path.steps:type_name -> tfplan.Path.Step
8, // 16: tfplan.Plan.VariablesEntry.value:type_name -> tfplan.DynamicValue
8, // 17: tfplan.Path.Step.element_key:type_name -> tfplan.DynamicValue
18, // [18:18] is the sub-list for method output_type
18, // [18:18] is the sub-list for method input_type
18, // [18:18] is the sub-list for extension type_name
18, // [18:18] is the sub-list for extension extendee
0, // [0:18] is the sub-list for field type_name
11, // 6: tfplan.Plan.relevant_attributes:type_name -> tfplan.Plan.resource_attr
8, // 7: tfplan.Backend.config:type_name -> tfplan.DynamicValue
1, // 8: tfplan.Change.action:type_name -> tfplan.Action
8, // 9: tfplan.Change.values:type_name -> tfplan.DynamicValue
9, // 10: tfplan.Change.before_sensitive_paths:type_name -> tfplan.Path
9, // 11: tfplan.Change.after_sensitive_paths:type_name -> tfplan.Path
5, // 12: tfplan.ResourceInstanceChange.change:type_name -> tfplan.Change
9, // 13: tfplan.ResourceInstanceChange.required_replace:type_name -> tfplan.Path
2, // 14: tfplan.ResourceInstanceChange.action_reason:type_name -> tfplan.ResourceInstanceActionReason
5, // 15: tfplan.OutputChange.change:type_name -> tfplan.Change
12, // 16: tfplan.Path.steps:type_name -> tfplan.Path.Step
8, // 17: tfplan.Plan.VariablesEntry.value:type_name -> tfplan.DynamicValue
9, // 18: tfplan.Plan.resource_attr.attr:type_name -> tfplan.Path
8, // 19: tfplan.Path.Step.element_key:type_name -> tfplan.DynamicValue
20, // [20:20] is the sub-list for method output_type
20, // [20:20] is the sub-list for method input_type
20, // [20:20] is the sub-list for extension type_name
20, // [20:20] is the sub-list for extension extendee
0, // [0:20] is the sub-list for field type_name
}
func init() { file_planfile_proto_init() }
@ -1189,6 +1267,18 @@ func file_planfile_proto_init() {
}
}
file_planfile_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PlanResourceAttr); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_planfile_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Path_Step); i {
case 0:
return &v.state
@ -1201,7 +1291,7 @@ func file_planfile_proto_init() {
}
}
}
file_planfile_proto_msgTypes[8].OneofWrappers = []interface{}{
file_planfile_proto_msgTypes[9].OneofWrappers = []interface{}{
(*Path_Step_AttributeName)(nil),
(*Path_Step_ElementKey)(nil),
}
@ -1211,7 +1301,7 @@ func file_planfile_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_planfile_proto_rawDesc,
NumEnums: 3,
NumMessages: 9,
NumMessages: 10,
NumExtensions: 0,
NumServices: 0,
},

View File

@ -61,6 +61,15 @@ message Plan {
// Backend is a description of the backend configuration and other related
// settings at the time the plan was created.
Backend backend = 13;
message resource_attr {
string resource = 1;
Path attr= 2;
};
// RelevantAttributes lists individual resource attributes from
// ResourceDrift which may have contributed to the plan changes.
repeated resource_attr relevant_attributes = 15;
}
// Mode describes the planning mode that created the plan.

View File

@ -5,6 +5,7 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/lang/globalref"
"github.com/hashicorp/terraform/internal/states"
"github.com/zclconf/go-cty/cty"
)
@ -36,6 +37,16 @@ type Plan struct {
ForceReplaceAddrs []addrs.AbsResourceInstance
Backend Backend
// RelevantAttributes is a set of resource instance addresses and
// attributes that are either directly affected by proposed changes or may
// have indirectly contributed to them via references in expressions.
//
// This is the result of a heuristic and is intended only as a hint to
// the UI layer in case it wants to emphasize or de-emphasize certain
// resources. Don't use this to drive any non-cosmetic behavior, especially
// including anything that would be subject to compatibility constraints.
RelevantAttributes []globalref.ResourceAttr
// PrevRunState and PriorState both describe the situation that the plan
// was derived from:
//

View File

@ -8,6 +8,7 @@ import (
"google.golang.org/protobuf/proto"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/lang/globalref"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/plans/internal/planproto"
@ -107,6 +108,14 @@ func readTfplan(r io.Reader) (*plans.Plan, error) {
plan.DriftedResources = append(plan.DriftedResources, change)
}
for _, rawRA := range rawPlan.RelevantAttributes {
ra, err := resourceAttrFromTfplan(rawRA)
if err != nil {
return nil, err
}
plan.RelevantAttributes = append(plan.RelevantAttributes, ra)
}
for _, rawTargetAddr := range rawPlan.TargetAddrs {
target, diags := addrs.ParseTargetStr(rawTargetAddr)
if diags.HasErrors() {
@ -407,6 +416,14 @@ func writeTfplan(plan *plans.Plan, w io.Writer) error {
rawPlan.ResourceDrift = append(rawPlan.ResourceDrift, rawRC)
}
for _, ra := range plan.RelevantAttributes {
rawRA, err := resourceAttrToTfplan(ra)
if err != nil {
return err
}
rawPlan.RelevantAttributes = append(rawPlan.RelevantAttributes, rawRA)
}
for _, targetAddr := range plan.TargetAddrs {
rawPlan.TargetAddrs = append(rawPlan.TargetAddrs, targetAddr.String())
}
@ -445,6 +462,39 @@ func writeTfplan(plan *plans.Plan, w io.Writer) error {
return nil
}
func resourceAttrToTfplan(ra globalref.ResourceAttr) (*planproto.PlanResourceAttr, error) {
res := &planproto.PlanResourceAttr{}
res.Resource = ra.Resource.String()
attr, err := pathToTfplan(ra.Attr)
if err != nil {
return res, err
}
res.Attr = attr
return res, nil
}
func resourceAttrFromTfplan(ra *planproto.PlanResourceAttr) (globalref.ResourceAttr, error) {
var res globalref.ResourceAttr
if ra.Resource == "" {
return res, fmt.Errorf("missing resource address from relevant attribute")
}
instAddr, diags := addrs.ParseAbsResourceInstanceStr(ra.Resource)
if diags.HasErrors() {
return res, fmt.Errorf("invalid resource instance address %q in relevant attributes: %w", ra.Resource, diags.Err())
}
res.Resource = instAddr
path, err := pathFromTfplan(ra.Attr)
if err != nil {
return res, fmt.Errorf("invalid path in %q relevant attribute: %s", res.Resource, err)
}
res.Attr = path
return res, nil
}
func resourceChangeToTfplan(change *plans.ResourceInstanceChangeSrc) (*planproto.ResourceInstanceChange, error) {
ret := &planproto.ResourceInstanceChange{}

View File

@ -8,6 +8,7 @@ import (
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/lang/globalref"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/hashicorp/terraform/internal/plans"
)
@ -158,6 +159,16 @@ func TestTFPlanRoundTrip(t *testing.T) {
},
},
},
RelevantAttributes: []globalref.ResourceAttr{
{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "woot",
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
Attr: cty.GetAttrPath("boop").Index(cty.NumberIntVal(1)),
},
},
TargetAddrs: []addrs.Targetable{
addrs.Resource{
Mode: addrs.ManagedResourceMode,

View File

@ -3,7 +3,6 @@ package providers
import (
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/tfdiags"
)
@ -88,13 +87,6 @@ type GetProviderSchemaResponse struct {
Diagnostics tfdiags.Diagnostics
}
// Schema pairs a provider or resource schema with that schema's version.
// This is used to be able to upgrade the schema in UpgradeResourceState.
type Schema struct {
Version int64
Block *configschema.Block
}
type ValidateProviderConfigRequest struct {
// Config is the raw configuration value for the provider.
Config cty.Value

View File

@ -0,0 +1,62 @@
package providers
import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
)
// Schemas is an overall container for all of the schemas for all configurable
// objects defined within a particular provider.
//
// The schema for each individual configurable object is represented by nested
// instances of type Schema (singular) within this data structure.
//
// This type used to be known as terraform.ProviderSchema, but moved out here
// as part of our ongoing efforts to shrink down the "terraform" package.
// There's still a type alias at the old name, but we should prefer using
// providers.Schema in new code. However, a consequence of this transitional
// situation is that the "terraform" package still has the responsibility for
// constructing a providers.Schemas object based on responses from the provider
// API; hopefully we'll continue this refactor later so that functions in this
// package totally encapsulate the unmarshalling and include this as part of
// providers.GetProviderSchemaResponse.
type Schemas struct {
Provider *configschema.Block
ProviderMeta *configschema.Block
ResourceTypes map[string]*configschema.Block
DataSources map[string]*configschema.Block
ResourceTypeSchemaVersions map[string]uint64
}
// SchemaForResourceType attempts to find a schema for the given mode and type.
// Returns nil if no such schema is available.
func (ss *Schemas) SchemaForResourceType(mode addrs.ResourceMode, typeName string) (schema *configschema.Block, version uint64) {
switch mode {
case addrs.ManagedResourceMode:
return ss.ResourceTypes[typeName], ss.ResourceTypeSchemaVersions[typeName]
case addrs.DataResourceMode:
// Data resources don't have schema versions right now, since state is discarded for each refresh
return ss.DataSources[typeName], 0
default:
// Shouldn't happen, because the above cases are comprehensive.
return nil, 0
}
}
// SchemaForResourceAddr attempts to find a schema for the mode and type from
// the given resource address. Returns nil if no such schema is available.
func (ss *Schemas) SchemaForResourceAddr(addr addrs.Resource) (schema *configschema.Block, version uint64) {
return ss.SchemaForResourceType(addr.Mode, addr.Type)
}
// Schema pairs a provider or resource schema with that schema's version.
// This is used to be able to upgrade the schema in UpgradeResourceState.
//
// This describes the schema for a single object within a provider. Type
// "Schemas" (plural) instead represents the overall collection of schemas
// for everything within a particular provider.
type Schema struct {
Version int64
Block *configschema.Block
}

View File

@ -12,6 +12,7 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/instances"
"github.com/hashicorp/terraform/internal/lang/globalref"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/refactoring"
"github.com/hashicorp/terraform/internal/states"
@ -199,6 +200,10 @@ The -target option is not for routine use, and is provided only for exceptional
panic("nil plan but no errors")
}
relevantAttrs, rDiags := c.relevantResourceAttrsForPlan(config, plan)
diags = diags.Append(rDiags)
plan.RelevantAttributes = relevantAttrs
return plan, diags
}
@ -285,6 +290,10 @@ func (c *Context) refreshOnlyPlan(config *configs.Config, prevRunState *states.S
// objects that would need to be created.
plan.PriorState.SyncWrapper().RemovePlannedResourceInstanceObjects()
// We don't populate RelevantResources for a refresh-only plan, because
// they never have any planned actions and so no resource can ever be
// "relevant" per the intended meaning of that field.
return plan, diags
}
@ -357,6 +366,10 @@ func (c *Context) destroyPlan(config *configs.Config, prevRunState *states.State
destroyPlan.PrevRunState = pendingPlan.PrevRunState
}
relevantAttrs, rDiags := c.relevantResourceAttrsForPlan(config, destroyPlan)
diags = diags.Append(rDiags)
destroyPlan.RelevantAttributes = relevantAttrs
return destroyPlan, diags
}
@ -736,3 +749,52 @@ func blockedMovesWarningDiag(results refactoring.MoveResults) tfdiags.Diagnostic
),
)
}
// referenceAnalyzer returns a globalref.Analyzer object to help with
// global analysis of references within the configuration that's attached
// to the receiving context.
func (c *Context) referenceAnalyzer(config *configs.Config, state *states.State) (*globalref.Analyzer, tfdiags.Diagnostics) {
schemas, diags := c.Schemas(config, state)
if diags.HasErrors() {
return nil, diags
}
return globalref.NewAnalyzer(config, schemas.Providers), diags
}
// relevantResourcesForPlan implements the heuristic we use to populate the
// RelevantResources field of returned plans.
func (c *Context) relevantResourceAttrsForPlan(config *configs.Config, plan *plans.Plan) ([]globalref.ResourceAttr, tfdiags.Diagnostics) {
azr, diags := c.referenceAnalyzer(config, plan.PriorState)
if diags.HasErrors() {
return nil, diags
}
var refs []globalref.Reference
for _, change := range plan.Changes.Resources {
if change.Action == plans.NoOp {
continue
}
moreRefs := azr.ReferencesFromResourceInstance(change.Addr)
refs = append(refs, moreRefs...)
}
for _, change := range plan.Changes.Outputs {
if change.Action == plans.NoOp {
continue
}
moreRefs := azr.ReferencesFromOutputValue(change.Addr)
refs = append(refs, moreRefs...)
}
var contributors []globalref.ResourceAttr
for _, ref := range azr.ContributingResourceReferences(refs...) {
if res, ok := ref.ResourceAttr(); ok {
contributors = append(contributors, res)
}
}
return contributors, diags
}

View File

@ -12,10 +12,16 @@ import (
"github.com/hashicorp/terraform/internal/tfdiags"
)
// ProviderSchema is an alias for providers.Schemas, which is the new location
// for what we originally called terraform.ProviderSchema but which has
// moved out as part of ongoing refactoring to shrink down the main "terraform"
// package.
type ProviderSchema = providers.Schemas
// Schemas is a container for various kinds of schema that Terraform needs
// during processing.
type Schemas struct {
Providers map[addrs.Provider]*ProviderSchema
Providers map[addrs.Provider]*providers.Schemas
Provisioners map[string]*configschema.Block
}
@ -24,7 +30,7 @@ type Schemas struct {
//
// It's usually better to go use the more precise methods offered by type
// Schemas to handle this detail automatically.
func (ss *Schemas) ProviderSchema(provider addrs.Provider) *ProviderSchema {
func (ss *Schemas) ProviderSchema(provider addrs.Provider) *providers.Schemas {
if ss.Providers == nil {
return nil
}
@ -76,7 +82,7 @@ func (ss *Schemas) ProvisionerConfig(name string) *configschema.Block {
// still valid but may be incomplete.
func loadSchemas(config *configs.Config, state *states.State, plugins *contextPlugins) (*Schemas, error) {
schemas := &Schemas{
Providers: map[addrs.Provider]*ProviderSchema{},
Providers: map[addrs.Provider]*providers.Schemas{},
Provisioners: map[string]*configschema.Block{},
}
var diags tfdiags.Diagnostics
@ -89,7 +95,7 @@ func loadSchemas(config *configs.Config, state *states.State, plugins *contextPl
return schemas, diags.Err()
}
func loadProviderSchemas(schemas map[addrs.Provider]*ProviderSchema, config *configs.Config, state *states.State, plugins *contextPlugins) tfdiags.Diagnostics {
func loadProviderSchemas(schemas map[addrs.Provider]*providers.Schemas, config *configs.Config, state *states.State, plugins *contextPlugins) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
ensure := func(fqn addrs.Provider) {
@ -105,7 +111,7 @@ func loadProviderSchemas(schemas map[addrs.Provider]*ProviderSchema, config *con
// We'll put a stub in the map so we won't re-attempt this on
// future calls, which would then repeat the same error message
// multiple times.
schemas[fqn] = &ProviderSchema{}
schemas[fqn] = &providers.Schemas{}
diags = diags.Append(
tfdiags.Sourceless(
tfdiags.Error,
@ -179,39 +185,3 @@ func loadProvisionerSchemas(schemas map[string]*configschema.Block, config *conf
return diags
}
// ProviderSchema represents the schema for a provider's own configuration
// and the configuration for some or all of its resources and data sources.
//
// The completeness of this structure depends on how it was constructed.
// When constructed for a configuration, it will generally include only
// resource types and data sources used by that configuration.
type ProviderSchema struct {
Provider *configschema.Block
ProviderMeta *configschema.Block
ResourceTypes map[string]*configschema.Block
DataSources map[string]*configschema.Block
ResourceTypeSchemaVersions map[string]uint64
}
// SchemaForResourceType attempts to find a schema for the given mode and type.
// Returns nil if no such schema is available.
func (ps *ProviderSchema) SchemaForResourceType(mode addrs.ResourceMode, typeName string) (schema *configschema.Block, version uint64) {
switch mode {
case addrs.ManagedResourceMode:
return ps.ResourceTypes[typeName], ps.ResourceTypeSchemaVersions[typeName]
case addrs.DataResourceMode:
// Data resources don't have schema versions right now, since state is discarded for each refresh
return ps.DataSources[typeName], 0
default:
// Shouldn't happen, because the above cases are comprehensive.
return nil, 0
}
}
// SchemaForResourceAddr attempts to find a schema for the mode and type from
// the given resource address. Returns nil if no such schema is available.
func (ps *ProviderSchema) SchemaForResourceAddr(addr addrs.Resource) (schema *configschema.Block, version uint64) {
return ps.SchemaForResourceType(addr.Mode, addr.Type)
}