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 }