github.com/codeherentuk/terraform@v0.11.12-beta1/backend/remote-state/gcs/client.go (about) 1 package gcs 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "strconv" 8 9 "cloud.google.com/go/storage" 10 multierror "github.com/hashicorp/go-multierror" 11 "github.com/hashicorp/terraform/state" 12 "github.com/hashicorp/terraform/state/remote" 13 "golang.org/x/net/context" 14 ) 15 16 // remoteClient is used by "state/remote".State to read and write 17 // blobs representing state. 18 // Implements "state/remote".ClientLocker 19 type remoteClient struct { 20 storageContext context.Context 21 storageClient *storage.Client 22 bucketName string 23 stateFilePath string 24 lockFilePath string 25 encryptionKey []byte 26 } 27 28 func (c *remoteClient) Get() (payload *remote.Payload, err error) { 29 stateFileReader, err := c.stateFile().NewReader(c.storageContext) 30 if err != nil { 31 if err == storage.ErrObjectNotExist { 32 return nil, nil 33 } else { 34 return nil, fmt.Errorf("Failed to open state file at %v: %v", c.stateFileURL(), err) 35 } 36 } 37 defer stateFileReader.Close() 38 39 stateFileContents, err := ioutil.ReadAll(stateFileReader) 40 if err != nil { 41 return nil, fmt.Errorf("Failed to read state file from %v: %v", c.stateFileURL(), err) 42 } 43 44 stateFileAttrs, err := c.stateFile().Attrs(c.storageContext) 45 if err != nil { 46 return nil, fmt.Errorf("Failed to read state file attrs from %v: %v", c.stateFileURL(), err) 47 } 48 49 result := &remote.Payload{ 50 Data: stateFileContents, 51 MD5: stateFileAttrs.MD5, 52 } 53 54 return result, nil 55 } 56 57 func (c *remoteClient) Put(data []byte) error { 58 err := func() error { 59 stateFileWriter := c.stateFile().NewWriter(c.storageContext) 60 if _, err := stateFileWriter.Write(data); err != nil { 61 return err 62 } 63 return stateFileWriter.Close() 64 }() 65 if err != nil { 66 return fmt.Errorf("Failed to upload state to %v: %v", c.stateFileURL(), err) 67 } 68 69 return nil 70 } 71 72 func (c *remoteClient) Delete() error { 73 if err := c.stateFile().Delete(c.storageContext); err != nil { 74 return fmt.Errorf("Failed to delete state file %v: %v", c.stateFileURL(), err) 75 } 76 77 return nil 78 } 79 80 // Lock writes to a lock file, ensuring file creation. Returns the generation 81 // number, which must be passed to Unlock(). 82 func (c *remoteClient) Lock(info *state.LockInfo) (string, error) { 83 // update the path we're using 84 // we can't set the ID until the info is written 85 info.Path = c.lockFileURL() 86 87 infoJson, err := json.Marshal(info) 88 if err != nil { 89 return "", err 90 } 91 92 lockFile := c.lockFile() 93 w := lockFile.If(storage.Conditions{DoesNotExist: true}).NewWriter(c.storageContext) 94 err = func() error { 95 if _, err := w.Write(infoJson); err != nil { 96 return err 97 } 98 return w.Close() 99 }() 100 101 if err != nil { 102 return "", c.lockError(fmt.Errorf("writing %q failed: %v", c.lockFileURL(), err)) 103 } 104 105 info.ID = strconv.FormatInt(w.Attrs().Generation, 10) 106 107 return info.ID, nil 108 } 109 110 func (c *remoteClient) Unlock(id string) error { 111 gen, err := strconv.ParseInt(id, 10, 64) 112 if err != nil { 113 return err 114 } 115 116 if err := c.lockFile().If(storage.Conditions{GenerationMatch: gen}).Delete(c.storageContext); err != nil { 117 return c.lockError(err) 118 } 119 120 return nil 121 } 122 123 func (c *remoteClient) lockError(err error) *state.LockError { 124 lockErr := &state.LockError{ 125 Err: err, 126 } 127 128 info, infoErr := c.lockInfo() 129 if infoErr != nil { 130 lockErr.Err = multierror.Append(lockErr.Err, infoErr) 131 } else { 132 lockErr.Info = info 133 } 134 return lockErr 135 } 136 137 // lockInfo reads the lock file, parses its contents and returns the parsed 138 // LockInfo struct. 139 func (c *remoteClient) lockInfo() (*state.LockInfo, error) { 140 r, err := c.lockFile().NewReader(c.storageContext) 141 if err != nil { 142 return nil, err 143 } 144 defer r.Close() 145 146 rawData, err := ioutil.ReadAll(r) 147 if err != nil { 148 return nil, err 149 } 150 151 info := &state.LockInfo{} 152 if err := json.Unmarshal(rawData, info); err != nil { 153 return nil, err 154 } 155 156 // We use the Generation as the ID, so overwrite the ID in the json. 157 // This can't be written into the Info, since the generation isn't known 158 // until it's written. 159 attrs, err := c.lockFile().Attrs(c.storageContext) 160 if err != nil { 161 return nil, err 162 } 163 info.ID = strconv.FormatInt(attrs.Generation, 10) 164 165 return info, nil 166 } 167 168 func (c *remoteClient) stateFile() *storage.ObjectHandle { 169 h := c.storageClient.Bucket(c.bucketName).Object(c.stateFilePath) 170 if len(c.encryptionKey) > 0 { 171 return h.Key(c.encryptionKey) 172 } 173 return h 174 } 175 176 func (c *remoteClient) stateFileURL() string { 177 return fmt.Sprintf("gs://%v/%v", c.bucketName, c.stateFilePath) 178 } 179 180 func (c *remoteClient) lockFile() *storage.ObjectHandle { 181 return c.storageClient.Bucket(c.bucketName).Object(c.lockFilePath) 182 } 183 184 func (c *remoteClient) lockFileURL() string { 185 return fmt.Sprintf("gs://%v/%v", c.bucketName, c.lockFilePath) 186 }