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