opentofu/internal/tofu/hook_stop.go

121 lines
3.5 KiB
Go
Raw Normal View History

// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
2023-09-20 07:16:53 -05:00
package tofu
2014-07-02 18:16:38 -05:00
import (
"errors"
2014-07-03 13:27:30 -05:00
"sync/atomic"
"github.com/zclconf/go-cty/cty"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/plans"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/states"
2014-07-02 18:16:38 -05:00
)
// stopHook is a private Hook implementation that OpenTofu uses to
2014-07-02 18:16:38 -05:00
// signal when to stop or cancel actions.
type stopHook struct {
2014-07-03 13:27:30 -05:00
stop uint32
2014-07-02 18:16:38 -05:00
}
var _ Hook = (*stopHook)(nil)
func (h *stopHook) PreApply(addr addrs.AbsResourceInstance, gen states.Generation, action plans.Action, priorState, plannedNewState cty.Value) (HookAction, error) {
2014-07-02 18:16:38 -05:00
return h.hook()
}
func (h *stopHook) PostApply(addr addrs.AbsResourceInstance, gen states.Generation, newState cty.Value, err error) (HookAction, error) {
2014-07-02 18:16:38 -05:00
return h.hook()
}
func (h *stopHook) PreDiff(addr addrs.AbsResourceInstance, gen states.Generation, priorState, proposedNewState cty.Value) (HookAction, error) {
2014-07-02 18:16:38 -05:00
return h.hook()
}
func (h *stopHook) PostDiff(addr addrs.AbsResourceInstance, gen states.Generation, action plans.Action, priorState, plannedNewState cty.Value) (HookAction, error) {
2014-07-02 18:16:38 -05:00
return h.hook()
}
func (h *stopHook) PreProvisionInstance(addr addrs.AbsResourceInstance, state cty.Value) (HookAction, error) {
2014-07-27 11:00:34 -05:00
return h.hook()
}
func (h *stopHook) PostProvisionInstance(addr addrs.AbsResourceInstance, state cty.Value) (HookAction, error) {
2014-07-27 11:00:34 -05:00
return h.hook()
}
func (h *stopHook) PreProvisionInstanceStep(addr addrs.AbsResourceInstance, typeName string) (HookAction, error) {
2014-07-27 11:00:34 -05:00
return h.hook()
}
func (h *stopHook) PostProvisionInstanceStep(addr addrs.AbsResourceInstance, typeName string, err error) (HookAction, error) {
2014-07-27 11:00:34 -05:00
return h.hook()
}
func (h *stopHook) ProvisionOutput(addr addrs.AbsResourceInstance, typeName string, line string) {
}
func (h *stopHook) PreRefresh(addr addrs.AbsResourceInstance, gen states.Generation, priorState cty.Value) (HookAction, error) {
2014-07-02 18:16:38 -05:00
return h.hook()
}
func (h *stopHook) PostRefresh(addr addrs.AbsResourceInstance, gen states.Generation, priorState cty.Value, newState cty.Value) (HookAction, error) {
2014-07-02 18:16:38 -05:00
return h.hook()
}
func (h *stopHook) PreImportState(addr addrs.AbsResourceInstance, importID string) (HookAction, error) {
return h.hook()
}
func (h *stopHook) PostImportState(addr addrs.AbsResourceInstance, imported []providers.ImportedResource) (HookAction, error) {
return h.hook()
}
func (h *stopHook) PrePlanImport(addr addrs.AbsResourceInstance, importID string) (HookAction, error) {
return h.hook()
}
func (h *stopHook) PostPlanImport(addr addrs.AbsResourceInstance, imported []providers.ImportedResource) (HookAction, error) {
return h.hook()
}
func (h *stopHook) PreApplyImport(addr addrs.AbsResourceInstance, importing plans.ImportingSrc) (HookAction, error) {
return h.hook()
}
func (h *stopHook) PostApplyImport(addr addrs.AbsResourceInstance, importing plans.ImportingSrc) (HookAction, error) {
return h.hook()
}
backend/local: Periodically persist intermediate state snapshots Terraform Core emits a hook event every time it writes a change into the in-memory state. Previously the local backend would just copy that into the transient storage of the state manager, but for most state storage implementations that doesn't really do anything useful because it just makes another copy of the state in memory. We originally added this hook mechanism with the intent of making Terraform _persist_ the state each time, but we backed that out after finding that it was a bit too aggressive and was making the state snapshot history much harder to use in storage systems that can preserve historical snapshots. However, sometimes Terraform gets killed mid-apply for whatever reason and in our previous implementation that meant always losing that transient state, forcing the user to edit the state manually (or use "import") to recover a useful state. In an attempt at finding a sweet spot between these extremes, here we change the rule so that if an apply runs for longer than 20 seconds then we'll try to persist the state to the backend in an update that arrives at least 20 seconds after the first update, and then again for each additional 20 second period as long as Terraform keeps announcing new state snapshots. This also introduces a special interruption mode where if the apply phase gets interrupted by SIGINT (or equivalent) then the local backend will try to persist the state immediately in anticipation of a possibly-imminent SIGKILL, and will then immediately persist any subsequent state update that arrives until the apply phase is complete. After interruption Terraform will not start any new operations and will instead just let any already-running operations run to completion, and so this will persist the state once per resource instance that is able to complete before being killed. This does mean that now long-running applies will generate intermediate state snapshots where they wouldn't before, but there should still be considerably fewer snapshots than were created when we were persisting for each individual state change. We can adjust the 20 second interval in future commits if we find that this spot isn't as sweet as first assumed.
2023-02-13 19:38:24 -06:00
func (h *stopHook) Stopping() {}
func (h *stopHook) PostStateUpdate(new *states.State) (HookAction, error) {
return h.hook()
}
2014-07-02 18:16:38 -05:00
func (h *stopHook) hook() (HookAction, error) {
2014-07-03 13:27:30 -05:00
if h.Stopped() {
return HookActionHalt, errors.New("execution halted")
2014-07-02 18:16:38 -05:00
}
2014-07-03 13:27:30 -05:00
return HookActionContinue, nil
2014-07-02 18:16:38 -05:00
}
// reset should be called within the lock context
2014-07-03 13:27:30 -05:00
func (h *stopHook) Reset() {
atomic.StoreUint32(&h.stop, 0)
2014-07-02 18:16:38 -05:00
}
2014-07-03 13:27:30 -05:00
func (h *stopHook) Stop() {
atomic.StoreUint32(&h.stop, 1)
2014-07-02 18:16:38 -05:00
}
2014-07-03 13:27:30 -05:00
func (h *stopHook) Stopped() bool {
return atomic.LoadUint32(&h.stop) == 1
2014-07-02 18:16:38 -05:00
}