github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/states/statemgr/plan.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package statemgr 5 6 import ( 7 "fmt" 8 9 "github.com/terramate-io/tf/states" 10 "github.com/terramate-io/tf/states/statefile" 11 ) 12 13 // PlannedStateUpdate is a special helper to obtain a statefile representation 14 // of a not-yet-written state snapshot that can be written later by a call 15 // to the companion function WritePlannedStateUpdate. 16 // 17 // The statefile object returned here has an unusual interpretation of its 18 // metadata that is understood only by WritePlannedStateUpdate, and so the 19 // returned object should not be used for any other purpose. 20 // 21 // If the state manager implements Locker then it is the caller's 22 // responsibility to hold the lock at least for the duration of this call. 23 // It is not safe to modify the given state concurrently while 24 // PlannedStateUpdate is running. 25 func PlannedStateUpdate(mgr Transient, planned *states.State) *statefile.File { 26 ret := &statefile.File{ 27 State: planned.DeepCopy(), 28 } 29 30 // If the given manager uses snapshot metadata then we'll save that 31 // in our file so we can check it again during WritePlannedStateUpdate. 32 if mr, ok := mgr.(PersistentMeta); ok { 33 m := mr.StateSnapshotMeta() 34 ret.Lineage = m.Lineage 35 ret.Serial = m.Serial 36 } 37 38 return ret 39 } 40 41 // WritePlannedStateUpdate is a companion to PlannedStateUpdate that attempts 42 // to apply a state update that was planned earlier to the given state 43 // manager. 44 // 45 // An error is returned if this function detects that a new state snapshot 46 // has been written to the backend since the update was planned, since that 47 // invalidates the plan. An error is returned also if the manager itself 48 // rejects the given state when asked to store it. 49 // 50 // If the returned error is nil, the given manager's transient state snapshot 51 // is updated to match what was planned. It is the caller's responsibility 52 // to then persist that state if the manager also implements Persistent and 53 // the snapshot should be written to the persistent store. 54 // 55 // If the state manager implements Locker then it is the caller's 56 // responsibility to hold the lock at least for the duration of this call. 57 func WritePlannedStateUpdate(mgr Transient, planned *statefile.File) error { 58 // If the given manager uses snapshot metadata then we'll check to make 59 // sure no new snapshots have been created since we planned to write 60 // the given state file. 61 if mr, ok := mgr.(PersistentMeta); ok { 62 m := mr.StateSnapshotMeta() 63 if planned.Lineage != "" { 64 if planned.Lineage != m.Lineage { 65 return fmt.Errorf("planned state update is from an unrelated state lineage than the current state") 66 } 67 if planned.Serial != m.Serial { 68 return fmt.Errorf("stored state has been changed by another operation since the given update was planned") 69 } 70 } 71 } 72 73 return mgr.WriteState(planned.State) 74 }