github.com/opentofu/opentofu@v1.7.1/internal/backend/local/backend_test.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package local
     7  
     8  import (
     9  	"errors"
    10  	"os"
    11  	"path/filepath"
    12  	"reflect"
    13  	"strings"
    14  	"testing"
    15  
    16  	"github.com/opentofu/opentofu/internal/backend"
    17  	"github.com/opentofu/opentofu/internal/encryption"
    18  	"github.com/opentofu/opentofu/internal/states/statefile"
    19  	"github.com/opentofu/opentofu/internal/states/statemgr"
    20  )
    21  
    22  func TestLocal_impl(t *testing.T) {
    23  	var _ backend.Enhanced = New(encryption.StateEncryptionDisabled())
    24  	var _ backend.Local = New(encryption.StateEncryptionDisabled())
    25  	var _ backend.CLI = New(encryption.StateEncryptionDisabled())
    26  }
    27  
    28  func TestLocal_backend(t *testing.T) {
    29  	testTmpDir(t)
    30  	b := New(encryption.StateEncryptionDisabled())
    31  	backend.TestBackendStates(t, b)
    32  	backend.TestBackendStateLocks(t, b, b)
    33  }
    34  
    35  func checkState(t *testing.T, path, expected string) {
    36  	t.Helper()
    37  	// Read the state
    38  	f, err := os.Open(path)
    39  	if err != nil {
    40  		t.Fatalf("err: %s", err)
    41  	}
    42  
    43  	state, err := statefile.Read(f, encryption.StateEncryptionDisabled())
    44  	f.Close()
    45  	if err != nil {
    46  		t.Fatalf("err: %s", err)
    47  	}
    48  
    49  	actual := state.State.String()
    50  	expected = strings.TrimSpace(expected)
    51  	if actual != expected {
    52  		t.Fatalf("state does not match! actual:\n%s\n\nexpected:\n%s", actual, expected)
    53  	}
    54  }
    55  
    56  func TestLocal_StatePaths(t *testing.T) {
    57  	b := New(encryption.StateEncryptionDisabled())
    58  
    59  	// Test the defaults
    60  	path, out, back := b.StatePaths("")
    61  
    62  	if path != DefaultStateFilename {
    63  		t.Fatalf("expected %q, got %q", DefaultStateFilename, path)
    64  	}
    65  
    66  	if out != DefaultStateFilename {
    67  		t.Fatalf("expected %q, got %q", DefaultStateFilename, out)
    68  	}
    69  
    70  	dfltBackup := DefaultStateFilename + DefaultBackupExtension
    71  	if back != dfltBackup {
    72  		t.Fatalf("expected %q, got %q", dfltBackup, back)
    73  	}
    74  
    75  	// check with env
    76  	testEnv := "test_env"
    77  	path, out, back = b.StatePaths(testEnv)
    78  
    79  	expectedPath := filepath.Join(DefaultWorkspaceDir, testEnv, DefaultStateFilename)
    80  	expectedOut := expectedPath
    81  	expectedBackup := expectedPath + DefaultBackupExtension
    82  
    83  	if path != expectedPath {
    84  		t.Fatalf("expected %q, got %q", expectedPath, path)
    85  	}
    86  
    87  	if out != expectedOut {
    88  		t.Fatalf("expected %q, got %q", expectedOut, out)
    89  	}
    90  
    91  	if back != expectedBackup {
    92  		t.Fatalf("expected %q, got %q", expectedBackup, back)
    93  	}
    94  
    95  }
    96  
    97  func TestLocal_addAndRemoveStates(t *testing.T) {
    98  	testTmpDir(t)
    99  	dflt := backend.DefaultStateName
   100  	expectedStates := []string{dflt}
   101  
   102  	b := New(encryption.StateEncryptionDisabled())
   103  	states, err := b.Workspaces()
   104  	if err != nil {
   105  		t.Fatal(err)
   106  	}
   107  
   108  	if !reflect.DeepEqual(states, expectedStates) {
   109  		t.Fatalf("expected []string{%q}, got %q", dflt, states)
   110  	}
   111  
   112  	expectedA := "test_A"
   113  	if _, err := b.StateMgr(expectedA); err != nil {
   114  		t.Fatal(err)
   115  	}
   116  
   117  	states, err = b.Workspaces()
   118  	if err != nil {
   119  		t.Fatal(err)
   120  	}
   121  
   122  	expectedStates = append(expectedStates, expectedA)
   123  	if !reflect.DeepEqual(states, expectedStates) {
   124  		t.Fatalf("expected %q, got %q", expectedStates, states)
   125  	}
   126  
   127  	expectedB := "test_B"
   128  	if _, err := b.StateMgr(expectedB); err != nil {
   129  		t.Fatal(err)
   130  	}
   131  
   132  	states, err = b.Workspaces()
   133  	if err != nil {
   134  		t.Fatal(err)
   135  	}
   136  
   137  	expectedStates = append(expectedStates, expectedB)
   138  	if !reflect.DeepEqual(states, expectedStates) {
   139  		t.Fatalf("expected %q, got %q", expectedStates, states)
   140  	}
   141  
   142  	if err := b.DeleteWorkspace(expectedA, true); err != nil {
   143  		t.Fatal(err)
   144  	}
   145  
   146  	states, err = b.Workspaces()
   147  	if err != nil {
   148  		t.Fatal(err)
   149  	}
   150  
   151  	expectedStates = []string{dflt, expectedB}
   152  	if !reflect.DeepEqual(states, expectedStates) {
   153  		t.Fatalf("expected %q, got %q", expectedStates, states)
   154  	}
   155  
   156  	if err := b.DeleteWorkspace(expectedB, true); err != nil {
   157  		t.Fatal(err)
   158  	}
   159  
   160  	states, err = b.Workspaces()
   161  	if err != nil {
   162  		t.Fatal(err)
   163  	}
   164  
   165  	expectedStates = []string{dflt}
   166  	if !reflect.DeepEqual(states, expectedStates) {
   167  		t.Fatalf("expected %q, got %q", expectedStates, states)
   168  	}
   169  
   170  	if err := b.DeleteWorkspace(dflt, true); err == nil {
   171  		t.Fatal("expected error deleting default state")
   172  	}
   173  }
   174  
   175  // a local backend which returns sentinel errors for NamedState methods to
   176  // verify it's being called.
   177  type testDelegateBackend struct {
   178  	*Local
   179  
   180  	// return a sentinel error on these calls
   181  	stateErr  bool
   182  	statesErr bool
   183  	deleteErr bool
   184  }
   185  
   186  var errTestDelegateState = errors.New("state called")
   187  var errTestDelegateStates = errors.New("states called")
   188  var errTestDelegateDeleteState = errors.New("delete called")
   189  
   190  func (b *testDelegateBackend) StateMgr(name string) (statemgr.Full, error) {
   191  	if b.stateErr {
   192  		return nil, errTestDelegateState
   193  	}
   194  	s := statemgr.NewFilesystem("terraform.tfstate", encryption.StateEncryptionDisabled())
   195  	return s, nil
   196  }
   197  
   198  func (b *testDelegateBackend) Workspaces() ([]string, error) {
   199  	if b.statesErr {
   200  		return nil, errTestDelegateStates
   201  	}
   202  	return []string{"default"}, nil
   203  }
   204  
   205  func (b *testDelegateBackend) DeleteWorkspace(name string, force bool) error {
   206  	if b.deleteErr {
   207  		return errTestDelegateDeleteState
   208  	}
   209  	return nil
   210  }
   211  
   212  // verify that the MultiState methods are dispatched to the correct Backend.
   213  func TestLocal_multiStateBackend(t *testing.T) {
   214  	// assign a separate backend where we can read the state
   215  	b := NewWithBackend(&testDelegateBackend{
   216  		stateErr:  true,
   217  		statesErr: true,
   218  		deleteErr: true,
   219  	}, nil)
   220  
   221  	if _, err := b.StateMgr("test"); err != errTestDelegateState {
   222  		t.Fatal("expected errTestDelegateState, got:", err)
   223  	}
   224  
   225  	if _, err := b.Workspaces(); err != errTestDelegateStates {
   226  		t.Fatal("expected errTestDelegateStates, got:", err)
   227  	}
   228  
   229  	if err := b.DeleteWorkspace("test", true); err != errTestDelegateDeleteState {
   230  		t.Fatal("expected errTestDelegateDeleteState, got:", err)
   231  	}
   232  }
   233  
   234  // testTmpDir changes into a tmp dir and change back automatically when the test
   235  // and all its subtests complete.
   236  func testTmpDir(t *testing.T) {
   237  	tmp := t.TempDir()
   238  
   239  	old, err := os.Getwd()
   240  	if err != nil {
   241  		t.Fatal(err)
   242  	}
   243  
   244  	if err := os.Chdir(tmp); err != nil {
   245  		t.Fatal(err)
   246  	}
   247  
   248  	t.Cleanup(func() {
   249  		// ignore errors and try to clean up
   250  		os.Chdir(old)
   251  	})
   252  }