Merge pull request #24163 from hashicorp/jbardin/destroy-provisioner-keys

Destroy provisioner each.key
This commit is contained in:
James Bardin 2020-02-20 08:41:55 -05:00 committed by GitHub
commit bf65b516c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 96 additions and 80 deletions

View File

@ -18,6 +18,10 @@ import (
type InstanceKey interface {
instanceKeySigil()
String() string
// Value returns the cty.Value of the appropriate type for the InstanceKey
// value.
Value() cty.Value
}
// ParseInstanceKey returns the instance key corresponding to the given value,
@ -56,6 +60,10 @@ func (k IntKey) String() string {
return fmt.Sprintf("[%d]", int(k))
}
func (k IntKey) Value() cty.Value {
return cty.NumberIntVal(int64(k))
}
// StringKey is the InstanceKey representation representing string indices, as
// used when the "for_each" argument is specified with a map or object type.
type StringKey string
@ -69,6 +77,10 @@ func (k StringKey) String() string {
return fmt.Sprintf("[%q]", string(k))
}
func (k StringKey) Value() cty.Value {
return cty.StringVal(string(k))
}
// InstanceKeyLess returns true if the first given instance key i should sort
// before the second key j, and false otherwise.
func InstanceKeyLess(i, j InstanceKey) bool {

View File

@ -5266,28 +5266,23 @@ func TestContext2Apply_provisionerDestroy(t *testing.T) {
p.DiffFn = testDiffFn
pr.ApplyFn = func(rs *InstanceState, c *ResourceConfig) error {
val, ok := c.Config["command"]
if !ok || val != "destroy" {
if !ok || val != "destroy a" {
t.Fatalf("bad value for foo: %v %#v", val, c)
}
return nil
}
state := MustShimLegacyState(&State{
Modules: []*ModuleState{
&ModuleState{
Path: rootModulePath,
Resources: map[string]*ResourceState{
"aws_instance.foo": &ResourceState{
Type: "aws_instance",
Primary: &InstanceState{
ID: "bar",
},
},
},
},
state := states.NewState()
root := state.RootModule()
root.SetResourceInstanceCurrent(
mustResourceInstanceAddr(`aws_instance.foo["a"]`).Resource,
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"id":"bar"}`),
},
})
mustProviderConfig(`provider["registry.terraform.io/-/aws"]`),
)
ctx := testContext2(t, &ContextOpts{
Config: m,
@ -5331,21 +5326,16 @@ func TestContext2Apply_provisionerDestroyFail(t *testing.T) {
return fmt.Errorf("provisioner error")
}
state := MustShimLegacyState(&State{
Modules: []*ModuleState{
&ModuleState{
Path: rootModulePath,
Resources: map[string]*ResourceState{
"aws_instance.foo": &ResourceState{
Type: "aws_instance",
Primary: &InstanceState{
ID: "bar",
},
},
},
},
state := states.NewState()
root := state.RootModule()
root.SetResourceInstanceCurrent(
mustResourceInstanceAddr(`aws_instance.foo["a"]`).Resource,
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"id":"bar"}`),
},
})
mustProviderConfig(`provider["registry.terraform.io/-/aws"]`),
)
ctx := testContext2(t, &ContextOpts{
Config: m,
@ -5371,7 +5361,7 @@ func TestContext2Apply_provisionerDestroyFail(t *testing.T) {
}
checkStateString(t, state, `
aws_instance.foo:
aws_instance.foo["a"]:
ID = bar
provider = provider["registry.terraform.io/-/aws"]
`)
@ -5405,21 +5395,16 @@ func TestContext2Apply_provisionerDestroyFailContinue(t *testing.T) {
return fmt.Errorf("provisioner error")
}
state := MustShimLegacyState(&State{
Modules: []*ModuleState{
&ModuleState{
Path: rootModulePath,
Resources: map[string]*ResourceState{
"aws_instance.foo": &ResourceState{
Type: "aws_instance",
Primary: &InstanceState{
ID: "bar",
},
},
},
},
state := states.NewState()
root := state.RootModule()
root.SetResourceInstanceCurrent(
mustResourceInstanceAddr(`aws_instance.foo["a"]`).Resource,
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"id":"bar"}`),
},
})
mustProviderConfig(`provider["registry.terraform.io/-/aws"]`),
)
ctx := testContext2(t, &ContextOpts{
Config: m,
@ -5547,7 +5532,7 @@ func TestContext2Apply_provisionerDestroyTainted(t *testing.T) {
destroyCalled := false
pr.ApplyFn = func(rs *InstanceState, c *ResourceConfig) error {
expected := "create"
expected := "create a b"
if rs.ID == "bar" {
destroyCalled = true
return nil
@ -5561,22 +5546,16 @@ func TestContext2Apply_provisionerDestroyTainted(t *testing.T) {
return nil
}
state := MustShimLegacyState(&State{
Modules: []*ModuleState{
&ModuleState{
Path: rootModulePath,
Resources: map[string]*ResourceState{
"aws_instance.foo": &ResourceState{
Type: "aws_instance",
Primary: &InstanceState{
ID: "bar",
Tainted: true,
},
},
},
},
state := states.NewState()
root := state.RootModule()
root.SetResourceInstanceCurrent(
mustResourceInstanceAddr(`aws_instance.foo["a"]`).Resource,
&states.ResourceInstanceObjectSrc{
Status: states.ObjectTainted,
AttrsJSON: []byte(`{"id":"bar"}`),
},
})
mustProviderConfig(`provider["registry.terraform.io/-/aws"]`),
)
ctx := testContext2(t, &ContextOpts{
Config: m,
@ -5589,6 +5568,14 @@ func TestContext2Apply_provisionerDestroyTainted(t *testing.T) {
Provisioners: map[string]ProvisionerFactory{
"shell": testProvisionerFuncFixed(pr),
},
Variables: InputValues{
"input": &InputValue{
Value: cty.MapVal(map[string]cty.Value{
"a": cty.StringVal("b"),
}),
SourceType: ValueFromInput,
},
},
})
if _, diags := ctx.Plan(); diags.HasErrors() {
@ -5601,7 +5588,7 @@ func TestContext2Apply_provisionerDestroyTainted(t *testing.T) {
}
checkStateString(t, state, `
aws_instance.foo:
aws_instance.foo["a"]:
ID = foo
provider = provider["registry.terraform.io/-/aws"]
foo = bar

View File

@ -561,8 +561,18 @@ func (n *EvalApplyProvisioners) apply(ctx EvalContext, provs []*configs.Provisio
provisioner := ctx.Provisioner(prov.Type)
schema := ctx.ProvisionerSchema(prov.Type)
forEach, forEachDiags := evaluateResourceForEachExpression(n.ResourceConfig.ForEach, ctx)
diags = diags.Append(forEachDiags)
var forEach map[string]cty.Value
// For a destroy-time provisioner forEach is intentionally nil here,
// which EvalDataForInstanceKey responds to by not populating EachValue
// in its result. That's okay because each.value is prohibited for
// destroy-time provisioners.
if n.When != configs.ProvisionerWhenDestroy {
m, forEachDiags := evaluateResourceForEachExpression(n.ResourceConfig.ForEach, ctx)
diags = diags.Append(forEachDiags)
forEach = m
}
keyData := EvalDataForInstanceKey(instanceAddr.Key, forEach)
// Evaluate the main provisioner configuration.

View File

@ -104,25 +104,26 @@ type InstanceKeyEvalData = instances.RepetitionData
// EvalDataForInstanceKey constructs a suitable InstanceKeyEvalData for
// evaluating in a context that has the given instance key.
//
// The forEachMap argument can be nil when preparing for evaluation
// in a context where each.value is prohibited, such as a destroy-time
// provisioner. In that case, the returned EachValue will always be
// cty.NilVal.
func EvalDataForInstanceKey(key addrs.InstanceKey, forEachMap map[string]cty.Value) InstanceKeyEvalData {
var countIdx cty.Value
var eachKey cty.Value
var eachVal cty.Value
if intKey, ok := key.(addrs.IntKey); ok {
countIdx = cty.NumberIntVal(int64(intKey))
var evalData InstanceKeyEvalData
if key == nil {
return evalData
}
if stringKey, ok := key.(addrs.StringKey); ok {
eachKey = cty.StringVal(string(stringKey))
eachVal = forEachMap[string(stringKey)]
}
return InstanceKeyEvalData{
CountIndex: countIdx,
EachKey: eachKey,
EachValue: eachVal,
keyValue := key.Value()
switch keyValue.Type() {
case cty.String:
evalData.EachKey = keyValue
evalData.EachValue = forEachMap[keyValue.AsString()]
case cty.Number:
evalData.CountIndex = keyValue
}
return evalData
}
// EvalDataForNoInstanceKey is a value of InstanceKeyData that sets no instance

View File

@ -1,12 +1,18 @@
resource "aws_instance" "foo" {
for_each = var.input
foo = "bar"
provisioner "shell" {
command = "create"
command = "create ${each.key} ${each.value}"
}
provisioner "shell" {
command = "destroy"
when = "destroy"
command = "destroy ${each.key}"
}
}
variable "input" {
type = map(string)
default = {}
}