github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/command/clistate/state.go (about)

     1  // Package state exposes common helpers for working with state from the CLI.
     2  //
     3  // This is a separate package so that backends can use this for consistent
     4  // messaging without creating a circular reference to the command package.
     5  package clistate
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"strings"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/hashicorp/errwrap"
    15  	multierror "github.com/hashicorp/go-multierror"
    16  	"github.com/hashicorp/terraform/helper/slowmessage"
    17  	"github.com/hashicorp/terraform/state"
    18  	"github.com/hashicorp/terraform/states/statemgr"
    19  	"github.com/mitchellh/cli"
    20  	"github.com/mitchellh/colorstring"
    21  )
    22  
    23  const (
    24  	LockThreshold    = 400 * time.Millisecond
    25  	LockMessage      = "Acquiring state lock. This may take a few moments..."
    26  	LockErrorMessage = `Error acquiring the state lock: {{err}}
    27  
    28  Terraform acquires a state lock to protect the state from being written
    29  by multiple users at the same time. Please resolve the issue above and try
    30  again. For most commands, you can disable locking with the "-lock=false"
    31  flag, but this is not recommended.`
    32  
    33  	UnlockMessage      = "Releasing state lock. This may take a few moments..."
    34  	UnlockErrorMessage = `
    35  [reset][bold][red]Error releasing the state lock![reset][red]
    36  
    37  Error message: %s
    38  
    39  Terraform acquires a lock when accessing your state to prevent others
    40  running Terraform to potentially modify the state at the same time. An
    41  error occurred while releasing this lock. This could mean that the lock
    42  did or did not release properly. If the lock didn't release properly,
    43  Terraform may not be able to run future commands since it'll appear as if
    44  the lock is held.
    45  
    46  In this scenario, please call the "force-unlock" command to unlock the
    47  state manually. This is a very dangerous operation since if it is done
    48  erroneously it could result in two people modifying state at the same time.
    49  Only call this command if you're certain that the unlock above failed and
    50  that no one else is holding a lock.
    51  `
    52  )
    53  
    54  // Locker allows for more convenient usage of the lower-level state.Locker
    55  // implementations.
    56  // The state.Locker API requires passing in a state.LockInfo struct. Locker
    57  // implementations are expected to create the required LockInfo struct when
    58  // Lock is called, populate the Operation field with the "reason" string
    59  // provided, and pass that on to the underlying state.Locker.
    60  // Locker implementations are also expected to store any state required to call
    61  // Unlock, which is at a minimum the LockID string returned by the
    62  // state.Locker.
    63  type Locker interface {
    64  	// Lock the provided state manager, storing the reason string in the LockInfo.
    65  	Lock(s statemgr.Locker, reason string) error
    66  	// Unlock the previously locked state.
    67  	// An optional error can be passed in, and will be combined with any error
    68  	// from the Unlock operation.
    69  	Unlock(error) error
    70  }
    71  
    72  type locker struct {
    73  	mu      sync.Mutex
    74  	ctx     context.Context
    75  	timeout time.Duration
    76  	state   statemgr.Locker
    77  	ui      cli.Ui
    78  	color   *colorstring.Colorize
    79  	lockID  string
    80  }
    81  
    82  // Create a new Locker.
    83  // This Locker uses state.LockWithContext to retry the lock until the provided
    84  // timeout is reached, or the context is canceled. Lock progress will be be
    85  // reported to the user through the provided UI.
    86  func NewLocker(
    87  	ctx context.Context,
    88  	timeout time.Duration,
    89  	ui cli.Ui,
    90  	color *colorstring.Colorize) Locker {
    91  
    92  	l := &locker{
    93  		ctx:     ctx,
    94  		timeout: timeout,
    95  		ui:      ui,
    96  		color:   color,
    97  	}
    98  	return l
    99  }
   100  
   101  // Locker locks the given state and outputs to the user if locking is taking
   102  // longer than the threshold. The lock is retried until the context is
   103  // cancelled.
   104  func (l *locker) Lock(s statemgr.Locker, reason string) error {
   105  	l.mu.Lock()
   106  	defer l.mu.Unlock()
   107  
   108  	l.state = s
   109  
   110  	ctx, cancel := context.WithTimeout(l.ctx, l.timeout)
   111  	defer cancel()
   112  
   113  	lockInfo := state.NewLockInfo()
   114  	lockInfo.Operation = reason
   115  
   116  	err := slowmessage.Do(LockThreshold, func() error {
   117  		id, err := statemgr.LockWithContext(ctx, s, lockInfo)
   118  		l.lockID = id
   119  		return err
   120  	}, func() {
   121  		if l.ui != nil {
   122  			l.ui.Output(l.color.Color(LockMessage))
   123  		}
   124  	})
   125  
   126  	if err != nil {
   127  		return errwrap.Wrapf(strings.TrimSpace(LockErrorMessage), err)
   128  	}
   129  
   130  	return nil
   131  }
   132  
   133  func (l *locker) Unlock(parentErr error) error {
   134  	l.mu.Lock()
   135  	defer l.mu.Unlock()
   136  
   137  	if l.lockID == "" {
   138  		return parentErr
   139  	}
   140  
   141  	err := slowmessage.Do(LockThreshold, func() error {
   142  		return l.state.Unlock(l.lockID)
   143  	}, func() {
   144  		if l.ui != nil {
   145  			l.ui.Output(l.color.Color(UnlockMessage))
   146  		}
   147  	})
   148  
   149  	if err != nil {
   150  		l.ui.Output(l.color.Color(fmt.Sprintf(
   151  			"\n"+strings.TrimSpace(UnlockErrorMessage)+"\n", err)))
   152  
   153  		if parentErr != nil {
   154  			parentErr = multierror.Append(parentErr, err)
   155  		}
   156  	}
   157  
   158  	return parentErr
   159  
   160  }
   161  
   162  type noopLocker struct{}
   163  
   164  // NewNoopLocker returns a valid Locker that does nothing.
   165  func NewNoopLocker() Locker {
   166  	return noopLocker{}
   167  }
   168  
   169  func (l noopLocker) Lock(statemgr.Locker, string) error {
   170  	return nil
   171  }
   172  
   173  func (l noopLocker) Unlock(err error) error {
   174  	return err
   175  }