github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/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 "sync" 11 "time" 12 13 "github.com/iaas-resource-provision/iaas-rpc/internal/command/views" 14 "github.com/iaas-resource-provision/iaas-rpc/internal/helper/slowmessage" 15 "github.com/iaas-resource-provision/iaas-rpc/internal/states/statemgr" 16 "github.com/iaas-resource-provision/iaas-rpc/internal/tfdiags" 17 ) 18 19 const ( 20 LockThreshold = 400 * time.Millisecond 21 LockErrorMessage = `Error message: %s 22 23 Terraform acquires a state lock to protect the state from being written 24 by multiple users at the same time. Please resolve the issue above and try 25 again. For most commands, you can disable locking with the "-lock=false" 26 flag, but this is not recommended.` 27 28 UnlockErrorMessage = `Error message: %s 29 30 Terraform acquires a lock when accessing your state to prevent others 31 running Terraform to potentially modify the state at the same time. An 32 error occurred while releasing this lock. This could mean that the lock 33 did or did not release properly. If the lock didn't release properly, 34 Terraform may not be able to run future commands since it'll appear as if 35 the lock is held. 36 37 In this scenario, please call the "force-unlock" command to unlock the 38 state manually. This is a very dangerous operation since if it is done 39 erroneously it could result in two people modifying state at the same time. 40 Only call this command if you're certain that the unlock above failed and 41 that no one else is holding a lock.` 42 ) 43 44 // Locker allows for more convenient usage of the lower-level statemgr.Locker 45 // implementations. 46 // The statemgr.Locker API requires passing in a statemgr.LockInfo struct. Locker 47 // implementations are expected to create the required LockInfo struct when 48 // Lock is called, populate the Operation field with the "reason" string 49 // provided, and pass that on to the underlying statemgr.Locker. 50 // Locker implementations are also expected to store any state required to call 51 // Unlock, which is at a minimum the LockID string returned by the 52 // statemgr.Locker. 53 type Locker interface { 54 // Returns a shallow copy of the locker with its context changed to ctx. 55 WithContext(ctx context.Context) Locker 56 57 // Lock the provided state manager, storing the reason string in the LockInfo. 58 Lock(s statemgr.Locker, reason string) tfdiags.Diagnostics 59 60 // Unlock the previously locked state. 61 Unlock() tfdiags.Diagnostics 62 63 // Timeout returns the configured timeout duration 64 Timeout() time.Duration 65 } 66 67 type locker struct { 68 mu sync.Mutex 69 ctx context.Context 70 timeout time.Duration 71 state statemgr.Locker 72 view views.StateLocker 73 lockID string 74 } 75 76 var _ Locker = (*locker)(nil) 77 78 // Create a new Locker. 79 // This Locker uses state.LockWithContext to retry the lock until the provided 80 // timeout is reached, or the context is canceled. Lock progress will be be 81 // reported to the user through the provided UI. 82 func NewLocker(timeout time.Duration, view views.StateLocker) Locker { 83 return &locker{ 84 ctx: context.Background(), 85 timeout: timeout, 86 view: view, 87 } 88 } 89 90 // WithContext returns a new Locker with the specified context, copying the 91 // timeout and view parameters from the original Locker. 92 func (l *locker) WithContext(ctx context.Context) Locker { 93 if ctx == nil { 94 panic("nil context") 95 } 96 return &locker{ 97 ctx: ctx, 98 timeout: l.timeout, 99 view: l.view, 100 } 101 } 102 103 // Locker locks the given state and outputs to the user if locking is taking 104 // longer than the threshold. The lock is retried until the context is 105 // cancelled. 106 func (l *locker) Lock(s statemgr.Locker, reason string) tfdiags.Diagnostics { 107 var diags tfdiags.Diagnostics 108 109 l.mu.Lock() 110 defer l.mu.Unlock() 111 112 l.state = s 113 114 ctx, cancel := context.WithTimeout(l.ctx, l.timeout) 115 defer cancel() 116 117 lockInfo := statemgr.NewLockInfo() 118 lockInfo.Operation = reason 119 120 err := slowmessage.Do(LockThreshold, func() error { 121 id, err := statemgr.LockWithContext(ctx, s, lockInfo) 122 l.lockID = id 123 return err 124 }, l.view.Locking) 125 126 if err != nil { 127 diags = diags.Append(tfdiags.Sourceless( 128 tfdiags.Error, 129 "Error acquiring the state lock", 130 fmt.Sprintf(LockErrorMessage, err), 131 )) 132 } 133 134 return diags 135 } 136 137 func (l *locker) Unlock() tfdiags.Diagnostics { 138 var diags tfdiags.Diagnostics 139 140 l.mu.Lock() 141 defer l.mu.Unlock() 142 143 if l.lockID == "" { 144 return diags 145 } 146 147 err := slowmessage.Do(LockThreshold, func() error { 148 return l.state.Unlock(l.lockID) 149 }, l.view.Unlocking) 150 151 if err != nil { 152 diags = diags.Append(tfdiags.Sourceless( 153 tfdiags.Error, 154 "Error releasing the state lock", 155 fmt.Sprintf(UnlockErrorMessage, err), 156 )) 157 } 158 159 return diags 160 161 } 162 163 func (l *locker) Timeout() time.Duration { 164 return l.timeout 165 } 166 167 type noopLocker struct{} 168 169 // NewNoopLocker returns a valid Locker that does nothing. 170 func NewNoopLocker() Locker { 171 return noopLocker{} 172 } 173 174 var _ Locker = noopLocker{} 175 176 func (l noopLocker) WithContext(ctx context.Context) Locker { 177 return l 178 } 179 180 func (l noopLocker) Lock(statemgr.Locker, string) tfdiags.Diagnostics { 181 return nil 182 } 183 184 func (l noopLocker) Unlock() tfdiags.Diagnostics { 185 return nil 186 } 187 188 func (l noopLocker) Timeout() time.Duration { 189 return 0 190 }