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