github.com/opentofu/opentofu@v1.7.1/internal/command/views/json/hook.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  	"time"
    11  
    12  	"github.com/opentofu/opentofu/internal/addrs"
    13  	"github.com/opentofu/opentofu/internal/plans"
    14  )
    15  
    16  type Hook interface {
    17  	HookType() MessageType
    18  	String() string
    19  }
    20  
    21  // ApplyStart: triggered by PreApply hook
    22  type applyStart struct {
    23  	Resource   ResourceAddr `json:"resource"`
    24  	Action     ChangeAction `json:"action"`
    25  	IDKey      string       `json:"id_key,omitempty"`
    26  	IDValue    string       `json:"id_value,omitempty"`
    27  	actionVerb string
    28  }
    29  
    30  var _ Hook = (*applyStart)(nil)
    31  
    32  func (h *applyStart) HookType() MessageType {
    33  	return MessageApplyStart
    34  }
    35  
    36  func (h *applyStart) String() string {
    37  	var id string
    38  	if h.IDKey != "" && h.IDValue != "" {
    39  		id = fmt.Sprintf(" [%s=%s]", h.IDKey, h.IDValue)
    40  	}
    41  	return fmt.Sprintf("%s: %s...%s", h.Resource.Addr, h.actionVerb, id)
    42  }
    43  
    44  func NewApplyStart(addr addrs.AbsResourceInstance, action plans.Action, idKey string, idValue string) Hook {
    45  	hook := &applyStart{
    46  		Resource:   newResourceAddr(addr),
    47  		Action:     changeAction(action),
    48  		IDKey:      idKey,
    49  		IDValue:    idValue,
    50  		actionVerb: startActionVerb(action),
    51  	}
    52  
    53  	return hook
    54  }
    55  
    56  // ApplyProgress: currently triggered by a timer started on PreApply. In
    57  // future, this might also be triggered by provider progress reporting.
    58  type applyProgress struct {
    59  	Resource   ResourceAddr `json:"resource"`
    60  	Action     ChangeAction `json:"action"`
    61  	Elapsed    float64      `json:"elapsed_seconds"`
    62  	actionVerb string
    63  	elapsed    time.Duration
    64  }
    65  
    66  var _ Hook = (*applyProgress)(nil)
    67  
    68  func (h *applyProgress) HookType() MessageType {
    69  	return MessageApplyProgress
    70  }
    71  
    72  func (h *applyProgress) String() string {
    73  	return fmt.Sprintf("%s: Still %s... [%s elapsed]", h.Resource.Addr, h.actionVerb, h.elapsed)
    74  }
    75  
    76  func NewApplyProgress(addr addrs.AbsResourceInstance, action plans.Action, elapsed time.Duration) Hook {
    77  	return &applyProgress{
    78  		Resource:   newResourceAddr(addr),
    79  		Action:     changeAction(action),
    80  		Elapsed:    elapsed.Seconds(),
    81  		actionVerb: progressActionVerb(action),
    82  		elapsed:    elapsed,
    83  	}
    84  }
    85  
    86  // ApplyComplete: triggered by PostApply hook
    87  type applyComplete struct {
    88  	Resource   ResourceAddr `json:"resource"`
    89  	Action     ChangeAction `json:"action"`
    90  	IDKey      string       `json:"id_key,omitempty"`
    91  	IDValue    string       `json:"id_value,omitempty"`
    92  	Elapsed    float64      `json:"elapsed_seconds"`
    93  	actionNoun string
    94  	elapsed    time.Duration
    95  }
    96  
    97  var _ Hook = (*applyComplete)(nil)
    98  
    99  func (h *applyComplete) HookType() MessageType {
   100  	return MessageApplyComplete
   101  }
   102  
   103  func (h *applyComplete) String() string {
   104  	var id string
   105  	if h.IDKey != "" && h.IDValue != "" {
   106  		id = fmt.Sprintf(" [%s=%s]", h.IDKey, h.IDValue)
   107  	}
   108  	return fmt.Sprintf("%s: %s complete after %s%s", h.Resource.Addr, h.actionNoun, h.elapsed, id)
   109  }
   110  
   111  func NewApplyComplete(addr addrs.AbsResourceInstance, action plans.Action, idKey, idValue string, elapsed time.Duration) Hook {
   112  	return &applyComplete{
   113  		Resource:   newResourceAddr(addr),
   114  		Action:     changeAction(action),
   115  		IDKey:      idKey,
   116  		IDValue:    idValue,
   117  		Elapsed:    elapsed.Seconds(),
   118  		actionNoun: actionNoun(action),
   119  		elapsed:    elapsed,
   120  	}
   121  }
   122  
   123  // ApplyErrored: triggered by PostApply hook on failure. This will be followed
   124  // by diagnostics when the apply finishes.
   125  type applyErrored struct {
   126  	Resource   ResourceAddr `json:"resource"`
   127  	Action     ChangeAction `json:"action"`
   128  	Elapsed    float64      `json:"elapsed_seconds"`
   129  	actionNoun string
   130  	elapsed    time.Duration
   131  }
   132  
   133  var _ Hook = (*applyErrored)(nil)
   134  
   135  func (h *applyErrored) HookType() MessageType {
   136  	return MessageApplyErrored
   137  }
   138  
   139  func (h *applyErrored) String() string {
   140  	return fmt.Sprintf("%s: %s errored after %s", h.Resource.Addr, h.actionNoun, h.elapsed)
   141  }
   142  
   143  func NewApplyErrored(addr addrs.AbsResourceInstance, action plans.Action, elapsed time.Duration) Hook {
   144  	return &applyErrored{
   145  		Resource:   newResourceAddr(addr),
   146  		Action:     changeAction(action),
   147  		Elapsed:    elapsed.Seconds(),
   148  		actionNoun: actionNoun(action),
   149  		elapsed:    elapsed,
   150  	}
   151  }
   152  
   153  // ProvisionStart: triggered by PreProvisionInstanceStep hook
   154  type provisionStart struct {
   155  	Resource    ResourceAddr `json:"resource"`
   156  	Provisioner string       `json:"provisioner"`
   157  }
   158  
   159  var _ Hook = (*provisionStart)(nil)
   160  
   161  func (h *provisionStart) HookType() MessageType {
   162  	return MessageProvisionStart
   163  }
   164  
   165  func (h *provisionStart) String() string {
   166  	return fmt.Sprintf("%s: Provisioning with '%s'...", h.Resource.Addr, h.Provisioner)
   167  }
   168  
   169  func NewProvisionStart(addr addrs.AbsResourceInstance, provisioner string) Hook {
   170  	return &provisionStart{
   171  		Resource:    newResourceAddr(addr),
   172  		Provisioner: provisioner,
   173  	}
   174  }
   175  
   176  // ProvisionProgress: triggered by ProvisionOutput hook
   177  type provisionProgress struct {
   178  	Resource    ResourceAddr `json:"resource"`
   179  	Provisioner string       `json:"provisioner"`
   180  	Output      string       `json:"output"`
   181  }
   182  
   183  var _ Hook = (*provisionProgress)(nil)
   184  
   185  func (h *provisionProgress) HookType() MessageType {
   186  	return MessageProvisionProgress
   187  }
   188  
   189  func (h *provisionProgress) String() string {
   190  	return fmt.Sprintf("%s: (%s): %s", h.Resource.Addr, h.Provisioner, h.Output)
   191  }
   192  
   193  func NewProvisionProgress(addr addrs.AbsResourceInstance, provisioner string, output string) Hook {
   194  	return &provisionProgress{
   195  		Resource:    newResourceAddr(addr),
   196  		Provisioner: provisioner,
   197  		Output:      output,
   198  	}
   199  }
   200  
   201  // ProvisionComplete: triggered by PostProvisionInstanceStep hook
   202  type provisionComplete struct {
   203  	Resource    ResourceAddr `json:"resource"`
   204  	Provisioner string       `json:"provisioner"`
   205  }
   206  
   207  var _ Hook = (*provisionComplete)(nil)
   208  
   209  func (h *provisionComplete) HookType() MessageType {
   210  	return MessageProvisionComplete
   211  }
   212  
   213  func (h *provisionComplete) String() string {
   214  	return fmt.Sprintf("%s: (%s) Provisioning complete", h.Resource.Addr, h.Provisioner)
   215  }
   216  
   217  func NewProvisionComplete(addr addrs.AbsResourceInstance, provisioner string) Hook {
   218  	return &provisionComplete{
   219  		Resource:    newResourceAddr(addr),
   220  		Provisioner: provisioner,
   221  	}
   222  }
   223  
   224  // ProvisionErrored: triggered by PostProvisionInstanceStep hook on failure.
   225  // This will be followed by diagnostics when the apply finishes.
   226  type provisionErrored struct {
   227  	Resource    ResourceAddr `json:"resource"`
   228  	Provisioner string       `json:"provisioner"`
   229  }
   230  
   231  var _ Hook = (*provisionErrored)(nil)
   232  
   233  func (h *provisionErrored) HookType() MessageType {
   234  	return MessageProvisionErrored
   235  }
   236  
   237  func (h *provisionErrored) String() string {
   238  	return fmt.Sprintf("%s: (%s) Provisioning errored", h.Resource.Addr, h.Provisioner)
   239  }
   240  
   241  func NewProvisionErrored(addr addrs.AbsResourceInstance, provisioner string) Hook {
   242  	return &provisionErrored{
   243  		Resource:    newResourceAddr(addr),
   244  		Provisioner: provisioner,
   245  	}
   246  }
   247  
   248  // RefreshStart: triggered by PreRefresh hook
   249  type refreshStart struct {
   250  	Resource ResourceAddr `json:"resource"`
   251  	IDKey    string       `json:"id_key,omitempty"`
   252  	IDValue  string       `json:"id_value,omitempty"`
   253  }
   254  
   255  var _ Hook = (*refreshStart)(nil)
   256  
   257  func (h *refreshStart) HookType() MessageType {
   258  	return MessageRefreshStart
   259  }
   260  
   261  func (h *refreshStart) String() string {
   262  	var id string
   263  	if h.IDKey != "" && h.IDValue != "" {
   264  		id = fmt.Sprintf(" [%s=%s]", h.IDKey, h.IDValue)
   265  	}
   266  	return fmt.Sprintf("%s: Refreshing state...%s", h.Resource.Addr, id)
   267  }
   268  
   269  func NewRefreshStart(addr addrs.AbsResourceInstance, idKey, idValue string) Hook {
   270  	return &refreshStart{
   271  		Resource: newResourceAddr(addr),
   272  		IDKey:    idKey,
   273  		IDValue:  idValue,
   274  	}
   275  }
   276  
   277  // RefreshComplete: triggered by PostRefresh hook
   278  type refreshComplete struct {
   279  	Resource ResourceAddr `json:"resource"`
   280  	IDKey    string       `json:"id_key,omitempty"`
   281  	IDValue  string       `json:"id_value,omitempty"`
   282  }
   283  
   284  var _ Hook = (*refreshComplete)(nil)
   285  
   286  func (h *refreshComplete) HookType() MessageType {
   287  	return MessageRefreshComplete
   288  }
   289  
   290  func (h *refreshComplete) String() string {
   291  	var id string
   292  	if h.IDKey != "" && h.IDValue != "" {
   293  		id = fmt.Sprintf(" [%s=%s]", h.IDKey, h.IDValue)
   294  	}
   295  	return fmt.Sprintf("%s: Refresh complete%s", h.Resource.Addr, id)
   296  }
   297  
   298  func NewRefreshComplete(addr addrs.AbsResourceInstance, idKey, idValue string) Hook {
   299  	return &refreshComplete{
   300  		Resource: newResourceAddr(addr),
   301  		IDKey:    idKey,
   302  		IDValue:  idValue,
   303  	}
   304  }
   305  
   306  // Convert the subset of plans.Action values we expect to receive into a
   307  // present-tense verb for the applyStart hook message.
   308  func startActionVerb(action plans.Action) string {
   309  	switch action {
   310  	case plans.Create:
   311  		return "Creating"
   312  	case plans.Update:
   313  		return "Modifying"
   314  	case plans.Delete:
   315  		return "Destroying"
   316  	case plans.Read:
   317  		return "Refreshing"
   318  	case plans.CreateThenDelete, plans.DeleteThenCreate:
   319  		// This is not currently possible to reach, as we receive separate
   320  		// passes for create and delete
   321  		return "Replacing"
   322  	case plans.Forget:
   323  		return "Removing"
   324  	case plans.NoOp:
   325  		// This should never be possible: a no-op planned change should not
   326  		// be applied. We'll fall back to "Applying".
   327  		fallthrough
   328  	default:
   329  		return "Applying"
   330  	}
   331  }
   332  
   333  // Convert the subset of plans.Action values we expect to receive into a
   334  // present-tense verb for the applyProgress hook message. This will be
   335  // prefixed with "Still ", so it is lower-case.
   336  func progressActionVerb(action plans.Action) string {
   337  	switch action {
   338  	case plans.Create:
   339  		return "creating"
   340  	case plans.Update:
   341  		return "modifying"
   342  	case plans.Delete:
   343  		return "destroying"
   344  	case plans.Read:
   345  		return "refreshing"
   346  	case plans.CreateThenDelete, plans.DeleteThenCreate:
   347  		// This is not currently possible to reach, as we receive separate
   348  		// passes for create and delete
   349  		return "replacing"
   350  	case plans.Forget:
   351  		return "removing"
   352  	case plans.NoOp:
   353  		// This should never be possible: a no-op planned change should not
   354  		// be applied. We'll fall back to "applying".
   355  		fallthrough
   356  	default:
   357  		return "applying"
   358  	}
   359  }
   360  
   361  // Convert the subset of plans.Action values we expect to receive into a
   362  // noun for the applyComplete and applyErrored hook messages. This will be
   363  // combined into a phrase like "Creation complete after 1m4s".
   364  func actionNoun(action plans.Action) string {
   365  	switch action {
   366  	case plans.Create:
   367  		return "Creation"
   368  	case plans.Update:
   369  		return "Modifications"
   370  	case plans.Delete:
   371  		return "Destruction"
   372  	case plans.Read:
   373  		return "Refresh"
   374  	case plans.CreateThenDelete, plans.DeleteThenCreate:
   375  		// This is not currently possible to reach, as we receive separate
   376  		// passes for create and delete
   377  		return "Replacement"
   378  	case plans.Forget:
   379  		return "Removal"
   380  	case plans.NoOp:
   381  		// This should never be possible: a no-op planned change should not
   382  		// be applied. We'll fall back to "Apply".
   383  		fallthrough
   384  	default:
   385  		return "Apply"
   386  	}
   387  }