github.com/opentofu/opentofu@v1.7.1/internal/states/statemgr/filesystem.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 statemgr 7 8 import ( 9 "bytes" 10 "encoding/json" 11 "fmt" 12 "io" 13 "log" 14 "os" 15 "path/filepath" 16 "sync" 17 "time" 18 19 multierror "github.com/hashicorp/go-multierror" 20 21 "github.com/opentofu/opentofu/internal/encryption" 22 "github.com/opentofu/opentofu/internal/states" 23 "github.com/opentofu/opentofu/internal/states/statefile" 24 "github.com/opentofu/opentofu/internal/tofu" 25 ) 26 27 // Filesystem is a full state manager that uses a file in the local filesystem 28 // for persistent storage. 29 // 30 // The transient storage for Filesystem is always in-memory. 31 type Filesystem struct { 32 mu sync.Mutex 33 34 // path is the location where a file will be created or replaced for 35 // each persistent snapshot. 36 path string 37 38 // readPath is read by RefreshState instead of "path" until the first 39 // call to PersistState, after which it is ignored. 40 // 41 // The file at readPath must never be written to by this manager. 42 readPath string 43 44 // backupPath is an optional extra path which, if non-empty, will be 45 // created or overwritten with the first state snapshot we read if there 46 // is a subsequent call to write a different state. 47 backupPath string 48 49 // the file handle corresponding to PathOut 50 stateFileOut *os.File 51 52 // While the stateFileOut will correspond to the lock directly, 53 // store and check the lock ID to maintain a strict statemgr.Locker 54 // implementation. 55 lockID string 56 57 // created is set to true if stateFileOut didn't exist before we created it. 58 // This is mostly so we can clean up empty files during tests, but doesn't 59 // hurt to remove file we never wrote to. 60 created bool 61 62 file *statefile.File 63 readFile *statefile.File 64 backupFile *statefile.File 65 writtenBackup bool 66 67 encryption encryption.StateEncryption 68 } 69 70 var ( 71 _ Full = (*Filesystem)(nil) 72 _ PersistentMeta = (*Filesystem)(nil) 73 _ Migrator = (*Filesystem)(nil) 74 ) 75 76 // NewFilesystem creates a filesystem-based state manager that reads and writes 77 // state snapshots at the given filesystem path. 78 // 79 // This is equivalent to calling NewFileSystemBetweenPaths with statePath as 80 // both of the path arguments. 81 func NewFilesystem(statePath string, enc encryption.StateEncryption) *Filesystem { 82 return &Filesystem{ 83 path: statePath, 84 readPath: statePath, 85 encryption: enc, 86 } 87 } 88 89 // NewFilesystemBetweenPaths creates a filesystem-based state manager that 90 // reads an initial snapshot from readPath and then writes all new snapshots to 91 // writePath. 92 func NewFilesystemBetweenPaths(readPath, writePath string, enc encryption.StateEncryption) *Filesystem { 93 return &Filesystem{ 94 path: writePath, 95 readPath: readPath, 96 encryption: enc, 97 } 98 } 99 100 // SetBackupPath configures the receiever so that it will create a local 101 // backup file of the next state snapshot it reads (in State) if a different 102 // snapshot is subsequently written (in WriteState). Only one backup is 103 // written for the lifetime of the object, unless reset as described below. 104 // 105 // For correct operation, this must be called before any other state methods 106 // are called. If called multiple times, each call resets the backup 107 // function so that the next read will become the backup snapshot and a 108 // following write will save a backup of it. 109 func (s *Filesystem) SetBackupPath(path string) { 110 s.backupPath = path 111 s.backupFile = nil 112 s.writtenBackup = false 113 } 114 115 // BackupPath returns the manager's backup path if backup files are enabled, 116 // or an empty string otherwise. 117 func (s *Filesystem) BackupPath() string { 118 return s.backupPath 119 } 120 121 // State is an implementation of Reader. 122 func (s *Filesystem) State() *states.State { 123 defer s.mutex()() 124 if s.file == nil { 125 return nil 126 } 127 return s.file.DeepCopy().State 128 } 129 130 // WriteState is an implementation of Writer. 131 func (s *Filesystem) WriteState(state *states.State) error { 132 defer s.mutex()() 133 134 if s.readFile == nil { 135 err := s.refreshState() 136 if err != nil { 137 return err 138 } 139 } 140 141 return s.writeState(state, nil) 142 } 143 144 func (s *Filesystem) writeState(state *states.State, meta *SnapshotMeta) error { 145 s.file = s.file.DeepCopy() 146 if s.file == nil { 147 s.file = NewStateFile() 148 } 149 s.file.State = state.DeepCopy() 150 151 if meta != nil { 152 // Force new metadata 153 s.file.Lineage = meta.Lineage 154 s.file.Serial = meta.Serial 155 log.Printf("[TRACE] statemgr.Filesystem: forcing lineage %q serial %d for migration/import", s.file.Lineage, s.file.Serial) 156 } 157 158 return nil 159 } 160 161 // PersistState writes state to a tfstate file. 162 func (s *Filesystem) PersistState(schemas *tofu.Schemas) error { 163 defer s.mutex()() 164 165 return s.persistState(schemas) 166 } 167 168 func (s *Filesystem) persistState(schemas *tofu.Schemas) error { 169 // TODO: this should use a more robust method of writing state, by first 170 // writing to a temp file on the same filesystem, and renaming the file over 171 // the original. 172 if s.stateFileOut == nil { 173 if err := s.createStateFiles(); err != nil { 174 return nil 175 } 176 } 177 defer s.stateFileOut.Sync() 178 179 if s.file == nil { 180 s.file = NewStateFile() 181 } 182 state := s.file.State 183 184 // We'll try to write our backup first, so we can be sure we've created 185 // it successfully before clobbering the original file it came from. 186 if !s.writtenBackup && s.backupFile != nil && s.backupPath != "" { 187 if !statefile.StatesMarshalEqual(state, s.backupFile.State) { 188 log.Printf("[TRACE] statemgr.Filesystem: creating backup snapshot at %s", s.backupPath) 189 bfh, err := os.Create(s.backupPath) 190 if err != nil { 191 return fmt.Errorf("failed to create local state backup file: %w", err) 192 } 193 defer bfh.Close() 194 195 err = statefile.Write(s.backupFile, bfh, s.encryption) 196 if err != nil { 197 return fmt.Errorf("failed to write to local state backup file: %w", err) 198 } 199 200 s.writtenBackup = true 201 } else { 202 log.Print("[TRACE] statemgr.Filesystem: not making a backup, because the new snapshot is identical to the old") 203 } 204 } else { 205 // This branch is all just logging, to help understand why we didn't make a backup. 206 switch { 207 case s.backupPath == "": 208 log.Print("[TRACE] statemgr.Filesystem: state file backups are disabled") 209 case s.writtenBackup: 210 log.Printf("[TRACE] statemgr.Filesystem: have already backed up original %s to %s on a previous write", s.path, s.backupPath) 211 case s.backupFile == nil: 212 log.Printf("[TRACE] statemgr.Filesystem: no original state snapshot to back up") 213 default: 214 log.Printf("[TRACE] statemgr.Filesystem: not creating a backup for an unknown reason") 215 } 216 } 217 218 if _, err := s.stateFileOut.Seek(0, io.SeekStart); err != nil { 219 return err 220 } 221 if err := s.stateFileOut.Truncate(0); err != nil { 222 return err 223 } 224 225 if state == nil { 226 // if we have no state, don't write anything else. 227 log.Print("[TRACE] statemgr.Filesystem: state is nil, so leaving the file empty") 228 return nil 229 } 230 231 if s.readFile == nil || !statefile.StatesMarshalEqual(s.file.State, s.readFile.State) { 232 s.file.Serial++ 233 log.Printf("[TRACE] statemgr.Filesystem: state has changed since last snapshot, so incrementing serial to %d", s.file.Serial) 234 } else { 235 log.Print("[TRACE] statemgr.Filesystem: no state changes since last snapshot") 236 } 237 238 log.Printf("[TRACE] statemgr.Filesystem: writing snapshot at %s", s.path) 239 if err := statefile.Write(s.file, s.stateFileOut, s.encryption); err != nil { 240 return err 241 } 242 243 // Any future reads must come from the file we've now updated 244 s.readPath = s.path 245 return nil 246 } 247 248 // RefreshState is an implementation of Refresher. 249 func (s *Filesystem) RefreshState() error { 250 defer s.mutex()() 251 return s.refreshState() 252 } 253 254 func (s *Filesystem) GetRootOutputValues() (map[string]*states.OutputValue, error) { 255 err := s.RefreshState() 256 if err != nil { 257 return nil, err 258 } 259 260 state := s.State() 261 if state == nil { 262 state = states.NewState() 263 } 264 265 return state.RootModule().OutputValues, nil 266 } 267 268 func (s *Filesystem) refreshState() error { 269 var reader io.Reader 270 271 // The s.readPath file is only OK to read if we have not written any state out 272 // (in which case the same state needs to be read in), and no state output file 273 // has been opened (possibly via a lock) or the input path is different 274 // than the output path. 275 // This is important for Windows, as if the input file is the same as the 276 // output file, and the output file has been locked already, we can't open 277 // the file again. 278 if s.stateFileOut == nil || s.readPath != s.path { 279 // we haven't written a state file yet, so load from readPath 280 log.Printf("[TRACE] statemgr.Filesystem: reading initial snapshot from %s", s.readPath) 281 f, err := os.Open(s.readPath) 282 if err != nil { 283 // It is okay if the file doesn't exist; we'll treat that as a nil state. 284 if !os.IsNotExist(err) { 285 return err 286 } 287 288 // we need a non-nil reader for ReadState and an empty buffer works 289 // to return EOF immediately 290 reader = bytes.NewBuffer(nil) 291 292 } else { 293 defer f.Close() 294 reader = f 295 } 296 } else { 297 log.Printf("[TRACE] statemgr.Filesystem: reading latest snapshot from %s", s.path) 298 // no state to refresh 299 if s.stateFileOut == nil { 300 return nil 301 } 302 303 // we have a state file, make sure we're at the start 304 _, err := s.stateFileOut.Seek(0, io.SeekStart) 305 if err != nil { 306 return err 307 } 308 reader = s.stateFileOut 309 } 310 311 f, err := statefile.Read(reader, s.encryption) 312 // if there's no state then a nil file is fine 313 if err != nil { 314 if err != statefile.ErrNoState { 315 return err 316 } 317 log.Printf("[TRACE] statemgr.Filesystem: snapshot file has nil snapshot, but that's okay") 318 } 319 320 s.file = f 321 s.readFile = s.file.DeepCopy() 322 if s.file != nil { 323 log.Printf("[TRACE] statemgr.Filesystem: read snapshot with lineage %q serial %d", s.file.Lineage, s.file.Serial) 324 } else { 325 log.Print("[TRACE] statemgr.Filesystem: read nil snapshot") 326 } 327 return nil 328 } 329 330 // Lock implements Locker using filesystem discretionary locks. 331 func (s *Filesystem) Lock(info *LockInfo) (string, error) { 332 defer s.mutex()() 333 334 if s.stateFileOut == nil { 335 if err := s.createStateFiles(); err != nil { 336 return "", err 337 } 338 } 339 340 if s.lockID != "" { 341 return "", fmt.Errorf("state %q already locked", s.stateFileOut.Name()) 342 } 343 344 if err := s.lock(); err != nil { 345 info, infoErr := s.lockInfo() 346 if infoErr != nil { 347 err = multierror.Append(err, infoErr) 348 } 349 350 lockErr := &LockError{ 351 Info: info, 352 Err: err, 353 } 354 355 return "", lockErr 356 } 357 358 s.lockID = info.ID 359 return s.lockID, s.writeLockInfo(info) 360 } 361 362 // Unlock is the companion to Lock, completing the implemention of Locker. 363 func (s *Filesystem) Unlock(id string) error { 364 defer s.mutex()() 365 366 if s.lockID == "" { 367 return fmt.Errorf("LocalState not locked") 368 } 369 370 if id != s.lockID { 371 idErr := fmt.Errorf("invalid lock id: %q. current id: %q", id, s.lockID) 372 info, err := s.lockInfo() 373 if err != nil { 374 idErr = multierror.Append(idErr, err) 375 } 376 377 return &LockError{ 378 Err: idErr, 379 Info: info, 380 } 381 } 382 383 lockInfoPath := s.lockInfoPath() 384 err := os.Remove(lockInfoPath) 385 if err != nil { 386 log.Printf( 387 "[ERROR] statemgr.Filesystem: error removing lock metadata file %q: %s", 388 lockInfoPath, 389 err, 390 ) 391 } else { 392 log.Printf("[TRACE] statemgr.Filesystem: removed lock metadata file %s", lockInfoPath) 393 } 394 fileName := s.stateFileOut.Name() 395 396 unlockErr := s.unlock() 397 398 s.stateFileOut.Close() 399 s.stateFileOut = nil 400 s.lockID = "" 401 402 // clean up the state file if we created it an never wrote to it 403 stat, err := os.Stat(fileName) 404 if err == nil && stat.Size() == 0 && s.created { 405 err = os.Remove(fileName) 406 if err != nil { 407 log.Printf("[ERROR] stagemgr.Filesystem: error removing empty state file %q: %s", fileName, err) 408 } 409 } 410 411 return unlockErr 412 } 413 414 // StateSnapshotMeta returns the metadata from the most recently persisted 415 // or refreshed persistent state snapshot. 416 // 417 // This is an implementation of PersistentMeta. 418 func (s *Filesystem) StateSnapshotMeta() SnapshotMeta { 419 if s.file == nil { 420 return SnapshotMeta{} // placeholder 421 } 422 423 return SnapshotMeta{ 424 Lineage: s.file.Lineage, 425 Serial: s.file.Serial, 426 427 TerraformVersion: s.file.TerraformVersion, 428 } 429 } 430 431 // StateForMigration is part of our implementation of Migrator. 432 func (s *Filesystem) StateForMigration() *statefile.File { 433 return s.file.DeepCopy() 434 } 435 436 // WriteStateForMigration is part of our implementation of Migrator. 437 func (s *Filesystem) WriteStateForMigration(f *statefile.File, force bool) error { 438 defer s.mutex()() 439 440 if s.readFile == nil { 441 err := s.refreshState() 442 if err != nil { 443 return err 444 } 445 } 446 447 if !force { 448 err := CheckValidImport(f, s.readFile) 449 if err != nil { 450 return err 451 } 452 } 453 454 if s.readFile != nil { 455 log.Printf( 456 "[TRACE] statemgr.Filesystem: Importing snapshot with lineage %q serial %d over snapshot with lineage %q serial %d at %s", 457 f.Lineage, f.Serial, 458 s.readFile.Lineage, s.readFile.Serial, 459 s.path, 460 ) 461 } else { 462 log.Printf( 463 "[TRACE] statemgr.Filesystem: Importing snapshot with lineage %q serial %d as the initial state snapshot at %s", 464 f.Lineage, f.Serial, 465 s.path, 466 ) 467 } 468 469 err := s.writeState(f.State, &SnapshotMeta{Lineage: f.Lineage, Serial: f.Serial}) 470 if err != nil { 471 return err 472 } 473 474 return s.persistState(nil) 475 } 476 477 // Open the state file, creating the directories and file as needed. 478 func (s *Filesystem) createStateFiles() error { 479 log.Printf("[TRACE] statemgr.Filesystem: preparing to manage state snapshots at %s", s.path) 480 481 // This could race, but we only use it to clean up empty files 482 if _, err := os.Stat(s.path); os.IsNotExist(err) { 483 s.created = true 484 } 485 486 // Create all the directories 487 if err := os.MkdirAll(filepath.Dir(s.path), 0755); err != nil { 488 return err 489 } 490 491 f, err := os.OpenFile(s.path, os.O_RDWR|os.O_CREATE, 0666) 492 if err != nil { 493 return err 494 } 495 496 s.stateFileOut = f 497 498 // If the file already existed with content then that'll be the content 499 // of our backup file if we write a change later. 500 s.backupFile, err = statefile.Read(s.stateFileOut, s.encryption) 501 if err != nil { 502 if err != statefile.ErrNoState { 503 return err 504 } 505 log.Printf("[TRACE] statemgr.Filesystem: no previously-stored snapshot exists") 506 } else { 507 log.Printf("[TRACE] statemgr.Filesystem: existing snapshot has lineage %q serial %d", s.backupFile.Lineage, s.backupFile.Serial) 508 } 509 510 // Refresh now, to load in the snapshot if the file already existed 511 return nil 512 } 513 514 // return the path for the lockInfo metadata. 515 func (s *Filesystem) lockInfoPath() string { 516 stateDir, stateName := filepath.Split(s.path) 517 if stateName == "" { 518 panic("empty state file path") 519 } 520 521 if stateName[0] == '.' { 522 stateName = stateName[1:] 523 } 524 525 return filepath.Join(stateDir, fmt.Sprintf(".%s.lock.info", stateName)) 526 } 527 528 // lockInfo returns the data in a lock info file 529 func (s *Filesystem) lockInfo() (*LockInfo, error) { 530 path := s.lockInfoPath() 531 infoData, err := os.ReadFile(path) 532 if err != nil { 533 return nil, err 534 } 535 536 info := LockInfo{} 537 err = json.Unmarshal(infoData, &info) 538 if err != nil { 539 return nil, fmt.Errorf("state file %q locked, but could not unmarshal lock info: %w", s.readPath, err) 540 } 541 return &info, nil 542 } 543 544 // write a new lock info file 545 func (s *Filesystem) writeLockInfo(info *LockInfo) error { 546 path := s.lockInfoPath() 547 info.Path = s.readPath 548 info.Created = time.Now().UTC() 549 550 log.Printf("[TRACE] statemgr.Filesystem: writing lock metadata to %s", path) 551 err := os.WriteFile(path, info.Marshal(), 0600) 552 if err != nil { 553 return fmt.Errorf("could not write lock info for %q: %w", s.readPath, err) 554 } 555 return nil 556 } 557 558 func (s *Filesystem) mutex() func() { 559 s.mu.Lock() 560 return s.mu.Unlock 561 }