github.com/chanzuckerberg/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 }