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 {
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:

View File

@ -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

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)))
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
}

View File

@ -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

View File

@ -7,8 +7,16 @@ const (
Create Action = '+'
Read Action = '←'
Update Action = '~'
Replace 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
}

View File

@ -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,

View File

@ -27,8 +27,9 @@ const (
Action_CREATE Action = 1
Action_READ Action = 2
Action_UPDATE Action = 3
Action_REPLACE Action = 4
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,
"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,
}

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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{

View File

@ -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:

View File

@ -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)

View File

@ -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,

View File

@ -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
}
}

View File

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

View File

@ -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,
},
},
},

View File

@ -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{},

View File

@ -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() {

View File

@ -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,6 +32,7 @@ type NodePlanDeposedResourceInstanceObject struct {
}
var (
_ GraphNodeDeposedResourceInstanceObject = (*NodePlanDeposedResourceInstanceObject)(nil)
_ GraphNodeResource = (*NodePlanDeposedResourceInstanceObject)(nil)
_ GraphNodeResourceInstance = (*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)
}
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,6 +164,7 @@ type NodeDestroyDeposedResourceInstanceObject struct {
}
var (
_ GraphNodeDeposedResourceInstanceObject = (*NodeDestroyDeposedResourceInstanceObject)(nil)
_ GraphNodeResource = (*NodeDestroyDeposedResourceInstanceObject)(nil)
_ GraphNodeResourceInstance = (*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)
}
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.

View File

@ -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(),
}
}

View File

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

View File

@ -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
}
if !dn.CreateBeforeDestroy() {
// If there are no CBD ancestors (dependent nodes), then we
// do nothing here.
if !t.hasCBDAncestor(g, v) {
dern, ok := v.(GraphNodeDestroyer)
if !ok {
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)
}
if !dn.CreateBeforeDestroy() {
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
}

View File

@ -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,