mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-19 13:12:58 -06:00
f0de9b60c1
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.
139 lines
4.6 KiB
Go
139 lines
4.6 KiB
Go
package terraform
|
|
|
|
import (
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/plans"
|
|
"github.com/hashicorp/terraform/internal/providers"
|
|
"github.com/hashicorp/terraform/internal/states"
|
|
)
|
|
|
|
func TestNilHook_impl(t *testing.T) {
|
|
var _ Hook = new(NilHook)
|
|
}
|
|
|
|
// testHook is a Hook implementation that logs the calls it receives.
|
|
// It is intended for testing that core code is emitting the correct hooks
|
|
// for a given situation.
|
|
type testHook struct {
|
|
mu sync.Mutex
|
|
Calls []*testHookCall
|
|
}
|
|
|
|
var _ Hook = (*testHook)(nil)
|
|
|
|
// testHookCall represents a single call in testHook.
|
|
// This hook just logs string names to make it easy to write "want" expressions
|
|
// in tests that can DeepEqual against the real calls.
|
|
type testHookCall struct {
|
|
Action string
|
|
InstanceID string
|
|
}
|
|
|
|
func (h *testHook) PreApply(addr addrs.AbsResourceInstance, gen states.Generation, action plans.Action, priorState, plannedNewState cty.Value) (HookAction, error) {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
h.Calls = append(h.Calls, &testHookCall{"PreApply", addr.String()})
|
|
return HookActionContinue, nil
|
|
}
|
|
|
|
func (h *testHook) PostApply(addr addrs.AbsResourceInstance, gen states.Generation, newState cty.Value, err error) (HookAction, error) {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
h.Calls = append(h.Calls, &testHookCall{"PostApply", addr.String()})
|
|
return HookActionContinue, nil
|
|
}
|
|
|
|
func (h *testHook) PreDiff(addr addrs.AbsResourceInstance, gen states.Generation, priorState, proposedNewState cty.Value) (HookAction, error) {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
h.Calls = append(h.Calls, &testHookCall{"PreDiff", addr.String()})
|
|
return HookActionContinue, nil
|
|
}
|
|
|
|
func (h *testHook) PostDiff(addr addrs.AbsResourceInstance, gen states.Generation, action plans.Action, priorState, plannedNewState cty.Value) (HookAction, error) {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
h.Calls = append(h.Calls, &testHookCall{"PostDiff", addr.String()})
|
|
return HookActionContinue, nil
|
|
}
|
|
|
|
func (h *testHook) PreProvisionInstance(addr addrs.AbsResourceInstance, state cty.Value) (HookAction, error) {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
h.Calls = append(h.Calls, &testHookCall{"PreProvisionInstance", addr.String()})
|
|
return HookActionContinue, nil
|
|
}
|
|
|
|
func (h *testHook) PostProvisionInstance(addr addrs.AbsResourceInstance, state cty.Value) (HookAction, error) {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
h.Calls = append(h.Calls, &testHookCall{"PostProvisionInstance", addr.String()})
|
|
return HookActionContinue, nil
|
|
}
|
|
|
|
func (h *testHook) PreProvisionInstanceStep(addr addrs.AbsResourceInstance, typeName string) (HookAction, error) {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
h.Calls = append(h.Calls, &testHookCall{"PreProvisionInstanceStep", addr.String()})
|
|
return HookActionContinue, nil
|
|
}
|
|
|
|
func (h *testHook) PostProvisionInstanceStep(addr addrs.AbsResourceInstance, typeName string, err error) (HookAction, error) {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
h.Calls = append(h.Calls, &testHookCall{"PostProvisionInstanceStep", addr.String()})
|
|
return HookActionContinue, nil
|
|
}
|
|
|
|
func (h *testHook) ProvisionOutput(addr addrs.AbsResourceInstance, typeName string, line string) {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
h.Calls = append(h.Calls, &testHookCall{"ProvisionOutput", addr.String()})
|
|
}
|
|
|
|
func (h *testHook) PreRefresh(addr addrs.AbsResourceInstance, gen states.Generation, priorState cty.Value) (HookAction, error) {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
h.Calls = append(h.Calls, &testHookCall{"PreRefresh", addr.String()})
|
|
return HookActionContinue, nil
|
|
}
|
|
|
|
func (h *testHook) PostRefresh(addr addrs.AbsResourceInstance, gen states.Generation, priorState cty.Value, newState cty.Value) (HookAction, error) {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
h.Calls = append(h.Calls, &testHookCall{"PostRefresh", addr.String()})
|
|
return HookActionContinue, nil
|
|
}
|
|
|
|
func (h *testHook) PreImportState(addr addrs.AbsResourceInstance, importID string) (HookAction, error) {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
h.Calls = append(h.Calls, &testHookCall{"PreImportState", addr.String()})
|
|
return HookActionContinue, nil
|
|
}
|
|
|
|
func (h *testHook) PostImportState(addr addrs.AbsResourceInstance, imported []providers.ImportedResource) (HookAction, error) {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
h.Calls = append(h.Calls, &testHookCall{"PostImportState", addr.String()})
|
|
return HookActionContinue, nil
|
|
}
|
|
|
|
func (h *testHook) Stopping() {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
h.Calls = append(h.Calls, &testHookCall{"Stopping", ""})
|
|
}
|
|
|
|
func (h *testHook) PostStateUpdate(new *states.State) (HookAction, error) {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
h.Calls = append(h.Calls, &testHookCall{"PostStateUpdate", ""})
|
|
return HookActionContinue, nil
|
|
}
|