mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-18 04:32:59 -06:00
d0993b0e80
Cleanup some more test fixtures to use t.TempDir Use EvalSymlinks with temp dir paths to help with MacOS errors from various terraform components.
231 lines
6.5 KiB
Go
231 lines
6.5 KiB
Go
package local
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/terraform/internal/backend"
|
|
"github.com/hashicorp/terraform/internal/command/arguments"
|
|
"github.com/hashicorp/terraform/internal/command/clistate"
|
|
"github.com/hashicorp/terraform/internal/command/views"
|
|
"github.com/hashicorp/terraform/internal/configs/configload"
|
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
|
"github.com/hashicorp/terraform/internal/initwd"
|
|
"github.com/hashicorp/terraform/internal/plans"
|
|
"github.com/hashicorp/terraform/internal/plans/planfile"
|
|
"github.com/hashicorp/terraform/internal/states"
|
|
"github.com/hashicorp/terraform/internal/states/statefile"
|
|
"github.com/hashicorp/terraform/internal/states/statemgr"
|
|
"github.com/hashicorp/terraform/internal/terminal"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
func TestLocalRun(t *testing.T) {
|
|
configDir := "./testdata/empty"
|
|
b, cleanup := TestLocal(t)
|
|
defer cleanup()
|
|
|
|
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
|
defer configCleanup()
|
|
|
|
streams, _ := terminal.StreamsForTesting(t)
|
|
view := views.NewView(streams)
|
|
stateLocker := clistate.NewLocker(0, views.NewStateLocker(arguments.ViewHuman, view))
|
|
|
|
op := &backend.Operation{
|
|
ConfigDir: configDir,
|
|
ConfigLoader: configLoader,
|
|
Workspace: backend.DefaultStateName,
|
|
StateLocker: stateLocker,
|
|
}
|
|
|
|
_, _, diags := b.LocalRun(op)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected error: %s", diags.Err().Error())
|
|
}
|
|
|
|
// LocalRun() retains a lock on success
|
|
assertBackendStateLocked(t, b)
|
|
}
|
|
|
|
func TestLocalRun_error(t *testing.T) {
|
|
configDir := "./testdata/invalid"
|
|
b, cleanup := TestLocal(t)
|
|
defer cleanup()
|
|
|
|
// This backend will return an error when asked to RefreshState, which
|
|
// should then cause LocalRun to return with the state unlocked.
|
|
b.Backend = backendWithStateStorageThatFailsRefresh{}
|
|
|
|
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
|
defer configCleanup()
|
|
|
|
streams, _ := terminal.StreamsForTesting(t)
|
|
view := views.NewView(streams)
|
|
stateLocker := clistate.NewLocker(0, views.NewStateLocker(arguments.ViewHuman, view))
|
|
|
|
op := &backend.Operation{
|
|
ConfigDir: configDir,
|
|
ConfigLoader: configLoader,
|
|
Workspace: backend.DefaultStateName,
|
|
StateLocker: stateLocker,
|
|
}
|
|
|
|
_, _, diags := b.LocalRun(op)
|
|
if !diags.HasErrors() {
|
|
t.Fatal("unexpected success")
|
|
}
|
|
|
|
// LocalRun() unlocks the state on failure
|
|
assertBackendStateUnlocked(t, b)
|
|
}
|
|
|
|
func TestLocalRun_stalePlan(t *testing.T) {
|
|
configDir := "./testdata/apply"
|
|
b, cleanup := TestLocal(t)
|
|
defer cleanup()
|
|
|
|
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
|
defer configCleanup()
|
|
|
|
// Write an empty state file with serial 3
|
|
sf, err := os.Create(b.StatePath)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error creating state file %s: %s", b.StatePath, err)
|
|
}
|
|
if err := statefile.Write(statefile.New(states.NewState(), "boop", 3), sf); err != nil {
|
|
t.Fatalf("unexpected error writing state file: %s", err)
|
|
}
|
|
|
|
// Refresh the state
|
|
sm, err := b.StateMgr("")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
if err := sm.RefreshState(); err != nil {
|
|
t.Fatalf("unexpected error refreshing state: %s", err)
|
|
}
|
|
|
|
// Create a minimal plan which also has state file serial 2, so is stale
|
|
backendConfig := cty.ObjectVal(map[string]cty.Value{
|
|
"path": cty.NullVal(cty.String),
|
|
"workspace_dir": cty.NullVal(cty.String),
|
|
})
|
|
backendConfigRaw, err := plans.NewDynamicValue(backendConfig, backendConfig.Type())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
plan := &plans.Plan{
|
|
UIMode: plans.NormalMode,
|
|
Changes: plans.NewChanges(),
|
|
Backend: plans.Backend{
|
|
Type: "local",
|
|
Config: backendConfigRaw,
|
|
},
|
|
PrevRunState: states.NewState(),
|
|
PriorState: states.NewState(),
|
|
}
|
|
prevStateFile := statefile.New(plan.PrevRunState, "boop", 1)
|
|
stateFile := statefile.New(plan.PriorState, "boop", 2)
|
|
|
|
// Roundtrip through serialization as expected by the operation
|
|
outDir := t.TempDir()
|
|
defer os.RemoveAll(outDir)
|
|
planPath := filepath.Join(outDir, "plan.tfplan")
|
|
if err := planfile.Create(planPath, configload.NewEmptySnapshot(), prevStateFile, stateFile, plan); err != nil {
|
|
t.Fatalf("unexpected error writing planfile: %s", err)
|
|
}
|
|
planFile, err := planfile.Open(planPath)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error reading planfile: %s", err)
|
|
}
|
|
|
|
streams, _ := terminal.StreamsForTesting(t)
|
|
view := views.NewView(streams)
|
|
stateLocker := clistate.NewLocker(0, views.NewStateLocker(arguments.ViewHuman, view))
|
|
|
|
op := &backend.Operation{
|
|
ConfigDir: configDir,
|
|
ConfigLoader: configLoader,
|
|
PlanFile: planFile,
|
|
Workspace: backend.DefaultStateName,
|
|
StateLocker: stateLocker,
|
|
}
|
|
|
|
_, _, diags := b.LocalRun(op)
|
|
if !diags.HasErrors() {
|
|
t.Fatal("unexpected success")
|
|
}
|
|
|
|
// LocalRun() unlocks the state on failure
|
|
assertBackendStateUnlocked(t, b)
|
|
}
|
|
|
|
type backendWithStateStorageThatFailsRefresh struct {
|
|
}
|
|
|
|
var _ backend.Backend = backendWithStateStorageThatFailsRefresh{}
|
|
|
|
func (b backendWithStateStorageThatFailsRefresh) StateMgr(workspace string) (statemgr.Full, error) {
|
|
return &stateStorageThatFailsRefresh{}, nil
|
|
}
|
|
|
|
func (b backendWithStateStorageThatFailsRefresh) ConfigSchema() *configschema.Block {
|
|
return &configschema.Block{}
|
|
}
|
|
|
|
func (b backendWithStateStorageThatFailsRefresh) PrepareConfig(in cty.Value) (cty.Value, tfdiags.Diagnostics) {
|
|
return in, nil
|
|
}
|
|
|
|
func (b backendWithStateStorageThatFailsRefresh) Configure(cty.Value) tfdiags.Diagnostics {
|
|
return nil
|
|
}
|
|
|
|
func (b backendWithStateStorageThatFailsRefresh) DeleteWorkspace(name string) error {
|
|
return fmt.Errorf("unimplemented")
|
|
}
|
|
|
|
func (b backendWithStateStorageThatFailsRefresh) Workspaces() ([]string, error) {
|
|
return []string{"default"}, nil
|
|
}
|
|
|
|
type stateStorageThatFailsRefresh struct {
|
|
locked bool
|
|
}
|
|
|
|
func (s *stateStorageThatFailsRefresh) Lock(info *statemgr.LockInfo) (string, error) {
|
|
if s.locked {
|
|
return "", fmt.Errorf("already locked")
|
|
}
|
|
s.locked = true
|
|
return "locked", nil
|
|
}
|
|
|
|
func (s *stateStorageThatFailsRefresh) Unlock(id string) error {
|
|
if !s.locked {
|
|
return fmt.Errorf("not locked")
|
|
}
|
|
s.locked = false
|
|
return nil
|
|
}
|
|
|
|
func (s *stateStorageThatFailsRefresh) State() *states.State {
|
|
return nil
|
|
}
|
|
|
|
func (s *stateStorageThatFailsRefresh) WriteState(*states.State) error {
|
|
return fmt.Errorf("unimplemented")
|
|
}
|
|
|
|
func (s *stateStorageThatFailsRefresh) RefreshState() error {
|
|
return fmt.Errorf("intentionally failing for testing purposes")
|
|
}
|
|
|
|
func (s *stateStorageThatFailsRefresh) PersistState() error {
|
|
return fmt.Errorf("unimplemented")
|
|
}
|