opentofu/internal/backend/local/backend_test.go
Christian Mesh 2f5dcd5c0a
Integrate Encryption into State Backends (#1288)
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
2024-03-04 09:25:14 -05:00

253 lines
5.9 KiB
Go

// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package local
import (
"errors"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/opentofu/opentofu/internal/backend"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/states/statefile"
"github.com/opentofu/opentofu/internal/states/statemgr"
)
func TestLocal_impl(t *testing.T) {
var _ backend.Enhanced = New(encryption.StateEncryptionDisabled())
var _ backend.Local = New(encryption.StateEncryptionDisabled())
var _ backend.CLI = New(encryption.StateEncryptionDisabled())
}
func TestLocal_backend(t *testing.T) {
testTmpDir(t)
b := New(encryption.StateEncryptionDisabled())
backend.TestBackendStates(t, b)
backend.TestBackendStateLocks(t, b, b)
}
func checkState(t *testing.T, path, expected string) {
t.Helper()
// Read the state
f, err := os.Open(path)
if err != nil {
t.Fatalf("err: %s", err)
}
state, err := statefile.Read(f, encryption.StateEncryptionDisabled())
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
actual := state.State.String()
expected = strings.TrimSpace(expected)
if actual != expected {
t.Fatalf("state does not match! actual:\n%s\n\nexpected:\n%s", actual, expected)
}
}
func TestLocal_StatePaths(t *testing.T) {
b := New(encryption.StateEncryptionDisabled())
// Test the defaults
path, out, back := b.StatePaths("")
if path != DefaultStateFilename {
t.Fatalf("expected %q, got %q", DefaultStateFilename, path)
}
if out != DefaultStateFilename {
t.Fatalf("expected %q, got %q", DefaultStateFilename, out)
}
dfltBackup := DefaultStateFilename + DefaultBackupExtension
if back != dfltBackup {
t.Fatalf("expected %q, got %q", dfltBackup, back)
}
// check with env
testEnv := "test_env"
path, out, back = b.StatePaths(testEnv)
expectedPath := filepath.Join(DefaultWorkspaceDir, testEnv, DefaultStateFilename)
expectedOut := expectedPath
expectedBackup := expectedPath + DefaultBackupExtension
if path != expectedPath {
t.Fatalf("expected %q, got %q", expectedPath, path)
}
if out != expectedOut {
t.Fatalf("expected %q, got %q", expectedOut, out)
}
if back != expectedBackup {
t.Fatalf("expected %q, got %q", expectedBackup, back)
}
}
func TestLocal_addAndRemoveStates(t *testing.T) {
testTmpDir(t)
dflt := backend.DefaultStateName
expectedStates := []string{dflt}
b := New(encryption.StateEncryptionDisabled())
states, err := b.Workspaces()
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(states, expectedStates) {
t.Fatalf("expected []string{%q}, got %q", dflt, states)
}
expectedA := "test_A"
if _, err := b.StateMgr(expectedA); err != nil {
t.Fatal(err)
}
states, err = b.Workspaces()
if err != nil {
t.Fatal(err)
}
expectedStates = append(expectedStates, expectedA)
if !reflect.DeepEqual(states, expectedStates) {
t.Fatalf("expected %q, got %q", expectedStates, states)
}
expectedB := "test_B"
if _, err := b.StateMgr(expectedB); err != nil {
t.Fatal(err)
}
states, err = b.Workspaces()
if err != nil {
t.Fatal(err)
}
expectedStates = append(expectedStates, expectedB)
if !reflect.DeepEqual(states, expectedStates) {
t.Fatalf("expected %q, got %q", expectedStates, states)
}
if err := b.DeleteWorkspace(expectedA, true); err != nil {
t.Fatal(err)
}
states, err = b.Workspaces()
if err != nil {
t.Fatal(err)
}
expectedStates = []string{dflt, expectedB}
if !reflect.DeepEqual(states, expectedStates) {
t.Fatalf("expected %q, got %q", expectedStates, states)
}
if err := b.DeleteWorkspace(expectedB, true); err != nil {
t.Fatal(err)
}
states, err = b.Workspaces()
if err != nil {
t.Fatal(err)
}
expectedStates = []string{dflt}
if !reflect.DeepEqual(states, expectedStates) {
t.Fatalf("expected %q, got %q", expectedStates, states)
}
if err := b.DeleteWorkspace(dflt, true); err == nil {
t.Fatal("expected error deleting default state")
}
}
// a local backend which returns sentinel errors for NamedState methods to
// verify it's being called.
type testDelegateBackend struct {
*Local
// return a sentinel error on these calls
stateErr bool
statesErr bool
deleteErr bool
}
var errTestDelegateState = errors.New("state called")
var errTestDelegateStates = errors.New("states called")
var errTestDelegateDeleteState = errors.New("delete called")
func (b *testDelegateBackend) StateMgr(name string) (statemgr.Full, error) {
if b.stateErr {
return nil, errTestDelegateState
}
s := statemgr.NewFilesystem("terraform.tfstate", encryption.StateEncryptionDisabled())
return s, nil
}
func (b *testDelegateBackend) Workspaces() ([]string, error) {
if b.statesErr {
return nil, errTestDelegateStates
}
return []string{"default"}, nil
}
func (b *testDelegateBackend) DeleteWorkspace(name string, force bool) error {
if b.deleteErr {
return errTestDelegateDeleteState
}
return nil
}
// verify that the MultiState methods are dispatched to the correct Backend.
func TestLocal_multiStateBackend(t *testing.T) {
// assign a separate backend where we can read the state
b := NewWithBackend(&testDelegateBackend{
stateErr: true,
statesErr: true,
deleteErr: true,
}, nil)
if _, err := b.StateMgr("test"); err != errTestDelegateState {
t.Fatal("expected errTestDelegateState, got:", err)
}
if _, err := b.Workspaces(); err != errTestDelegateStates {
t.Fatal("expected errTestDelegateStates, got:", err)
}
if err := b.DeleteWorkspace("test", true); err != errTestDelegateDeleteState {
t.Fatal("expected errTestDelegateDeleteState, got:", err)
}
}
// testTmpDir changes into a tmp dir and change back automatically when the test
// and all its subtests complete.
func testTmpDir(t *testing.T) {
tmp := t.TempDir()
old, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
if err := os.Chdir(tmp); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
// ignore errors and try to clean up
os.Chdir(old)
})
}