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