github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/views/json/hook.go (about)

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