blob: d4fc8aa036eb41598276b2bdd88620664256905f [file] [log] [blame] [edit]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package json
import (
"fmt"
"time"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/plans"
)
type Hook interface {
HookType() MessageType
String() string
}
// operationStart: triggered by Pre{Apply,EphemeralOp} hook
type operationStart struct {
Resource ResourceAddr `json:"resource"`
Action ChangeAction `json:"action"`
IDKey string `json:"id_key,omitempty"`
IDValue string `json:"id_value,omitempty"`
actionVerb string
msgType MessageType
}
var _ Hook = (*operationStart)(nil)
func (h *operationStart) HookType() MessageType {
return h.msgType
}
func (h *operationStart) String() string {
var id string
if h.IDKey != "" && h.IDValue != "" {
id = fmt.Sprintf(" [%s=%s]", h.IDKey, h.IDValue)
}
return fmt.Sprintf("%s: %s...%s", h.Resource.Addr, h.actionVerb, id)
}
func NewApplyStart(addr addrs.AbsResourceInstance, action plans.Action, idKey string, idValue string) Hook {
hook := &operationStart{
Resource: newResourceAddr(addr),
Action: changeAction(action),
IDKey: idKey,
IDValue: idValue,
actionVerb: startActionVerb(action),
msgType: MessageApplyStart,
}
return hook
}
func NewEphemeralOpStart(addr addrs.AbsResourceInstance, action plans.Action) Hook {
hook := &operationStart{
Resource: newResourceAddr(addr),
Action: changeAction(action),
actionVerb: startActionVerb(action),
msgType: MessageEphemeralOpStart,
}
return hook
}
// operationProgress: currently triggered by a timer started on Pre{Apply,EphemeralOp}. In
// future, this might also be triggered by provider progress reporting.
type operationProgress struct {
Resource ResourceAddr `json:"resource"`
Action ChangeAction `json:"action"`
Elapsed float64 `json:"elapsed_seconds"`
actionVerb string
elapsed time.Duration
msgType MessageType
}
var _ Hook = (*operationProgress)(nil)
func (h *operationProgress) HookType() MessageType {
return h.msgType
}
func (h *operationProgress) String() string {
return fmt.Sprintf("%s: Still %s... [%s elapsed]", h.Resource.Addr, h.actionVerb, h.elapsed)
}
func NewApplyProgress(addr addrs.AbsResourceInstance, action plans.Action, elapsed time.Duration) Hook {
return &operationProgress{
Resource: newResourceAddr(addr),
Action: changeAction(action),
Elapsed: elapsed.Seconds(),
actionVerb: progressActionVerb(action),
elapsed: elapsed,
msgType: MessageApplyProgress,
}
}
func NewEphemeralOpProgress(addr addrs.AbsResourceInstance, action plans.Action, elapsed time.Duration) Hook {
return &operationProgress{
Resource: newResourceAddr(addr),
Action: changeAction(action),
Elapsed: elapsed.Seconds(),
actionVerb: progressActionVerb(action),
elapsed: elapsed,
msgType: MessageEphemeralOpProgress,
}
}
// operationComplete: triggered by PostApply hook
type operationComplete struct {
Resource ResourceAddr `json:"resource"`
Action ChangeAction `json:"action"`
IDKey string `json:"id_key,omitempty"`
IDValue string `json:"id_value,omitempty"`
Elapsed float64 `json:"elapsed_seconds"`
actionNoun string
elapsed time.Duration
msgType MessageType
}
var _ Hook = (*operationComplete)(nil)
func (h *operationComplete) HookType() MessageType {
return h.msgType
}
func (h *operationComplete) String() string {
var id string
if h.IDKey != "" && h.IDValue != "" {
id = fmt.Sprintf(" [%s=%s]", h.IDKey, h.IDValue)
}
return fmt.Sprintf("%s: %s complete after %s%s", h.Resource.Addr, h.actionNoun, h.elapsed, id)
}
func NewApplyComplete(addr addrs.AbsResourceInstance, action plans.Action, idKey, idValue string, elapsed time.Duration) Hook {
return &operationComplete{
Resource: newResourceAddr(addr),
Action: changeAction(action),
IDKey: idKey,
IDValue: idValue,
Elapsed: elapsed.Seconds(),
actionNoun: actionNoun(action),
elapsed: elapsed,
msgType: MessageApplyComplete,
}
}
func NewEphemeralOpComplete(addr addrs.AbsResourceInstance, action plans.Action, elapsed time.Duration) Hook {
return &operationComplete{
Resource: newResourceAddr(addr),
Action: changeAction(action),
Elapsed: elapsed.Seconds(),
actionNoun: actionNoun(action),
elapsed: elapsed,
msgType: MessageEphemeralOpComplete,
}
}
// operationErrored: triggered by PostApply hook on failure. This will be followed
// by diagnostics when the apply finishes.
type operationErrored struct {
Resource ResourceAddr `json:"resource"`
Action ChangeAction `json:"action"`
Elapsed float64 `json:"elapsed_seconds"`
actionNoun string
elapsed time.Duration
msgType MessageType
}
var _ Hook = (*operationErrored)(nil)
func (h *operationErrored) HookType() MessageType {
return h.msgType
}
func (h *operationErrored) String() string {
return fmt.Sprintf("%s: %s errored after %s", h.Resource.Addr, h.actionNoun, h.elapsed)
}
func NewApplyErrored(addr addrs.AbsResourceInstance, action plans.Action, elapsed time.Duration) Hook {
return &operationErrored{
Resource: newResourceAddr(addr),
Action: changeAction(action),
Elapsed: elapsed.Seconds(),
actionNoun: actionNoun(action),
elapsed: elapsed,
msgType: MessageApplyErrored,
}
}
func NewEphemeralOpErrored(addr addrs.AbsResourceInstance, action plans.Action, elapsed time.Duration) Hook {
return &operationErrored{
Resource: newResourceAddr(addr),
Action: changeAction(action),
Elapsed: elapsed.Seconds(),
actionNoun: actionNoun(action),
elapsed: elapsed,
msgType: MessageEphemeralOpErrored,
}
}
// ProvisionStart: triggered by PreProvisionInstanceStep hook
type provisionStart struct {
Resource ResourceAddr `json:"resource"`
Provisioner string `json:"provisioner"`
}
var _ Hook = (*provisionStart)(nil)
func (h *provisionStart) HookType() MessageType {
return MessageProvisionStart
}
func (h *provisionStart) String() string {
return fmt.Sprintf("%s: Provisioning with '%s'...", h.Resource.Addr, h.Provisioner)
}
func NewProvisionStart(addr addrs.AbsResourceInstance, provisioner string) Hook {
return &provisionStart{
Resource: newResourceAddr(addr),
Provisioner: provisioner,
}
}
// ProvisionProgress: triggered by ProvisionOutput hook
type provisionProgress struct {
Resource ResourceAddr `json:"resource"`
Provisioner string `json:"provisioner"`
Output string `json:"output"`
}
var _ Hook = (*provisionProgress)(nil)
func (h *provisionProgress) HookType() MessageType {
return MessageProvisionProgress
}
func (h *provisionProgress) String() string {
return fmt.Sprintf("%s: (%s): %s", h.Resource.Addr, h.Provisioner, h.Output)
}
func NewProvisionProgress(addr addrs.AbsResourceInstance, provisioner string, output string) Hook {
return &provisionProgress{
Resource: newResourceAddr(addr),
Provisioner: provisioner,
Output: output,
}
}
// ProvisionComplete: triggered by PostProvisionInstanceStep hook
type provisionComplete struct {
Resource ResourceAddr `json:"resource"`
Provisioner string `json:"provisioner"`
}
var _ Hook = (*provisionComplete)(nil)
func (h *provisionComplete) HookType() MessageType {
return MessageProvisionComplete
}
func (h *provisionComplete) String() string {
return fmt.Sprintf("%s: (%s) Provisioning complete", h.Resource.Addr, h.Provisioner)
}
func NewProvisionComplete(addr addrs.AbsResourceInstance, provisioner string) Hook {
return &provisionComplete{
Resource: newResourceAddr(addr),
Provisioner: provisioner,
}
}
// ProvisionErrored: triggered by PostProvisionInstanceStep hook on failure.
// This will be followed by diagnostics when the apply finishes.
type provisionErrored struct {
Resource ResourceAddr `json:"resource"`
Provisioner string `json:"provisioner"`
}
var _ Hook = (*provisionErrored)(nil)
func (h *provisionErrored) HookType() MessageType {
return MessageProvisionErrored
}
func (h *provisionErrored) String() string {
return fmt.Sprintf("%s: (%s) Provisioning errored", h.Resource.Addr, h.Provisioner)
}
func NewProvisionErrored(addr addrs.AbsResourceInstance, provisioner string) Hook {
return &provisionErrored{
Resource: newResourceAddr(addr),
Provisioner: provisioner,
}
}
// RefreshStart: triggered by PreRefresh hook
type refreshStart struct {
Resource ResourceAddr `json:"resource"`
IDKey string `json:"id_key,omitempty"`
IDValue string `json:"id_value,omitempty"`
}
var _ Hook = (*refreshStart)(nil)
func (h *refreshStart) HookType() MessageType {
return MessageRefreshStart
}
func (h *refreshStart) String() string {
var id string
if h.IDKey != "" && h.IDValue != "" {
id = fmt.Sprintf(" [%s=%s]", h.IDKey, h.IDValue)
}
return fmt.Sprintf("%s: Refreshing state...%s", h.Resource.Addr, id)
}
func NewRefreshStart(addr addrs.AbsResourceInstance, idKey, idValue string) Hook {
return &refreshStart{
Resource: newResourceAddr(addr),
IDKey: idKey,
IDValue: idValue,
}
}
// RefreshComplete: triggered by PostRefresh hook
type refreshComplete struct {
Resource ResourceAddr `json:"resource"`
IDKey string `json:"id_key,omitempty"`
IDValue string `json:"id_value,omitempty"`
}
var _ Hook = (*refreshComplete)(nil)
func (h *refreshComplete) HookType() MessageType {
return MessageRefreshComplete
}
func (h *refreshComplete) String() string {
var id string
if h.IDKey != "" && h.IDValue != "" {
id = fmt.Sprintf(" [%s=%s]", h.IDKey, h.IDValue)
}
return fmt.Sprintf("%s: Refresh complete%s", h.Resource.Addr, id)
}
func NewRefreshComplete(addr addrs.AbsResourceInstance, idKey, idValue string) Hook {
return &refreshComplete{
Resource: newResourceAddr(addr),
IDKey: idKey,
IDValue: idValue,
}
}
// Convert the subset of plans.Action values we expect to receive into a
// present-tense verb for the applyStart hook message.
func startActionVerb(action plans.Action) string {
switch action {
case plans.Create:
return "Creating"
case plans.Update:
return "Modifying"
case plans.Delete:
return "Destroying"
case plans.Read:
return "Refreshing"
case plans.CreateThenDelete, plans.DeleteThenCreate, plans.CreateThenForget:
// This is not currently possible to reach, as we receive separate
// passes for create and delete
return "Replacing"
case plans.Forget:
return "Removing"
case plans.Open:
return "Opening"
case plans.Renew:
return "Renewing"
case plans.Close:
return "Closing"
case plans.NoOp:
// This should never be possible: a no-op planned change should not
// be applied. We'll fall back to "Applying".
fallthrough
default:
return "Applying"
}
}
// Convert the subset of plans.Action values we expect to receive into a
// present-tense verb for the applyProgress hook message. This will be
// prefixed with "Still ", so it is lower-case.
func progressActionVerb(action plans.Action) string {
switch action {
case plans.Create:
return "creating"
case plans.Update:
return "modifying"
case plans.Delete:
return "destroying"
case plans.Read:
return "refreshing"
case plans.CreateThenDelete, plans.CreateThenForget, plans.DeleteThenCreate:
// This is not currently possible to reach, as we receive separate
// passes for create and delete
return "replacing"
case plans.Open:
return "opening"
case plans.Renew:
return "renewing"
case plans.Close:
return "closing"
case plans.Forget:
// Removing a resource from state should not take very long. Fall back
// to "applying" just in case, since the terminology "forgetting" is
// meant to be internal to Terraform.
fallthrough
case plans.NoOp:
// This should never be possible: a no-op planned change should not
// be applied. We'll fall back to "applying".
fallthrough
default:
return "applying"
}
}
// Convert the subset of plans.Action values we expect to receive into a
// noun for the operationComplete and operationErrored hook messages. This will be
// combined into a phrase like "Creation complete after 1m4s".
func actionNoun(action plans.Action) string {
switch action {
case plans.Create:
return "Creation"
case plans.Update:
return "Modifications"
case plans.Delete:
return "Destruction"
case plans.Read:
return "Refresh"
case plans.CreateThenDelete, plans.DeleteThenCreate, plans.CreateThenForget:
// This is not currently possible to reach, as we receive separate
// passes for create and delete
return "Replacement"
case plans.Forget:
return "Removal"
case plans.Open:
return "Opening"
case plans.Renew:
return "Renewal"
case plans.Close:
return "Closing"
case plans.NoOp:
// This should never be possible: a no-op planned change should not
// be applied. We'll fall back to "Apply".
fallthrough
default:
return "Apply"
}
}