diff --git a/backend/local/backend_plan.go b/backend/local/backend_plan.go index 629e450ee..60c3df37b 100644 --- a/backend/local/backend_plan.go +++ b/backend/local/backend_plan.go @@ -189,9 +189,15 @@ func (b *Local) renderPlan(plan *plans.Plan, schemas *terraform.Schemas) { if counts[plans.Delete] > 0 { 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)) } + 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 { 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{} for _, change := range rChanges { switch change.Action { - case plans.Replace: + case plans.CreateThenDelete, plans.DeleteThenCreate: stats[plans.Create]++ stats[plans.Delete]++ default: diff --git a/backend/local/hook_count.go b/backend/local/hook_count.go index dccb93fef..59fc2cc10 100644 --- a/backend/local/hook_count.go +++ b/backend/local/hook_count.go @@ -65,7 +65,7 @@ func (h *CountHook) PostApply(addr addrs.AbsResourceInstance, gen states.Generat if err == nil { switch action { - case plans.Replace: + case plans.CreateThenDelete, plans.DeleteThenCreate: h.Added++ h.Removed++ case plans.Create: @@ -92,7 +92,7 @@ func (h *CountHook) PostDiff(addr addrs.AbsResourceInstance, gen states.Generati } switch action { - case plans.Replace: + case plans.CreateThenDelete, plans.DeleteThenCreate: h.ToRemoveAndAdd += 1 case plans.Create: h.ToAdd += 1 diff --git a/command/format/diff.go b/command/format/diff.go index cb34dac0d..0bbaabe12 100644 --- a/command/format/diff.go +++ b/command/format/diff.go @@ -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))) case plans.Update: 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))) case plans.Delete: 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] ")) case plans.Update: buf.WriteString(color.Color("[yellow] ~[reset] ")) - case plans.Replace: + case plans.DeleteThenCreate: buf.WriteString(color.Color("[red]-[reset]/[green]+[reset] ")) + case plans.CreateThenDelete: + buf.WriteString(color.Color("[green]+[reset]/[red]-[reset] ")) case plans.Delete: buf.WriteString(color.Color("[red] -[reset] ")) default: @@ -837,7 +839,7 @@ func (p *blockBodyDiffPrinter) writeActionSymbol(action plans.Action) { } 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 return false } diff --git a/command/format/plan.go b/command/format/plan.go index e02190f9e..bca7af5eb 100644 --- a/command/format/plan.go +++ b/command/format/plan.go @@ -107,7 +107,7 @@ func NewPlan(changes *plans.Changes) *Plan { did.Action = terraform.DiffRefresh case plans.Delete: did.Action = terraform.DiffDestroy - case plans.Replace: + case plans.DeleteThenCreate, plans.CreateThenDelete: did.Action = terraform.DiffDestroyCreate case plans.Update: did.Action = terraform.DiffUpdate diff --git a/plans/action.go b/plans/action.go index 63dbfbeb6..c3e6a32ae 100644 --- a/plans/action.go +++ b/plans/action.go @@ -3,12 +3,20 @@ package plans type Action rune const ( - NoOp Action = 0 - Create Action = '+' - Read Action = '←' - Update Action = '~' - Replace Action = '±' - Delete Action = '-' + NoOp Action = 0 + Create Action = '+' + Read Action = '←' + Update Action = '~' + DeleteThenCreate Action = '∓' + CreateThenDelete Action = '±' + Delete 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 +} diff --git a/plans/changes.go b/plans/changes.go index 1b85ba21c..cc6e58263 100644 --- a/plans/changes.go +++ b/plans/changes.go @@ -169,7 +169,7 @@ func (rc *ResourceInstanceChange) Simplify(destroying bool) *ResourceInstanceCha switch rc.Action { case Delete: // We'll fall out and just return rc verbatim, then. - case Replace: + case CreateThenDelete, DeleteThenCreate: return &ResourceInstanceChange{ Addr: rc.Addr, DeposedKey: rc.DeposedKey, @@ -208,7 +208,7 @@ func (rc *ResourceInstanceChange) Simplify(destroying bool) *ResourceInstanceCha After: rc.Before, }, } - case Replace: + case CreateThenDelete, DeleteThenCreate: return &ResourceInstanceChange{ Addr: rc.Addr, DeposedKey: rc.DeposedKey, diff --git a/plans/internal/planproto/planfile.pb.go b/plans/internal/planproto/planfile.pb.go index 073d66868..6feb09302 100644 --- a/plans/internal/planproto/planfile.pb.go +++ b/plans/internal/planproto/planfile.pb.go @@ -23,12 +23,13 @@ const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type Action int32 const ( - Action_NOOP Action = 0 - Action_CREATE Action = 1 - Action_READ Action = 2 - Action_UPDATE Action = 3 - Action_REPLACE Action = 4 - Action_DELETE Action = 5 + Action_NOOP Action = 0 + Action_CREATE Action = 1 + Action_READ Action = 2 + Action_UPDATE Action = 3 + Action_DELETE Action = 5 + Action_DELETE_THEN_CREATE Action = 6 + Action_CREATE_THEN_DELETE Action = 7 ) var Action_name = map[int32]string{ @@ -36,23 +37,25 @@ var Action_name = map[int32]string{ 1: "CREATE", 2: "READ", 3: "UPDATE", - 4: "REPLACE", 5: "DELETE", + 6: "DELETE_THEN_CREATE", + 7: "CREATE_THEN_DELETE", } var Action_value = map[string]int32{ - "NOOP": 0, - "CREATE": 1, - "READ": 2, - "UPDATE": 3, - "REPLACE": 4, - "DELETE": 5, + "NOOP": 0, + "CREATE": 1, + "READ": 2, + "UPDATE": 3, + "DELETE": 5, + "DELETE_THEN_CREATE": 6, + "CREATE_THEN_DELETE": 7, } func (x Action) String() string { return proto.EnumName(Action_name, int32(x)) } func (Action) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_planfile_2f8b9a0ae177578e, []int{0} + return fileDescriptor_planfile_f1de017ed03cb7aa, []int{0} } type ResourceInstanceChange_ResourceMode int32 @@ -75,7 +78,7 @@ func (x ResourceInstanceChange_ResourceMode) String() string { return proto.EnumName(ResourceInstanceChange_ResourceMode_name, int32(x)) } 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 @@ -122,7 +125,7 @@ func (m *Plan) Reset() { *m = Plan{} } func (m *Plan) String() string { return proto.CompactTextString(m) } func (*Plan) ProtoMessage() {} 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 { 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 (*Backend) ProtoMessage() {} 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 { 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 (*Change) ProtoMessage() {} 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 { 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 (*ResourceInstanceChange) ProtoMessage() {} 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 { 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 (*OutputChange) ProtoMessage() {} 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 { 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 (*DynamicValue) ProtoMessage() {} 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 { 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 (*Hash) ProtoMessage() {} 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 { 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 (*Path) ProtoMessage() {} 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 { 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 (*Path_Step) ProtoMessage() {} 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 { 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) } -func init() { proto.RegisterFile("planfile.proto", fileDescriptor_planfile_2f8b9a0ae177578e) } +func init() { proto.RegisterFile("planfile.proto", fileDescriptor_planfile_f1de017ed03cb7aa) } -var fileDescriptor_planfile_2f8b9a0ae177578e = []byte{ - // 885 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x55, 0xdb, 0x6e, 0xe3, 0x36, - 0x13, 0xb6, 0x2c, 0xf9, 0x34, 0x76, 0x14, 0x2d, 0xff, 0x1f, 0x0b, 0x21, 0x5d, 0x6c, 0x5d, 0x01, - 0xed, 0xba, 0xbb, 0x85, 0x03, 0xb8, 0x68, 0xd3, 0x6d, 0x2f, 0x0a, 0x27, 0x31, 0x90, 0x60, 0x0f, - 0x31, 0xd8, 0x6d, 0x2e, 0x7a, 0x51, 0x83, 0x96, 0x26, 0xb6, 0x10, 0x9d, 0x4a, 0xd2, 0x2e, 0xfc, - 0x40, 0x7d, 0x88, 0xbe, 0x44, 0x9f, 0xa9, 0x20, 0x29, 0xc9, 0x0e, 0x90, 0xe6, 0xca, 0x9a, 0x6f, - 0x86, 0x1f, 0x67, 0xbe, 0x99, 0xa1, 0xc1, 0x2d, 0x12, 0x96, 0xdd, 0xc5, 0x09, 0x8e, 0x0b, 0x9e, - 0xcb, 0x9c, 0xb4, 0xe5, 0x9d, 0x42, 0x82, 0x7f, 0x1c, 0x70, 0xe6, 0x09, 0xcb, 0x88, 0x0f, 0x9d, - 0x2d, 0x72, 0x11, 0xe7, 0x99, 0x6f, 0x0d, 0xad, 0x91, 0x43, 0x2b, 0x93, 0xbc, 0x85, 0xde, 0x96, - 0xf1, 0x98, 0x2d, 0x13, 0x14, 0x7e, 0x73, 0x68, 0x8f, 0xfa, 0x93, 0xcf, 0xc6, 0xe6, 0xf8, 0x58, - 0x1d, 0x1d, 0xdf, 0x56, 0xde, 0x59, 0x26, 0xf9, 0x8e, 0xee, 0xa3, 0xc9, 0x35, 0x78, 0x1c, 0x45, - 0xbe, 0xe1, 0x21, 0x2e, 0xc2, 0x35, 0xcb, 0x56, 0x28, 0x7c, 0x5b, 0x33, 0xbc, 0xac, 0x18, 0x68, - 0xe9, 0xbf, 0xce, 0x84, 0x64, 0x59, 0x88, 0x17, 0x3a, 0x8c, 0x1e, 0x57, 0xe7, 0x8c, 0x2d, 0xc8, - 0x4f, 0xe0, 0xe6, 0x1b, 0x59, 0x6c, 0x64, 0x4d, 0xe4, 0x68, 0xa2, 0xff, 0x57, 0x44, 0x37, 0xda, - 0x5b, 0x1e, 0x3f, 0xca, 0x0f, 0x2c, 0x41, 0xbe, 0x80, 0x81, 0x64, 0x7c, 0x85, 0x72, 0xc1, 0xa2, - 0x88, 0x0b, 0xbf, 0x35, 0xb4, 0x47, 0x3d, 0xda, 0x37, 0xd8, 0x54, 0x41, 0xe4, 0x0d, 0x3c, 0x93, - 0xc8, 0x39, 0xbb, 0xcb, 0x79, 0xba, 0xa8, 0x94, 0x70, 0x87, 0xd6, 0xa8, 0x47, 0xbd, 0xda, 0x71, - 0x5b, 0x4a, 0x72, 0x0d, 0xc7, 0x05, 0xcf, 0xb7, 0x71, 0x84, 0x7c, 0xb1, 0x66, 0x62, 0x8d, 0xc2, - 0x3f, 0xd6, 0xd9, 0x0c, 0x1f, 0x08, 0x33, 0x2f, 0x63, 0xae, 0x74, 0x88, 0x51, 0xc7, 0x2d, 0x1e, - 0x80, 0xe4, 0x6b, 0xe8, 0x2c, 0x59, 0x78, 0x8f, 0x59, 0xe4, 0x1f, 0x0d, 0xad, 0x51, 0x7f, 0x72, - 0x5c, 0x51, 0x9c, 0x1b, 0x98, 0x56, 0xfe, 0x13, 0x0a, 0xee, 0x43, 0xa9, 0x89, 0x07, 0xf6, 0x3d, - 0xee, 0x74, 0xc3, 0x7a, 0x54, 0x7d, 0x92, 0xd7, 0xd0, 0xda, 0xb2, 0x64, 0x83, 0x7e, 0x53, 0x93, - 0xd5, 0xea, 0x5c, 0xee, 0x32, 0x96, 0xc6, 0xe1, 0xad, 0xf2, 0x51, 0x13, 0xf2, 0x63, 0xf3, 0x07, - 0xeb, 0xe4, 0x06, 0xfe, 0xf7, 0x48, 0x96, 0x8f, 0x10, 0x07, 0x0f, 0x89, 0x07, 0x15, 0xb1, 0x3a, - 0x75, 0x40, 0x18, 0xc4, 0xd0, 0x29, 0x13, 0x27, 0x04, 0x1c, 0xb9, 0x2b, 0xb0, 0x64, 0xd1, 0xdf, - 0xe4, 0x1b, 0x68, 0x87, 0x79, 0x76, 0x17, 0xaf, 0x9e, 0x4c, 0xb0, 0x8c, 0x21, 0x2f, 0xa0, 0xf7, - 0x67, 0xce, 0xef, 0x45, 0xc1, 0x42, 0xf4, 0x6d, 0x4d, 0xb3, 0x07, 0x82, 0xdf, 0xa1, 0x6d, 0x1a, - 0x4c, 0xbe, 0x82, 0x36, 0x0b, 0x65, 0x35, 0xbb, 0xee, 0xc4, 0xad, 0x58, 0xa7, 0x1a, 0xa5, 0xa5, - 0x57, 0xdd, 0xae, 0x33, 0xad, 0xe6, 0xf8, 0x3f, 0x6e, 0x37, 0x31, 0xc1, 0xdf, 0x36, 0x3c, 0x7f, - 0x7c, 0x3c, 0xc9, 0xe7, 0xd0, 0x4f, 0xf3, 0x68, 0x93, 0xe0, 0xa2, 0x60, 0x72, 0x5d, 0x56, 0x08, - 0x06, 0x9a, 0x33, 0xb9, 0x26, 0x3f, 0x83, 0x93, 0xe6, 0x91, 0x51, 0xcb, 0x9d, 0xbc, 0x79, 0x7a, - 0xda, 0x6b, 0xf8, 0x43, 0x1e, 0x21, 0xd5, 0x07, 0x6b, 0xf1, 0xec, 0x03, 0xf1, 0x08, 0x38, 0x19, - 0x4b, 0xd1, 0x77, 0x0c, 0xa6, 0xbe, 0x09, 0x01, 0x5b, 0x48, 0xee, 0xb7, 0x14, 0x74, 0xd5, 0xa0, - 0xca, 0x50, 0x58, 0x9c, 0x49, 0xbf, 0x3d, 0xb4, 0x46, 0xb6, 0xc2, 0xe2, 0x4c, 0xaa, 0x8c, 0x23, - 0x2c, 0x72, 0x81, 0xd1, 0x42, 0x75, 0xb6, 0x63, 0x32, 0x2e, 0xa1, 0x77, 0xb8, 0x23, 0x27, 0xd0, - 0xad, 0x46, 0xd3, 0xef, 0x6a, 0x6f, 0x6d, 0x2b, 0x7d, 0xcd, 0xd6, 0xf9, 0x3d, 0xdd, 0xb5, 0x5a, - 0xdf, 0x72, 0xdd, 0x4a, 0xaf, 0x7a, 0x44, 0x0a, 0x1e, 0x6f, 0x99, 0x44, 0x1f, 0x86, 0xd6, 0x68, - 0x40, 0x2b, 0x93, 0x9c, 0xa9, 0x97, 0xe0, 0x8f, 0x4d, 0xcc, 0x31, 0x5a, 0x70, 0x2c, 0x12, 0xd5, - 0xd0, 0xbe, 0xee, 0x41, 0x3d, 0x49, 0x4a, 0x37, 0xb5, 0xf7, 0x26, 0x8a, 0x9a, 0xa0, 0xe0, 0x4b, - 0x18, 0x1c, 0xaa, 0x43, 0xfa, 0xd0, 0x49, 0x59, 0xc6, 0x56, 0x18, 0x79, 0x0d, 0xd2, 0x05, 0x27, - 0x62, 0x92, 0x79, 0xd6, 0xb9, 0x0b, 0x83, 0xb8, 0xd4, 0x54, 0xd5, 0x17, 0xac, 0x61, 0x70, 0xf8, - 0x20, 0xd4, 0xd2, 0x59, 0x07, 0xd2, 0xed, 0xab, 0x6a, 0x3e, 0x59, 0xd5, 0x0b, 0xe8, 0x09, 0xcc, - 0x44, 0x2c, 0xe3, 0xad, 0xe9, 0x47, 0x97, 0xee, 0x81, 0x60, 0x04, 0x83, 0xc3, 0xe9, 0x51, 0x1a, - 0xa4, 0x62, 0x55, 0xb0, 0xf0, 0x5e, 0x5f, 0x36, 0xa0, 0x95, 0x19, 0xbc, 0x04, 0x47, 0x6d, 0x0b, - 0x79, 0x0e, 0x6d, 0xb1, 0x66, 0x93, 0xef, 0xbe, 0x2f, 0x03, 0x4a, 0x2b, 0xf8, 0xcb, 0x02, 0x47, - 0x0f, 0xcf, 0x2b, 0x68, 0x09, 0x89, 0x85, 0xf0, 0x2d, 0xad, 0xd0, 0xb3, 0x43, 0x85, 0xc6, 0xbf, - 0x48, 0x2c, 0xa8, 0xf1, 0x9f, 0x48, 0x70, 0x94, 0x49, 0x5e, 0x81, 0xcb, 0xa4, 0xe4, 0xf1, 0x72, - 0x23, 0x71, 0xb1, 0xaf, 0xf3, 0xaa, 0x41, 0x8f, 0x6a, 0xfc, 0xa3, 0x2a, 0xf9, 0x0c, 0xfa, 0x98, - 0x60, 0x8a, 0x99, 0xd4, 0x53, 0xf0, 0xc4, 0x0e, 0x5e, 0x35, 0x28, 0x94, 0xa1, 0xef, 0x70, 0x77, - 0x0e, 0xd0, 0x15, 0x98, 0x60, 0x28, 0x73, 0xfe, 0xfa, 0x03, 0xb4, 0xcd, 0x5e, 0x29, 0xfd, 0x3f, - 0xde, 0xdc, 0xcc, 0xbd, 0x06, 0x01, 0x68, 0x5f, 0xd0, 0xd9, 0xf4, 0xd3, 0xcc, 0xb3, 0x14, 0x4a, - 0x67, 0xd3, 0x4b, 0xaf, 0xa9, 0xd0, 0x5f, 0xe7, 0x97, 0x0a, 0xb5, 0x55, 0xe3, 0xe8, 0x6c, 0xfe, - 0x7e, 0x7a, 0x31, 0xf3, 0x1c, 0xe5, 0xb8, 0x9c, 0xbd, 0x9f, 0x7d, 0x9a, 0x79, 0xad, 0xf3, 0xb7, - 0xbf, 0x9d, 0xad, 0x62, 0xb9, 0xde, 0x2c, 0xc7, 0x61, 0x9e, 0x9e, 0xaa, 0xe7, 0x34, 0x0e, 0x73, - 0x5e, 0x9c, 0xd6, 0xaf, 0xee, 0xa9, 0x4a, 0x4e, 0x9c, 0xc6, 0x99, 0x44, 0x9e, 0xb1, 0x44, 0x9b, - 0xfa, 0x5f, 0x6c, 0xd9, 0xd6, 0x3f, 0xdf, 0xfe, 0x1b, 0x00, 0x00, 0xff, 0xff, 0x9e, 0x41, 0x65, - 0x5b, 0xde, 0x06, 0x00, 0x00, +var fileDescriptor_planfile_f1de017ed03cb7aa = []byte{ + // 893 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x55, 0xe1, 0x6e, 0xe3, 0x44, + 0x10, 0xae, 0x63, 0xc7, 0x49, 0x26, 0xa9, 0x9b, 0x5b, 0x50, 0x65, 0x95, 0xd3, 0x11, 0x2c, 0xc1, + 0x85, 0x3b, 0x94, 0x4a, 0x41, 0x50, 0x0e, 0x7e, 0xa0, 0xf6, 0x1a, 0x29, 0xd5, 0x41, 0x1b, 0x2d, + 0xa5, 0x3f, 0xf8, 0x81, 0xb5, 0xb1, 0xa7, 0x89, 0x55, 0xc7, 0x36, 0xbb, 0x9b, 0xa0, 0x3c, 0x10, + 0x0f, 0xc1, 0x4b, 0xf0, 0x4c, 0x68, 0x77, 0x6d, 0x27, 0x95, 0x7a, 0xfd, 0x95, 0x9d, 0x6f, 0x66, + 0x3e, 0xcf, 0x7e, 0x33, 0xb3, 0x01, 0xaf, 0x48, 0x59, 0x76, 0x9f, 0xa4, 0x38, 0x2a, 0x78, 0x2e, + 0x73, 0xe2, 0xca, 0x7b, 0x85, 0x04, 0xff, 0x39, 0xe0, 0xcc, 0x52, 0x96, 0x11, 0x1f, 0x5a, 0x1b, + 0xe4, 0x22, 0xc9, 0x33, 0xdf, 0x1a, 0x58, 0x43, 0x87, 0x56, 0x26, 0x79, 0x07, 0x9d, 0x0d, 0xe3, + 0x09, 0x9b, 0xa7, 0x28, 0xfc, 0xc6, 0xc0, 0x1e, 0x76, 0xc7, 0x9f, 0x8d, 0x4c, 0xfa, 0x48, 0xa5, + 0x8e, 0xee, 0x2a, 0xef, 0x24, 0x93, 0x7c, 0x4b, 0x77, 0xd1, 0xe4, 0x0a, 0xfa, 0x1c, 0x45, 0xbe, + 0xe6, 0x11, 0x86, 0xd1, 0x92, 0x65, 0x0b, 0x14, 0xbe, 0xad, 0x19, 0x5e, 0x55, 0x0c, 0xb4, 0xf4, + 0x5f, 0x65, 0x42, 0xb2, 0x2c, 0xc2, 0xf7, 0x3a, 0x8c, 0x1e, 0x55, 0x79, 0xc6, 0x16, 0xe4, 0x27, + 0xf0, 0xf2, 0xb5, 0x2c, 0xd6, 0xb2, 0x26, 0x72, 0x34, 0xd1, 0xa7, 0x15, 0xd1, 0x8d, 0xf6, 0x96, + 0xe9, 0x87, 0xf9, 0x9e, 0x25, 0xc8, 0x17, 0xd0, 0x93, 0x8c, 0x2f, 0x50, 0x86, 0x2c, 0x8e, 0xb9, + 0xf0, 0x9b, 0x03, 0x7b, 0xd8, 0xa1, 0x5d, 0x83, 0x9d, 0x2b, 0x88, 0xbc, 0x85, 0x17, 0x12, 0x39, + 0x67, 0xf7, 0x39, 0x5f, 0x85, 0x95, 0x12, 0xde, 0xc0, 0x1a, 0x76, 0x68, 0xbf, 0x76, 0xdc, 0x95, + 0x92, 0x5c, 0xc1, 0x51, 0xc1, 0xf3, 0x4d, 0x12, 0x23, 0x0f, 0x97, 0x4c, 0x2c, 0x51, 0xf8, 0x47, + 0xba, 0x9a, 0xc1, 0x23, 0x61, 0x66, 0x65, 0xcc, 0x54, 0x87, 0x18, 0x75, 0xbc, 0xe2, 0x11, 0x48, + 0xbe, 0x86, 0xd6, 0x9c, 0x45, 0x0f, 0x98, 0xc5, 0xfe, 0xe1, 0xc0, 0x1a, 0x76, 0xc7, 0x47, 0x15, + 0xc5, 0x85, 0x81, 0x69, 0xe5, 0x3f, 0xa1, 0xe0, 0x3d, 0x96, 0x9a, 0xf4, 0xc1, 0x7e, 0xc0, 0xad, + 0x6e, 0x58, 0x87, 0xaa, 0x23, 0x79, 0x03, 0xcd, 0x0d, 0x4b, 0xd7, 0xe8, 0x37, 0x34, 0x59, 0xad, + 0xce, 0xe5, 0x36, 0x63, 0xab, 0x24, 0xba, 0x53, 0x3e, 0x6a, 0x42, 0x7e, 0x6c, 0xfc, 0x60, 0x9d, + 0xdc, 0xc0, 0x27, 0x4f, 0x54, 0xf9, 0x04, 0x71, 0xf0, 0x98, 0xb8, 0x57, 0x11, 0xab, 0xac, 0x3d, + 0xc2, 0x20, 0x81, 0x56, 0x59, 0x38, 0x21, 0xe0, 0xc8, 0x6d, 0x81, 0x25, 0x8b, 0x3e, 0x93, 0x6f, + 0xc0, 0x8d, 0xf2, 0xec, 0x3e, 0x59, 0x3c, 0x5b, 0x60, 0x19, 0x43, 0x5e, 0x42, 0xe7, 0xef, 0x9c, + 0x3f, 0x88, 0x82, 0x45, 0xe8, 0xdb, 0x9a, 0x66, 0x07, 0x04, 0x7f, 0x82, 0x6b, 0x1a, 0x4c, 0xbe, + 0x02, 0x97, 0x45, 0xb2, 0x9a, 0x5d, 0x6f, 0xec, 0x55, 0xac, 0xe7, 0x1a, 0xa5, 0xa5, 0x57, 0x7d, + 0x5d, 0x57, 0x5a, 0xcd, 0xf1, 0x47, 0xbe, 0x6e, 0x62, 0x82, 0x7f, 0x6d, 0x38, 0x7e, 0x7a, 0x3c, + 0xc9, 0xe7, 0xd0, 0x5d, 0xe5, 0xf1, 0x3a, 0xc5, 0xb0, 0x60, 0x72, 0x59, 0xde, 0x10, 0x0c, 0x34, + 0x63, 0x72, 0x49, 0x7e, 0x06, 0x67, 0x95, 0xc7, 0x46, 0x2d, 0x6f, 0xfc, 0xf6, 0xf9, 0x69, 0xaf, + 0xe1, 0x5f, 0xf3, 0x18, 0xa9, 0x4e, 0xac, 0xc5, 0xb3, 0xf7, 0xc4, 0x23, 0xe0, 0x64, 0x6c, 0x85, + 0xbe, 0x63, 0x30, 0x75, 0x26, 0x04, 0x6c, 0x21, 0xb9, 0xdf, 0x54, 0xd0, 0xf4, 0x80, 0x2a, 0x43, + 0x61, 0x49, 0x26, 0x7d, 0x77, 0x60, 0x0d, 0x6d, 0x85, 0x25, 0x99, 0x54, 0x15, 0xc7, 0x58, 0xe4, + 0x02, 0xe3, 0x50, 0x75, 0xb6, 0x65, 0x2a, 0x2e, 0xa1, 0x0f, 0xb8, 0x25, 0x27, 0xd0, 0xae, 0x46, + 0xd3, 0x6f, 0x6b, 0x6f, 0x6d, 0x2b, 0x7d, 0xcd, 0xd6, 0xf9, 0x1d, 0xdd, 0xb5, 0x5a, 0xdf, 0x72, + 0xdd, 0x4a, 0xaf, 0x7a, 0x44, 0x0a, 0x9e, 0x6c, 0x98, 0x44, 0x1f, 0x06, 0xd6, 0xb0, 0x47, 0x2b, + 0x93, 0x9c, 0xa9, 0x97, 0xe0, 0xaf, 0x75, 0xc2, 0x31, 0x0e, 0x39, 0x16, 0xa9, 0x6a, 0x68, 0x57, + 0xf7, 0xa0, 0x9e, 0x24, 0xa5, 0x9b, 0xda, 0x7b, 0x13, 0x45, 0x4d, 0x50, 0xf0, 0x25, 0xf4, 0xf6, + 0xd5, 0x21, 0x5d, 0x68, 0xad, 0x58, 0xc6, 0x16, 0x18, 0xf7, 0x0f, 0x48, 0x1b, 0x9c, 0x98, 0x49, + 0xd6, 0xb7, 0x2e, 0x3c, 0xe8, 0x25, 0xa5, 0xa6, 0xea, 0x7e, 0xc1, 0x12, 0x7a, 0xfb, 0x0f, 0x42, + 0x2d, 0x9d, 0xb5, 0x27, 0xdd, 0xee, 0x56, 0x8d, 0x67, 0x6f, 0xf5, 0x12, 0x3a, 0x02, 0x33, 0x91, + 0xc8, 0x64, 0x63, 0xfa, 0xd1, 0xa6, 0x3b, 0x20, 0x18, 0x42, 0x6f, 0x7f, 0x7a, 0x94, 0x06, 0x2b, + 0xb1, 0x28, 0x58, 0xf4, 0xa0, 0x3f, 0xd6, 0xa3, 0x95, 0x19, 0xbc, 0x02, 0x47, 0x6d, 0x0b, 0x39, + 0x06, 0x57, 0x2c, 0xd9, 0xf8, 0xbb, 0xef, 0xcb, 0x80, 0xd2, 0x0a, 0xfe, 0xb1, 0xc0, 0xd1, 0xc3, + 0xf3, 0x1a, 0x9a, 0x42, 0x62, 0x21, 0x7c, 0x4b, 0x2b, 0xf4, 0x62, 0x5f, 0xa1, 0xd1, 0x6f, 0x12, + 0x0b, 0x6a, 0xfc, 0x27, 0x12, 0x1c, 0x65, 0x92, 0xd7, 0xe0, 0x31, 0x29, 0x79, 0x32, 0x5f, 0x4b, + 0x0c, 0x77, 0xf7, 0x9c, 0x1e, 0xd0, 0xc3, 0x1a, 0xbf, 0x56, 0x57, 0x3e, 0x83, 0x2e, 0xa6, 0xb8, + 0xc2, 0x4c, 0xea, 0x29, 0x78, 0x66, 0x07, 0xa7, 0x07, 0x14, 0xca, 0xd0, 0x0f, 0xb8, 0xbd, 0x00, + 0x68, 0x0b, 0x4c, 0x31, 0x92, 0x39, 0x7f, 0x53, 0x80, 0x6b, 0xf6, 0x4a, 0xe9, 0x7f, 0x7d, 0x73, + 0x33, 0xeb, 0x1f, 0x10, 0x00, 0xf7, 0x3d, 0x9d, 0x9c, 0xdf, 0x4e, 0xfa, 0x96, 0x42, 0xe9, 0xe4, + 0xfc, 0xb2, 0xdf, 0x50, 0xe8, 0xef, 0xb3, 0x4b, 0x85, 0xda, 0xea, 0x7c, 0x39, 0xf9, 0x65, 0x72, + 0x3b, 0xe9, 0x37, 0xc9, 0x31, 0x10, 0x73, 0x0e, 0x6f, 0xa7, 0x93, 0xeb, 0xb0, 0xcc, 0x74, 0x15, + 0x6e, 0xce, 0x06, 0x2f, 0xe3, 0x5b, 0x17, 0xef, 0xfe, 0x38, 0x5b, 0x24, 0x72, 0xb9, 0x9e, 0x8f, + 0xa2, 0x7c, 0x75, 0xaa, 0x5e, 0xdc, 0x24, 0xca, 0x79, 0x71, 0x5a, 0x3f, 0xcc, 0xa7, 0xaa, 0x7e, + 0x71, 0x9a, 0x64, 0x12, 0x79, 0xc6, 0x52, 0x6d, 0xea, 0x3f, 0xba, 0xb9, 0xab, 0x7f, 0xbe, 0xfd, + 0x3f, 0x00, 0x00, 0xff, 0xff, 0x30, 0x3e, 0x4e, 0x33, 0x01, 0x07, 0x00, 0x00, } diff --git a/plans/internal/planproto/planfile.proto b/plans/internal/planproto/planfile.proto index fe5bfb94d..f45c3005a 100644 --- a/plans/internal/planproto/planfile.proto +++ b/plans/internal/planproto/planfile.proto @@ -63,8 +63,9 @@ enum Action { CREATE = 1; READ = 2; UPDATE = 3; - REPLACE = 4; DELETE = 5; + DELETE_THEN_CREATE = 6; + CREATE_THEN_DELETE = 7; } // Change represents a change made to some object, transforming it from an old diff --git a/plans/planfile/tfplan.go b/plans/planfile/tfplan.go index 787542225..7f5499f86 100644 --- a/plans/planfile/tfplan.go +++ b/plans/planfile/tfplan.go @@ -230,13 +230,17 @@ func changeFromTfplan(rawChange *planproto.Change) (*plans.ChangeSrc, error) { ret.Action = plans.Update beforeIdx = 0 afterIdx = 1 - case planproto.Action_REPLACE: - ret.Action = plans.Replace - beforeIdx = 0 - afterIdx = 1 case planproto.Action_DELETE: ret.Action = plans.Delete 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: 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: ret.Action = planproto.Action_UPDATE ret.Values = []*planproto.DynamicValue{before, after} - case plans.Replace: - ret.Action = planproto.Action_REPLACE - ret.Values = []*planproto.DynamicValue{before, after} case plans.Delete: ret.Action = planproto.Action_DELETE 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: return nil, fmt.Errorf("invalid change action %s", change.Action) } diff --git a/terraform/context_apply_test.go b/terraform/context_apply_test.go index 47b298769..02599619c 100644 --- a/terraform/context_apply_test.go +++ b/terraform/context_apply_test.go @@ -980,6 +980,9 @@ aws_instance.bar: require_new = yes type = aws_instance value = foo + + Dependencies: + aws_instance.foo aws_instance.foo: ID = foo provider = provider.aws diff --git a/terraform/context_plan_test.go b/terraform/context_plan_test.go index 272ff18e1..007e15a0f 100644 --- a/terraform/context_plan_test.go +++ b/terraform/context_plan_test.go @@ -3885,7 +3885,7 @@ func TestContext2Plan_taint(t *testing.T) { switch i := ric.Addr.String(); i { case "aws_instance.bar": - if res.Action != plans.Replace { + if res.Action != plans.DeleteThenCreate { t.Fatalf("resource %s should be replaced", i) } 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 { case "aws_instance.foo": - if res.Action != plans.Replace { + if res.Action != plans.DeleteThenCreate { t.Fatalf("resource %s should be replaced", i) } 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 { case "aws_instance.foo[0]": - if res.Action != plans.Replace { + if res.Action != plans.DeleteThenCreate { t.Fatalf("resource %s should be replaced", i) } checkVals(t, objectVal(t, schema, map[string]cty.Value{ diff --git a/terraform/context_test.go b/terraform/context_test.go index a63b5426f..861d5b2d2 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -876,8 +876,10 @@ func legacyDiffComparisonString(changes *plans.Changes) string { crud := "UPDATE" if rc.Current != nil { switch rc.Current.Action { - case plans.Replace: + case plans.DeleteThenCreate: crud = "DESTROY/CREATE" + case plans.CreateThenDelete: + crud = "CREATE/DESTROY" case plans.Delete: crud = "DESTROY" case plans.Create: diff --git a/terraform/eval_apply.go b/terraform/eval_apply.go index 4be930bf8..e2674cd27 100644 --- a/terraform/eval_apply.go +++ b/terraform/eval_apply.go @@ -53,7 +53,7 @@ func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) { } 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) diff --git a/terraform/eval_check_prevent_destroy.go b/terraform/eval_check_prevent_destroy.go index fb397a257..4dff0c84d 100644 --- a/terraform/eval_check_prevent_destroy.go +++ b/terraform/eval_check_prevent_destroy.go @@ -29,7 +29,7 @@ func (n *EvalCheckPreventDestroy) Eval(ctx EvalContext) (interface{}, error) { change := *n.Change 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 diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, diff --git a/terraform/eval_diff.go b/terraform/eval_diff.go index 991460e85..b94e25cd8 100644 --- a/terraform/eval_diff.go +++ b/terraform/eval_diff.go @@ -92,6 +92,12 @@ type EvalDiff struct { State **states.ResourceInstanceObject 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 OutputValue *cty.Value OutputState **states.ResourceInstanceObject @@ -270,14 +276,18 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) { case !reqRep.Empty(): // If there are any "requires replace" paths left _after our filtering // above_ then this is a replace action. - action = plans.Replace + if n.CreateBeforeDestroy { + action = plans.CreateThenDelete + } else { + action = plans.DeleteThenCreate + } default: action = plans.Update // "Delete" is never chosen here, because deletion plans are always // 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 // 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 @@ -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 // create. if action == plans.Create && priorValTainted != cty.NilVal { - action = plans.Replace + if n.CreateBeforeDestroy { + action = plans.CreateThenDelete + } else { + action = plans.DeleteThenCreate + } priorVal = priorValTainted } @@ -339,9 +353,9 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) { // we originally intended. if n.PreviousDiff != nil { prevChange := *n.PreviousDiff - if prevChange.Action == plans.Replace && action == plans.Create { - log.Printf("[TRACE] EvalDiff: %s treating Create change as Replace change to match with earlier plan", absAddr) - action = plans.Replace + if prevChange.Action.IsReplace() && action == plans.Create { + log.Printf("[TRACE] EvalDiff: %s treating Create change as %s change to match with earlier plan", absAddr, prevChange.Action) + action = prevChange.Action priorVal = prevChange.Before } } diff --git a/terraform/graph_builder_apply.go b/terraform/graph_builder_apply.go index 0db65fe62..c34cb1a3e 100644 --- a/terraform/graph_builder_apply.go +++ b/terraform/graph_builder_apply.go @@ -95,7 +95,6 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer { // ConfigTransformer above. &DiffTransformer{ Concrete: concreteResourceInstance, - Config: b.Config, State: b.State, Changes: b.Changes, }, diff --git a/terraform/graph_builder_apply_test.go b/terraform/graph_builder_apply_test.go index 413be7213..bad77ced5 100644 --- a/terraform/graph_builder_apply_test.go +++ b/terraform/graph_builder_apply_test.go @@ -76,7 +76,7 @@ func TestApplyGraphBuilder_depCbd(t *testing.T) { { Addr: mustResourceInstanceAddr("test_object.A"), ChangeSrc: plans.ChangeSrc{ - Action: plans.Replace, + Action: plans.CreateThenDelete, }, }, { @@ -152,13 +152,13 @@ func TestApplyGraphBuilder_doubleCBD(t *testing.T) { { Addr: mustResourceInstanceAddr("test_object.A"), ChangeSrc: plans.ChangeSrc{ - Action: plans.Replace, + Action: plans.CreateThenDelete, }, }, { Addr: mustResourceInstanceAddr("test_object.B"), ChangeSrc: plans.ChangeSrc{ - Action: plans.Replace, + Action: plans.CreateThenDelete, }, }, }, diff --git a/terraform/graph_builder_plan.go b/terraform/graph_builder_plan.go index 7b9cda1c4..1bea33c1b 100644 --- a/terraform/graph_builder_plan.go +++ b/terraform/graph_builder_plan.go @@ -157,6 +157,10 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer { 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 &CloseProviderTransformer{}, &CloseProvisionerTransformer{}, diff --git a/terraform/node_resource_apply_instance.go b/terraform/node_resource_apply_instance.go index 2a66eef99..857e1a80d 100644 --- a/terraform/node_resource_apply_instance.go +++ b/terraform/node_resource_apply_instance.go @@ -241,7 +241,7 @@ func (n *NodeApplyableResourceInstance) evalTreeManagedResource(addr addrs.AbsRe If: func(ctx EvalContext) (bool, error) { destroy := false if diffApply != nil { - destroy = (diffApply.Action == plans.Delete || diffApply.Action == plans.Replace) + destroy = (diffApply.Action == plans.Delete || diffApply.Action.IsReplace()) } if destroy && n.createBeforeDestroy() { createBeforeDestroyEnabled = true @@ -395,7 +395,7 @@ func (n *NodeApplyableResourceInstance) evalTreeManagedResource(addr addrs.AbsRe // is no longer a diff! &EvalIf{ If: func(ctx EvalContext) (bool, error) { - if diff.Action != plans.Replace { + if !diff.Action.IsReplace() { return true, nil } if !n.createBeforeDestroy() { diff --git a/terraform/node_resource_destroy_deposed.go b/terraform/node_resource_destroy_deposed.go index c85657975..2ecb5bffa 100644 --- a/terraform/node_resource_destroy_deposed.go +++ b/terraform/node_resource_destroy_deposed.go @@ -15,6 +15,10 @@ import ( // an associated deposed object key. type ConcreteResourceInstanceDeposedNodeFunc func(*NodeAbstractResourceInstance, states.DeposedKey) dag.Vertex +type GraphNodeDeposedResourceInstanceObject interface { + DeposedInstanceObjectKey() states.DeposedKey +} + // NodePlanDeposedResourceInstanceObject represents deposed resource // instance objects during plan. These are distinct from the primary object // for each resource instance since the only valid operation to do with them @@ -28,19 +32,24 @@ type NodePlanDeposedResourceInstanceObject struct { } var ( - _ GraphNodeResource = (*NodePlanDeposedResourceInstanceObject)(nil) - _ GraphNodeResourceInstance = (*NodePlanDeposedResourceInstanceObject)(nil) - _ GraphNodeReferenceable = (*NodePlanDeposedResourceInstanceObject)(nil) - _ GraphNodeReferencer = (*NodePlanDeposedResourceInstanceObject)(nil) - _ GraphNodeEvalable = (*NodePlanDeposedResourceInstanceObject)(nil) - _ GraphNodeProviderConsumer = (*NodePlanDeposedResourceInstanceObject)(nil) - _ GraphNodeProvisionerConsumer = (*NodePlanDeposedResourceInstanceObject)(nil) + _ GraphNodeDeposedResourceInstanceObject = (*NodePlanDeposedResourceInstanceObject)(nil) + _ GraphNodeResource = (*NodePlanDeposedResourceInstanceObject)(nil) + _ GraphNodeResourceInstance = (*NodePlanDeposedResourceInstanceObject)(nil) + _ GraphNodeReferenceable = (*NodePlanDeposedResourceInstanceObject)(nil) + _ GraphNodeReferencer = (*NodePlanDeposedResourceInstanceObject)(nil) + _ GraphNodeEvalable = (*NodePlanDeposedResourceInstanceObject)(nil) + _ GraphNodeProviderConsumer = (*NodePlanDeposedResourceInstanceObject)(nil) + _ GraphNodeProvisionerConsumer = (*NodePlanDeposedResourceInstanceObject)(nil) ) func (n *NodePlanDeposedResourceInstanceObject) Name() string { 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 func (n *NodePlanDeposedResourceInstanceObject) ReferenceableAddrs() []addrs.Referenceable { // Deposed objects don't participate in references. @@ -155,21 +164,26 @@ type NodeDestroyDeposedResourceInstanceObject struct { } var ( - _ GraphNodeResource = (*NodeDestroyDeposedResourceInstanceObject)(nil) - _ GraphNodeResourceInstance = (*NodeDestroyDeposedResourceInstanceObject)(nil) - _ GraphNodeDestroyer = (*NodeDestroyDeposedResourceInstanceObject)(nil) - _ GraphNodeDestroyerCBD = (*NodeDestroyDeposedResourceInstanceObject)(nil) - _ GraphNodeReferenceable = (*NodeDestroyDeposedResourceInstanceObject)(nil) - _ GraphNodeReferencer = (*NodeDestroyDeposedResourceInstanceObject)(nil) - _ GraphNodeEvalable = (*NodeDestroyDeposedResourceInstanceObject)(nil) - _ GraphNodeProviderConsumer = (*NodeDestroyDeposedResourceInstanceObject)(nil) - _ GraphNodeProvisionerConsumer = (*NodeDestroyDeposedResourceInstanceObject)(nil) + _ GraphNodeDeposedResourceInstanceObject = (*NodeDestroyDeposedResourceInstanceObject)(nil) + _ GraphNodeResource = (*NodeDestroyDeposedResourceInstanceObject)(nil) + _ GraphNodeResourceInstance = (*NodeDestroyDeposedResourceInstanceObject)(nil) + _ GraphNodeDestroyer = (*NodeDestroyDeposedResourceInstanceObject)(nil) + _ GraphNodeDestroyerCBD = (*NodeDestroyDeposedResourceInstanceObject)(nil) + _ GraphNodeReferenceable = (*NodeDestroyDeposedResourceInstanceObject)(nil) + _ GraphNodeReferencer = (*NodeDestroyDeposedResourceInstanceObject)(nil) + _ GraphNodeEvalable = (*NodeDestroyDeposedResourceInstanceObject)(nil) + _ GraphNodeProviderConsumer = (*NodeDestroyDeposedResourceInstanceObject)(nil) + _ GraphNodeProvisionerConsumer = (*NodeDestroyDeposedResourceInstanceObject)(nil) ) func (n *NodeDestroyDeposedResourceInstanceObject) Name() string { 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 func (n *NodeDestroyDeposedResourceInstanceObject) ReferenceableAddrs() []addrs.Referenceable { // Deposed objects don't participate in references. diff --git a/terraform/node_resource_plan.go b/terraform/node_resource_plan.go index 1d15b0503..633c1c466 100644 --- a/terraform/node_resource_plan.go +++ b/terraform/node_resource_plan.go @@ -11,10 +11,16 @@ import ( // it is ready to be planned in order to create a diff. type NodePlannableResource struct { *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 ( _ GraphNodeSubPath = (*NodePlannableResource)(nil) + _ GraphNodeDestroyerCBD = (*NodePlannableResource)(nil) _ GraphNodeDynamicExpandable = (*NodePlannableResource)(nil) _ GraphNodeReferenceable = (*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 func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) { var diags tfdiags.Diagnostics @@ -70,6 +96,11 @@ func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) { return &NodePlannableResourceInstance{ 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(), } } diff --git a/terraform/node_resource_plan_instance.go b/terraform/node_resource_plan_instance.go index 5591fc973..37285ec82 100644 --- a/terraform/node_resource_plan_instance.go +++ b/terraform/node_resource_plan_instance.go @@ -16,6 +16,7 @@ import ( // count index, for example. type NodePlannableResourceInstance struct { *NodeAbstractResourceInstance + ForceCreateBeforeDestroy bool } var ( @@ -137,14 +138,15 @@ func (n *NodePlannableResourceInstance) evalTreeManagedResource(addr addrs.AbsRe }, &EvalDiff{ - Addr: addr.Resource, - Config: n.Config, - Provider: &provider, - ProviderAddr: n.ResolvedProvider, - ProviderSchema: &providerSchema, - State: &state, - OutputChange: &change, - OutputState: &state, + Addr: addr.Resource, + Config: n.Config, + CreateBeforeDestroy: n.ForceCreateBeforeDestroy, + Provider: &provider, + ProviderAddr: n.ResolvedProvider, + ProviderSchema: &providerSchema, + State: &state, + OutputChange: &change, + OutputState: &state, }, &EvalCheckPreventDestroy{ Addr: addr.Resource, diff --git a/terraform/transform_destroy_cbd.go b/terraform/transform_destroy_cbd.go index 3a729c8db..570b2ba54 100644 --- a/terraform/transform_destroy_cbd.go +++ b/terraform/transform_destroy_cbd.go @@ -10,10 +10,9 @@ import ( ) // 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 { - GraphNodeDestroyer - // CreateBeforeDestroy returns true if this node represents a node // that is doing a CBD. CreateBeforeDestroy() bool @@ -38,6 +37,75 @@ type GraphNodeAttachDestroyer interface { 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 // the DestroyEdgeTransformer to have the right dependencies. There are // two real tasks here: @@ -50,6 +118,12 @@ type GraphNodeAttachDestroyer interface { // update to A. Example: adding a web server updates the load balancer // 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 { // Module and State are only needed to look up dependencies in // any way possible. Either can be nil if not availabile. @@ -70,26 +144,13 @@ func (t *CBDEdgeTransformer) Transform(g *Graph) error { if !ok { continue } + dern, ok := v.(GraphNodeDestroyer) + if !ok { + continue + } if !dn.CreateBeforeDestroy() { - // If there are no CBD ancestors (dependent nodes), then we - // do nothing here. - if !t.hasCBDAncestor(g, 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] 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) - } + continue } // Find the destroy edge. There should only be one. @@ -105,7 +166,9 @@ func (t *CBDEdgeTransformer) Transform(g *Graph) error { // Found it! Invert. 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 @@ -113,7 +176,7 @@ func (t *CBDEdgeTransformer) Transform(g *Graph) error { // dependencies. One day when we limit dependencies more exactly // this will have to change. We have a test case covering this // (depNonCBDCountBoth) so it'll be caught. - addr := dn.DestroyAddr() + addr := dern.DestroyAddr() key := addr.ContainingResource().String() // 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 } - -// 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 -} diff --git a/terraform/transform_diff.go b/terraform/transform_diff.go index 1d991770a..6fb915f87 100644 --- a/terraform/transform_diff.go +++ b/terraform/transform_diff.go @@ -4,7 +4,6 @@ import ( "fmt" "log" - "github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/dag" "github.com/hashicorp/terraform/plans" "github.com/hashicorp/terraform/states" @@ -15,7 +14,6 @@ import ( // each of the resource changes described in the given Changes object. type DiffTransformer struct { Concrete ConcreteResourceInstanceNodeFunc - Config *configs.Config State *states.State Changes *plans.Changes } @@ -30,7 +28,6 @@ func (t *DiffTransformer) Transform(g *Graph) error { log.Printf("[TRACE] DiffTransformer starting") var diags tfdiags.Diagnostics - config := t.Config state := t.State changes := t.Changes @@ -59,15 +56,9 @@ func (t *DiffTransformer) Transform(g *Graph) error { for _, rc := range changes.Resources { addr := rc.Addr dk := rc.DeposedKey - var rCfg *configs.Resource 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 // nodes, because destroying uses a special node type separate from // other actions. @@ -77,9 +68,10 @@ func (t *DiffTransformer) Transform(g *Graph) error { continue case plans.Delete: delete = true - case plans.Replace: + case plans.DeleteThenCreate, plans.CreateThenDelete: update = true delete = true + createBeforeDestroy = (rc.Action == plans.CreateThenDelete) default: update = true } @@ -93,10 +85,6 @@ func (t *DiffTransformer) Transform(g *Graph) error { 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 // we need to allocate a DeposedKey to use to retain the // 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, DeposedKey: dk, } + node.(*NodeDestroyResourceInstance).ModifyCreateBeforeDestroy(createBeforeDestroy) } else { node = &NodeDestroyDeposedResourceInstanceObject{ NodeAbstractResourceInstance: abstract,