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