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 }