github.com/nevins-b/terraform@v0.3.8-0.20170215184714-bbae22007d5a/backend/remote-state/consul/client.go (about) 1 package consul 2 3 import ( 4 "crypto/md5" 5 "encoding/json" 6 "errors" 7 "time" 8 9 consulapi "github.com/hashicorp/consul/api" 10 "github.com/hashicorp/errwrap" 11 multierror "github.com/hashicorp/go-multierror" 12 "github.com/hashicorp/terraform/state" 13 "github.com/hashicorp/terraform/state/remote" 14 ) 15 16 const ( 17 lockSuffix = "/.lock" 18 lockInfoSuffix = "/.lockinfo" 19 ) 20 21 // RemoteClient is a remote client that stores data in Consul. 22 type RemoteClient struct { 23 Client *consulapi.Client 24 Path string 25 26 consulLock *consulapi.Lock 27 lockCh <-chan struct{} 28 } 29 30 func (c *RemoteClient) Get() (*remote.Payload, error) { 31 pair, _, err := c.Client.KV().Get(c.Path, nil) 32 if err != nil { 33 return nil, err 34 } 35 if pair == nil { 36 return nil, nil 37 } 38 39 md5 := md5.Sum(pair.Value) 40 return &remote.Payload{ 41 Data: pair.Value, 42 MD5: md5[:], 43 }, nil 44 } 45 46 func (c *RemoteClient) Put(data []byte) error { 47 kv := c.Client.KV() 48 _, err := kv.Put(&consulapi.KVPair{ 49 Key: c.Path, 50 Value: data, 51 }, nil) 52 return err 53 } 54 55 func (c *RemoteClient) Delete() error { 56 kv := c.Client.KV() 57 _, err := kv.Delete(c.Path, nil) 58 return err 59 } 60 61 func (c *RemoteClient) putLockInfo(info string) error { 62 li := &state.LockInfo{ 63 Path: c.Path, 64 Created: time.Now().UTC(), 65 Info: info, 66 } 67 68 js, err := json.Marshal(li) 69 if err != nil { 70 return err 71 } 72 73 kv := c.Client.KV() 74 _, err = kv.Put(&consulapi.KVPair{ 75 Key: c.Path + lockInfoSuffix, 76 Value: js, 77 }, nil) 78 79 return err 80 } 81 82 func (c *RemoteClient) getLockInfo() (*state.LockInfo, error) { 83 path := c.Path + lockInfoSuffix 84 pair, _, err := c.Client.KV().Get(path, nil) 85 if err != nil { 86 return nil, err 87 } 88 if pair == nil { 89 return nil, nil 90 } 91 92 li := &state.LockInfo{} 93 err = json.Unmarshal(pair.Value, li) 94 if err != nil { 95 return nil, errwrap.Wrapf("error unmarshaling lock info: {{err}}", err) 96 } 97 98 return li, nil 99 } 100 101 func (c *RemoteClient) Lock(info string) error { 102 select { 103 case <-c.lockCh: 104 // We had a lock, but lost it. 105 // Since we typically only call lock once, we shouldn't ever see this. 106 return errors.New("lost consul lock") 107 default: 108 if c.lockCh != nil { 109 // we have an active lock already 110 return nil 111 } 112 } 113 114 if c.consulLock == nil { 115 opts := &consulapi.LockOptions{ 116 Key: c.Path + lockSuffix, 117 // We currently don't procide any options to block terraform and 118 // retry lock acquisition, but we can wait briefly in case the 119 // lock is about to be freed. 120 LockWaitTime: time.Second, 121 LockTryOnce: true, 122 } 123 124 lock, err := c.Client.LockOpts(opts) 125 if err != nil { 126 return nil 127 } 128 129 c.consulLock = lock 130 } 131 132 lockCh, err := c.consulLock.Lock(make(chan struct{})) 133 if err != nil { 134 return err 135 } 136 137 if lockCh == nil { 138 lockInfo, e := c.getLockInfo() 139 if e != nil { 140 return e 141 } 142 return lockInfo.Err() 143 } 144 145 c.lockCh = lockCh 146 147 err = c.putLockInfo(info) 148 if err != nil { 149 err = multierror.Append(err, c.Unlock()) 150 return err 151 } 152 153 return nil 154 } 155 156 func (c *RemoteClient) Unlock() error { 157 if c.consulLock == nil || c.lockCh == nil { 158 return nil 159 } 160 161 select { 162 case <-c.lockCh: 163 return errors.New("consul lock was lost") 164 default: 165 } 166 167 err := c.consulLock.Unlock() 168 c.lockCh = nil 169 170 kv := c.Client.KV() 171 _, delErr := kv.Delete(c.Path+lockInfoSuffix, nil) 172 if delErr != nil { 173 err = multierror.Append(err, delErr) 174 } 175 176 return err 177 }