Merge pull request #26507 from hashicorp/pselle/sensitive-vars-change

Update state when sensitivity changes
This commit is contained in:
Pam Selle 2020-10-07 15:39:07 -04:00 committed by GitHub
commit ece9f8c1f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 132 additions and 41 deletions

View File

@ -76,16 +76,14 @@ func (ms *Module) RemoveResource(addr addrs.Resource) {
// SetResourceInstanceCurrent saves the given instance object as the current // SetResourceInstanceCurrent saves the given instance object as the current
// generation of the resource instance with the given address, simultaneously // generation of the resource instance with the given address, simultaneously
// updating the recorded provider configuration address, dependencies, and // updating the recorded provider configuration address and dependencies.
// resource EachMode.
// //
// Any existing current instance object for the given resource is overwritten. // Any existing current instance object for the given resource is overwritten.
// Set obj to nil to remove the primary generation object altogether. If there // Set obj to nil to remove the primary generation object altogether. If there
// are no deposed objects then the instance will be removed altogether. // are no deposed objects then the instance will be removed altogether.
// //
// The provider address and "each mode" are resource-wide settings and so they // The provider address is a resource-wide setting and is updated for all other
// are updated for all other instances of the same resource as a side-effect of // instances of the same resource as a side-effect of this call.
// this call.
func (ms *Module) SetResourceInstanceCurrent(addr addrs.ResourceInstance, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) { func (ms *Module) SetResourceInstanceCurrent(addr addrs.ResourceInstance, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) {
rs := ms.Resource(addr.Resource) rs := ms.Resource(addr.Resource)
// if the resource is nil and the object is nil, don't do anything! // if the resource is nil and the object is nil, don't do anything!

View File

@ -316,9 +316,8 @@ func (s *SyncState) MaybeFixUpResourceInstanceAddressForCount(addr addrs.ConfigR
// concurrently mutated during this call, but may be freely used again once // concurrently mutated during this call, but may be freely used again once
// this function returns. // this function returns.
// //
// The provider address and "each mode" are resource-wide settings and so they // The provider address is a resource-wide settings and is updated
// are updated for all other instances of the same resource as a side-effect of // for all other instances of the same resource as a side-effect of this call.
// this call.
// //
// If the containing module for this resource or the resource itself are not // If the containing module for this resource or the resource itself are not
// already tracked in state then they will be added as a side-effect. // already tracked in state then they will be added as a side-effect.

View File

@ -12088,3 +12088,105 @@ resource "test_resource" "foo" {
fooState := state.ResourceInstance(addr) fooState := state.ResourceInstance(addr)
verifySensitiveValue(fooState.Current.AttrSensitivePaths) verifySensitiveValue(fooState.Current.AttrSensitivePaths)
} }
func TestContext2Apply_variableSensitivityChange(t *testing.T) {
m := testModuleInline(t, map[string]string{
"main.tf": `
terraform {
experiments = [sensitive_variables]
}
variable "sensitive_var" {
default = "hello"
sensitive = true
}
resource "test_resource" "foo" {
value = var.sensitive_var
}`,
})
p := testProvider("test")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
State: states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_resource",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"id":"foo", "value":"hello"}`),
// No AttrSensitivePaths present
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
)
}),
})
_, diags := ctx.Plan()
assertNoErrors(t, diags)
addr := mustResourceInstanceAddr("test_resource.foo")
state, diags := ctx.Apply()
assertNoErrors(t, diags)
fooState := state.ResourceInstance(addr)
got := fooState.Current.AttrSensitivePaths[0]
want := cty.PathValueMarks{
Path: cty.GetAttrPath("value"),
Marks: cty.NewValueMarks("sensitive"),
}
if !got.Equal(want) {
t.Fatalf("wrong value marks; got:\n%#v\n\nwant:\n%#v\n", got, want)
}
m2 := testModuleInline(t, map[string]string{
"main.tf": `
terraform {
experiments = [sensitive_variables]
}
variable "sensitive_var" {
default = "hello"
sensitive = false
}
resource "test_resource" "foo" {
value = var.sensitive_var
}`,
})
ctx2 := testContext2(t, &ContextOpts{
Config: m2,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
State: state,
})
_, diags = ctx2.Plan()
assertNoErrors(t, diags)
stateWithoutSensitive, diags := ctx.Apply()
assertNoErrors(t, diags)
fooState2 := stateWithoutSensitive.ResourceInstance(addr)
if len(fooState2.Current.AttrSensitivePaths) > 0 {
t.Fatalf("wrong number of sensitive paths, expected 0, got, %v", len(fooState2.Current.AttrSensitivePaths))
}
}

View File

@ -3,6 +3,7 @@ package terraform
import ( import (
"fmt" "fmt"
"log" "log"
"reflect"
"strings" "strings"
multierror "github.com/hashicorp/go-multierror" multierror "github.com/hashicorp/go-multierror"
@ -109,20 +110,17 @@ func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) {
// If our config, Before or After value contain any marked values, // If our config, Before or After value contain any marked values,
// ensure those are stripped out before sending // ensure those are stripped out before sending
// this to the provider // this to the provider
unmarkedConfigVal := configVal unmarkedConfigVal, _ := configVal.UnmarkDeep()
if configVal.ContainsMarked() { unmarkedBefore, beforePaths := change.Before.UnmarkDeepWithPaths()
unmarkedConfigVal, _ = configVal.UnmarkDeep() unmarkedAfter, afterPaths := change.After.UnmarkDeepWithPaths()
}
unmarkedBefore := change.Before // If we have an Update action, our before and after values are equal,
if change.Before.ContainsMarked() { // and only differ on their sensitivity, the newVal is the after val
unmarkedBefore, _ = change.Before.UnmarkDeep() // and we should not communicate with the provider or perform further action.
} eqV := unmarkedBefore.Equals(unmarkedAfter)
eq := eqV.IsKnown() && eqV.True()
unmarkedAfter := change.After if change.Action == plans.Update && eq && !reflect.DeepEqual(beforePaths, afterPaths) {
var afterPaths []cty.PathValueMarks return nil, diags.ErrWithWarnings()
if change.After.ContainsMarked() {
unmarkedAfter, afterPaths = change.After.UnmarkDeepWithPaths()
} }
resp := provider.ApplyResourceChange(providers.ApplyResourceChangeRequest{ resp := provider.ApplyResourceChange(providers.ApplyResourceChangeRequest{

View File

@ -3,6 +3,7 @@ package terraform
import ( import (
"fmt" "fmt"
"log" "log"
"reflect"
"strings" "strings"
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
@ -212,23 +213,11 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) {
return nil, diags.Err() return nil, diags.Err()
} }
// Create an unmarked version of our config val, defaulting // Create an unmarked version of our config val and our prior val.
// to the configVal so we don't do the work of unmarking unless // Store the paths for the config val to re-markafter
// necessary // we've sent things over the wire.
unmarkedConfigVal := configValIgnored unmarkedConfigVal, unmarkedPaths := configValIgnored.UnmarkDeepWithPaths()
var unmarkedPaths []cty.PathValueMarks unmarkedPriorVal, priorPaths := priorVal.UnmarkDeepWithPaths()
if configValIgnored.ContainsMarked() {
// store the marked values so we can re-mark them later after
// we've sent things over the wire.
unmarkedConfigVal, unmarkedPaths = configValIgnored.UnmarkDeepWithPaths()
}
unmarkedPriorVal := priorVal
if priorVal.ContainsMarked() {
// store the marked values so we can re-mark them later after
// we've sent things over the wire.
unmarkedPriorVal, _ = priorVal.UnmarkDeep()
}
proposedNewVal := objchange.ProposedNewObject(schema, unmarkedPriorVal, unmarkedConfigVal) proposedNewVal := objchange.ProposedNewObject(schema, unmarkedPriorVal, unmarkedConfigVal)
@ -395,8 +384,7 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) {
} }
} }
// Unmark for this test for equality. If only sensitivity has changed, // Unmark for this test for value equality.
// this does not require an Update or Replace
eqV := unmarkedPlannedNewVal.Equals(unmarkedPriorVal) eqV := unmarkedPlannedNewVal.Equals(unmarkedPriorVal)
eq := eqV.IsKnown() && eqV.True() eq := eqV.IsKnown() && eqV.True()
@ -495,6 +483,12 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) {
priorVal = priorValTainted priorVal = priorValTainted
} }
// If we plan to write or delete sensitive paths from state,
// this is an Update action
if action == plans.NoOp && !reflect.DeepEqual(priorPaths, unmarkedPaths) {
action = plans.Update
}
// As a special case, if we have a previous diff (presumably from the plan // As a special case, if we have a previous diff (presumably from the plan
// phases, whereas we're now in the apply phase) and it was for a replace, // phases, whereas we're now in the apply phase) and it was for a replace,
// we've already deleted the original object from state by the time we // we've already deleted the original object from state by the time we

View File

@ -12,7 +12,7 @@ import (
"github.com/zclconf/go-cty/cty/convert" "github.com/zclconf/go-cty/cty/convert"
) )
// evalVariableValidations ensures ta all of the configured custom validations // evalVariableValidations ensures that all of the configured custom validations
// for a variable are passing. // for a variable are passing.
// //
// This must be used only after any side-effects that make the value of the // This must be used only after any side-effects that make the value of the