github.com/hooklift/terraform@v0.11.0-beta1.0.20171117000744-6786c1361ffe/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 } 26 27 func (c *remoteClient) Get() (payload *remote.Payload, err error) { 28 stateFileReader, err := c.stateFile().NewReader(c.storageContext) 29 if err != nil { 30 if err == storage.ErrObjectNotExist { 31 return nil, nil 32 } else { 33 return nil, fmt.Errorf("Failed to open state file at %v: %v", c.stateFileURL(), err) 34 } 35 } 36 defer stateFileReader.Close() 37 38 stateFileContents, err := ioutil.ReadAll(stateFileReader) 39 if err != nil { 40 return nil, fmt.Errorf("Failed to read state file from %v: %v", c.stateFileURL(), err) 41 } 42 43 stateFileAttrs, err := c.stateFile().Attrs(c.storageContext) 44 if err != nil { 45 return nil, fmt.Errorf("Failed to read state file attrs from %v: %v", c.stateFileURL(), err) 46 } 47 48 result := &remote.Payload{ 49 Data: stateFileContents, 50 MD5: stateFileAttrs.MD5, 51 } 52 53 return result, nil 54 } 55 56 func (c *remoteClient) Put(data []byte) error { 57 err := func() error { 58 stateFileWriter := c.stateFile().NewWriter(c.storageContext) 59 if _, err := stateFileWriter.Write(data); err != nil { 60 return err 61 } 62 return stateFileWriter.Close() 63 }() 64 if err != nil { 65 return fmt.Errorf("Failed to upload state to %v: %v", c.stateFileURL(), err) 66 } 67 68 return nil 69 } 70 71 func (c *remoteClient) Delete() error { 72 if err := c.stateFile().Delete(c.storageContext); err != nil { 73 return fmt.Errorf("Failed to delete state file %v: %v", c.stateFileURL(), err) 74 } 75 76 return nil 77 } 78 79 // Lock writes to a lock file, ensuring file creation. Returns the generation 80 // number, which must be passed to Unlock(). 81 func (c *remoteClient) Lock(info *state.LockInfo) (string, error) { 82 infoJson, err := json.Marshal(info) 83 if err != nil { 84 return "", err 85 } 86 87 lockFile := c.lockFile() 88 w := lockFile.If(storage.Conditions{DoesNotExist: true}).NewWriter(c.storageContext) 89 err = func() error { 90 if _, err := w.Write(infoJson); err != nil { 91 return err 92 } 93 return w.Close() 94 }() 95 if err != nil { 96 return "", c.lockError(fmt.Errorf("writing %q failed: %v", c.lockFileURL(), err)) 97 } 98 99 info.ID = strconv.FormatInt(w.Attrs().Generation, 10) 100 info.Path = c.lockFileURL() 101 102 return info.ID, nil 103 } 104 105 func (c *remoteClient) Unlock(id string) error { 106 gen, err := strconv.ParseInt(id, 10, 64) 107 if err != nil { 108 return err 109 } 110 111 if err := c.lockFile().If(storage.Conditions{GenerationMatch: gen}).Delete(c.storageContext); err != nil { 112 return c.lockError(err) 113 } 114 115 return nil 116 } 117 118 func (c *remoteClient) lockError(err error) *state.LockError { 119 lockErr := &state.LockError{ 120 Err: err, 121 } 122 123 info, infoErr := c.lockInfo() 124 if infoErr != nil { 125 lockErr.Err = multierror.Append(lockErr.Err, infoErr) 126 } else { 127 lockErr.Info = info 128 } 129 return lockErr 130 } 131 132 // lockInfo reads the lock file, parses its contents and returns the parsed 133 // LockInfo struct. 134 func (c *remoteClient) lockInfo() (*state.LockInfo, error) { 135 r, err := c.lockFile().NewReader(c.storageContext) 136 if err != nil { 137 return nil, err 138 } 139 defer r.Close() 140 141 rawData, err := ioutil.ReadAll(r) 142 if err != nil { 143 return nil, err 144 } 145 146 info := &state.LockInfo{} 147 if err := json.Unmarshal(rawData, info); err != nil { 148 return nil, err 149 } 150 151 return info, nil 152 } 153 154 func (c *remoteClient) stateFile() *storage.ObjectHandle { 155 return c.storageClient.Bucket(c.bucketName).Object(c.stateFilePath) 156 } 157 158 func (c *remoteClient) stateFileURL() string { 159 return fmt.Sprintf("gs://%v/%v", c.bucketName, c.stateFilePath) 160 } 161 162 func (c *remoteClient) lockFile() *storage.ObjectHandle { 163 return c.storageClient.Bucket(c.bucketName).Object(c.lockFilePath) 164 } 165 166 func (c *remoteClient) lockFileURL() string { 167 return fmt.Sprintf("gs://%v/%v", c.bucketName, c.lockFilePath) 168 }