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