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  }