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