github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/views/json/change.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package json 5 6 import ( 7 "fmt" 8 9 "github.com/terramate-io/tf/plans" 10 ) 11 12 func NewResourceInstanceChange(change *plans.ResourceInstanceChangeSrc) *ResourceInstanceChange { 13 c := &ResourceInstanceChange{ 14 Resource: newResourceAddr(change.Addr), 15 Action: changeAction(change.Action), 16 Reason: changeReason(change.ActionReason), 17 GeneratedConfig: change.GeneratedConfig, 18 } 19 20 // The order here matters, we want the moved action to take precedence over 21 // the import action. We're basically taking "the most recent action" as the 22 // primary action in the streamed logs. That is to say, that if a resource 23 // is imported and then moved in a single operation then the change for that 24 // resource will be reported as ActionMove while the Importing flag will 25 // still be set to true. 26 // 27 // Since both the moved and imported actions only overwrite a NoOp this 28 // behaviour is consistent across the other actions as well. Something that 29 // is imported and then updated, or moved and then updated, will have the 30 // ActionUpdate as the recognised action for the change. 31 32 if !change.Addr.Equal(change.PrevRunAddr) { 33 if c.Action == ActionNoOp { 34 c.Action = ActionMove 35 } 36 pr := newResourceAddr(change.PrevRunAddr) 37 c.PreviousResource = &pr 38 } 39 if change.Importing != nil { 40 if c.Action == ActionNoOp { 41 c.Action = ActionImport 42 } 43 c.Importing = &Importing{ID: change.Importing.ID} 44 } 45 46 return c 47 } 48 49 type ResourceInstanceChange struct { 50 Resource ResourceAddr `json:"resource"` 51 PreviousResource *ResourceAddr `json:"previous_resource,omitempty"` 52 Action ChangeAction `json:"action"` 53 Reason ChangeReason `json:"reason,omitempty"` 54 Importing *Importing `json:"importing,omitempty"` 55 GeneratedConfig string `json:"generated_config,omitempty"` 56 } 57 58 func (c *ResourceInstanceChange) String() string { 59 return fmt.Sprintf("%s: Plan to %s", c.Resource.Addr, c.Action) 60 } 61 62 type ChangeAction string 63 64 const ( 65 ActionNoOp ChangeAction = "noop" 66 ActionMove ChangeAction = "move" 67 ActionCreate ChangeAction = "create" 68 ActionRead ChangeAction = "read" 69 ActionUpdate ChangeAction = "update" 70 ActionReplace ChangeAction = "replace" 71 ActionDelete ChangeAction = "delete" 72 ActionImport ChangeAction = "import" 73 ) 74 75 func changeAction(action plans.Action) ChangeAction { 76 switch action { 77 case plans.NoOp: 78 return ActionNoOp 79 case plans.Create: 80 return ActionCreate 81 case plans.Read: 82 return ActionRead 83 case plans.Update: 84 return ActionUpdate 85 case plans.DeleteThenCreate, plans.CreateThenDelete: 86 return ActionReplace 87 case plans.Delete: 88 return ActionDelete 89 default: 90 return ActionNoOp 91 } 92 } 93 94 type ChangeReason string 95 96 const ( 97 ReasonNone ChangeReason = "" 98 ReasonTainted ChangeReason = "tainted" 99 ReasonRequested ChangeReason = "requested" 100 ReasonReplaceTriggeredBy ChangeReason = "replace_triggered_by" 101 ReasonCannotUpdate ChangeReason = "cannot_update" 102 ReasonUnknown ChangeReason = "unknown" 103 104 ReasonDeleteBecauseNoResourceConfig ChangeReason = "delete_because_no_resource_config" 105 ReasonDeleteBecauseWrongRepetition ChangeReason = "delete_because_wrong_repetition" 106 ReasonDeleteBecauseCountIndex ChangeReason = "delete_because_count_index" 107 ReasonDeleteBecauseEachKey ChangeReason = "delete_because_each_key" 108 ReasonDeleteBecauseNoModule ChangeReason = "delete_because_no_module" 109 ReasonDeleteBecauseNoMoveTarget ChangeReason = "delete_because_no_move_target" 110 ReasonReadBecauseConfigUnknown ChangeReason = "read_because_config_unknown" 111 ReasonReadBecauseDependencyPending ChangeReason = "read_because_dependency_pending" 112 ReasonReadBecauseCheckNested ChangeReason = "read_because_check_nested" 113 ) 114 115 func changeReason(reason plans.ResourceInstanceChangeActionReason) ChangeReason { 116 switch reason { 117 case plans.ResourceInstanceChangeNoReason: 118 return ReasonNone 119 case plans.ResourceInstanceReplaceBecauseTainted: 120 return ReasonTainted 121 case plans.ResourceInstanceReplaceByRequest: 122 return ReasonRequested 123 case plans.ResourceInstanceReplaceBecauseCannotUpdate: 124 return ReasonCannotUpdate 125 case plans.ResourceInstanceReplaceByTriggers: 126 return ReasonReplaceTriggeredBy 127 case plans.ResourceInstanceDeleteBecauseNoResourceConfig: 128 return ReasonDeleteBecauseNoResourceConfig 129 case plans.ResourceInstanceDeleteBecauseWrongRepetition: 130 return ReasonDeleteBecauseWrongRepetition 131 case plans.ResourceInstanceDeleteBecauseCountIndex: 132 return ReasonDeleteBecauseCountIndex 133 case plans.ResourceInstanceDeleteBecauseEachKey: 134 return ReasonDeleteBecauseEachKey 135 case plans.ResourceInstanceDeleteBecauseNoModule: 136 return ReasonDeleteBecauseNoModule 137 case plans.ResourceInstanceReadBecauseConfigUnknown: 138 return ReasonReadBecauseConfigUnknown 139 case plans.ResourceInstanceDeleteBecauseNoMoveTarget: 140 return ReasonDeleteBecauseNoMoveTarget 141 case plans.ResourceInstanceReadBecauseDependencyPending: 142 return ReasonReadBecauseDependencyPending 143 case plans.ResourceInstanceReadBecauseCheckNested: 144 return ReasonReadBecauseCheckNested 145 default: 146 // This should never happen, but there's no good way to guarantee 147 // exhaustive handling of the enum, so a generic fall back is better 148 // than a misleading result or a panic 149 return ReasonUnknown 150 } 151 }