github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/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/addrs" 17 "github.com/hashicorp/terraform/states" 18 "github.com/hashicorp/terraform/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", "-mod=vendor", "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, err := ioutil.TempDir("", "tf") 189 if err != nil { 190 t.Fatalf("failed to create temporary directory: %s", err) 191 } 192 defer os.RemoveAll(workDir) 193 194 markerOutput := addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance) 195 196 outState := states.BuildState(func(ss *states.SyncState) { 197 ss.SetOutputValue( 198 markerOutput, 199 cty.StringVal("from-output-state"), 200 false, // not sensitive 201 ) 202 }) 203 outFile, err := os.Create(filepath.Join(workDir, "output.tfstate")) 204 if err != nil { 205 t.Fatalf("failed to create temporary outFile %s", err) 206 } 207 defer outFile.Close() 208 err = statefile.Write(&statefile.File{ 209 Lineage: "-", 210 Serial: 0, 211 TerraformVersion: version.Must(version.NewVersion("1.2.3")), 212 State: outState, 213 }, outFile) 214 if err != nil { 215 t.Fatalf("failed to write initial outfile state to %s: %s", outFile.Name(), err) 216 } 217 218 inState := states.BuildState(func(ss *states.SyncState) { 219 ss.SetOutputValue( 220 markerOutput, 221 cty.StringVal("from-input-state"), 222 false, // not sensitive 223 ) 224 }) 225 inFile, err := os.Create(filepath.Join(workDir, "input.tfstate")) 226 if err != nil { 227 t.Fatalf("failed to create temporary inFile %s", err) 228 } 229 defer inFile.Close() 230 err = statefile.Write(&statefile.File{ 231 Lineage: "-", 232 Serial: 0, 233 TerraformVersion: version.Must(version.NewVersion("1.2.3")), 234 State: inState, 235 }, inFile) 236 if err != nil { 237 t.Fatalf("failed to write initial infile state to %s: %s", inFile.Name(), err) 238 } 239 240 backupPath := outFile.Name() + ".backup" 241 242 ls := NewFilesystemBetweenPaths(inFile.Name(), outFile.Name()) 243 ls.SetBackupPath(backupPath) 244 245 newState := states.BuildState(func(ss *states.SyncState) { 246 ss.SetOutputValue( 247 markerOutput, 248 cty.StringVal("from-new-state"), 249 false, // not sensitive 250 ) 251 }) 252 err = ls.WriteState(newState) 253 if err != nil { 254 t.Fatalf("failed to write new state: %s", err) 255 } 256 257 // The backup functionality should've saved a copy of the original contents 258 // of the _output_ file, even though the first snapshot was read from 259 // the _input_ file. 260 t.Run("backup file", func(t *testing.T) { 261 bfh, err := os.Open(backupPath) 262 if err != nil { 263 t.Fatal(err) 264 } 265 bf, err := statefile.Read(bfh) 266 if err != nil { 267 t.Fatal(err) 268 } 269 os := bf.State.OutputValue(markerOutput) 270 if got, want := os.Value, cty.StringVal("from-output-state"); !want.RawEquals(got) { 271 t.Errorf("wrong marker value in backup state file\ngot: %#v\nwant: %#v", got, want) 272 } 273 }) 274 t.Run("output file", func(t *testing.T) { 275 ofh, err := os.Open(outFile.Name()) 276 if err != nil { 277 t.Fatal(err) 278 } 279 of, err := statefile.Read(ofh) 280 if err != nil { 281 t.Fatal(err) 282 } 283 os := of.State.OutputValue(markerOutput) 284 if got, want := os.Value, cty.StringVal("from-new-state"); !want.RawEquals(got) { 285 t.Errorf("wrong marker value in backup state file\ngot: %#v\nwant: %#v", got, want) 286 } 287 }) 288 } 289 290 func TestFilesystem_nonExist(t *testing.T) { 291 defer testOverrideVersion(t, "1.2.3")() 292 ls := NewFilesystem("ishouldntexist") 293 if err := ls.RefreshState(); err != nil { 294 t.Fatalf("err: %s", err) 295 } 296 297 if state := ls.State(); state != nil { 298 t.Fatalf("bad: %#v", state) 299 } 300 } 301 302 func TestFilesystem_impl(t *testing.T) { 303 defer testOverrideVersion(t, "1.2.3")() 304 var _ Reader = new(Filesystem) 305 var _ Writer = new(Filesystem) 306 var _ Persister = new(Filesystem) 307 var _ Refresher = new(Filesystem) 308 var _ Locker = new(Filesystem) 309 } 310 311 func testFilesystem(t *testing.T) *Filesystem { 312 f, err := ioutil.TempFile("", "tf") 313 if err != nil { 314 t.Fatalf("failed to create temporary file %s", err) 315 } 316 t.Logf("temporary state file at %s", f.Name()) 317 318 err = statefile.Write(&statefile.File{ 319 Lineage: "test-lineage", 320 Serial: 0, 321 TerraformVersion: version.Must(version.NewVersion("1.2.3")), 322 State: TestFullInitialState(), 323 }, f) 324 if err != nil { 325 t.Fatalf("failed to write initial state to %s: %s", f.Name(), err) 326 } 327 f.Close() 328 329 ls := NewFilesystem(f.Name()) 330 if err := ls.RefreshState(); err != nil { 331 t.Fatalf("initial refresh failed: %s", err) 332 } 333 334 return ls 335 } 336 337 // Make sure we can refresh while the state is locked 338 func TestFilesystem_refreshWhileLocked(t *testing.T) { 339 defer testOverrideVersion(t, "1.2.3")() 340 f, err := ioutil.TempFile("", "tf") 341 if err != nil { 342 t.Fatalf("err: %s", err) 343 } 344 345 err = statefile.Write(&statefile.File{ 346 Lineage: "test-lineage", 347 Serial: 0, 348 TerraformVersion: version.Must(version.NewVersion("1.2.3")), 349 State: TestFullInitialState(), 350 }, f) 351 if err != nil { 352 t.Fatalf("err: %s", err) 353 } 354 f.Close() 355 356 s := NewFilesystem(f.Name()) 357 defer os.Remove(s.path) 358 359 // lock first 360 info := NewLockInfo() 361 info.Operation = "test" 362 lockID, err := s.Lock(info) 363 if err != nil { 364 t.Fatal(err) 365 } 366 defer func() { 367 if err := s.Unlock(lockID); err != nil { 368 t.Fatal(err) 369 } 370 }() 371 372 if err := s.RefreshState(); err != nil { 373 t.Fatal(err) 374 } 375 376 readState := s.State() 377 if readState == nil { 378 t.Fatal("missing state") 379 } 380 } 381 382 func testOverrideVersion(t *testing.T, v string) func() { 383 oldVersionStr := tfversion.Version 384 oldPrereleaseStr := tfversion.Prerelease 385 oldSemVer := tfversion.SemVer 386 387 var newPrereleaseStr string 388 if dash := strings.Index(v, "-"); dash != -1 { 389 newPrereleaseStr = v[dash+1:] 390 v = v[:dash] 391 } 392 393 newSemVer, err := version.NewVersion(v) 394 if err != nil { 395 t.Errorf("invalid override version %q: %s", v, err) 396 } 397 newVersionStr := newSemVer.String() 398 399 tfversion.Version = newVersionStr 400 tfversion.Prerelease = newPrereleaseStr 401 tfversion.SemVer = newSemVer 402 403 return func() { // reset function 404 tfversion.Version = oldVersionStr 405 tfversion.Prerelease = oldPrereleaseStr 406 tfversion.SemVer = oldSemVer 407 } 408 }