github.com/profects/terraform@v0.9.0-beta1.0.20170227135739-92d4809db30d/state/state.go (about) 1 package state 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "math/rand" 9 "os" 10 "os/user" 11 "strings" 12 "text/template" 13 "time" 14 15 uuid "github.com/hashicorp/go-uuid" 16 "github.com/hashicorp/terraform/terraform" 17 ) 18 19 var rngSource *rand.Rand 20 21 func init() { 22 rngSource = rand.New(rand.NewSource(time.Now().UnixNano())) 23 } 24 25 // State is the collection of all state interfaces. 26 type State interface { 27 StateReader 28 StateWriter 29 StateRefresher 30 StatePersister 31 } 32 33 // StateReader is the interface for things that can return a state. Retrieving 34 // the state here must not error. Loading the state fresh (an operation that 35 // can likely error) should be implemented by RefreshState. If a state hasn't 36 // been loaded yet, it is okay for State to return nil. 37 type StateReader interface { 38 State() *terraform.State 39 } 40 41 // StateWriter is the interface that must be implemented by something that 42 // can write a state. Writing the state can be cached or in-memory, as 43 // full persistence should be implemented by StatePersister. 44 type StateWriter interface { 45 WriteState(*terraform.State) error 46 } 47 48 // StateRefresher is the interface that is implemented by something that 49 // can load a state. This might be refreshing it from a remote location or 50 // it might simply be reloading it from disk. 51 type StateRefresher interface { 52 RefreshState() error 53 } 54 55 // StatePersister is implemented to truly persist a state. Whereas StateWriter 56 // is allowed to perhaps be caching in memory, PersistState must write the 57 // state to some durable storage. 58 type StatePersister interface { 59 PersistState() error 60 } 61 62 // Locker is implemented to lock state during command execution. 63 // The info parameter can be recorded with the lock, but the 64 // implementation should not depend in its value. The string returned by Lock 65 // is an ID corresponding to the lock acquired, and must be passed to Unlock to 66 // ensure that the correct lock is being released. 67 // 68 // Lock and Unlock may return an error value of type LockError which in turn 69 // can contain the LockInfo of a conflicting lock. 70 type Locker interface { 71 Lock(info *LockInfo) (string, error) 72 Unlock(id string) error 73 } 74 75 // Generate a LockInfo structure, populating the required fields. 76 func NewLockInfo() *LockInfo { 77 // this doesn't need to be cryptographically secure, just unique. 78 // Using math/rand alleviates the need to check handle the read error. 79 // Use a uuid format to match other IDs used throughout Terraform. 80 buf := make([]byte, 16) 81 rngSource.Read(buf) 82 83 id, err := uuid.FormatUUID(buf) 84 if err != nil { 85 // this of course shouldn't happen 86 panic(err) 87 } 88 89 // don't error out on user and hostname, as we don't require them 90 userName := "" 91 if userInfo, err := user.Current(); err == nil { 92 userName = userInfo.Username 93 } 94 host, _ := os.Hostname() 95 96 info := &LockInfo{ 97 ID: id, 98 Who: fmt.Sprintf("%s@%s", userName, host), 99 Version: terraform.Version, 100 Created: time.Now().UTC(), 101 } 102 return info 103 } 104 105 // LockInfo stores lock metadata. 106 // 107 // Only Operation and Info are required to be set by the caller of Lock. 108 type LockInfo struct { 109 // Unique ID for the lock. NewLockInfo provides a random ID, but this may 110 // be overridden by the lock implementation. The final value if ID will be 111 // returned by the call to Lock. 112 ID string 113 114 // Terraform operation, provided by the caller. 115 Operation string 116 // Extra information to store with the lock, provided by the caller. 117 Info string 118 119 // user@hostname when available 120 Who string 121 // Terraform version 122 Version string 123 // Time that the lock was taken. 124 Created time.Time 125 126 // Path to the state file when applicable. Set by the Lock implementation. 127 Path string 128 } 129 130 // Err returns the lock info formatted in an error 131 func (l *LockInfo) Err() error { 132 return errors.New(l.String()) 133 } 134 135 // Marshal returns a string json representation of the LockInfo 136 func (l *LockInfo) Marshal() []byte { 137 js, err := json.Marshal(l) 138 if err != nil { 139 panic(err) 140 } 141 return js 142 } 143 144 // String return a multi-line string representation of LockInfo 145 func (l *LockInfo) String() string { 146 tmpl := `Lock Info: 147 ID: {{.ID}} 148 Path: {{.Path}} 149 Operation: {{.Operation}} 150 Who: {{.Who}} 151 Version: {{.Version}} 152 Created: {{.Created}} 153 Info: {{.Info}} 154 ` 155 156 t := template.Must(template.New("LockInfo").Parse(tmpl)) 157 var out bytes.Buffer 158 if err := t.Execute(&out, l); err != nil { 159 panic(err) 160 } 161 return out.String() 162 } 163 164 type LockError struct { 165 Info *LockInfo 166 Err error 167 } 168 169 func (e *LockError) Error() string { 170 var out []string 171 if e.Err != nil { 172 out = append(out, e.Err.Error()) 173 } 174 175 if e.Info != nil { 176 out = append(out, e.Info.String()) 177 } 178 return strings.Join(out, "\n") 179 }