diff --git a/internal/command/format/diff.go b/internal/command/format/diff.go index e111485e0..c70918af4 100644 --- a/internal/command/format/diff.go +++ b/internal/command/format/diff.go @@ -20,6 +20,21 @@ import ( "github.com/hashicorp/terraform/internal/states" ) +// DiffLanguage controls the description of the resource change reasons. +type DiffLanguage rune + +//go:generate go run golang.org/x/tools/cmd/stringer -type=DiffLanguage diff.go + +const ( + // DiffLanguageProposedChange indicates that the change is one which is + // planned to be applied. + DiffLanguageProposedChange DiffLanguage = 'P' + + // DiffLanguageDetectedDrift indicates that the change is detected drift + // from the configuration. + DiffLanguageDetectedDrift DiffLanguage = 'D' +) + // ResourceChange returns a string representation of a change to a particular // resource, for inclusion in user-facing plan output. // @@ -33,6 +48,7 @@ func ResourceChange( change *plans.ResourceInstanceChangeSrc, schema *configschema.Block, color *colorstring.Colorize, + language DiffLanguage, ) string { addr := change.Addr var buf bytes.Buffer @@ -52,29 +68,43 @@ func ResourceChange( switch change.Action { case plans.Create: - buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] will be created", dispAddr))) + buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] will be created"), dispAddr)) case plans.Read: - buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] will be read during apply\n # (config refers to values not yet known)", dispAddr))) + buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] will be read during apply\n # (config refers to values not yet known)"), dispAddr)) case plans.Update: - buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] will be updated in-place", dispAddr))) + switch language { + case DiffLanguageProposedChange: + buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] will be updated in-place"), dispAddr)) + case DiffLanguageDetectedDrift: + buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] has changed"), dispAddr)) + default: + buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] update (unknown reason %s)"), dispAddr, language)) + } case plans.CreateThenDelete, plans.DeleteThenCreate: switch change.ActionReason { case plans.ResourceInstanceReplaceBecauseTainted: - buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] is tainted, so must be [bold][red]replaced", dispAddr))) + buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] is tainted, so must be [bold][red]replaced"), dispAddr)) case plans.ResourceInstanceReplaceByRequest: - buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] will be [bold][red]replaced[reset], as requested", dispAddr))) + buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] will be [bold][red]replaced[reset], as requested"), dispAddr)) default: - buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] must be [bold][red]replaced", dispAddr))) + buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] must be [bold][red]replaced"), dispAddr)) } case plans.Delete: - buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] will be [bold][red]destroyed", dispAddr))) + switch language { + case DiffLanguageProposedChange: + buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] will be [bold][red]destroyed"), dispAddr)) + case DiffLanguageDetectedDrift: + buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] has been deleted"), dispAddr)) + default: + buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] delete (unknown reason %s)"), dispAddr, language)) + } if change.DeposedKey != states.NotDeposed { // Some extra context about this unusual situation. buf.WriteString(color.Color("\n # (left over from a partially-failed replacement of this instance)")) } case plans.NoOp: if change.Moved() { - buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] has moved to [bold]%s[reset]", change.PrevRunAddr.String(), dispAddr))) + buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] has moved to [bold]%s[reset]"), change.PrevRunAddr.String(), dispAddr)) break } fallthrough @@ -85,7 +115,7 @@ func ResourceChange( buf.WriteString(color.Color("[reset]\n")) if change.Moved() && change.Action != plans.NoOp { - buf.WriteString(color.Color(fmt.Sprintf("[bold] # [reset]([bold]%s[reset] has moved to [bold]%s[reset])\n", change.PrevRunAddr.String(), dispAddr))) + buf.WriteString(fmt.Sprintf(color.Color("[bold] # [reset]([bold]%s[reset] has moved to [bold]%s[reset])\n"), change.PrevRunAddr.String(), dispAddr)) } if change.Moved() && change.Action == plans.NoOp { @@ -154,147 +184,6 @@ func ResourceChange( return buf.String() } -// ResourceInstanceDrift returns a string representation of a change to a -// particular resource instance that was made outside of Terraform, for -// reporting a change that has already happened rather than one that is planned. -// -// The the two resource instances have equal current objects then the result -// will be an empty string to indicate that there is no drift to render. -// -// The resource schema must be provided along with the change so that the -// formatted change can reflect the configuration structure for the associated -// resource. -// -// If "color" is non-nil, it will be used to color the result. Otherwise, -// no color codes will be included. -func ResourceInstanceDrift( - addr addrs.AbsResourceInstance, - before, after *states.ResourceInstance, - schema *configschema.Block, - color *colorstring.Colorize, -) string { - var buf bytes.Buffer - - if color == nil { - color = &colorstring.Colorize{ - Colors: colorstring.DefaultColors, - Disable: true, - Reset: false, - } - } - - dispAddr := addr.String() - action := plans.Update - - switch { - case before == nil || before.Current == nil: - // before should never be nil, but before.Current can be if the - // instance was deposed. There is nothing to render for a deposed - // instance, since we intend to remove it. - return "" - - case after == nil || after.Current == nil: - // The object was deleted - buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] has been deleted", dispAddr))) - action = plans.Delete - default: - // The object was changed - buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] has been changed", dispAddr))) - } - - buf.WriteString(color.Color("[reset]\n")) - - buf.WriteString(color.Color(DiffActionSymbol(action)) + " ") - - switch addr.Resource.Resource.Mode { - case addrs.ManagedResourceMode: - buf.WriteString(fmt.Sprintf( - "resource %q %q", - addr.Resource.Resource.Type, - addr.Resource.Resource.Name, - )) - case addrs.DataResourceMode: - buf.WriteString(fmt.Sprintf( - "data %q %q ", - addr.Resource.Resource.Type, - addr.Resource.Resource.Name, - )) - default: - // should never happen, since the above is exhaustive - buf.WriteString(addr.String()) - } - - buf.WriteString(" {") - - p := blockBodyDiffPrinter{ - buf: &buf, - color: color, - action: action, - } - - // Most commonly-used resources have nested blocks that result in us - // going at least three traversals deep while we recurse here, so we'll - // start with that much capacity and then grow as needed for deeper - // structures. - path := make(cty.Path, 0, 3) - - ty := schema.ImpliedType() - - var err error - var oldObj, newObj *states.ResourceInstanceObject - oldObj, err = before.Current.Decode(ty) - if err != nil { - // We shouldn't encounter errors here because Terraform Core should've - // made sure that the previous run object conforms to the current - // schema by having the provider upgrade it, but we'll be robust here - // in case there are some edges we didn't find yet. - return fmt.Sprintf(" # %s previous run state doesn't conform to current schema; this is a Terraform bug\n # %s\n", addr, err) - } - if after != nil && after.Current != nil { - newObj, err = after.Current.Decode(ty) - if err != nil { - // We shouldn't encounter errors here because Terraform Core should've - // made sure that the prior state object conforms to the current - // schema by having the provider upgrade it, even if we skipped - // refreshing on this run, but we'll be robust here in case there are - // some edges we didn't find yet. - return fmt.Sprintf(" # %s refreshed state doesn't conform to current schema; this is a Terraform bug\n # %s\n", addr, err) - } - } - - oldVal := oldObj.Value - var newVal cty.Value - if newObj != nil { - newVal = newObj.Value - } else { - newVal = cty.NullVal(ty) - } - - if newVal.RawEquals(oldVal) { - // Nothing to show, then. - return "" - } - - // 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.) - oldVal = objchange.NormalizeObjectFromLegacySDK(oldVal, schema) - newVal = objchange.NormalizeObjectFromLegacySDK(newVal, schema) - - result := p.writeBlockBodyDiff(schema, oldVal, newVal, 6, path) - if result.bodyWritten { - buf.WriteString("\n") - buf.WriteString(strings.Repeat(" ", 4)) - } - buf.WriteString("}\n") - - return buf.String() -} - // OutputChanges returns a string representation of a set of changes to output // values for inclusion in user-facing plan output. // @@ -401,7 +290,7 @@ func (p *blockBodyDiffPrinter) writeBlockBodyDiff(schema *configschema.Block, ol } p.buf.WriteString("\n") p.buf.WriteString(strings.Repeat(" ", indent+2)) - p.buf.WriteString(p.color.Color(fmt.Sprintf("[dark_gray]# (%d unchanged %s hidden)[reset]", result.skippedBlocks, noun))) + p.buf.WriteString(fmt.Sprintf(p.color.Color("[dark_gray]# (%d unchanged %s hidden)[reset]"), result.skippedBlocks, noun)) } } @@ -1421,7 +1310,7 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa if suppressedElements == 1 { noun = "element" } - p.buf.WriteString(p.color.Color(fmt.Sprintf("[dark_gray]# (%d unchanged %s hidden)[reset]", suppressedElements, noun))) + p.buf.WriteString(fmt.Sprintf(p.color.Color("[dark_gray]# (%d unchanged %s hidden)[reset]"), suppressedElements, noun)) p.buf.WriteString("\n") } @@ -1482,7 +1371,7 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa if hidden == 1 { noun = "element" } - p.buf.WriteString(p.color.Color(fmt.Sprintf("[dark_gray]# (%d unchanged %s hidden)[reset]", hidden, noun))) + p.buf.WriteString(fmt.Sprintf(p.color.Color("[dark_gray]# (%d unchanged %s hidden)[reset]"), hidden, noun)) p.buf.WriteString("\n") } @@ -1624,7 +1513,7 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa if suppressedElements == 1 { noun = "element" } - p.buf.WriteString(p.color.Color(fmt.Sprintf("[dark_gray]# (%d unchanged %s hidden)[reset]", suppressedElements, noun))) + p.buf.WriteString(fmt.Sprintf(p.color.Color("[dark_gray]# (%d unchanged %s hidden)[reset]"), suppressedElements, noun)) p.buf.WriteString("\n") } @@ -1716,7 +1605,7 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa if suppressedElements == 1 { noun = "element" } - p.buf.WriteString(p.color.Color(fmt.Sprintf("[dark_gray]# (%d unchanged %s hidden)[reset]", suppressedElements, noun))) + p.buf.WriteString(fmt.Sprintf(p.color.Color("[dark_gray]# (%d unchanged %s hidden)[reset]"), suppressedElements, noun)) p.buf.WriteString("\n") } @@ -1789,7 +1678,7 @@ func (p *blockBodyDiffPrinter) writeSensitivityWarning(old, new cty.Value, inden if new.HasMark(marks.Sensitive) && !old.HasMark(marks.Sensitive) { p.buf.WriteString(strings.Repeat(" ", indent)) - p.buf.WriteString(p.color.Color(fmt.Sprintf("# [yellow]Warning:[reset] this %s will be marked as sensitive and will not\n", diffType))) + p.buf.WriteString(fmt.Sprintf(p.color.Color("# [yellow]Warning:[reset] this %s will be marked as sensitive and will not\n"), diffType)) p.buf.WriteString(strings.Repeat(" ", indent)) p.buf.WriteString(fmt.Sprintf("# display in UI output after applying this change.%s\n", valueUnchangedSuffix)) } @@ -1797,7 +1686,7 @@ func (p *blockBodyDiffPrinter) writeSensitivityWarning(old, new cty.Value, inden // Note if changing this attribute will change its sensitivity if old.HasMark(marks.Sensitive) && !new.HasMark(marks.Sensitive) { p.buf.WriteString(strings.Repeat(" ", indent)) - p.buf.WriteString(p.color.Color(fmt.Sprintf("# [yellow]Warning:[reset] this %s will no longer be marked as sensitive\n", diffType))) + p.buf.WriteString(fmt.Sprintf(p.color.Color("# [yellow]Warning:[reset] this %s will no longer be marked as sensitive\n"), diffType)) p.buf.WriteString(strings.Repeat(" ", indent)) p.buf.WriteString(fmt.Sprintf("# after applying this change.%s\n", valueUnchangedSuffix)) } @@ -2059,7 +1948,7 @@ func (p *blockBodyDiffPrinter) writeSkippedAttr(skipped, indent int) { } p.buf.WriteString("\n") p.buf.WriteString(strings.Repeat(" ", indent)) - p.buf.WriteString(p.color.Color(fmt.Sprintf("[dark_gray]# (%d unchanged %s hidden)[reset]", skipped, noun))) + p.buf.WriteString(fmt.Sprintf(p.color.Color("[dark_gray]# (%d unchanged %s hidden)[reset]"), skipped, noun)) } } @@ -2070,7 +1959,7 @@ func (p *blockBodyDiffPrinter) writeSkippedElems(skipped, indent int) { noun = "element" } p.buf.WriteString(strings.Repeat(" ", indent)) - p.buf.WriteString(p.color.Color(fmt.Sprintf("[dark_gray]# (%d unchanged %s hidden)[reset]", skipped, noun))) + p.buf.WriteString(fmt.Sprintf(p.color.Color("[dark_gray]# (%d unchanged %s hidden)[reset]"), skipped, noun)) p.buf.WriteString("\n") } } diff --git a/internal/command/format/diff_test.go b/internal/command/format/diff_test.go index 7ca289c79..b59a3af56 100644 --- a/internal/command/format/diff_test.go +++ b/internal/command/format/diff_test.go @@ -4599,7 +4599,7 @@ func runTestCases(t *testing.T, testCases map[string]testCase) { RequiredReplace: tc.RequiredReplace, } - output := ResourceChange(change, tc.Schema, color) + output := ResourceChange(change, tc.Schema, color, DiffLanguageProposedChange) if diff := cmp.Diff(output, tc.ExpectedOutput); diff != "" { t.Errorf("wrong output\n%s", diff) } diff --git a/internal/command/format/difflanguage_string.go b/internal/command/format/difflanguage_string.go new file mode 100644 index 000000000..8399cddc4 --- /dev/null +++ b/internal/command/format/difflanguage_string.go @@ -0,0 +1,29 @@ +// Code generated by "stringer -type=DiffLanguage diff.go"; DO NOT EDIT. + +package format + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[DiffLanguageProposedChange-80] + _ = x[DiffLanguageDetectedDrift-68] +} + +const ( + _DiffLanguage_name_0 = "DiffLanguageDetectedDrift" + _DiffLanguage_name_1 = "DiffLanguageProposedChange" +) + +func (i DiffLanguage) String() string { + switch { + case i == 68: + return _DiffLanguage_name_0 + case i == 80: + return _DiffLanguage_name_1 + default: + return "DiffLanguage(" + strconv.FormatInt(int64(i), 10) + ")" + } +} diff --git a/internal/command/jsonplan/plan.go b/internal/command/jsonplan/plan.go index 1b90daf2d..1d7eff1ff 100644 --- a/internal/command/jsonplan/plan.go +++ b/internal/command/jsonplan/plan.go @@ -130,15 +130,17 @@ func Marshal( } // output.ResourceDrift - err = output.marshalResourceDrift(p.PrevRunState, p.PriorState, schemas) + output.ResourceDrift, err = output.marshalResourceChanges(p.DriftedResources, schemas) if err != nil { - return nil, fmt.Errorf("error in marshalResourceDrift: %s", err) + return nil, fmt.Errorf("error in marshaling resource drift: %s", err) } // output.ResourceChanges - err = output.marshalResourceChanges(p.Changes, schemas) - if err != nil { - return nil, fmt.Errorf("error in marshalResourceChanges: %s", err) + if p.Changes != nil { + output.ResourceChanges, err = output.marshalResourceChanges(p.Changes.Resources, schemas) + if err != nil { + return nil, fmt.Errorf("error in marshaling resource changes: %s", err) + } } // output.OutputChanges @@ -188,149 +190,10 @@ func (p *plan) marshalPlanVariables(vars map[string]plans.DynamicValue, schemas return nil } -func (p *plan) marshalResourceDrift(oldState, newState *states.State, schemas *terraform.Schemas) error { - // Our goal here is to build a data structure of the same shape as we use - // to describe planned resource changes, but in this case we'll be - // taking the old and new values from different state snapshots rather - // than from a real "Changes" object. - // - // In doing this we make an assumption that drift detection can only - // ever show objects as updated or removed, and will never show anything - // as created because we only refresh objects we were already tracking - // after the previous run. This means we can use oldState as our baseline - // for what resource instances we might include, and check for each item - // whether it's present in newState. If we ever have some mechanism to - // detect "additive drift" later then we'll need to take a different - // approach here, but we have no plans for that at the time of writing. - // - // We also assume that both states have had all managed resource objects - // upgraded to match the current schemas given in schemas, so we shouldn't - // need to contend with oldState having old-shaped objects even if the - // user changed provider versions since the last run. +func (p *plan) marshalResourceChanges(resources []*plans.ResourceInstanceChangeSrc, schemas *terraform.Schemas) ([]resourceChange, error) { + var ret []resourceChange - if newState.ManagedResourcesEqual(oldState) { - // Nothing to do, because we only detect and report drift for managed - // resource instances. - return nil - } - for _, ms := range oldState.Modules { - for _, rs := range ms.Resources { - if rs.Addr.Resource.Mode != addrs.ManagedResourceMode { - // Drift reporting is only for managed resources - continue - } - - provider := rs.ProviderConfig.Provider - for key, oldIS := range rs.Instances { - if oldIS.Current == nil { - // Not interested in instances that only have deposed objects - continue - } - addr := rs.Addr.Instance(key) - newIS := newState.ResourceInstance(addr) - - schema, _ := schemas.ResourceTypeConfig( - provider, - addr.Resource.Resource.Mode, - addr.Resource.Resource.Type, - ) - if schema == nil { - return fmt.Errorf("no schema found for %s (in provider %s)", addr, provider) - } - ty := schema.ImpliedType() - - oldObj, err := oldIS.Current.Decode(ty) - if err != nil { - return fmt.Errorf("failed to decode previous run data for %s: %s", addr, err) - } - - var newObj *states.ResourceInstanceObject - if newIS != nil && newIS.Current != nil { - newObj, err = newIS.Current.Decode(ty) - if err != nil { - return fmt.Errorf("failed to decode refreshed data for %s: %s", addr, err) - } - } - - var oldVal, newVal cty.Value - oldVal = oldObj.Value - if newObj != nil { - newVal = newObj.Value - } else { - newVal = cty.NullVal(ty) - } - - if oldVal.RawEquals(newVal) { - // No drift if the two values are semantically equivalent - continue - } - - oldSensitive := jsonstate.SensitiveAsBool(oldVal) - newSensitive := jsonstate.SensitiveAsBool(newVal) - oldVal, _ = oldVal.UnmarkDeep() - newVal, _ = newVal.UnmarkDeep() - - var before, after []byte - var beforeSensitive, afterSensitive []byte - before, err = ctyjson.Marshal(oldVal, oldVal.Type()) - if err != nil { - return fmt.Errorf("failed to encode previous run data for %s as JSON: %s", addr, err) - } - after, err = ctyjson.Marshal(newVal, oldVal.Type()) - if err != nil { - return fmt.Errorf("failed to encode refreshed data for %s as JSON: %s", addr, err) - } - beforeSensitive, err = ctyjson.Marshal(oldSensitive, oldSensitive.Type()) - if err != nil { - return fmt.Errorf("failed to encode previous run data sensitivity for %s as JSON: %s", addr, err) - } - afterSensitive, err = ctyjson.Marshal(newSensitive, newSensitive.Type()) - if err != nil { - return fmt.Errorf("failed to encode refreshed data sensitivity for %s as JSON: %s", addr, err) - } - - // We can only detect updates and deletes as drift. - action := plans.Update - if newVal.IsNull() { - action = plans.Delete - } - - change := resourceChange{ - Address: addr.String(), - ModuleAddress: addr.Module.String(), - Mode: "managed", // drift reporting is only for managed resources - Name: addr.Resource.Resource.Name, - Type: addr.Resource.Resource.Type, - ProviderName: provider.String(), - - Change: change{ - Actions: actionString(action.String()), - Before: json.RawMessage(before), - BeforeSensitive: json.RawMessage(beforeSensitive), - After: json.RawMessage(after), - AfterSensitive: json.RawMessage(afterSensitive), - // AfterUnknown is never populated here because - // values in a state are always fully known. - }, - } - p.ResourceDrift = append(p.ResourceDrift, change) - } - } - } - - sort.Slice(p.ResourceChanges, func(i, j int) bool { - return p.ResourceChanges[i].Address < p.ResourceChanges[j].Address - }) - - return nil -} - -func (p *plan) marshalResourceChanges(changes *plans.Changes, schemas *terraform.Schemas) error { - if changes == nil { - // Nothing to do! - return nil - } - for _, rc := range changes.Resources { + for _, rc := range resources { var r resourceChange addr := rc.Addr r.Address = addr.String() @@ -349,12 +212,12 @@ func (p *plan) marshalResourceChanges(changes *plans.Changes, schemas *terraform addr.Resource.Resource.Type, ) if schema == nil { - return fmt.Errorf("no schema found for %s (in provider %s)", r.Address, rc.ProviderAddr.Provider) + return nil, fmt.Errorf("no schema found for %s (in provider %s)", r.Address, rc.ProviderAddr.Provider) } changeV, err := rc.Decode(schema.ImpliedType()) if err != nil { - return err + return nil, err } // We drop the marks from the change, as decoding is only an // intermediate step to re-encode the values as json @@ -368,7 +231,7 @@ func (p *plan) marshalResourceChanges(changes *plans.Changes, schemas *terraform if changeV.Before != cty.NilVal { before, err = ctyjson.Marshal(changeV.Before, changeV.Before.Type()) if err != nil { - return err + return nil, err } marks := rc.BeforeValMarks if schema.ContainsSensitive() { @@ -377,14 +240,14 @@ func (p *plan) marshalResourceChanges(changes *plans.Changes, schemas *terraform bs := jsonstate.SensitiveAsBool(changeV.Before.MarkWithPaths(marks)) beforeSensitive, err = ctyjson.Marshal(bs, bs.Type()) if err != nil { - return err + return nil, err } } if changeV.After != cty.NilVal { if changeV.After.IsWhollyKnown() { after, err = ctyjson.Marshal(changeV.After, changeV.After.Type()) if err != nil { - return err + return nil, err } afterUnknown = cty.EmptyObjectVal } else { @@ -394,7 +257,7 @@ func (p *plan) marshalResourceChanges(changes *plans.Changes, schemas *terraform } else { after, err = ctyjson.Marshal(filteredAfter, filteredAfter.Type()) if err != nil { - return err + return nil, err } } afterUnknown = unknownAsBool(changeV.After) @@ -406,17 +269,17 @@ func (p *plan) marshalResourceChanges(changes *plans.Changes, schemas *terraform as := jsonstate.SensitiveAsBool(changeV.After.MarkWithPaths(marks)) afterSensitive, err = ctyjson.Marshal(as, as.Type()) if err != nil { - return err + return nil, err } } a, err := ctyjson.Marshal(afterUnknown, afterUnknown.Type()) if err != nil { - return err + return nil, err } replacePaths, err := encodePaths(rc.RequiredReplace) if err != nil { - return err + return nil, err } r.Change = change{ @@ -444,7 +307,7 @@ func (p *plan) marshalResourceChanges(changes *plans.Changes, schemas *terraform case addrs.DataResourceMode: r.Mode = "data" default: - return fmt.Errorf("resource %s has an unsupported mode %s", r.Address, addr.Resource.Resource.Mode.String()) + return nil, fmt.Errorf("resource %s has an unsupported mode %s", r.Address, addr.Resource.Resource.Mode.String()) } r.ModuleAddress = addr.Module.String() r.Name = addr.Resource.Resource.Name @@ -461,18 +324,18 @@ func (p *plan) marshalResourceChanges(changes *plans.Changes, schemas *terraform case plans.ResourceInstanceReplaceByRequest: r.ActionReason = "replace_by_request" default: - return fmt.Errorf("resource %s has an unsupported action reason %s", r.Address, rc.ActionReason) + return nil, fmt.Errorf("resource %s has an unsupported action reason %s", r.Address, rc.ActionReason) } - p.ResourceChanges = append(p.ResourceChanges, r) + ret = append(ret, r) } - sort.Slice(p.ResourceChanges, func(i, j int) bool { - return p.ResourceChanges[i].Address < p.ResourceChanges[j].Address + sort.Slice(ret, func(i, j int) bool { + return ret[i].Address < ret[j].Address }) - return nil + return ret, nil } func (p *plan) marshalOutputChanges(changes *plans.Changes) error { diff --git a/internal/command/testdata/show-json/drift/output.json b/internal/command/testdata/show-json/drift/output.json index 7badb45e5..79e702161 100644 --- a/internal/command/testdata/show-json/drift/output.json +++ b/internal/command/testdata/show-json/drift/output.json @@ -52,6 +52,7 @@ "id": "placeholder" }, "after_sensitive": {}, + "after_unknown": {}, "before_sensitive": {} } } diff --git a/internal/command/testdata/show-json/multi-resource-update/output.json b/internal/command/testdata/show-json/multi-resource-update/output.json index d84bc5b08..a7a6a3053 100644 --- a/internal/command/testdata/show-json/multi-resource-update/output.json +++ b/internal/command/testdata/show-json/multi-resource-update/output.json @@ -62,7 +62,8 @@ }, "after": null, "before_sensitive": {}, - "after_sensitive": false + "after_sensitive": false, + "after_unknown": {} } } ], diff --git a/internal/command/views/operation_test.go b/internal/command/views/operation_test.go index fd56350c5..b2e4f8f8e 100644 --- a/internal/command/views/operation_test.go +++ b/internal/command/views/operation_test.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform/internal/states/statefile" "github.com/hashicorp/terraform/internal/terminal" "github.com/hashicorp/terraform/internal/terraform" + "github.com/zclconf/go-cty/cty" ) func TestOperation_stopping(t *testing.T) { @@ -82,10 +83,8 @@ func TestOperation_planNoChanges(t *testing.T) { "nothing at all in normal mode": { func(schemas *terraform.Schemas) *plans.Plan { return &plans.Plan{ - UIMode: plans.NormalMode, - Changes: plans.NewChanges(), - PrevRunState: states.NewState(), - PriorState: states.NewState(), + UIMode: plans.NormalMode, + Changes: plans.NewChanges(), } }, "no differences, so no changes are needed.", @@ -93,10 +92,8 @@ func TestOperation_planNoChanges(t *testing.T) { "nothing at all in refresh-only mode": { func(schemas *terraform.Schemas) *plans.Plan { return &plans.Plan{ - UIMode: plans.RefreshOnlyMode, - Changes: plans.NewChanges(), - PrevRunState: states.NewState(), - PriorState: states.NewState(), + UIMode: plans.RefreshOnlyMode, + Changes: plans.NewChanges(), } }, "Terraform has checked that the real remote objects still match", @@ -104,148 +101,90 @@ func TestOperation_planNoChanges(t *testing.T) { "nothing at all in destroy mode": { func(schemas *terraform.Schemas) *plans.Plan { return &plans.Plan{ - UIMode: plans.DestroyMode, - Changes: plans.NewChanges(), - PrevRunState: states.NewState(), - PriorState: states.NewState(), + UIMode: plans.DestroyMode, + Changes: plans.NewChanges(), } }, "No objects need to be destroyed.", }, - "no drift to display with only deposed instances": { - // changes in deposed instances will cause a change in state, but - // have nothing to display to the user - func(schemas *terraform.Schemas) *plans.Plan { - return &plans.Plan{ - UIMode: plans.NormalMode, - Changes: plans.NewChanges(), - PrevRunState: states.BuildState(func(state *states.SyncState) { - state.SetResourceInstanceDeposed( - addrs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "test_resource", - Name: "somewhere", - }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), - states.NewDeposedKey(), - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectReady, - AttrsJSON: []byte(`{"foo": "ok", "bars":[]}`), - }, - addrs.RootModuleInstance.ProviderConfigDefault(addrs.NewDefaultProvider("test")), - ) - }), - PriorState: states.NewState(), - } - }, - "no differences, so no changes are needed.", - }, "drift detected in normal mode": { func(schemas *terraform.Schemas) *plans.Plan { - return &plans.Plan{ - UIMode: plans.NormalMode, - Changes: plans.NewChanges(), - PrevRunState: states.BuildState(func(state *states.SyncState) { - state.SetResourceInstanceCurrent( - addrs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "test_resource", - Name: "somewhere", - }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectReady, - AttrsJSON: []byte(`{}`), - }, - addrs.RootModuleInstance.ProviderConfigDefault(addrs.NewDefaultProvider("test")), - ) - }), - PriorState: states.NewState(), + 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"), + }), + }, } - }, - "to update the Terraform state to match, create and apply a refresh-only plan", - }, - "drift detected with deposed": { - func(schemas *terraform.Schemas) *plans.Plan { + rcs, err := rc.Encode(ty) + if err != nil { + panic(err) + } + drs := []*plans.ResourceInstanceChangeSrc{rcs} return &plans.Plan{ - UIMode: plans.NormalMode, - Changes: plans.NewChanges(), - PrevRunState: states.BuildState(func(state *states.SyncState) { - state.SetResourceInstanceCurrent( - addrs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "test_resource", - Name: "changes", - }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectReady, - AttrsJSON: []byte(`{"foo":"b"}`), - }, - addrs.RootModuleInstance.ProviderConfigDefault(addrs.NewDefaultProvider("test")), - ) - state.SetResourceInstanceDeposed( - addrs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "test_resource", - Name: "broken", - }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), - states.NewDeposedKey(), - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectReady, - AttrsJSON: []byte(`{"foo":"c"}`), - }, - addrs.RootModuleInstance.ProviderConfigDefault(addrs.NewDefaultProvider("test")), - ) - }), - PriorState: states.BuildState(func(state *states.SyncState) { - state.SetResourceInstanceCurrent( - addrs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "test_resource", - Name: "changed", - }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectReady, - AttrsJSON: []byte(`{"foo":"b"}`), - }, - addrs.RootModuleInstance.ProviderConfigDefault(addrs.NewDefaultProvider("test")), - ) - state.SetResourceInstanceDeposed( - addrs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "test_resource", - Name: "broken", - }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), - states.NewDeposedKey(), - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectReady, - AttrsJSON: []byte(`{"foo":"d"}`), - }, - addrs.RootModuleInstance.ProviderConfigDefault(addrs.NewDefaultProvider("test")), - ) - }), + UIMode: plans.NormalMode, + Changes: plans.NewChanges(), + DriftedResources: drs, } }, "to update the Terraform state to match, create and apply a refresh-only plan", }, "drift detected in refresh-only 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} return &plans.Plan{ - UIMode: plans.RefreshOnlyMode, - Changes: plans.NewChanges(), - PrevRunState: states.BuildState(func(state *states.SyncState) { - state.SetResourceInstanceCurrent( - addrs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "test_resource", - Name: "somewhere", - }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectReady, - AttrsJSON: []byte(`{}`), - }, - addrs.RootModuleInstance.ProviderConfigDefault(addrs.NewDefaultProvider("test")), - ) - }), - PriorState: states.NewState(), + UIMode: plans.RefreshOnlyMode, + Changes: plans.NewChanges(), + DriftedResources: drs, } }, "If you were expecting these changes then you can apply this plan", diff --git a/internal/command/views/plan.go b/internal/command/views/plan.go index ff794933b..175c26369 100644 --- a/internal/command/views/plan.go +++ b/internal/command/views/plan.go @@ -10,7 +10,6 @@ import ( "github.com/hashicorp/terraform/internal/command/arguments" "github.com/hashicorp/terraform/internal/command/format" "github.com/hashicorp/terraform/internal/plans" - "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/terraform" "github.com/hashicorp/terraform/internal/tfdiags" ) @@ -97,8 +96,9 @@ 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) { - haveRefreshChanges := renderChangesDetectedByRefresh(plan.PrevRunState, plan.PriorState, schemas, view) + haveRefreshChanges := len(plan.DriftedResources) > 0 if haveRefreshChanges { + renderChangesDetectedByRefresh(plan.DriftedResources, schemas, view) switch plan.UIMode { case plans.RefreshOnlyMode: view.streams.Println(format.WordWrap( @@ -292,6 +292,7 @@ func renderPlan(plan *plans.Plan, schemas *terraform.Schemas, view *View) { rcs, rSchema, view.colorize, + format.DiffLanguageProposedChange, )) } @@ -344,82 +345,53 @@ 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(before, after *states.State, schemas *terraform.Schemas, view *View) bool { - // ManagedResourceEqual checks that the state is exactly equal for all - // managed resources; but semantically equivalent states, or changes to - // deposed instances may not actually represent changes we need to present - // to the user, so for now this only serves as a short-circuit to skip - // attempting to render the diffs below. - if after.ManagedResourcesEqual(before) { - return false - } +func renderChangesDetectedByRefresh(drs []*plans.ResourceInstanceChangeSrc, schemas *terraform.Schemas, view *View) { + 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", + view.outputColumns(), + )) - var diffs []string - - for _, bms := range before.Modules { - for _, brs := range bms.Resources { - if brs.Addr.Resource.Mode != addrs.ManagedResourceMode { - continue // only managed resources can "drift" - } - addr := brs.Addr - prs := after.Resource(brs.Addr) - - provider := brs.ProviderConfig.Provider - providerSchema := schemas.ProviderSchema(provider) - if providerSchema == nil { - // Should never happen - view.streams.Printf("(schema missing for %s)\n", provider) - continue - } - rSchema, _ := providerSchema.SchemaForResourceAddr(addr.Resource) - if rSchema == nil { - // Should never happen - view.streams.Printf("(schema missing for %s)\n", addr) - continue - } - - for key, bis := range brs.Instances { - if bis.Current == nil { - // No current instance to render here - continue - } - var pis *states.ResourceInstance - if prs != nil { - pis = prs.Instance(key) - } - - diff := format.ResourceInstanceDrift( - addr.Instance(key), - bis, pis, - rSchema, - view.colorize, - ) - if diff != "" { - diffs = append(diffs, diff) - } - } + // Note: we're modifying the backing slice of this plan object in-place + // here. The ordering of resource changes in a plan is not significant, + // but we can only do this safely here because we can assume that nobody + // is concurrently modifying our changes while we're trying to print it. + sort.Slice(drs, func(i, j int) bool { + iA := drs[i].Addr + jA := drs[j].Addr + if iA.String() == jA.String() { + return drs[i].DeposedKey < drs[j].DeposedKey } - } + return iA.Less(jA) + }) - // If we only have changes regarding deposed instances, or the diff - // renderer is suppressing irrelevant changes from the legacy SDK, there - // may not have been anything to display to the user. - if len(diffs) > 0 { - 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", - view.outputColumns(), + for _, rcs := range drs { + if rcs.Action == plans.NoOp && !rcs.Moved() { + continue + } + + 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 + } + + view.streams.Println(format.ResourceChange( + rcs, + rSchema, + view.colorize, + format.DiffLanguageDetectedDrift, )) - - for _, diff := range diffs { - view.streams.Print(diff) - } - return true } - - return false } const planHeaderIntro = ` diff --git a/internal/plans/internal/planproto/planfile.pb.go b/internal/plans/internal/planproto/planfile.pb.go index 9f541946c..a612c03a3 100644 --- a/internal/plans/internal/planproto/planfile.pb.go +++ b/internal/plans/internal/planproto/planfile.pb.go @@ -222,6 +222,10 @@ type Plan struct { // configuration, including any nested modules. Use the address of // each resource to determine which module it belongs to. ResourceChanges []*ResourceInstanceChange `protobuf:"bytes,3,rep,name=resource_changes,json=resourceChanges,proto3" json:"resource_changes,omitempty"` + // An unordered set of detected drift: changes made to resources outside of + // Terraform, computed by comparing the previous run's state to the state + // after refresh. + ResourceDrift []*ResourceInstanceChange `protobuf:"bytes,18,rep,name=resource_drift,json=resourceDrift,proto3" json:"resource_drift,omitempty"` // An unordered set of proposed changes to outputs in the root module // of the configuration. This set also includes "no action" changes for // outputs that are not changing, as context for detecting inconsistencies @@ -306,6 +310,13 @@ func (x *Plan) GetResourceChanges() []*ResourceInstanceChange { return nil } +func (x *Plan) GetResourceDrift() []*ResourceInstanceChange { + if x != nil { + return x.ResourceDrift + } + return nil +} + func (x *Plan) GetOutputChanges() []*OutputChange { if x != nil { return x.OutputChanges @@ -952,7 +963,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, 0xa5, 0x05, 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, @@ -965,124 +976,128 @@ var file_planfile_proto_rawDesc = []byte{ 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x0e, 0x6f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x14, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, - 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0d, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x43, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, - 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x66, 0x6f, 0x72, 0x63, - 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, - 0x10, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x52, 0x65, 0x70, 0x6c, - 0x61, 0x63, 0x65, 0x41, 0x64, 0x64, 0x72, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x74, 0x65, 0x72, 0x72, - 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0e, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x10, 0x74, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x49, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, - 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x72, 0x6f, - 0x76, 0x69, 0x64, 0x65, 0x72, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, - 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, + 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x45, 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x64, 0x72, 0x69, 0x66, 0x74, 0x18, 0x12, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1e, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x52, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x44, 0x72, 0x69, 0x66, 0x74, 0x12, + 0x3b, 0x0a, 0x0e, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, + 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0d, 0x6f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x73, 0x12, + 0x2e, 0x0a, 0x13, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, + 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x66, 0x6f, + 0x72, 0x63, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x41, 0x64, 0x64, 0x72, 0x73, 0x12, + 0x2b, 0x0a, 0x11, 0x74, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x74, 0x65, 0x72, 0x72, + 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x49, 0x0a, 0x0f, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, + 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, + 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x48, 0x61, 0x73, 0x68, + 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 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, 0x1a, 0x4f, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, + 0x65, 0x72, 0x48, 0x61, 0x73, 0x68, 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, - 0x4f, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x48, 0x61, 0x73, 0x68, 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, 0x22, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, - 0x2e, 0x48, 0x61, 0x73, 0x68, 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, 0x1e, 0x0a, 0x04, - 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 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, 0x80, 0x01, 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, 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, + 0x22, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, + 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x48, 0x61, 0x73, 0x68, 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, 0x1e, 0x0a, 0x04, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x68, 0x61, 0x32, 0x35, 0x36, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x68, 0x61, + 0x32, 0x35, 0x36, 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, 0x80, 0x01, 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, 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 ( @@ -1119,27 +1134,28 @@ var file_planfile_proto_depIdxs = []int32{ 0, // 0: tfplan.Plan.ui_mode:type_name -> tfplan.Mode 11, // 1: tfplan.Plan.variables:type_name -> tfplan.Plan.VariablesEntry 6, // 2: tfplan.Plan.resource_changes:type_name -> tfplan.ResourceInstanceChange - 7, // 3: tfplan.Plan.output_changes:type_name -> tfplan.OutputChange - 12, // 4: tfplan.Plan.provider_hashes:type_name -> tfplan.Plan.ProviderHashesEntry - 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 - 10, // 9: tfplan.Change.before_sensitive_paths:type_name -> tfplan.Path - 10, // 10: tfplan.Change.after_sensitive_paths:type_name -> tfplan.Path - 5, // 11: tfplan.ResourceInstanceChange.change:type_name -> tfplan.Change - 10, // 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 - 13, // 15: tfplan.Path.steps:type_name -> tfplan.Path.Step - 8, // 16: tfplan.Plan.VariablesEntry.value:type_name -> tfplan.DynamicValue - 9, // 17: tfplan.Plan.ProviderHashesEntry.value:type_name -> tfplan.Hash - 8, // 18: tfplan.Path.Step.element_key:type_name -> tfplan.DynamicValue - 19, // [19:19] is the sub-list for method output_type - 19, // [19:19] is the sub-list for method input_type - 19, // [19:19] is the sub-list for extension type_name - 19, // [19:19] is the sub-list for extension extendee - 0, // [0:19] is the sub-list for field type_name + 6, // 3: tfplan.Plan.resource_drift:type_name -> tfplan.ResourceInstanceChange + 7, // 4: tfplan.Plan.output_changes:type_name -> tfplan.OutputChange + 12, // 5: tfplan.Plan.provider_hashes:type_name -> tfplan.Plan.ProviderHashesEntry + 4, // 6: tfplan.Plan.backend:type_name -> tfplan.Backend + 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 + 10, // 10: tfplan.Change.before_sensitive_paths:type_name -> tfplan.Path + 10, // 11: tfplan.Change.after_sensitive_paths:type_name -> tfplan.Path + 5, // 12: tfplan.ResourceInstanceChange.change:type_name -> tfplan.Change + 10, // 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 + 13, // 16: tfplan.Path.steps:type_name -> tfplan.Path.Step + 8, // 17: tfplan.Plan.VariablesEntry.value:type_name -> tfplan.DynamicValue + 9, // 18: tfplan.Plan.ProviderHashesEntry.value:type_name -> tfplan.Hash + 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() } diff --git a/internal/plans/internal/planproto/planfile.proto b/internal/plans/internal/planproto/planfile.proto index 6ec4ae402..d1427bfbe 100644 --- a/internal/plans/internal/planproto/planfile.proto +++ b/internal/plans/internal/planproto/planfile.proto @@ -33,6 +33,11 @@ message Plan { // each resource to determine which module it belongs to. repeated ResourceInstanceChange resource_changes = 3; + // An unordered set of detected drift: changes made to resources outside of + // Terraform, computed by comparing the previous run's state to the state + // after refresh. + repeated ResourceInstanceChange resource_drift = 18; + // An unordered set of proposed changes to outputs in the root module // of the configuration. This set also includes "no action" changes for // outputs that are not changing, as context for detecting inconsistencies diff --git a/internal/plans/plan.go b/internal/plans/plan.go index 68e60ad98..a96a05648 100644 --- a/internal/plans/plan.go +++ b/internal/plans/plan.go @@ -31,6 +31,7 @@ type Plan struct { VariableValues map[string]DynamicValue Changes *Changes + DriftedResources []*ResourceInstanceChangeSrc TargetAddrs []addrs.Targetable ForceReplaceAddrs []addrs.AbsResourceInstance ProviderSHA256s map[string][]byte diff --git a/internal/plans/planfile/planfile_test.go b/internal/plans/planfile/planfile_test.go index b0001312d..14d23c87a 100644 --- a/internal/plans/planfile/planfile_test.go +++ b/internal/plans/planfile/planfile_test.go @@ -51,7 +51,8 @@ func TestRoundtrip(t *testing.T) { Resources: []*plans.ResourceInstanceChangeSrc{}, Outputs: []*plans.OutputChangeSrc{}, }, - ProviderSHA256s: map[string][]byte{}, + DriftedResources: []*plans.ResourceInstanceChangeSrc{}, + ProviderSHA256s: map[string][]byte{}, VariableValues: map[string]plans.DynamicValue{ "foo": plans.DynamicValue([]byte("foo placeholder")), }, diff --git a/internal/plans/planfile/tfplan.go b/internal/plans/planfile/tfplan.go index 7572020b6..8cfd3694f 100644 --- a/internal/plans/planfile/tfplan.go +++ b/internal/plans/planfile/tfplan.go @@ -56,6 +56,7 @@ func readTfplan(r io.Reader) (*plans.Plan, error) { Outputs: []*plans.OutputChangeSrc{}, Resources: []*plans.ResourceInstanceChangeSrc{}, }, + DriftedResources: []*plans.ResourceInstanceChangeSrc{}, ProviderSHA256s: map[string][]byte{}, } @@ -98,6 +99,16 @@ func readTfplan(r io.Reader) (*plans.Plan, error) { plan.Changes.Resources = append(plan.Changes.Resources, change) } + for _, rawRC := range rawPlan.ResourceDrift { + change, err := resourceChangeFromTfplan(rawRC) + if err != nil { + // errors from resourceChangeFromTfplan already include context + return nil, err + } + + plan.DriftedResources = append(plan.DriftedResources, change) + } + for _, rawTargetAddr := range rawPlan.TargetAddrs { target, diags := addrs.ParseTargetStr(rawTargetAddr) if diags.HasErrors() { @@ -342,6 +353,7 @@ func writeTfplan(plan *plans.Plan, w io.Writer) error { Variables: map[string]*planproto.DynamicValue{}, OutputChanges: []*planproto.OutputChange{}, ResourceChanges: []*planproto.ResourceInstanceChange{}, + ResourceDrift: []*planproto.ResourceInstanceChange{}, } switch plan.UIMode { @@ -388,6 +400,14 @@ func writeTfplan(plan *plans.Plan, w io.Writer) error { rawPlan.ResourceChanges = append(rawPlan.ResourceChanges, rawRC) } + for _, rc := range plan.DriftedResources { + rawRC, err := resourceChangeToTfplan(rc) + if err != nil { + return err + } + rawPlan.ResourceDrift = append(rawPlan.ResourceDrift, rawRC) + } + for _, targetAddr := range plan.TargetAddrs { rawPlan.TargetAddrs = append(rawPlan.TargetAddrs, targetAddr.String()) } diff --git a/internal/plans/planfile/tfplan_test.go b/internal/plans/planfile/tfplan_test.go index b6c69657e..7ab62de53 100644 --- a/internal/plans/planfile/tfplan_test.go +++ b/internal/plans/planfile/tfplan_test.go @@ -118,6 +118,46 @@ func TestTFPlanRoundTrip(t *testing.T) { }, }, }, + DriftedResources: []*plans.ResourceInstanceChangeSrc{ + { + Addr: addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_thing", + Name: "woot", + }.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance), + PrevRunAddr: addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_thing", + Name: "woot", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + ProviderAddr: addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ChangeSrc: plans.ChangeSrc{ + Action: plans.DeleteThenCreate, + Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("foo-bar-baz"), + "boop": cty.ListVal([]cty.Value{ + cty.StringVal("beep"), + }), + }), objTy), + After: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{ + "id": cty.UnknownVal(cty.String), + "boop": cty.ListVal([]cty.Value{ + cty.StringVal("beep"), + cty.StringVal("bonk"), + }), + }), objTy), + AfterValMarks: []cty.PathValueMarks{ + { + Path: cty.GetAttrPath("boop").IndexInt(1), + Marks: cty.NewValueMarks(marks.Sensitive), + }, + }, + }, + }, + }, TargetAddrs: []addrs.Targetable{ addrs.Resource{ Mode: addrs.ManagedResourceMode, @@ -243,6 +283,7 @@ func TestTFPlanRoundTripDestroy(t *testing.T) { }, }, }, + DriftedResources: []*plans.ResourceInstanceChangeSrc{}, TargetAddrs: []addrs.Targetable{ addrs.Resource{ Mode: addrs.ManagedResourceMode, diff --git a/internal/terraform/context_plan.go b/internal/terraform/context_plan.go index 7d1353c6a..fce564bf3 100644 --- a/internal/terraform/context_plan.go +++ b/internal/terraform/context_plan.go @@ -411,11 +411,17 @@ func (c *Context) planWalk(config *configs.Config, prevRunState *states.State, r diags = diags.Append(walkDiags) diags = diags.Append(c.postPlanValidateMoves(config, moveStmts, walker.InstanceExpander.AllInstances())) + prevRunState = walker.PrevRunState.Close() + priorState := walker.RefreshState.Close() + driftedResources, driftDiags := c.driftedResources(config, prevRunState, priorState, moveResults) + diags = diags.Append(driftDiags) + plan := &plans.Plan{ - UIMode: opts.Mode, - Changes: changes, - PriorState: walker.RefreshState.Close(), - PrevRunState: walker.PrevRunState.Close(), + UIMode: opts.Mode, + Changes: changes, + DriftedResources: driftedResources, + PrevRunState: prevRunState, + PriorState: priorState, // Other fields get populated by Context.Plan after we return } @@ -462,6 +468,126 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State, } } +func (c *Context) driftedResources(config *configs.Config, oldState, newState *states.State, moves map[addrs.UniqueKey]refactoring.MoveResult) ([]*plans.ResourceInstanceChangeSrc, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + + if newState.ManagedResourcesEqual(oldState) { + // Nothing to do, because we only detect and report drift for managed + // resource instances. + return nil, diags + } + + schemas, schemaDiags := c.Schemas(config, newState) + diags = diags.Append(schemaDiags) + if diags.HasErrors() { + return nil, diags + } + + var drs []*plans.ResourceInstanceChangeSrc + + for _, ms := range oldState.Modules { + for _, rs := range ms.Resources { + if rs.Addr.Resource.Mode != addrs.ManagedResourceMode { + // Drift reporting is only for managed resources + continue + } + + provider := rs.ProviderConfig.Provider + for key, oldIS := range rs.Instances { + if oldIS.Current == nil { + // Not interested in instances that only have deposed objects + continue + } + addr := rs.Addr.Instance(key) + newIS := newState.ResourceInstance(addr) + + schema, _ := schemas.ResourceTypeConfig( + provider, + addr.Resource.Resource.Mode, + addr.Resource.Resource.Type, + ) + if schema == nil { + // This should never happen, but just in case + return nil, diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Missing resource schema from provider", + fmt.Sprintf("No resource schema found for %s.", addr.Resource.Resource.Type), + )) + } + ty := schema.ImpliedType() + + oldObj, err := oldIS.Current.Decode(ty) + if err != nil { + // This should also never happen + return nil, diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Failed to decode resource from state", + fmt.Sprintf("Error decoding %q from previous state: %s", addr.String(), err), + )) + } + + var newObj *states.ResourceInstanceObject + if newIS != nil && newIS.Current != nil { + newObj, err = newIS.Current.Decode(ty) + if err != nil { + // This should also never happen + return nil, diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Failed to decode resource from state", + fmt.Sprintf("Error decoding %q from prior state: %s", addr.String(), err), + )) + } + } + + var oldVal, newVal cty.Value + oldVal = oldObj.Value + if newObj != nil { + newVal = newObj.Value + } else { + newVal = cty.NullVal(ty) + } + + if oldVal.RawEquals(newVal) { + // No drift if the two values are semantically equivalent + continue + } + + // We can only detect updates and deletes as drift. + action := plans.Update + if newVal.IsNull() { + action = plans.Delete + } + + prevRunAddr := addr + if move, ok := moves[addr.UniqueKey()]; ok { + prevRunAddr = move.From + } + + change := &plans.ResourceInstanceChange{ + Addr: addr, + PrevRunAddr: prevRunAddr, + ProviderAddr: rs.ProviderConfig, + Change: plans.Change{ + Action: action, + Before: oldVal, + After: newVal, + }, + } + + changeSrc, err := change.Encode(ty) + if err != nil { + diags = diags.Append(err) + return nil, diags + } + + drs = append(drs, changeSrc) + } + } + } + + return drs, diags +} + // PlanGraphForUI is a last vestage of graphs in the public interface of Context // (as opposed to graphs as an implementation detail) intended only for use // by the "terraform graph" command when asked to render a plan-time graph. diff --git a/internal/terraform/context_plan2_test.go b/internal/terraform/context_plan2_test.go index a1c4e9feb..46185ae22 100644 --- a/internal/terraform/context_plan2_test.go +++ b/internal/terraform/context_plan2_test.go @@ -108,6 +108,23 @@ resource "test_object" "a" { } } + // This situation should result in a drifted resource change. + var drifted *plans.ResourceInstanceChangeSrc + for _, dr := range plan.DriftedResources { + if dr.Addr.Equal(addr) { + drifted = dr + break + } + } + + if drifted == nil { + t.Errorf("instance %s is missing from the drifted resource changes", addr) + } else { + if got, want := drifted.Action, plans.Delete; got != want { + t.Errorf("unexpected instance %s drifted resource change action. got: %s, want: %s", addr, got, want) + } + } + // Because the configuration still mentions test_object.a, we should've // planned to recreate it in order to fix the drift. for _, c := range plan.Changes.Resources { @@ -1236,6 +1253,11 @@ func TestContext2Plan_refreshOnlyMode_deposed(t *testing.T) { t.Errorf("wrong value for output value 'out'\ngot: %#v\nwant: %#v", got, want) } } + + // Deposed objects should not be represented in drift. + if len(plan.DriftedResources) > 0 { + t.Errorf("unexpected drifted resources (%d)", len(plan.DriftedResources)) + } } func TestContext2Plan_invalidSensitiveModuleOutput(t *testing.T) { diff --git a/internal/terraform/context_refresh_test.go b/internal/terraform/context_refresh_test.go index dd319254a..49cd02e0e 100644 --- a/internal/terraform/context_refresh_test.go +++ b/internal/terraform/context_refresh_test.go @@ -219,6 +219,10 @@ func TestContext2Refresh_targeted(t *testing.T) { ResourceTypes: map[string]*configschema.Block{ "aws_elb": { Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, "instances": { Type: cty.Set(cty.String), Optional: true, @@ -295,6 +299,10 @@ func TestContext2Refresh_targetedCount(t *testing.T) { ResourceTypes: map[string]*configschema.Block{ "aws_elb": { Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, "instances": { Type: cty.Set(cty.String), Optional: true, @@ -381,6 +389,10 @@ func TestContext2Refresh_targetedCountIndex(t *testing.T) { ResourceTypes: map[string]*configschema.Block{ "aws_elb": { Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, "instances": { Type: cty.Set(cty.String), Optional: true,