package json import ( "fmt" "time" "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/plans" ) type Hook interface { HookType() MessageType String() string } // ApplyStart: triggered by PreApply hook type applyStart struct { Resource ResourceAddr `json:"resource"` Action ChangeAction `json:"action"` IDKey string `json:"id_key,omitempty"` IDValue string `json:"id_value,omitempty"` actionVerb string } var _ Hook = (*applyStart)(nil) func (h *applyStart) HookType() MessageType { return MessageApplyStart } func (h *applyStart) 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 := &applyStart{ Resource: newResourceAddr(addr), Action: changeAction(action), IDKey: idKey, IDValue: idValue, actionVerb: startActionVerb(action), } return hook } // ApplyProgress: currently triggered by a timer started on PreApply. In // future, this might also be triggered by provider progress reporting. type applyProgress struct { Resource ResourceAddr `json:"resource"` Action ChangeAction `json:"action"` Elapsed float64 `json:"elapsed_seconds"` actionVerb string elapsed time.Duration } var _ Hook = (*applyProgress)(nil) func (h *applyProgress) HookType() MessageType { return MessageApplyProgress } func (h *applyProgress) 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 &applyProgress{ Resource: newResourceAddr(addr), Action: changeAction(action), Elapsed: elapsed.Seconds(), actionVerb: progressActionVerb(action), elapsed: elapsed, } } // ApplyComplete: triggered by PostApply hook type applyComplete 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 } var _ Hook = (*applyComplete)(nil) func (h *applyComplete) HookType() MessageType { return MessageApplyComplete } func (h *applyComplete) 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 &applyComplete{ Resource: newResourceAddr(addr), Action: changeAction(action), IDKey: idKey, IDValue: idValue, Elapsed: elapsed.Seconds(), actionNoun: actionNoun(action), elapsed: elapsed, } } // ApplyErrored: triggered by PostApply hook on failure. This will be followed // by diagnostics when the apply finishes. type applyErrored struct { Resource ResourceAddr `json:"resource"` Action ChangeAction `json:"action"` Elapsed float64 `json:"elapsed_seconds"` actionNoun string elapsed time.Duration } var _ Hook = (*applyErrored)(nil) func (h *applyErrored) HookType() MessageType { return MessageApplyErrored } func (h *applyErrored) 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 &applyErrored{ Resource: newResourceAddr(addr), Action: changeAction(action), Elapsed: elapsed.Seconds(), actionNoun: actionNoun(action), elapsed: elapsed, } } // 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: // This is not currently possible to reach, as we receive separate // passes for create and delete return "Replacing" 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.DeleteThenCreate: // This is not currently possible to reach, as we receive separate // passes for create and delete return "replacing" 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 applyComplete and applyErrored 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: // This is not currently possible to reach, as we receive separate // passes for create and delete return "Replacement" 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" } }