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  }