github.com/magodo/terraform@v0.11.12-beta1/state/state.go (about) 1 package state 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "math/rand" 10 "os" 11 "os/user" 12 "strings" 13 "text/template" 14 "time" 15 16 uuid "github.com/hashicorp/go-uuid" 17 "github.com/hashicorp/terraform/terraform" 18 "github.com/hashicorp/terraform/version" 19 ) 20 21 var rngSource *rand.Rand 22 23 func init() { 24 rngSource = rand.New(rand.NewSource(time.Now().UnixNano())) 25 } 26 27 // State is the collection of all state interfaces. 28 type State interface { 29 StateReader 30 StateWriter 31 StateRefresher 32 StatePersister 33 Locker 34 } 35 36 // StateReader is the interface for things that can return a state. Retrieving 37 // the state here must not error. Loading the state fresh (an operation that 38 // can likely error) should be implemented by RefreshState. If a state hasn't 39 // been loaded yet, it is okay for State to return nil. 40 // 41 // Each caller of this function must get a distinct copy of the state, and 42 // it must also be distinct from any instance cached inside the reader, to 43 // ensure that mutations of the returned state will not affect the values 44 // returned to other callers. 45 type StateReader interface { 46 State() *terraform.State 47 } 48 49 // StateWriter is the interface that must be implemented by something that 50 // can write a state. Writing the state can be cached or in-memory, as 51 // full persistence should be implemented by StatePersister. 52 // 53 // Implementors that cache the state in memory _must_ take a copy of it 54 // before returning, since the caller may continue to modify it once 55 // control returns. The caller must ensure that the state instance is not 56 // concurrently modified _during_ the call, or behavior is undefined. 57 // 58 // If an object implements StatePersister in conjunction with StateReader 59 // then these methods must coordinate such that a subsequent read returns 60 // a copy of the most recent write, even if it has not yet been persisted. 61 type StateWriter interface { 62 WriteState(*terraform.State) error 63 } 64 65 // StateRefresher is the interface that is implemented by something that 66 // can load a state. This might be refreshing it from a remote location or 67 // it might simply be reloading it from disk. 68 type StateRefresher interface { 69 RefreshState() error 70 } 71 72 // StatePersister is implemented to truly persist a state. Whereas StateWriter 73 // is allowed to perhaps be caching in memory, PersistState must write the 74 // state to some durable storage. 75 // 76 // If an object implements StatePersister in conjunction with StateReader 77 // and/or StateRefresher then these methods must coordinate such that 78 // subsequent reads after a persist return an updated value. 79 type StatePersister interface { 80 PersistState() error 81 } 82 83 // Locker is implemented to lock state during command execution. 84 // The info parameter can be recorded with the lock, but the 85 // implementation should not depend in its value. The string returned by Lock 86 // is an ID corresponding to the lock acquired, and must be passed to Unlock to 87 // ensure that the correct lock is being released. 88 // 89 // Lock and Unlock may return an error value of type LockError which in turn 90 // can contain the LockInfo of a conflicting lock. 91 type Locker interface { 92 Lock(info *LockInfo) (string, error) 93 Unlock(id string) error 94 } 95 96 // test hook to verify that LockWithContext has attempted a lock 97 var postLockHook func() 98 99 // Lock the state, using the provided context for timeout and cancellation. 100 // This backs off slightly to an upper limit. 101 func LockWithContext(ctx context.Context, s State, info *LockInfo) (string, error) { 102 delay := time.Second 103 maxDelay := 16 * time.Second 104 for { 105 id, err := s.Lock(info) 106 if err == nil { 107 return id, nil 108 } 109 110 le, ok := err.(*LockError) 111 if !ok { 112 // not a lock error, so we can't retry 113 return "", err 114 } 115 116 if le == nil || le.Info == nil || le.Info.ID == "" { 117 // If we dont' have a complete LockError, there's something wrong with the lock 118 return "", err 119 } 120 121 if postLockHook != nil { 122 postLockHook() 123 } 124 125 // there's an existing lock, wait and try again 126 select { 127 case <-ctx.Done(): 128 // return the last lock error with the info 129 return "", err 130 case <-time.After(delay): 131 if delay < maxDelay { 132 delay *= 2 133 } 134 } 135 } 136 } 137 138 // Generate a LockInfo structure, populating the required fields. 139 func NewLockInfo() *LockInfo { 140 // this doesn't need to be cryptographically secure, just unique. 141 // Using math/rand alleviates the need to check handle the read error. 142 // Use a uuid format to match other IDs used throughout Terraform. 143 buf := make([]byte, 16) 144 rngSource.Read(buf) 145 146 id, err := uuid.FormatUUID(buf) 147 if err != nil { 148 // this of course shouldn't happen 149 panic(err) 150 } 151 152 // don't error out on user and hostname, as we don't require them 153 userName := "" 154 if userInfo, err := user.Current(); err == nil { 155 userName = userInfo.Username 156 } 157 host, _ := os.Hostname() 158 159 info := &LockInfo{ 160 ID: id, 161 Who: fmt.Sprintf("%s@%s", userName, host), 162 Version: version.Version, 163 Created: time.Now().UTC(), 164 } 165 return info 166 } 167 168 // LockInfo stores lock metadata. 169 // 170 // Only Operation and Info are required to be set by the caller of Lock. 171 type LockInfo struct { 172 // Unique ID for the lock. NewLockInfo provides a random ID, but this may 173 // be overridden by the lock implementation. The final value if ID will be 174 // returned by the call to Lock. 175 ID string 176 177 // Terraform operation, provided by the caller. 178 Operation string 179 // Extra information to store with the lock, provided by the caller. 180 Info string 181 182 // user@hostname when available 183 Who string 184 // Terraform version 185 Version string 186 // Time that the lock was taken. 187 Created time.Time 188 189 // Path to the state file when applicable. Set by the Lock implementation. 190 Path string 191 } 192 193 // Err returns the lock info formatted in an error 194 func (l *LockInfo) Err() error { 195 return errors.New(l.String()) 196 } 197 198 // Marshal returns a string json representation of the LockInfo 199 func (l *LockInfo) Marshal() []byte { 200 js, err := json.Marshal(l) 201 if err != nil { 202 panic(err) 203 } 204 return js 205 } 206 207 // String return a multi-line string representation of LockInfo 208 func (l *LockInfo) String() string { 209 tmpl := `Lock Info: 210 ID: {{.ID}} 211 Path: {{.Path}} 212 Operation: {{.Operation}} 213 Who: {{.Who}} 214 Version: {{.Version}} 215 Created: {{.Created}} 216 Info: {{.Info}} 217 ` 218 219 t := template.Must(template.New("LockInfo").Parse(tmpl)) 220 var out bytes.Buffer 221 if err := t.Execute(&out, l); err != nil { 222 panic(err) 223 } 224 return out.String() 225 } 226 227 type LockError struct { 228 Info *LockInfo 229 Err error 230 } 231 232 func (e *LockError) Error() string { 233 var out []string 234 if e.Err != nil { 235 out = append(out, e.Err.Error()) 236 } 237 238 if e.Info != nil { 239 out = append(out, e.Info.String()) 240 } 241 return strings.Join(out, "\n") 242 }