github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/states/statemgr/filesystem_test.go (about) 1 package statemgr 2 3 import ( 4 "io/ioutil" 5 "os" 6 "os/exec" 7 "path/filepath" 8 "strings" 9 "sync" 10 "testing" 11 12 "github.com/go-test/deep" 13 version "github.com/hashicorp/go-version" 14 "github.com/zclconf/go-cty/cty" 15 16 "github.com/hashicorp/terraform/internal/addrs" 17 "github.com/hashicorp/terraform/internal/states" 18 "github.com/hashicorp/terraform/internal/states/statefile" 19 tfversion "github.com/hashicorp/terraform/version" 20 ) 21 22 func TestFilesystem(t *testing.T) { 23 defer testOverrideVersion(t, "1.2.3")() 24 ls := testFilesystem(t) 25 defer os.Remove(ls.readPath) 26 TestFull(t, ls) 27 } 28 29 func TestFilesystemRace(t *testing.T) { 30 defer testOverrideVersion(t, "1.2.3")() 31 ls := testFilesystem(t) 32 defer os.Remove(ls.readPath) 33 34 current := TestFullInitialState() 35 36 var wg sync.WaitGroup 37 for i := 0; i < 100; i++ { 38 wg.Add(1) 39 go func() { 40 defer wg.Done() 41 ls.WriteState(current) 42 }() 43 } 44 wg.Wait() 45 } 46 47 func TestFilesystemLocks(t *testing.T) { 48 defer testOverrideVersion(t, "1.2.3")() 49 s := testFilesystem(t) 50 defer os.Remove(s.readPath) 51 52 // lock first 53 info := NewLockInfo() 54 info.Operation = "test" 55 lockID, err := s.Lock(info) 56 if err != nil { 57 t.Fatal(err) 58 } 59 60 out, err := exec.Command("go", "run", "testdata/lockstate.go", s.path).CombinedOutput() 61 if err != nil { 62 t.Fatal("unexpected lock failure", err, string(out)) 63 } 64 65 if !strings.Contains(string(out), "lock failed") { 66 t.Fatal("expected 'locked failed', got", string(out)) 67 } 68 69 // check our lock info 70 lockInfo, err := s.lockInfo() 71 if err != nil { 72 t.Fatal(err) 73 } 74 75 if lockInfo.Operation != "test" { 76 t.Fatalf("invalid lock info %#v\n", lockInfo) 77 } 78 79 // a noop, since we unlock on exit 80 if err := s.Unlock(lockID); err != nil { 81 t.Fatal(err) 82 } 83 84 // local locks can re-lock 85 lockID, err = s.Lock(info) 86 if err != nil { 87 t.Fatal(err) 88 } 89 90 if err := s.Unlock(lockID); err != nil { 91 t.Fatal(err) 92 } 93 94 // we should not be able to unlock the same lock twice 95 if err := s.Unlock(lockID); err == nil { 96 t.Fatal("unlocking an unlocked state should fail") 97 } 98 99 // make sure lock info is gone 100 lockInfoPath := s.lockInfoPath() 101 if _, err := os.Stat(lockInfoPath); !os.IsNotExist(err) { 102 t.Fatal("lock info not removed") 103 } 104 } 105 106 // Verify that we can write to the state file, as Windows' mandatory locking 107 // will prevent writing to a handle different than the one that hold the lock. 108 func TestFilesystem_writeWhileLocked(t *testing.T) { 109 defer testOverrideVersion(t, "1.2.3")() 110 s := testFilesystem(t) 111 defer os.Remove(s.readPath) 112 113 // lock first 114 info := NewLockInfo() 115 info.Operation = "test" 116 lockID, err := s.Lock(info) 117 if err != nil { 118 t.Fatal(err) 119 } 120 defer func() { 121 if err := s.Unlock(lockID); err != nil { 122 t.Fatal(err) 123 } 124 }() 125 126 if err := s.WriteState(TestFullInitialState()); err != nil { 127 t.Fatal(err) 128 } 129 } 130 131 func TestFilesystem_pathOut(t *testing.T) { 132 defer testOverrideVersion(t, "1.2.3")() 133 f, err := ioutil.TempFile("", "tf") 134 if err != nil { 135 t.Fatalf("err: %s", err) 136 } 137 f.Close() 138 defer os.Remove(f.Name()) 139 140 ls := testFilesystem(t) 141 ls.path = f.Name() 142 defer os.Remove(ls.path) 143 144 TestFull(t, ls) 145 } 146 147 func TestFilesystem_backup(t *testing.T) { 148 defer testOverrideVersion(t, "1.2.3")() 149 f, err := ioutil.TempFile("", "tf") 150 if err != nil { 151 t.Fatalf("err: %s", err) 152 } 153 f.Close() 154 defer os.Remove(f.Name()) 155 156 ls := testFilesystem(t) 157 backupPath := f.Name() 158 ls.SetBackupPath(backupPath) 159 160 TestFull(t, ls) 161 162 // The backup functionality should've saved a copy of the original state 163 // prior to all of the modifications that TestFull does. 164 bfh, err := os.Open(backupPath) 165 if err != nil { 166 t.Fatal(err) 167 } 168 bf, err := statefile.Read(bfh) 169 if err != nil { 170 t.Fatal(err) 171 } 172 origState := TestFullInitialState() 173 if !bf.State.Equal(origState) { 174 for _, problem := range deep.Equal(origState, bf.State) { 175 t.Error(problem) 176 } 177 } 178 } 179 180 // This test verifies a particularly tricky behavior where the input file 181 // is overridden and backups are enabled at the same time. This combination 182 // requires special care because we must ensure that when we create a backup 183 // it is of the original contents of the output file (which we're overwriting), 184 // not the contents of the input file (which is left unchanged). 185 func TestFilesystem_backupAndReadPath(t *testing.T) { 186 defer testOverrideVersion(t, "1.2.3")() 187 188 workDir := t.TempDir() 189 190 markerOutput := addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance) 191 192 outState := states.BuildState(func(ss *states.SyncState) { 193 ss.SetOutputValue( 194 markerOutput, 195 cty.StringVal("from-output-state"), 196 false, // not sensitive 197 ) 198 }) 199 outFile, err := os.Create(filepath.Join(workDir, "output.tfstate")) 200 if err != nil { 201 t.Fatalf("failed to create temporary outFile %s", err) 202 } 203 defer outFile.Close() 204 err = statefile.Write(&statefile.File{ 205 Lineage: "-", 206 Serial: 0, 207 TerraformVersion: version.Must(version.NewVersion("1.2.3")), 208 State: outState, 209 }, outFile) 210 if err != nil { 211 t.Fatalf("failed to write initial outfile state to %s: %s", outFile.Name(), err) 212 } 213 214 inState := states.BuildState(func(ss *states.SyncState) { 215 ss.SetOutputValue( 216 markerOutput, 217 cty.StringVal("from-input-state"), 218 false, // not sensitive 219 ) 220 }) 221 inFile, err := os.Create(filepath.Join(workDir, "input.tfstate")) 222 if err != nil { 223 t.Fatalf("failed to create temporary inFile %s", err) 224 } 225 defer inFile.Close() 226 err = statefile.Write(&statefile.File{ 227 Lineage: "-", 228 Serial: 0, 229 TerraformVersion: version.Must(version.NewVersion("1.2.3")), 230 State: inState, 231 }, inFile) 232 if err != nil { 233 t.Fatalf("failed to write initial infile state to %s: %s", inFile.Name(), err) 234 } 235 236 backupPath := outFile.Name() + ".backup" 237 238 ls := NewFilesystemBetweenPaths(inFile.Name(), outFile.Name()) 239 ls.SetBackupPath(backupPath) 240 241 newState := states.BuildState(func(ss *states.SyncState) { 242 ss.SetOutputValue( 243 markerOutput, 244 cty.StringVal("from-new-state"), 245 false, // not sensitive 246 ) 247 }) 248 err = ls.WriteState(newState) 249 if err != nil { 250 t.Fatalf("failed to write new state: %s", err) 251 } 252 253 // The backup functionality should've saved a copy of the original contents 254 // of the _output_ file, even though the first snapshot was read from 255 // the _input_ file. 256 t.Run("backup file", func(t *testing.T) { 257 bfh, err := os.Open(backupPath) 258 if err != nil { 259 t.Fatal(err) 260 } 261 bf, err := statefile.Read(bfh) 262 if err != nil { 263 t.Fatal(err) 264 } 265 os := bf.State.OutputValue(markerOutput) 266 if got, want := os.Value, cty.StringVal("from-output-state"); !want.RawEquals(got) { 267 t.Errorf("wrong marker value in backup state file\ngot: %#v\nwant: %#v", got, want) 268 } 269 }) 270 t.Run("output file", func(t *testing.T) { 271 ofh, err := os.Open(outFile.Name()) 272 if err != nil { 273 t.Fatal(err) 274 } 275 of, err := statefile.Read(ofh) 276 if err != nil { 277 t.Fatal(err) 278 } 279 os := of.State.OutputValue(markerOutput) 280 if got, want := os.Value, cty.StringVal("from-new-state"); !want.RawEquals(got) { 281 t.Errorf("wrong marker value in backup state file\ngot: %#v\nwant: %#v", got, want) 282 } 283 }) 284 } 285 286 func TestFilesystem_nonExist(t *testing.T) { 287 defer testOverrideVersion(t, "1.2.3")() 288 ls := NewFilesystem("ishouldntexist") 289 if err := ls.RefreshState(); err != nil { 290 t.Fatalf("err: %s", err) 291 } 292 293 if state := ls.State(); state != nil { 294 t.Fatalf("bad: %#v", state) 295 } 296 } 297 298 func TestFilesystem_lockUnlockWithoutWrite(t *testing.T) { 299 info := NewLockInfo() 300 info.Operation = "test" 301 302 ls := testFilesystem(t) 303 304 // Delete the just-created tempfile so that Lock recreates it 305 os.Remove(ls.path) 306 307 // Lock the state, and in doing so recreate the tempfile 308 lockID, err := ls.Lock(info) 309 if err != nil { 310 t.Fatal(err) 311 } 312 313 if !ls.created { 314 t.Fatal("should have marked state as created") 315 } 316 317 if err := ls.Unlock(lockID); err != nil { 318 t.Fatal(err) 319 } 320 321 _, err = os.Stat(ls.path) 322 if os.IsNotExist(err) { 323 // Success! Unlocking the state successfully deleted the tempfile 324 return 325 } else if err != nil { 326 t.Fatalf("unexpected error from os.Stat: %s", err) 327 } else { 328 os.Remove(ls.readPath) 329 t.Fatal("should have removed path, but exists") 330 } 331 } 332 333 func TestFilesystem_impl(t *testing.T) { 334 defer testOverrideVersion(t, "1.2.3")() 335 var _ Reader = new(Filesystem) 336 var _ Writer = new(Filesystem) 337 var _ Persister = new(Filesystem) 338 var _ Refresher = new(Filesystem) 339 var _ OutputReader = new(Filesystem) 340 var _ Locker = new(Filesystem) 341 } 342 343 func testFilesystem(t *testing.T) *Filesystem { 344 f, err := ioutil.TempFile("", "tf") 345 if err != nil { 346 t.Fatalf("failed to create temporary file %s", err) 347 } 348 t.Logf("temporary state file at %s", f.Name()) 349 350 err = statefile.Write(&statefile.File{ 351 Lineage: "test-lineage", 352 Serial: 0, 353 TerraformVersion: version.Must(version.NewVersion("1.2.3")), 354 State: TestFullInitialState(), 355 }, f) 356 if err != nil { 357 t.Fatalf("failed to write initial state to %s: %s", f.Name(), err) 358 } 359 f.Close() 360 361 ls := NewFilesystem(f.Name()) 362 if err := ls.RefreshState(); err != nil { 363 t.Fatalf("initial refresh failed: %s", err) 364 } 365 366 return ls 367 } 368 369 // Make sure we can refresh while the state is locked 370 func TestFilesystem_refreshWhileLocked(t *testing.T) { 371 defer testOverrideVersion(t, "1.2.3")() 372 f, err := ioutil.TempFile("", "tf") 373 if err != nil { 374 t.Fatalf("err: %s", err) 375 } 376 377 err = statefile.Write(&statefile.File{ 378 Lineage: "test-lineage", 379 Serial: 0, 380 TerraformVersion: version.Must(version.NewVersion("1.2.3")), 381 State: TestFullInitialState(), 382 }, f) 383 if err != nil { 384 t.Fatalf("err: %s", err) 385 } 386 f.Close() 387 388 s := NewFilesystem(f.Name()) 389 defer os.Remove(s.path) 390 391 // lock first 392 info := NewLockInfo() 393 info.Operation = "test" 394 lockID, err := s.Lock(info) 395 if err != nil { 396 t.Fatal(err) 397 } 398 defer func() { 399 if err := s.Unlock(lockID); err != nil { 400 t.Fatal(err) 401 } 402 }() 403 404 if err := s.RefreshState(); err != nil { 405 t.Fatal(err) 406 } 407 408 readState := s.State() 409 if readState == nil { 410 t.Fatal("missing state") 411 } 412 } 413 414 func TestFilesystem_GetRootOutputValues(t *testing.T) { 415 fs := testFilesystem(t) 416 417 outputs, err := fs.GetRootOutputValues() 418 if err != nil { 419 t.Errorf("Expected GetRootOutputValues to not return an error, but it returned %v", err) 420 } 421 422 if len(outputs) != 2 { 423 t.Errorf("Expected %d outputs, but received %d", 2, len(outputs)) 424 } 425 } 426 427 func testOverrideVersion(t *testing.T, v string) func() { 428 oldVersionStr := tfversion.Version 429 oldPrereleaseStr := tfversion.Prerelease 430 oldSemVer := tfversion.SemVer 431 432 var newPrereleaseStr string 433 if dash := strings.Index(v, "-"); dash != -1 { 434 newPrereleaseStr = v[dash+1:] 435 v = v[:dash] 436 } 437 438 newSemVer, err := version.NewVersion(v) 439 if err != nil { 440 t.Errorf("invalid override version %q: %s", v, err) 441 } 442 newVersionStr := newSemVer.String() 443 444 tfversion.Version = newVersionStr 445 tfversion.Prerelease = newPrereleaseStr 446 tfversion.SemVer = newSemVer 447 448 return func() { // reset function 449 tfversion.Version = oldVersionStr 450 tfversion.Prerelease = oldPrereleaseStr 451 tfversion.SemVer = oldSemVer 452 } 453 }