core: Handle forced-create_before_destroy during the plan walk

Previously we used a single plan action "Replace" to represent both the
destroy-before-create and the create-before-destroy variants of replacing.
However, this forces the apply graph builder to jump through a lot of
hoops to figure out which nodes need it forced on and rebuild parts of
the graph to represent that.

If we instead decide between these two cases at plan time, the actual
determination of it is more straightforward because each resource is
represented by only one node in the plan graph, and then we can ensure
we put the right nodes in the graph during DiffTransformer and thus avoid
the logic for dealing with deposed instances being spread across various
different transformers and node types.

As a nice side-effect, this also allows us to show the difference between
destroy-then-create and create-then-destroy in the rendered diff in the
CLI, although this change doesn't fully implement that yet.
This commit is contained in:
Martin Atkins 2018-09-21 17:08:52 -07:00
parent ebe3754fe6
commit a43b7df282
24 changed files with 334 additions and 209 deletions

View File

@ -189,9 +189,15 @@ func (b *Local) renderPlan(plan *plans.Plan, schemas *terraform.Schemas) {
if counts[plans.Delete] > 0 { if counts[plans.Delete] > 0 {
fmt.Fprintf(headerBuf, "%s destroy\n", format.DiffActionSymbol(terraform.DiffDestroy)) fmt.Fprintf(headerBuf, "%s destroy\n", format.DiffActionSymbol(terraform.DiffDestroy))
} }
if counts[plans.Replace] > 0 { if counts[plans.DeleteThenCreate] > 0 {
fmt.Fprintf(headerBuf, "%s destroy and then create replacement\n", format.DiffActionSymbol(terraform.DiffDestroyCreate)) fmt.Fprintf(headerBuf, "%s destroy and then create replacement\n", format.DiffActionSymbol(terraform.DiffDestroyCreate))
} }
if counts[plans.CreateThenDelete] > 0 {
// FIXME: This shows the wrong symbol, because our old diff action
// type can't represent CreateThenDelete. We should switch
// format.DiffActionSymbol over to using plans.Action instead.
fmt.Fprintf(headerBuf, "%s create replacement and then destroy prior\n", format.DiffActionSymbol(terraform.DiffDestroyCreate))
}
if counts[plans.Read] > 0 { if counts[plans.Read] > 0 {
fmt.Fprintf(headerBuf, "%s read (data resources)\n", format.DiffActionSymbol(terraform.DiffRefresh)) fmt.Fprintf(headerBuf, "%s read (data resources)\n", format.DiffActionSymbol(terraform.DiffRefresh))
} }
@ -243,7 +249,7 @@ func (b *Local) renderPlan(plan *plans.Plan, schemas *terraform.Schemas) {
stats := map[plans.Action]int{} stats := map[plans.Action]int{}
for _, change := range rChanges { for _, change := range rChanges {
switch change.Action { switch change.Action {
case plans.Replace: case plans.CreateThenDelete, plans.DeleteThenCreate:
stats[plans.Create]++ stats[plans.Create]++
stats[plans.Delete]++ stats[plans.Delete]++
default: default:

View File

@ -65,7 +65,7 @@ func (h *CountHook) PostApply(addr addrs.AbsResourceInstance, gen states.Generat
if err == nil { if err == nil {
switch action { switch action {
case plans.Replace: case plans.CreateThenDelete, plans.DeleteThenCreate:
h.Added++ h.Added++
h.Removed++ h.Removed++
case plans.Create: case plans.Create:
@ -92,7 +92,7 @@ func (h *CountHook) PostDiff(addr addrs.AbsResourceInstance, gen states.Generati
} }
switch action { switch action {
case plans.Replace: case plans.CreateThenDelete, plans.DeleteThenCreate:
h.ToRemoveAndAdd += 1 h.ToRemoveAndAdd += 1
case plans.Create: case plans.Create:
h.ToAdd += 1 h.ToAdd += 1

View File

@ -55,7 +55,7 @@ func ResourceChange(
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(color.Color(fmt.Sprintf("[bold] # %s[reset] will be read during apply\n # (config refers to values not yet known)", dispAddr)))
case plans.Update: case plans.Update:
buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] will be updated in-place", dispAddr))) buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] will be updated in-place", dispAddr)))
case plans.Replace: case plans.CreateThenDelete, plans.DeleteThenCreate:
buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] must be [bold][red]replaced", dispAddr))) buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] must be [bold][red]replaced", dispAddr)))
case plans.Delete: case plans.Delete:
buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] will be [bold][red]destroyed", dispAddr))) buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] will be [bold][red]destroyed", dispAddr)))
@ -72,8 +72,10 @@ func ResourceChange(
buf.WriteString(color.Color("[cyan] <=[reset] ")) buf.WriteString(color.Color("[cyan] <=[reset] "))
case plans.Update: case plans.Update:
buf.WriteString(color.Color("[yellow] ~[reset] ")) buf.WriteString(color.Color("[yellow] ~[reset] "))
case plans.Replace: case plans.DeleteThenCreate:
buf.WriteString(color.Color("[red]-[reset]/[green]+[reset] ")) buf.WriteString(color.Color("[red]-[reset]/[green]+[reset] "))
case plans.CreateThenDelete:
buf.WriteString(color.Color("[green]+[reset]/[red]-[reset] "))
case plans.Delete: case plans.Delete:
buf.WriteString(color.Color("[red] -[reset] ")) buf.WriteString(color.Color("[red] -[reset] "))
default: default:
@ -837,7 +839,7 @@ func (p *blockBodyDiffPrinter) writeActionSymbol(action plans.Action) {
} }
func (p *blockBodyDiffPrinter) pathForcesNewResource(path cty.Path) bool { func (p *blockBodyDiffPrinter) pathForcesNewResource(path cty.Path) bool {
if p.action != plans.Replace { if p.action.IsReplace() {
// "requiredReplace" only applies when the instance is being replaced // "requiredReplace" only applies when the instance is being replaced
return false return false
} }

View File

@ -107,7 +107,7 @@ func NewPlan(changes *plans.Changes) *Plan {
did.Action = terraform.DiffRefresh did.Action = terraform.DiffRefresh
case plans.Delete: case plans.Delete:
did.Action = terraform.DiffDestroy did.Action = terraform.DiffDestroy
case plans.Replace: case plans.DeleteThenCreate, plans.CreateThenDelete:
did.Action = terraform.DiffDestroyCreate did.Action = terraform.DiffDestroyCreate
case plans.Update: case plans.Update:
did.Action = terraform.DiffUpdate did.Action = terraform.DiffUpdate

View File

@ -7,8 +7,16 @@ const (
Create Action = '+' Create Action = '+'
Read Action = '←' Read Action = '←'
Update Action = '~' Update Action = '~'
Replace Action = '±' DeleteThenCreate Action = '∓'
CreateThenDelete Action = '±'
Delete Action = '-' Delete Action = '-'
) )
//go:generate stringer -type Action //go:generate stringer -type Action
// IsReplace returns true if the action is one of the two actions that
// represents replacing an existing object with a new object:
// DeleteThenCreate or CreateThenDelete.
func (a Action) IsReplace() bool {
return a == DeleteThenCreate || a == CreateThenDelete
}

View File

@ -169,7 +169,7 @@ func (rc *ResourceInstanceChange) Simplify(destroying bool) *ResourceInstanceCha
switch rc.Action { switch rc.Action {
case Delete: case Delete:
// We'll fall out and just return rc verbatim, then. // We'll fall out and just return rc verbatim, then.
case Replace: case CreateThenDelete, DeleteThenCreate:
return &ResourceInstanceChange{ return &ResourceInstanceChange{
Addr: rc.Addr, Addr: rc.Addr,
DeposedKey: rc.DeposedKey, DeposedKey: rc.DeposedKey,
@ -208,7 +208,7 @@ func (rc *ResourceInstanceChange) Simplify(destroying bool) *ResourceInstanceCha
After: rc.Before, After: rc.Before,
}, },
} }
case Replace: case CreateThenDelete, DeleteThenCreate:
return &ResourceInstanceChange{ return &ResourceInstanceChange{
Addr: rc.Addr, Addr: rc.Addr,
DeposedKey: rc.DeposedKey, DeposedKey: rc.DeposedKey,

View File

@ -27,8 +27,9 @@ const (
Action_CREATE Action = 1 Action_CREATE Action = 1
Action_READ Action = 2 Action_READ Action = 2
Action_UPDATE Action = 3 Action_UPDATE Action = 3
Action_REPLACE Action = 4
Action_DELETE Action = 5 Action_DELETE Action = 5
Action_DELETE_THEN_CREATE Action = 6
Action_CREATE_THEN_DELETE Action = 7
) )
var Action_name = map[int32]string{ var Action_name = map[int32]string{
@ -36,23 +37,25 @@ var Action_name = map[int32]string{
1: "CREATE", 1: "CREATE",
2: "READ", 2: "READ",
3: "UPDATE", 3: "UPDATE",
4: "REPLACE",
5: "DELETE", 5: "DELETE",
6: "DELETE_THEN_CREATE",
7: "CREATE_THEN_DELETE",
} }
var Action_value = map[string]int32{ var Action_value = map[string]int32{
"NOOP": 0, "NOOP": 0,
"CREATE": 1, "CREATE": 1,
"READ": 2, "READ": 2,
"UPDATE": 3, "UPDATE": 3,
"REPLACE": 4,
"DELETE": 5, "DELETE": 5,
"DELETE_THEN_CREATE": 6,
"CREATE_THEN_DELETE": 7,
} }
func (x Action) String() string { func (x Action) String() string {
return proto.EnumName(Action_name, int32(x)) return proto.EnumName(Action_name, int32(x))
} }
func (Action) EnumDescriptor() ([]byte, []int) { func (Action) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_planfile_2f8b9a0ae177578e, []int{0} return fileDescriptor_planfile_f1de017ed03cb7aa, []int{0}
} }
type ResourceInstanceChange_ResourceMode int32 type ResourceInstanceChange_ResourceMode int32
@ -75,7 +78,7 @@ func (x ResourceInstanceChange_ResourceMode) String() string {
return proto.EnumName(ResourceInstanceChange_ResourceMode_name, int32(x)) return proto.EnumName(ResourceInstanceChange_ResourceMode_name, int32(x))
} }
func (ResourceInstanceChange_ResourceMode) EnumDescriptor() ([]byte, []int) { func (ResourceInstanceChange_ResourceMode) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_planfile_2f8b9a0ae177578e, []int{3, 0} return fileDescriptor_planfile_f1de017ed03cb7aa, []int{3, 0}
} }
// Plan is the root message type for the tfplan file // Plan is the root message type for the tfplan file
@ -122,7 +125,7 @@ func (m *Plan) Reset() { *m = Plan{} }
func (m *Plan) String() string { return proto.CompactTextString(m) } func (m *Plan) String() string { return proto.CompactTextString(m) }
func (*Plan) ProtoMessage() {} func (*Plan) ProtoMessage() {}
func (*Plan) Descriptor() ([]byte, []int) { func (*Plan) Descriptor() ([]byte, []int) {
return fileDescriptor_planfile_2f8b9a0ae177578e, []int{0} return fileDescriptor_planfile_f1de017ed03cb7aa, []int{0}
} }
func (m *Plan) XXX_Unmarshal(b []byte) error { func (m *Plan) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Plan.Unmarshal(m, b) return xxx_messageInfo_Plan.Unmarshal(m, b)
@ -212,7 +215,7 @@ func (m *Backend) Reset() { *m = Backend{} }
func (m *Backend) String() string { return proto.CompactTextString(m) } func (m *Backend) String() string { return proto.CompactTextString(m) }
func (*Backend) ProtoMessage() {} func (*Backend) ProtoMessage() {}
func (*Backend) Descriptor() ([]byte, []int) { func (*Backend) Descriptor() ([]byte, []int) {
return fileDescriptor_planfile_2f8b9a0ae177578e, []int{1} return fileDescriptor_planfile_f1de017ed03cb7aa, []int{1}
} }
func (m *Backend) XXX_Unmarshal(b []byte) error { func (m *Backend) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Backend.Unmarshal(m, b) return xxx_messageInfo_Backend.Unmarshal(m, b)
@ -278,7 +281,7 @@ func (m *Change) Reset() { *m = Change{} }
func (m *Change) String() string { return proto.CompactTextString(m) } func (m *Change) String() string { return proto.CompactTextString(m) }
func (*Change) ProtoMessage() {} func (*Change) ProtoMessage() {}
func (*Change) Descriptor() ([]byte, []int) { func (*Change) Descriptor() ([]byte, []int) {
return fileDescriptor_planfile_2f8b9a0ae177578e, []int{2} return fileDescriptor_planfile_f1de017ed03cb7aa, []int{2}
} }
func (m *Change) XXX_Unmarshal(b []byte) error { func (m *Change) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Change.Unmarshal(m, b) return xxx_messageInfo_Change.Unmarshal(m, b)
@ -364,7 +367,7 @@ func (m *ResourceInstanceChange) Reset() { *m = ResourceInstanceChange{}
func (m *ResourceInstanceChange) String() string { return proto.CompactTextString(m) } func (m *ResourceInstanceChange) String() string { return proto.CompactTextString(m) }
func (*ResourceInstanceChange) ProtoMessage() {} func (*ResourceInstanceChange) ProtoMessage() {}
func (*ResourceInstanceChange) Descriptor() ([]byte, []int) { func (*ResourceInstanceChange) Descriptor() ([]byte, []int) {
return fileDescriptor_planfile_2f8b9a0ae177578e, []int{3} return fileDescriptor_planfile_f1de017ed03cb7aa, []int{3}
} }
func (m *ResourceInstanceChange) XXX_Unmarshal(b []byte) error { func (m *ResourceInstanceChange) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ResourceInstanceChange.Unmarshal(m, b) return xxx_messageInfo_ResourceInstanceChange.Unmarshal(m, b)
@ -566,7 +569,7 @@ func (m *OutputChange) Reset() { *m = OutputChange{} }
func (m *OutputChange) String() string { return proto.CompactTextString(m) } func (m *OutputChange) String() string { return proto.CompactTextString(m) }
func (*OutputChange) ProtoMessage() {} func (*OutputChange) ProtoMessage() {}
func (*OutputChange) Descriptor() ([]byte, []int) { func (*OutputChange) Descriptor() ([]byte, []int) {
return fileDescriptor_planfile_2f8b9a0ae177578e, []int{4} return fileDescriptor_planfile_f1de017ed03cb7aa, []int{4}
} }
func (m *OutputChange) XXX_Unmarshal(b []byte) error { func (m *OutputChange) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_OutputChange.Unmarshal(m, b) return xxx_messageInfo_OutputChange.Unmarshal(m, b)
@ -630,7 +633,7 @@ func (m *DynamicValue) Reset() { *m = DynamicValue{} }
func (m *DynamicValue) String() string { return proto.CompactTextString(m) } func (m *DynamicValue) String() string { return proto.CompactTextString(m) }
func (*DynamicValue) ProtoMessage() {} func (*DynamicValue) ProtoMessage() {}
func (*DynamicValue) Descriptor() ([]byte, []int) { func (*DynamicValue) Descriptor() ([]byte, []int) {
return fileDescriptor_planfile_2f8b9a0ae177578e, []int{5} return fileDescriptor_planfile_f1de017ed03cb7aa, []int{5}
} }
func (m *DynamicValue) XXX_Unmarshal(b []byte) error { func (m *DynamicValue) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DynamicValue.Unmarshal(m, b) return xxx_messageInfo_DynamicValue.Unmarshal(m, b)
@ -676,7 +679,7 @@ func (m *Hash) Reset() { *m = Hash{} }
func (m *Hash) String() string { return proto.CompactTextString(m) } func (m *Hash) String() string { return proto.CompactTextString(m) }
func (*Hash) ProtoMessage() {} func (*Hash) ProtoMessage() {}
func (*Hash) Descriptor() ([]byte, []int) { func (*Hash) Descriptor() ([]byte, []int) {
return fileDescriptor_planfile_2f8b9a0ae177578e, []int{6} return fileDescriptor_planfile_f1de017ed03cb7aa, []int{6}
} }
func (m *Hash) XXX_Unmarshal(b []byte) error { func (m *Hash) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Hash.Unmarshal(m, b) return xxx_messageInfo_Hash.Unmarshal(m, b)
@ -717,7 +720,7 @@ func (m *Path) Reset() { *m = Path{} }
func (m *Path) String() string { return proto.CompactTextString(m) } func (m *Path) String() string { return proto.CompactTextString(m) }
func (*Path) ProtoMessage() {} func (*Path) ProtoMessage() {}
func (*Path) Descriptor() ([]byte, []int) { func (*Path) Descriptor() ([]byte, []int) {
return fileDescriptor_planfile_2f8b9a0ae177578e, []int{7} return fileDescriptor_planfile_f1de017ed03cb7aa, []int{7}
} }
func (m *Path) XXX_Unmarshal(b []byte) error { func (m *Path) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Path.Unmarshal(m, b) return xxx_messageInfo_Path.Unmarshal(m, b)
@ -758,7 +761,7 @@ func (m *Path_Step) Reset() { *m = Path_Step{} }
func (m *Path_Step) String() string { return proto.CompactTextString(m) } func (m *Path_Step) String() string { return proto.CompactTextString(m) }
func (*Path_Step) ProtoMessage() {} func (*Path_Step) ProtoMessage() {}
func (*Path_Step) Descriptor() ([]byte, []int) { func (*Path_Step) Descriptor() ([]byte, []int) {
return fileDescriptor_planfile_2f8b9a0ae177578e, []int{7, 0} return fileDescriptor_planfile_f1de017ed03cb7aa, []int{7, 0}
} }
func (m *Path_Step) XXX_Unmarshal(b []byte) error { func (m *Path_Step) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Path_Step.Unmarshal(m, b) return xxx_messageInfo_Path_Step.Unmarshal(m, b)
@ -899,64 +902,64 @@ func init() {
proto.RegisterEnum("tfplan.ResourceInstanceChange_ResourceMode", ResourceInstanceChange_ResourceMode_name, ResourceInstanceChange_ResourceMode_value) proto.RegisterEnum("tfplan.ResourceInstanceChange_ResourceMode", ResourceInstanceChange_ResourceMode_name, ResourceInstanceChange_ResourceMode_value)
} }
func init() { proto.RegisterFile("planfile.proto", fileDescriptor_planfile_2f8b9a0ae177578e) } func init() { proto.RegisterFile("planfile.proto", fileDescriptor_planfile_f1de017ed03cb7aa) }
var fileDescriptor_planfile_2f8b9a0ae177578e = []byte{ var fileDescriptor_planfile_f1de017ed03cb7aa = []byte{
// 885 bytes of a gzipped FileDescriptorProto // 893 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x55, 0xdb, 0x6e, 0xe3, 0x36, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x55, 0xe1, 0x6e, 0xe3, 0x44,
0x13, 0xb6, 0x2c, 0xf9, 0x34, 0x76, 0x14, 0x2d, 0xff, 0x1f, 0x0b, 0x21, 0x5d, 0x6c, 0x5d, 0x01, 0x10, 0xae, 0x63, 0xc7, 0x49, 0x26, 0xa9, 0x9b, 0x5b, 0x50, 0x65, 0x95, 0xd3, 0x11, 0x2c, 0xc1,
0xed, 0xba, 0xbb, 0x85, 0x03, 0xb8, 0x68, 0xd3, 0x6d, 0x2f, 0x0a, 0x27, 0x31, 0x90, 0x60, 0x0f, 0x85, 0x3b, 0x94, 0x4a, 0x41, 0x50, 0x0e, 0x7e, 0xa0, 0xf6, 0x1a, 0x29, 0xd5, 0x41, 0x1b, 0x2d,
0x31, 0xd8, 0x6d, 0x2e, 0x7a, 0x51, 0x83, 0x96, 0x26, 0xb6, 0x10, 0x9d, 0x4a, 0xd2, 0x2e, 0xfc, 0xa5, 0x3f, 0xf8, 0x81, 0xb5, 0xb1, 0xa7, 0x89, 0x55, 0xc7, 0x36, 0xbb, 0x9b, 0xa0, 0x3c, 0x10,
0x40, 0x7d, 0x88, 0xbe, 0x44, 0x9f, 0xa9, 0x20, 0x29, 0xc9, 0x0e, 0x90, 0xe6, 0xca, 0x9a, 0x6f, 0x0f, 0xc1, 0x4b, 0xf0, 0x4c, 0x68, 0x77, 0x6d, 0x27, 0x95, 0x7a, 0xfd, 0x95, 0x9d, 0x6f, 0x66,
0x86, 0x1f, 0x67, 0xbe, 0x99, 0xa1, 0xc1, 0x2d, 0x12, 0x96, 0xdd, 0xc5, 0x09, 0x8e, 0x0b, 0x9e, 0x3e, 0xcf, 0x7e, 0x33, 0xb3, 0x01, 0xaf, 0x48, 0x59, 0x76, 0x9f, 0xa4, 0x38, 0x2a, 0x78, 0x2e,
0xcb, 0x9c, 0xb4, 0xe5, 0x9d, 0x42, 0x82, 0x7f, 0x1c, 0x70, 0xe6, 0x09, 0xcb, 0x88, 0x0f, 0x9d, 0x73, 0xe2, 0xca, 0x7b, 0x85, 0x04, 0xff, 0x39, 0xe0, 0xcc, 0x52, 0x96, 0x11, 0x1f, 0x5a, 0x1b,
0x2d, 0x72, 0x11, 0xe7, 0x99, 0x6f, 0x0d, 0xad, 0x91, 0x43, 0x2b, 0x93, 0xbc, 0x85, 0xde, 0x96, 0xe4, 0x22, 0xc9, 0x33, 0xdf, 0x1a, 0x58, 0x43, 0x87, 0x56, 0x26, 0x79, 0x07, 0x9d, 0x0d, 0xe3,
0xf1, 0x98, 0x2d, 0x13, 0x14, 0x7e, 0x73, 0x68, 0x8f, 0xfa, 0x93, 0xcf, 0xc6, 0xe6, 0xf8, 0x58, 0x09, 0x9b, 0xa7, 0x28, 0xfc, 0xc6, 0xc0, 0x1e, 0x76, 0xc7, 0x9f, 0x8d, 0x4c, 0xfa, 0x48, 0xa5,
0x1d, 0x1d, 0xdf, 0x56, 0xde, 0x59, 0x26, 0xf9, 0x8e, 0xee, 0xa3, 0xc9, 0x35, 0x78, 0x1c, 0x45, 0x8e, 0xee, 0x2a, 0xef, 0x24, 0x93, 0x7c, 0x4b, 0x77, 0xd1, 0xe4, 0x0a, 0xfa, 0x1c, 0x45, 0xbe,
0xbe, 0xe1, 0x21, 0x2e, 0xc2, 0x35, 0xcb, 0x56, 0x28, 0x7c, 0x5b, 0x33, 0xbc, 0xac, 0x18, 0x68, 0xe6, 0x11, 0x86, 0xd1, 0x92, 0x65, 0x0b, 0x14, 0xbe, 0xad, 0x19, 0x5e, 0x55, 0x0c, 0xb4, 0xf4,
0xe9, 0xbf, 0xce, 0x84, 0x64, 0x59, 0x88, 0x17, 0x3a, 0x8c, 0x1e, 0x57, 0xe7, 0x8c, 0x2d, 0xc8, 0x5f, 0x65, 0x42, 0xb2, 0x2c, 0xc2, 0xf7, 0x3a, 0x8c, 0x1e, 0x55, 0x79, 0xc6, 0x16, 0xe4, 0x27,
0x4f, 0xe0, 0xe6, 0x1b, 0x59, 0x6c, 0x64, 0x4d, 0xe4, 0x68, 0xa2, 0xff, 0x57, 0x44, 0x37, 0xda, 0xf0, 0xf2, 0xb5, 0x2c, 0xd6, 0xb2, 0x26, 0x72, 0x34, 0xd1, 0xa7, 0x15, 0xd1, 0x8d, 0xf6, 0x96,
0x5b, 0x1e, 0x3f, 0xca, 0x0f, 0x2c, 0x41, 0xbe, 0x80, 0x81, 0x64, 0x7c, 0x85, 0x72, 0xc1, 0xa2, 0xe9, 0x87, 0xf9, 0x9e, 0x25, 0xc8, 0x17, 0xd0, 0x93, 0x8c, 0x2f, 0x50, 0x86, 0x2c, 0x8e, 0xb9,
0x88, 0x0b, 0xbf, 0x35, 0xb4, 0x47, 0x3d, 0xda, 0x37, 0xd8, 0x54, 0x41, 0xe4, 0x0d, 0x3c, 0x93, 0xf0, 0x9b, 0x03, 0x7b, 0xd8, 0xa1, 0x5d, 0x83, 0x9d, 0x2b, 0x88, 0xbc, 0x85, 0x17, 0x12, 0x39,
0xc8, 0x39, 0xbb, 0xcb, 0x79, 0xba, 0xa8, 0x94, 0x70, 0x87, 0xd6, 0xa8, 0x47, 0xbd, 0xda, 0x71, 0x67, 0xf7, 0x39, 0x5f, 0x85, 0x95, 0x12, 0xde, 0xc0, 0x1a, 0x76, 0x68, 0xbf, 0x76, 0xdc, 0x95,
0x5b, 0x4a, 0x72, 0x0d, 0xc7, 0x05, 0xcf, 0xb7, 0x71, 0x84, 0x7c, 0xb1, 0x66, 0x62, 0x8d, 0xc2, 0x92, 0x5c, 0xc1, 0x51, 0xc1, 0xf3, 0x4d, 0x12, 0x23, 0x0f, 0x97, 0x4c, 0x2c, 0x51, 0xf8, 0x47,
0x3f, 0xd6, 0xd9, 0x0c, 0x1f, 0x08, 0x33, 0x2f, 0x63, 0xae, 0x74, 0x88, 0x51, 0xc7, 0x2d, 0x1e, 0xba, 0x9a, 0xc1, 0x23, 0x61, 0x66, 0x65, 0xcc, 0x54, 0x87, 0x18, 0x75, 0xbc, 0xe2, 0x11, 0x48,
0x80, 0xe4, 0x6b, 0xe8, 0x2c, 0x59, 0x78, 0x8f, 0x59, 0xe4, 0x1f, 0x0d, 0xad, 0x51, 0x7f, 0x72, 0xbe, 0x86, 0xd6, 0x9c, 0x45, 0x0f, 0x98, 0xc5, 0xfe, 0xe1, 0xc0, 0x1a, 0x76, 0xc7, 0x47, 0x15,
0x5c, 0x51, 0x9c, 0x1b, 0x98, 0x56, 0xfe, 0x13, 0x0a, 0xee, 0x43, 0xa9, 0x89, 0x07, 0xf6, 0x3d, 0xc5, 0x85, 0x81, 0x69, 0xe5, 0x3f, 0xa1, 0xe0, 0x3d, 0x96, 0x9a, 0xf4, 0xc1, 0x7e, 0xc0, 0xad,
0xee, 0x74, 0xc3, 0x7a, 0x54, 0x7d, 0x92, 0xd7, 0xd0, 0xda, 0xb2, 0x64, 0x83, 0x7e, 0x53, 0x93, 0x6e, 0x58, 0x87, 0xaa, 0x23, 0x79, 0x03, 0xcd, 0x0d, 0x4b, 0xd7, 0xe8, 0x37, 0x34, 0x59, 0xad,
0xd5, 0xea, 0x5c, 0xee, 0x32, 0x96, 0xc6, 0xe1, 0xad, 0xf2, 0x51, 0x13, 0xf2, 0x63, 0xf3, 0x07, 0xce, 0xe5, 0x36, 0x63, 0xab, 0x24, 0xba, 0x53, 0x3e, 0x6a, 0x42, 0x7e, 0x6c, 0xfc, 0x60, 0x9d,
0xeb, 0xe4, 0x06, 0xfe, 0xf7, 0x48, 0x96, 0x8f, 0x10, 0x07, 0x0f, 0x89, 0x07, 0x15, 0xb1, 0x3a, 0xdc, 0xc0, 0x27, 0x4f, 0x54, 0xf9, 0x04, 0x71, 0xf0, 0x98, 0xb8, 0x57, 0x11, 0xab, 0xac, 0x3d,
0x75, 0x40, 0x18, 0xc4, 0xd0, 0x29, 0x13, 0x27, 0x04, 0x1c, 0xb9, 0x2b, 0xb0, 0x64, 0xd1, 0xdf, 0xc2, 0x20, 0x81, 0x56, 0x59, 0x38, 0x21, 0xe0, 0xc8, 0x6d, 0x81, 0x25, 0x8b, 0x3e, 0x93, 0x6f,
0xe4, 0x1b, 0x68, 0x87, 0x79, 0x76, 0x17, 0xaf, 0x9e, 0x4c, 0xb0, 0x8c, 0x21, 0x2f, 0xa0, 0xf7, 0xc0, 0x8d, 0xf2, 0xec, 0x3e, 0x59, 0x3c, 0x5b, 0x60, 0x19, 0x43, 0x5e, 0x42, 0xe7, 0xef, 0x9c,
0x67, 0xce, 0xef, 0x45, 0xc1, 0x42, 0xf4, 0x6d, 0x4d, 0xb3, 0x07, 0x82, 0xdf, 0xa1, 0x6d, 0x1a, 0x3f, 0x88, 0x82, 0x45, 0xe8, 0xdb, 0x9a, 0x66, 0x07, 0x04, 0x7f, 0x82, 0x6b, 0x1a, 0x4c, 0xbe,
0x4c, 0xbe, 0x82, 0x36, 0x0b, 0x65, 0x35, 0xbb, 0xee, 0xc4, 0xad, 0x58, 0xa7, 0x1a, 0xa5, 0xa5, 0x02, 0x97, 0x45, 0xb2, 0x9a, 0x5d, 0x6f, 0xec, 0x55, 0xac, 0xe7, 0x1a, 0xa5, 0xa5, 0x57, 0x7d,
0x57, 0xdd, 0xae, 0x33, 0xad, 0xe6, 0xf8, 0x3f, 0x6e, 0x37, 0x31, 0xc1, 0xdf, 0x36, 0x3c, 0x7f, 0x5d, 0x57, 0x5a, 0xcd, 0xf1, 0x47, 0xbe, 0x6e, 0x62, 0x82, 0x7f, 0x6d, 0x38, 0x7e, 0x7a, 0x3c,
0x7c, 0x3c, 0xc9, 0xe7, 0xd0, 0x4f, 0xf3, 0x68, 0x93, 0xe0, 0xa2, 0x60, 0x72, 0x5d, 0x56, 0x08, 0xc9, 0xe7, 0xd0, 0x5d, 0xe5, 0xf1, 0x3a, 0xc5, 0xb0, 0x60, 0x72, 0x59, 0xde, 0x10, 0x0c, 0x34,
0x06, 0x9a, 0x33, 0xb9, 0x26, 0x3f, 0x83, 0x93, 0xe6, 0x91, 0x51, 0xcb, 0x9d, 0xbc, 0x79, 0x7a, 0x63, 0x72, 0x49, 0x7e, 0x06, 0x67, 0x95, 0xc7, 0x46, 0x2d, 0x6f, 0xfc, 0xf6, 0xf9, 0x69, 0xaf,
0xda, 0x6b, 0xf8, 0x43, 0x1e, 0x21, 0xd5, 0x07, 0x6b, 0xf1, 0xec, 0x03, 0xf1, 0x08, 0x38, 0x19, 0xe1, 0x5f, 0xf3, 0x18, 0xa9, 0x4e, 0xac, 0xc5, 0xb3, 0xf7, 0xc4, 0x23, 0xe0, 0x64, 0x6c, 0x85,
0x4b, 0xd1, 0x77, 0x0c, 0xa6, 0xbe, 0x09, 0x01, 0x5b, 0x48, 0xee, 0xb7, 0x14, 0x74, 0xd5, 0xa0, 0xbe, 0x63, 0x30, 0x75, 0x26, 0x04, 0x6c, 0x21, 0xb9, 0xdf, 0x54, 0xd0, 0xf4, 0x80, 0x2a, 0x43,
0xca, 0x50, 0x58, 0x9c, 0x49, 0xbf, 0x3d, 0xb4, 0x46, 0xb6, 0xc2, 0xe2, 0x4c, 0xaa, 0x8c, 0x23, 0x61, 0x49, 0x26, 0x7d, 0x77, 0x60, 0x0d, 0x6d, 0x85, 0x25, 0x99, 0x54, 0x15, 0xc7, 0x58, 0xe4,
0x2c, 0x72, 0x81, 0xd1, 0x42, 0x75, 0xb6, 0x63, 0x32, 0x2e, 0xa1, 0x77, 0xb8, 0x23, 0x27, 0xd0, 0x02, 0xe3, 0x50, 0x75, 0xb6, 0x65, 0x2a, 0x2e, 0xa1, 0x0f, 0xb8, 0x25, 0x27, 0xd0, 0xae, 0x46,
0xad, 0x46, 0xd3, 0xef, 0x6a, 0x6f, 0x6d, 0x2b, 0x7d, 0xcd, 0xd6, 0xf9, 0x3d, 0xdd, 0xb5, 0x5a, 0xd3, 0x6f, 0x6b, 0x6f, 0x6d, 0x2b, 0x7d, 0xcd, 0xd6, 0xf9, 0x1d, 0xdd, 0xb5, 0x5a, 0xdf, 0x72,
0xdf, 0x72, 0xdd, 0x4a, 0xaf, 0x7a, 0x44, 0x0a, 0x1e, 0x6f, 0x99, 0x44, 0x1f, 0x86, 0xd6, 0x68, 0xdd, 0x4a, 0xaf, 0x7a, 0x44, 0x0a, 0x9e, 0x6c, 0x98, 0x44, 0x1f, 0x06, 0xd6, 0xb0, 0x47, 0x2b,
0x40, 0x2b, 0x93, 0x9c, 0xa9, 0x97, 0xe0, 0x8f, 0x4d, 0xcc, 0x31, 0x5a, 0x70, 0x2c, 0x12, 0xd5, 0x93, 0x9c, 0xa9, 0x97, 0xe0, 0xaf, 0x75, 0xc2, 0x31, 0x0e, 0x39, 0x16, 0xa9, 0x6a, 0x68, 0x57,
0xd0, 0xbe, 0xee, 0x41, 0x3d, 0x49, 0x4a, 0x37, 0xb5, 0xf7, 0x26, 0x8a, 0x9a, 0xa0, 0xe0, 0x4b, 0xf7, 0xa0, 0x9e, 0x24, 0xa5, 0x9b, 0xda, 0x7b, 0x13, 0x45, 0x4d, 0x50, 0xf0, 0x25, 0xf4, 0xf6,
0x18, 0x1c, 0xaa, 0x43, 0xfa, 0xd0, 0x49, 0x59, 0xc6, 0x56, 0x18, 0x79, 0x0d, 0xd2, 0x05, 0x27, 0xd5, 0x21, 0x5d, 0x68, 0xad, 0x58, 0xc6, 0x16, 0x18, 0xf7, 0x0f, 0x48, 0x1b, 0x9c, 0x98, 0x49,
0x62, 0x92, 0x79, 0xd6, 0xb9, 0x0b, 0x83, 0xb8, 0xd4, 0x54, 0xd5, 0x17, 0xac, 0x61, 0x70, 0xf8, 0xd6, 0xb7, 0x2e, 0x3c, 0xe8, 0x25, 0xa5, 0xa6, 0xea, 0x7e, 0xc1, 0x12, 0x7a, 0xfb, 0x0f, 0x42,
0x20, 0xd4, 0xd2, 0x59, 0x07, 0xd2, 0xed, 0xab, 0x6a, 0x3e, 0x59, 0xd5, 0x0b, 0xe8, 0x09, 0xcc, 0x2d, 0x9d, 0xb5, 0x27, 0xdd, 0xee, 0x56, 0x8d, 0x67, 0x6f, 0xf5, 0x12, 0x3a, 0x02, 0x33, 0x91,
0x44, 0x2c, 0xe3, 0xad, 0xe9, 0x47, 0x97, 0xee, 0x81, 0x60, 0x04, 0x83, 0xc3, 0xe9, 0x51, 0x1a, 0xc8, 0x64, 0x63, 0xfa, 0xd1, 0xa6, 0x3b, 0x20, 0x18, 0x42, 0x6f, 0x7f, 0x7a, 0x94, 0x06, 0x2b,
0xa4, 0x62, 0x55, 0xb0, 0xf0, 0x5e, 0x5f, 0x36, 0xa0, 0x95, 0x19, 0xbc, 0x04, 0x47, 0x6d, 0x0b, 0xb1, 0x28, 0x58, 0xf4, 0xa0, 0x3f, 0xd6, 0xa3, 0x95, 0x19, 0xbc, 0x02, 0x47, 0x6d, 0x0b, 0x39,
0x79, 0x0e, 0x6d, 0xb1, 0x66, 0x93, 0xef, 0xbe, 0x2f, 0x03, 0x4a, 0x2b, 0xf8, 0xcb, 0x02, 0x47, 0x06, 0x57, 0x2c, 0xd9, 0xf8, 0xbb, 0xef, 0xcb, 0x80, 0xd2, 0x0a, 0xfe, 0xb1, 0xc0, 0xd1, 0xc3,
0x0f, 0xcf, 0x2b, 0x68, 0x09, 0x89, 0x85, 0xf0, 0x2d, 0xad, 0xd0, 0xb3, 0x43, 0x85, 0xc6, 0xbf, 0xf3, 0x1a, 0x9a, 0x42, 0x62, 0x21, 0x7c, 0x4b, 0x2b, 0xf4, 0x62, 0x5f, 0xa1, 0xd1, 0x6f, 0x12,
0x48, 0x2c, 0xa8, 0xf1, 0x9f, 0x48, 0x70, 0x94, 0x49, 0x5e, 0x81, 0xcb, 0xa4, 0xe4, 0xf1, 0x72, 0x0b, 0x6a, 0xfc, 0x27, 0x12, 0x1c, 0x65, 0x92, 0xd7, 0xe0, 0x31, 0x29, 0x79, 0x32, 0x5f, 0x4b,
0x23, 0x71, 0xb1, 0xaf, 0xf3, 0xaa, 0x41, 0x8f, 0x6a, 0xfc, 0xa3, 0x2a, 0xf9, 0x0c, 0xfa, 0x98, 0x0c, 0x77, 0xf7, 0x9c, 0x1e, 0xd0, 0xc3, 0x1a, 0xbf, 0x56, 0x57, 0x3e, 0x83, 0x2e, 0xa6, 0xb8,
0x60, 0x8a, 0x99, 0xd4, 0x53, 0xf0, 0xc4, 0x0e, 0x5e, 0x35, 0x28, 0x94, 0xa1, 0xef, 0x70, 0x77, 0xc2, 0x4c, 0xea, 0x29, 0x78, 0x66, 0x07, 0xa7, 0x07, 0x14, 0xca, 0xd0, 0x0f, 0xb8, 0xbd, 0x00,
0x0e, 0xd0, 0x15, 0x98, 0x60, 0x28, 0x73, 0xfe, 0xfa, 0x03, 0xb4, 0xcd, 0x5e, 0x29, 0xfd, 0x3f, 0x68, 0x0b, 0x4c, 0x31, 0x92, 0x39, 0x7f, 0x53, 0x80, 0x6b, 0xf6, 0x4a, 0xe9, 0x7f, 0x7d, 0x73,
0xde, 0xdc, 0xcc, 0xbd, 0x06, 0x01, 0x68, 0x5f, 0xd0, 0xd9, 0xf4, 0xd3, 0xcc, 0xb3, 0x14, 0x4a, 0x33, 0xeb, 0x1f, 0x10, 0x00, 0xf7, 0x3d, 0x9d, 0x9c, 0xdf, 0x4e, 0xfa, 0x96, 0x42, 0xe9, 0xe4,
0x67, 0xd3, 0x4b, 0xaf, 0xa9, 0xd0, 0x5f, 0xe7, 0x97, 0x0a, 0xb5, 0x55, 0xe3, 0xe8, 0x6c, 0xfe, 0xfc, 0xb2, 0xdf, 0x50, 0xe8, 0xef, 0xb3, 0x4b, 0x85, 0xda, 0xea, 0x7c, 0x39, 0xf9, 0x65, 0x72,
0x7e, 0x7a, 0x31, 0xf3, 0x1c, 0xe5, 0xb8, 0x9c, 0xbd, 0x9f, 0x7d, 0x9a, 0x79, 0xad, 0xf3, 0xb7, 0x3b, 0xe9, 0x37, 0xc9, 0x31, 0x10, 0x73, 0x0e, 0x6f, 0xa7, 0x93, 0xeb, 0xb0, 0xcc, 0x74, 0x15,
0xbf, 0x9d, 0xad, 0x62, 0xb9, 0xde, 0x2c, 0xc7, 0x61, 0x9e, 0x9e, 0xaa, 0xe7, 0x34, 0x0e, 0x73, 0x6e, 0xce, 0x06, 0x2f, 0xe3, 0x5b, 0x17, 0xef, 0xfe, 0x38, 0x5b, 0x24, 0x72, 0xb9, 0x9e, 0x8f,
0x5e, 0x9c, 0xd6, 0xaf, 0xee, 0xa9, 0x4a, 0x4e, 0x9c, 0xc6, 0x99, 0x44, 0x9e, 0xb1, 0x44, 0x9b, 0xa2, 0x7c, 0x75, 0xaa, 0x5e, 0xdc, 0x24, 0xca, 0x79, 0x71, 0x5a, 0x3f, 0xcc, 0xa7, 0xaa, 0x7e,
0xfa, 0x5f, 0x6c, 0xd9, 0xd6, 0x3f, 0xdf, 0xfe, 0x1b, 0x00, 0x00, 0xff, 0xff, 0x9e, 0x41, 0x65, 0x71, 0x9a, 0x64, 0x12, 0x79, 0xc6, 0x52, 0x6d, 0xea, 0x3f, 0xba, 0xb9, 0xab, 0x7f, 0xbe, 0xfd,
0x5b, 0xde, 0x06, 0x00, 0x00, 0x3f, 0x00, 0x00, 0xff, 0xff, 0x30, 0x3e, 0x4e, 0x33, 0x01, 0x07, 0x00, 0x00,
} }

View File

@ -63,8 +63,9 @@ enum Action {
CREATE = 1; CREATE = 1;
READ = 2; READ = 2;
UPDATE = 3; UPDATE = 3;
REPLACE = 4;
DELETE = 5; DELETE = 5;
DELETE_THEN_CREATE = 6;
CREATE_THEN_DELETE = 7;
} }
// Change represents a change made to some object, transforming it from an old // Change represents a change made to some object, transforming it from an old

View File

@ -230,13 +230,17 @@ func changeFromTfplan(rawChange *planproto.Change) (*plans.ChangeSrc, error) {
ret.Action = plans.Update ret.Action = plans.Update
beforeIdx = 0 beforeIdx = 0
afterIdx = 1 afterIdx = 1
case planproto.Action_REPLACE:
ret.Action = plans.Replace
beforeIdx = 0
afterIdx = 1
case planproto.Action_DELETE: case planproto.Action_DELETE:
ret.Action = plans.Delete ret.Action = plans.Delete
beforeIdx = 0 beforeIdx = 0
case planproto.Action_CREATE_THEN_DELETE:
ret.Action = plans.CreateThenDelete
beforeIdx = 0
afterIdx = 1
case planproto.Action_DELETE_THEN_CREATE:
ret.Action = plans.DeleteThenCreate
beforeIdx = 0
afterIdx = 1
default: default:
return nil, fmt.Errorf("invalid change action %s", rawChange.Action) return nil, fmt.Errorf("invalid change action %s", rawChange.Action)
} }
@ -425,12 +429,15 @@ func changeToTfplan(change *plans.ChangeSrc) (*planproto.Change, error) {
case plans.Update: case plans.Update:
ret.Action = planproto.Action_UPDATE ret.Action = planproto.Action_UPDATE
ret.Values = []*planproto.DynamicValue{before, after} ret.Values = []*planproto.DynamicValue{before, after}
case plans.Replace:
ret.Action = planproto.Action_REPLACE
ret.Values = []*planproto.DynamicValue{before, after}
case plans.Delete: case plans.Delete:
ret.Action = planproto.Action_DELETE ret.Action = planproto.Action_DELETE
ret.Values = []*planproto.DynamicValue{before} ret.Values = []*planproto.DynamicValue{before}
case plans.DeleteThenCreate:
ret.Action = planproto.Action_DELETE_THEN_CREATE
ret.Values = []*planproto.DynamicValue{before, after}
case plans.CreateThenDelete:
ret.Action = planproto.Action_CREATE_THEN_DELETE
ret.Values = []*planproto.DynamicValue{before, after}
default: default:
return nil, fmt.Errorf("invalid change action %s", change.Action) return nil, fmt.Errorf("invalid change action %s", change.Action)
} }

View File

@ -980,6 +980,9 @@ aws_instance.bar:
require_new = yes require_new = yes
type = aws_instance type = aws_instance
value = foo value = foo
Dependencies:
aws_instance.foo
aws_instance.foo: aws_instance.foo:
ID = foo ID = foo
provider = provider.aws provider = provider.aws

View File

@ -3885,7 +3885,7 @@ func TestContext2Plan_taint(t *testing.T) {
switch i := ric.Addr.String(); i { switch i := ric.Addr.String(); i {
case "aws_instance.bar": case "aws_instance.bar":
if res.Action != plans.Replace { if res.Action != plans.DeleteThenCreate {
t.Fatalf("resource %s should be replaced", i) t.Fatalf("resource %s should be replaced", i)
} }
checkVals(t, objectVal(t, schema, map[string]cty.Value{ checkVals(t, objectVal(t, schema, map[string]cty.Value{
@ -3970,7 +3970,7 @@ func TestContext2Plan_taintIgnoreChanges(t *testing.T) {
switch i := ric.Addr.String(); i { switch i := ric.Addr.String(); i {
case "aws_instance.foo": case "aws_instance.foo":
if res.Action != plans.Replace { if res.Action != plans.DeleteThenCreate {
t.Fatalf("resource %s should be replaced", i) t.Fatalf("resource %s should be replaced", i)
} }
checkVals(t, objectVal(t, schema, map[string]cty.Value{ checkVals(t, objectVal(t, schema, map[string]cty.Value{
@ -4050,7 +4050,7 @@ func TestContext2Plan_taintDestroyInterpolatedCountRace(t *testing.T) {
switch i := ric.Addr.String(); i { switch i := ric.Addr.String(); i {
case "aws_instance.foo[0]": case "aws_instance.foo[0]":
if res.Action != plans.Replace { if res.Action != plans.DeleteThenCreate {
t.Fatalf("resource %s should be replaced", i) t.Fatalf("resource %s should be replaced", i)
} }
checkVals(t, objectVal(t, schema, map[string]cty.Value{ checkVals(t, objectVal(t, schema, map[string]cty.Value{

View File

@ -876,8 +876,10 @@ func legacyDiffComparisonString(changes *plans.Changes) string {
crud := "UPDATE" crud := "UPDATE"
if rc.Current != nil { if rc.Current != nil {
switch rc.Current.Action { switch rc.Current.Action {
case plans.Replace: case plans.DeleteThenCreate:
crud = "DESTROY/CREATE" crud = "DESTROY/CREATE"
case plans.CreateThenDelete:
crud = "CREATE/DESTROY"
case plans.Delete: case plans.Delete:
crud = "DESTROY" crud = "DESTROY"
case plans.Create: case plans.Create:

View File

@ -53,7 +53,7 @@ func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) {
} }
if n.CreateNew != nil { if n.CreateNew != nil {
*n.CreateNew = (change.Action == plans.Create || change.Action == plans.Replace) *n.CreateNew = (change.Action == plans.Create || change.Action.IsReplace())
} }
configVal := cty.NullVal(cty.DynamicPseudoType) configVal := cty.NullVal(cty.DynamicPseudoType)

View File

@ -29,7 +29,7 @@ func (n *EvalCheckPreventDestroy) Eval(ctx EvalContext) (interface{}, error) {
change := *n.Change change := *n.Change
preventDestroy := n.Config.Managed.PreventDestroy preventDestroy := n.Config.Managed.PreventDestroy
if (change.Action == plans.Delete || change.Action == plans.Replace) && preventDestroy { if (change.Action == plans.Delete || change.Action.IsReplace()) && preventDestroy {
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
diags = diags.Append(&hcl.Diagnostic{ diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError, Severity: hcl.DiagError,

View File

@ -92,6 +92,12 @@ type EvalDiff struct {
State **states.ResourceInstanceObject State **states.ResourceInstanceObject
PreviousDiff **plans.ResourceInstanceChange PreviousDiff **plans.ResourceInstanceChange
// CreateBeforeDestroy is set if either the resource's own config sets
// create_before_destroy explicitly or if dependencies have forced the
// resource to be handled as create_before_destroy in order to avoid
// a dependency cycle.
CreateBeforeDestroy bool
OutputChange **plans.ResourceInstanceChange OutputChange **plans.ResourceInstanceChange
OutputValue *cty.Value OutputValue *cty.Value
OutputState **states.ResourceInstanceObject OutputState **states.ResourceInstanceObject
@ -270,14 +276,18 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) {
case !reqRep.Empty(): case !reqRep.Empty():
// If there are any "requires replace" paths left _after our filtering // If there are any "requires replace" paths left _after our filtering
// above_ then this is a replace action. // above_ then this is a replace action.
action = plans.Replace if n.CreateBeforeDestroy {
action = plans.CreateThenDelete
} else {
action = plans.DeleteThenCreate
}
default: default:
action = plans.Update action = plans.Update
// "Delete" is never chosen here, because deletion plans are always // "Delete" is never chosen here, because deletion plans are always
// created more directly elsewhere, such as in "orphan" handling. // created more directly elsewhere, such as in "orphan" handling.
} }
if action == plans.Replace { if action.IsReplace() {
// In this strange situation we want to produce a change object that // In this strange situation we want to produce a change object that
// shows our real prior object but has a _new_ object that is built // shows our real prior object but has a _new_ object that is built
// from a null prior object, since we're going to delete the one // from a null prior object, since we're going to delete the one
@ -327,7 +337,11 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) {
// as a replace change, even though so far we've been treating it as a // as a replace change, even though so far we've been treating it as a
// create. // create.
if action == plans.Create && priorValTainted != cty.NilVal { if action == plans.Create && priorValTainted != cty.NilVal {
action = plans.Replace if n.CreateBeforeDestroy {
action = plans.CreateThenDelete
} else {
action = plans.DeleteThenCreate
}
priorVal = priorValTainted priorVal = priorValTainted
} }
@ -339,9 +353,9 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) {
// we originally intended. // we originally intended.
if n.PreviousDiff != nil { if n.PreviousDiff != nil {
prevChange := *n.PreviousDiff prevChange := *n.PreviousDiff
if prevChange.Action == plans.Replace && action == plans.Create { if prevChange.Action.IsReplace() && action == plans.Create {
log.Printf("[TRACE] EvalDiff: %s treating Create change as Replace change to match with earlier plan", absAddr) log.Printf("[TRACE] EvalDiff: %s treating Create change as %s change to match with earlier plan", absAddr, prevChange.Action)
action = plans.Replace action = prevChange.Action
priorVal = prevChange.Before priorVal = prevChange.Before
} }
} }

View File

@ -95,7 +95,6 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
// ConfigTransformer above. // ConfigTransformer above.
&DiffTransformer{ &DiffTransformer{
Concrete: concreteResourceInstance, Concrete: concreteResourceInstance,
Config: b.Config,
State: b.State, State: b.State,
Changes: b.Changes, Changes: b.Changes,
}, },

View File

@ -76,7 +76,7 @@ func TestApplyGraphBuilder_depCbd(t *testing.T) {
{ {
Addr: mustResourceInstanceAddr("test_object.A"), Addr: mustResourceInstanceAddr("test_object.A"),
ChangeSrc: plans.ChangeSrc{ ChangeSrc: plans.ChangeSrc{
Action: plans.Replace, Action: plans.CreateThenDelete,
}, },
}, },
{ {
@ -152,13 +152,13 @@ func TestApplyGraphBuilder_doubleCBD(t *testing.T) {
{ {
Addr: mustResourceInstanceAddr("test_object.A"), Addr: mustResourceInstanceAddr("test_object.A"),
ChangeSrc: plans.ChangeSrc{ ChangeSrc: plans.ChangeSrc{
Action: plans.Replace, Action: plans.CreateThenDelete,
}, },
}, },
{ {
Addr: mustResourceInstanceAddr("test_object.B"), Addr: mustResourceInstanceAddr("test_object.B"),
ChangeSrc: plans.ChangeSrc{ ChangeSrc: plans.ChangeSrc{
Action: plans.Replace, Action: plans.CreateThenDelete,
}, },
}, },
}, },

View File

@ -157,6 +157,10 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
IgnoreIndices: true, IgnoreIndices: true,
}, },
// Detect when create_before_destroy must be forced on for a particular
// node due to dependency edges, to avoid graph cycles during apply.
&ForcedCBDTransformer{},
// Close opened plugin connections // Close opened plugin connections
&CloseProviderTransformer{}, &CloseProviderTransformer{},
&CloseProvisionerTransformer{}, &CloseProvisionerTransformer{},

View File

@ -241,7 +241,7 @@ func (n *NodeApplyableResourceInstance) evalTreeManagedResource(addr addrs.AbsRe
If: func(ctx EvalContext) (bool, error) { If: func(ctx EvalContext) (bool, error) {
destroy := false destroy := false
if diffApply != nil { if diffApply != nil {
destroy = (diffApply.Action == plans.Delete || diffApply.Action == plans.Replace) destroy = (diffApply.Action == plans.Delete || diffApply.Action.IsReplace())
} }
if destroy && n.createBeforeDestroy() { if destroy && n.createBeforeDestroy() {
createBeforeDestroyEnabled = true createBeforeDestroyEnabled = true
@ -395,7 +395,7 @@ func (n *NodeApplyableResourceInstance) evalTreeManagedResource(addr addrs.AbsRe
// is no longer a diff! // is no longer a diff!
&EvalIf{ &EvalIf{
If: func(ctx EvalContext) (bool, error) { If: func(ctx EvalContext) (bool, error) {
if diff.Action != plans.Replace { if !diff.Action.IsReplace() {
return true, nil return true, nil
} }
if !n.createBeforeDestroy() { if !n.createBeforeDestroy() {

View File

@ -15,6 +15,10 @@ import (
// an associated deposed object key. // an associated deposed object key.
type ConcreteResourceInstanceDeposedNodeFunc func(*NodeAbstractResourceInstance, states.DeposedKey) dag.Vertex type ConcreteResourceInstanceDeposedNodeFunc func(*NodeAbstractResourceInstance, states.DeposedKey) dag.Vertex
type GraphNodeDeposedResourceInstanceObject interface {
DeposedInstanceObjectKey() states.DeposedKey
}
// NodePlanDeposedResourceInstanceObject represents deposed resource // NodePlanDeposedResourceInstanceObject represents deposed resource
// instance objects during plan. These are distinct from the primary object // instance objects during plan. These are distinct from the primary object
// for each resource instance since the only valid operation to do with them // for each resource instance since the only valid operation to do with them
@ -28,6 +32,7 @@ type NodePlanDeposedResourceInstanceObject struct {
} }
var ( var (
_ GraphNodeDeposedResourceInstanceObject = (*NodePlanDeposedResourceInstanceObject)(nil)
_ GraphNodeResource = (*NodePlanDeposedResourceInstanceObject)(nil) _ GraphNodeResource = (*NodePlanDeposedResourceInstanceObject)(nil)
_ GraphNodeResourceInstance = (*NodePlanDeposedResourceInstanceObject)(nil) _ GraphNodeResourceInstance = (*NodePlanDeposedResourceInstanceObject)(nil)
_ GraphNodeReferenceable = (*NodePlanDeposedResourceInstanceObject)(nil) _ GraphNodeReferenceable = (*NodePlanDeposedResourceInstanceObject)(nil)
@ -41,6 +46,10 @@ func (n *NodePlanDeposedResourceInstanceObject) Name() string {
return fmt.Sprintf("%s (deposed %s)", n.Addr.String(), n.DeposedKey) return fmt.Sprintf("%s (deposed %s)", n.Addr.String(), n.DeposedKey)
} }
func (n *NodePlanDeposedResourceInstanceObject) DeposedInstanceObjectKey() states.DeposedKey {
return n.DeposedKey
}
// GraphNodeReferenceable implementation, overriding the one from NodeAbstractResourceInstance // GraphNodeReferenceable implementation, overriding the one from NodeAbstractResourceInstance
func (n *NodePlanDeposedResourceInstanceObject) ReferenceableAddrs() []addrs.Referenceable { func (n *NodePlanDeposedResourceInstanceObject) ReferenceableAddrs() []addrs.Referenceable {
// Deposed objects don't participate in references. // Deposed objects don't participate in references.
@ -155,6 +164,7 @@ type NodeDestroyDeposedResourceInstanceObject struct {
} }
var ( var (
_ GraphNodeDeposedResourceInstanceObject = (*NodeDestroyDeposedResourceInstanceObject)(nil)
_ GraphNodeResource = (*NodeDestroyDeposedResourceInstanceObject)(nil) _ GraphNodeResource = (*NodeDestroyDeposedResourceInstanceObject)(nil)
_ GraphNodeResourceInstance = (*NodeDestroyDeposedResourceInstanceObject)(nil) _ GraphNodeResourceInstance = (*NodeDestroyDeposedResourceInstanceObject)(nil)
_ GraphNodeDestroyer = (*NodeDestroyDeposedResourceInstanceObject)(nil) _ GraphNodeDestroyer = (*NodeDestroyDeposedResourceInstanceObject)(nil)
@ -170,6 +180,10 @@ func (n *NodeDestroyDeposedResourceInstanceObject) Name() string {
return fmt.Sprintf("%s (destroy deposed %s)", n.Addr.String(), n.DeposedKey) return fmt.Sprintf("%s (destroy deposed %s)", n.Addr.String(), n.DeposedKey)
} }
func (n *NodeDestroyDeposedResourceInstanceObject) DeposedInstanceObjectKey() states.DeposedKey {
return n.DeposedKey
}
// GraphNodeReferenceable implementation, overriding the one from NodeAbstractResourceInstance // GraphNodeReferenceable implementation, overriding the one from NodeAbstractResourceInstance
func (n *NodeDestroyDeposedResourceInstanceObject) ReferenceableAddrs() []addrs.Referenceable { func (n *NodeDestroyDeposedResourceInstanceObject) ReferenceableAddrs() []addrs.Referenceable {
// Deposed objects don't participate in references. // Deposed objects don't participate in references.

View File

@ -11,10 +11,16 @@ import (
// it is ready to be planned in order to create a diff. // it is ready to be planned in order to create a diff.
type NodePlannableResource struct { type NodePlannableResource struct {
*NodeAbstractResource *NodeAbstractResource
// ForceCreateBeforeDestroy might be set via our GraphNodeDestroyerCBD
// during graph construction, if dependencies require us to force this
// on regardless of what the configuration says.
ForceCreateBeforeDestroy *bool
} }
var ( var (
_ GraphNodeSubPath = (*NodePlannableResource)(nil) _ GraphNodeSubPath = (*NodePlannableResource)(nil)
_ GraphNodeDestroyerCBD = (*NodePlannableResource)(nil)
_ GraphNodeDynamicExpandable = (*NodePlannableResource)(nil) _ GraphNodeDynamicExpandable = (*NodePlannableResource)(nil)
_ GraphNodeReferenceable = (*NodePlannableResource)(nil) _ GraphNodeReferenceable = (*NodePlannableResource)(nil)
_ GraphNodeReferencer = (*NodePlannableResource)(nil) _ GraphNodeReferencer = (*NodePlannableResource)(nil)
@ -41,6 +47,26 @@ func (n *NodePlannableResource) EvalTree() EvalNode {
} }
} }
// GraphNodeDestroyerCBD
func (n *NodePlannableResource) CreateBeforeDestroy() bool {
if n.ForceCreateBeforeDestroy != nil {
return *n.ForceCreateBeforeDestroy
}
// If we have no config, we just assume no
if n.Config == nil || n.Config.Managed == nil {
return false
}
return n.Config.Managed.CreateBeforeDestroy
}
// GraphNodeDestroyerCBD
func (n *NodePlannableResource) ModifyCreateBeforeDestroy(v bool) error {
n.ForceCreateBeforeDestroy = &v
return nil
}
// GraphNodeDynamicExpandable // GraphNodeDynamicExpandable
func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) { func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
@ -70,6 +96,11 @@ func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
return &NodePlannableResourceInstance{ return &NodePlannableResourceInstance{
NodeAbstractResourceInstance: a, NodeAbstractResourceInstance: a,
// By the time we're walking, we've figured out whether we need
// to force on CreateBeforeDestroy due to dependencies on other
// nodes that have it.
ForceCreateBeforeDestroy: n.CreateBeforeDestroy(),
} }
} }

View File

@ -16,6 +16,7 @@ import (
// count index, for example. // count index, for example.
type NodePlannableResourceInstance struct { type NodePlannableResourceInstance struct {
*NodeAbstractResourceInstance *NodeAbstractResourceInstance
ForceCreateBeforeDestroy bool
} }
var ( var (
@ -139,6 +140,7 @@ func (n *NodePlannableResourceInstance) evalTreeManagedResource(addr addrs.AbsRe
&EvalDiff{ &EvalDiff{
Addr: addr.Resource, Addr: addr.Resource,
Config: n.Config, Config: n.Config,
CreateBeforeDestroy: n.ForceCreateBeforeDestroy,
Provider: &provider, Provider: &provider,
ProviderAddr: n.ResolvedProvider, ProviderAddr: n.ResolvedProvider,
ProviderSchema: &providerSchema, ProviderSchema: &providerSchema,

View File

@ -10,10 +10,9 @@ import (
) )
// GraphNodeDestroyerCBD must be implemented by nodes that might be // GraphNodeDestroyerCBD must be implemented by nodes that might be
// create-before-destroy destroyers. // create-before-destroy destroyers, or might plan a create-before-destroy
// action.
type GraphNodeDestroyerCBD interface { type GraphNodeDestroyerCBD interface {
GraphNodeDestroyer
// CreateBeforeDestroy returns true if this node represents a node // CreateBeforeDestroy returns true if this node represents a node
// that is doing a CBD. // that is doing a CBD.
CreateBeforeDestroy() bool CreateBeforeDestroy() bool
@ -38,6 +37,75 @@ type GraphNodeAttachDestroyer interface {
AttachDestroyNode(n GraphNodeDestroyerCBD) AttachDestroyNode(n GraphNodeDestroyerCBD)
} }
// ForcedCBDTransformer detects when a particular CBD-able graph node has
// dependencies with another that has create_before_destroy set that require
// it to be forced on, and forces it on.
//
// This must be used in the plan graph builder to ensure that
// create_before_destroy settings are properly propagated before constructing
// the planned changes. This requires that the plannable resource nodes
// implement GraphNodeDestroyerCBD.
type ForcedCBDTransformer struct {
}
func (t *ForcedCBDTransformer) Transform(g *Graph) error {
for _, v := range g.Vertices() {
dn, ok := v.(GraphNodeDestroyerCBD)
if !ok {
continue
}
if !dn.CreateBeforeDestroy() {
// If there are no CBD decendent (dependent nodes), then we
// do nothing here.
if !t.hasCBDDescendent(g, v) {
log.Printf("[TRACE] ForcedCBDTransformer: %q (%T) has no CBD ancestors, so skipping", dag.VertexName(v), v)
continue
}
// If this isn't naturally a CBD node, this means that an ancestor is
// and we need to auto-upgrade this node to CBD. We do this because
// a CBD node depending on non-CBD will result in cycles. To avoid this,
// we always attempt to upgrade it.
log.Printf("[TRACE] ForcedCBDTransformer: forcing create_before_destroy on for %q (%T)", dag.VertexName(v), v)
if err := dn.ModifyCreateBeforeDestroy(true); err != nil {
return fmt.Errorf(
"%s: must have create before destroy enabled because "+
"a dependent resource has CBD enabled. However, when "+
"attempting to automatically do this, an error occurred: %s",
dag.VertexName(v), err)
}
} else {
log.Printf("[TRACE] ForcedCBDTransformer: %q (%T) already has create_before_destroy set", dag.VertexName(v), v)
}
}
return nil
}
// hasCBDAncestor returns true if any ancestor (node that depends on this)
// has CBD set.
func (t *ForcedCBDTransformer) hasCBDDescendent(g *Graph, v dag.Vertex) bool {
s, _ := g.Descendents(v)
if s == nil {
return true
}
for _, ov := range s.List() {
dn, ok := ov.(GraphNodeDestroyerCBD)
if !ok {
continue
}
if dn.CreateBeforeDestroy() {
// some descendent is CreateBeforeDestroy, so we need to follow suit
log.Printf("[TRACE] ForcedCBDTransformer: %q has CBD descendent %q", dag.VertexName(v), dag.VertexName(ov))
return true
}
}
return false
}
// CBDEdgeTransformer modifies the edges of CBD nodes that went through // CBDEdgeTransformer modifies the edges of CBD nodes that went through
// the DestroyEdgeTransformer to have the right dependencies. There are // the DestroyEdgeTransformer to have the right dependencies. There are
// two real tasks here: // two real tasks here:
@ -50,6 +118,12 @@ type GraphNodeAttachDestroyer interface {
// update to A. Example: adding a web server updates the load balancer // update to A. Example: adding a web server updates the load balancer
// before deleting the old web server. // before deleting the old web server.
// //
// This transformer requires that a previous transformer has already forced
// create_before_destroy on for nodes that are depended on by explicit CBD
// nodes. This is the logic in ForcedCBDTransformer, though in practice we
// will get here by recording the CBD-ness of each change in the plan during
// the plan walk and then forcing the nodes into the appropriate setting during
// DiffTransformer when building the apply graph.
type CBDEdgeTransformer struct { type CBDEdgeTransformer struct {
// Module and State are only needed to look up dependencies in // Module and State are only needed to look up dependencies in
// any way possible. Either can be nil if not availabile. // any way possible. Either can be nil if not availabile.
@ -70,26 +144,13 @@ func (t *CBDEdgeTransformer) Transform(g *Graph) error {
if !ok { if !ok {
continue continue
} }
dern, ok := v.(GraphNodeDestroyer)
if !dn.CreateBeforeDestroy() { if !ok {
// If there are no CBD ancestors (dependent nodes), then we
// do nothing here.
if !t.hasCBDAncestor(g, v) {
continue continue
} }
// If this isn't naturally a CBD node, this means that an ancestor is if !dn.CreateBeforeDestroy() {
// and we need to auto-upgrade this node to CBD. We do this because continue
// a CBD node depending on non-CBD will result in cycles. To avoid this,
// we always attempt to upgrade it.
log.Printf("[TRACE] CBDEdgeTransformer: forcing create_before_destroy on for %q (%T)", dag.VertexName(v), v)
if err := dn.ModifyCreateBeforeDestroy(true); err != nil {
return fmt.Errorf(
"%s: must have create before destroy enabled because "+
"a dependent resource has CBD enabled. However, when "+
"attempting to automatically do this, an error occurred: %s",
dag.VertexName(v), err)
}
} }
// Find the destroy edge. There should only be one. // Find the destroy edge. There should only be one.
@ -105,7 +166,9 @@ func (t *CBDEdgeTransformer) Transform(g *Graph) error {
// Found it! Invert. // Found it! Invert.
g.RemoveEdge(de) g.RemoveEdge(de)
g.Connect(&DestroyEdge{S: de.Target(), T: de.Source()}) applyNode := de.Source()
destroyNode := de.Target()
g.Connect(&DestroyEdge{S: destroyNode, T: applyNode})
} }
// If the address has an index, we strip that. Our depMap creation // If the address has an index, we strip that. Our depMap creation
@ -113,7 +176,7 @@ func (t *CBDEdgeTransformer) Transform(g *Graph) error {
// dependencies. One day when we limit dependencies more exactly // dependencies. One day when we limit dependencies more exactly
// this will have to change. We have a test case covering this // this will have to change. We have a test case covering this
// (depNonCBDCountBoth) so it'll be caught. // (depNonCBDCountBoth) so it'll be caught.
addr := dn.DestroyAddr() addr := dern.DestroyAddr()
key := addr.ContainingResource().String() key := addr.ContainingResource().String()
// Add this to the list of nodes that we need to fix up // Add this to the list of nodes that we need to fix up
@ -243,26 +306,3 @@ func (t *CBDEdgeTransformer) depMap(destroyMap map[string][]dag.Vertex) (map[str
return depMap, nil return depMap, nil
} }
// hasCBDAncestor returns true if any ancestor (node that depends on this)
// has CBD set.
func (t *CBDEdgeTransformer) hasCBDAncestor(g *Graph, v dag.Vertex) bool {
s, _ := g.Ancestors(v)
if s == nil {
return true
}
for _, v := range s.List() {
dn, ok := v.(GraphNodeDestroyerCBD)
if !ok {
continue
}
if dn.CreateBeforeDestroy() {
// some ancestor is CreateBeforeDestroy, so we need to follow suit
return true
}
}
return false
}

View File

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/dag" "github.com/hashicorp/terraform/dag"
"github.com/hashicorp/terraform/plans" "github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/states"
@ -15,7 +14,6 @@ import (
// each of the resource changes described in the given Changes object. // each of the resource changes described in the given Changes object.
type DiffTransformer struct { type DiffTransformer struct {
Concrete ConcreteResourceInstanceNodeFunc Concrete ConcreteResourceInstanceNodeFunc
Config *configs.Config
State *states.State State *states.State
Changes *plans.Changes Changes *plans.Changes
} }
@ -30,7 +28,6 @@ func (t *DiffTransformer) Transform(g *Graph) error {
log.Printf("[TRACE] DiffTransformer starting") log.Printf("[TRACE] DiffTransformer starting")
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
config := t.Config
state := t.State state := t.State
changes := t.Changes changes := t.Changes
@ -59,15 +56,9 @@ func (t *DiffTransformer) Transform(g *Graph) error {
for _, rc := range changes.Resources { for _, rc := range changes.Resources {
addr := rc.Addr addr := rc.Addr
dk := rc.DeposedKey dk := rc.DeposedKey
var rCfg *configs.Resource
log.Printf("[TRACE] DiffTransformer: found %s change for %s %s", rc.Action, addr, dk) log.Printf("[TRACE] DiffTransformer: found %s change for %s %s", rc.Action, addr, dk)
modCfg := config.DescendentForInstance(addr.Module)
if modCfg != nil {
rCfg = modCfg.Module.ResourceByAddr(addr.Resource.Resource)
}
// Depending on the action we'll need some different combinations of // Depending on the action we'll need some different combinations of
// nodes, because destroying uses a special node type separate from // nodes, because destroying uses a special node type separate from
// other actions. // other actions.
@ -77,9 +68,10 @@ func (t *DiffTransformer) Transform(g *Graph) error {
continue continue
case plans.Delete: case plans.Delete:
delete = true delete = true
case plans.Replace: case plans.DeleteThenCreate, plans.CreateThenDelete:
update = true update = true
delete = true delete = true
createBeforeDestroy = (rc.Action == plans.CreateThenDelete)
default: default:
update = true update = true
} }
@ -93,10 +85,6 @@ func (t *DiffTransformer) Transform(g *Graph) error {
continue continue
} }
if rCfg != nil && rCfg.Managed != nil && rCfg.Managed.CreateBeforeDestroy {
createBeforeDestroy = true
}
// If we're going to do a create_before_destroy Replace operation then // If we're going to do a create_before_destroy Replace operation then
// we need to allocate a DeposedKey to use to retain the // we need to allocate a DeposedKey to use to retain the
// not-yet-destroyed prior object, so that the delete node can destroy // not-yet-destroyed prior object, so that the delete node can destroy
@ -173,6 +161,7 @@ func (t *DiffTransformer) Transform(g *Graph) error {
NodeAbstractResourceInstance: abstract, NodeAbstractResourceInstance: abstract,
DeposedKey: dk, DeposedKey: dk,
} }
node.(*NodeDestroyResourceInstance).ModifyCreateBeforeDestroy(createBeforeDestroy)
} else { } else {
node = &NodeDestroyDeposedResourceInstanceObject{ node = &NodeDestroyDeposedResourceInstanceObject{
NodeAbstractResourceInstance: abstract, NodeAbstractResourceInstance: abstract,