github.com/richardmarshall/terraform@v0.9.5-0.20170429023105-15704cc6ee35/backend/local/backend_test.go (about) 1 package local 2 3 import ( 4 "errors" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "reflect" 9 "strings" 10 "testing" 11 12 "github.com/hashicorp/terraform/backend" 13 "github.com/hashicorp/terraform/state" 14 "github.com/hashicorp/terraform/terraform" 15 ) 16 17 func TestLocal_impl(t *testing.T) { 18 var _ backend.Enhanced = new(Local) 19 var _ backend.Local = new(Local) 20 var _ backend.CLI = new(Local) 21 } 22 23 func TestLocal_backend(t *testing.T) { 24 defer testTmpDir(t)() 25 b := &Local{} 26 backend.TestBackend(t, b, b) 27 } 28 29 func checkState(t *testing.T, path, expected string) { 30 // Read the state 31 f, err := os.Open(path) 32 if err != nil { 33 t.Fatalf("err: %s", err) 34 } 35 36 state, err := terraform.ReadState(f) 37 f.Close() 38 if err != nil { 39 t.Fatalf("err: %s", err) 40 } 41 42 actual := strings.TrimSpace(state.String()) 43 expected = strings.TrimSpace(expected) 44 if actual != expected { 45 t.Fatalf("state does not match! actual:\n%s\n\nexpected:\n%s", actual, expected) 46 } 47 } 48 49 func TestLocal_StatePaths(t *testing.T) { 50 b := &Local{} 51 52 // Test the defaults 53 path, out, back := b.StatePaths("") 54 55 if path != DefaultStateFilename { 56 t.Fatalf("expected %q, got %q", DefaultStateFilename, path) 57 } 58 59 if out != DefaultStateFilename { 60 t.Fatalf("expected %q, got %q", DefaultStateFilename, out) 61 } 62 63 dfltBackup := DefaultStateFilename + DefaultBackupExtension 64 if back != dfltBackup { 65 t.Fatalf("expected %q, got %q", dfltBackup, back) 66 } 67 68 // check with env 69 testEnv := "test_env" 70 path, out, back = b.StatePaths(testEnv) 71 72 expectedPath := filepath.Join(DefaultEnvDir, testEnv, DefaultStateFilename) 73 expectedOut := expectedPath 74 expectedBackup := expectedPath + DefaultBackupExtension 75 76 if path != expectedPath { 77 t.Fatalf("expected %q, got %q", expectedPath, path) 78 } 79 80 if out != expectedOut { 81 t.Fatalf("expected %q, got %q", expectedOut, out) 82 } 83 84 if back != expectedBackup { 85 t.Fatalf("expected %q, got %q", expectedBackup, back) 86 } 87 88 } 89 90 func TestLocal_addAndRemoveStates(t *testing.T) { 91 defer testTmpDir(t)() 92 dflt := backend.DefaultStateName 93 expectedStates := []string{dflt} 94 95 b := &Local{} 96 states, err := b.States() 97 if err != nil { 98 t.Fatal(err) 99 } 100 101 if !reflect.DeepEqual(states, expectedStates) { 102 t.Fatalf("expected []string{%q}, got %q", dflt, states) 103 } 104 105 expectedA := "test_A" 106 if _, err := b.State(expectedA); err != nil { 107 t.Fatal(err) 108 } 109 110 states, err = b.States() 111 if err != nil { 112 t.Fatal(err) 113 } 114 115 expectedStates = append(expectedStates, expectedA) 116 if !reflect.DeepEqual(states, expectedStates) { 117 t.Fatalf("expected %q, got %q", expectedStates, states) 118 } 119 120 expectedB := "test_B" 121 if _, err := b.State(expectedB); err != nil { 122 t.Fatal(err) 123 } 124 125 states, err = b.States() 126 if err != nil { 127 t.Fatal(err) 128 } 129 130 expectedStates = append(expectedStates, expectedB) 131 if !reflect.DeepEqual(states, expectedStates) { 132 t.Fatalf("expected %q, got %q", expectedStates, states) 133 } 134 135 if err := b.DeleteState(expectedA); err != nil { 136 t.Fatal(err) 137 } 138 139 states, err = b.States() 140 if err != nil { 141 t.Fatal(err) 142 } 143 144 expectedStates = []string{dflt, expectedB} 145 if !reflect.DeepEqual(states, expectedStates) { 146 t.Fatalf("expected %q, got %q", expectedStates, states) 147 } 148 149 if err := b.DeleteState(expectedB); err != nil { 150 t.Fatal(err) 151 } 152 153 states, err = b.States() 154 if err != nil { 155 t.Fatal(err) 156 } 157 158 expectedStates = []string{dflt} 159 if !reflect.DeepEqual(states, expectedStates) { 160 t.Fatalf("expected %q, got %q", expectedStates, states) 161 } 162 163 if err := b.DeleteState(dflt); err == nil { 164 t.Fatal("expected error deleting default state") 165 } 166 } 167 168 // a local backend which returns sentinel errors for NamedState methods to 169 // verify it's being called. 170 type testDelegateBackend struct { 171 *Local 172 173 // return a sentinel error on these calls 174 stateErr bool 175 statesErr bool 176 deleteErr bool 177 } 178 179 var errTestDelegateState = errors.New("State called") 180 var errTestDelegateStates = errors.New("States called") 181 var errTestDelegateDeleteState = errors.New("Delete called") 182 183 func (b *testDelegateBackend) State(name string) (state.State, error) { 184 if b.stateErr { 185 return nil, errTestDelegateState 186 } 187 s := &state.LocalState{ 188 Path: "terraform.tfstate", 189 PathOut: "terraform.tfstate", 190 } 191 return s, nil 192 } 193 194 func (b *testDelegateBackend) States() ([]string, error) { 195 if b.statesErr { 196 return nil, errTestDelegateStates 197 } 198 return []string{"default"}, nil 199 } 200 201 func (b *testDelegateBackend) DeleteState(name string) error { 202 if b.deleteErr { 203 return errTestDelegateDeleteState 204 } 205 return nil 206 } 207 208 // verify that the MultiState methods are dispatched to the correct Backend. 209 func TestLocal_multiStateBackend(t *testing.T) { 210 // assign a separate backend where we can read the state 211 b := &Local{ 212 Backend: &testDelegateBackend{ 213 stateErr: true, 214 statesErr: true, 215 deleteErr: true, 216 }, 217 } 218 219 if _, err := b.State("test"); err != errTestDelegateState { 220 t.Fatal("expected errTestDelegateState, got:", err) 221 } 222 223 if _, err := b.States(); err != errTestDelegateStates { 224 t.Fatal("expected errTestDelegateStates, got:", err) 225 } 226 227 if err := b.DeleteState("test"); err != errTestDelegateDeleteState { 228 t.Fatal("expected errTestDelegateDeleteState, got:", err) 229 } 230 } 231 232 // verify that a remote state backend is always wrapped in a BackupState 233 func TestLocal_remoteStateBackup(t *testing.T) { 234 // assign a separate backend to mock a remote state backend 235 b := &Local{ 236 Backend: &testDelegateBackend{}, 237 } 238 239 s, err := b.State("default") 240 if err != nil { 241 t.Fatal(err) 242 } 243 244 bs, ok := s.(*state.BackupState) 245 if !ok { 246 t.Fatal("remote state is not backed up") 247 } 248 249 if bs.Path != DefaultStateFilename+DefaultBackupExtension { 250 t.Fatal("bad backup location:", bs.Path) 251 } 252 253 // do the same with a named state, which should use the local env directories 254 s, err = b.State("test") 255 if err != nil { 256 t.Fatal(err) 257 } 258 259 bs, ok = s.(*state.BackupState) 260 if !ok { 261 t.Fatal("remote state is not backed up") 262 } 263 264 if bs.Path != filepath.Join(DefaultEnvDir, "test", DefaultStateFilename+DefaultBackupExtension) { 265 t.Fatal("bad backup location:", bs.Path) 266 } 267 } 268 269 // change into a tmp dir and return a deferable func to change back and cleanup 270 func testTmpDir(t *testing.T) func() { 271 tmp, err := ioutil.TempDir("", "tf") 272 if err != nil { 273 t.Fatal(err) 274 } 275 276 old, err := os.Getwd() 277 if err != nil { 278 t.Fatal(err) 279 } 280 281 if err := os.Chdir(tmp); err != nil { 282 t.Fatal(err) 283 } 284 285 return func() { 286 // ignore errors and try to clean up 287 os.Chdir(old) 288 os.RemoveAll(tmp) 289 } 290 }