2017-07-19 11:07:24 +02:00
|
|
|
package gcloud
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2017-09-07 13:10:56 +02:00
|
|
|
"io/ioutil"
|
|
|
|
|
|
|
|
"cloud.google.com/go/storage"
|
2017-07-19 11:07:24 +02:00
|
|
|
uuid "github.com/hashicorp/go-uuid"
|
|
|
|
"github.com/hashicorp/terraform/state"
|
|
|
|
"github.com/hashicorp/terraform/state/remote"
|
|
|
|
"golang.org/x/net/context"
|
|
|
|
)
|
|
|
|
|
|
|
|
type RemoteClient struct {
|
|
|
|
storageContext context.Context
|
|
|
|
storageClient *storage.Client
|
|
|
|
bucketName string
|
|
|
|
stateFilePath string
|
|
|
|
lockFilePath string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RemoteClient) Get() (payload *remote.Payload, err error) {
|
2017-09-07 13:16:53 +02:00
|
|
|
stateFileReader, err := c.stateFile().NewReader(c.storageContext)
|
2017-07-19 11:07:24 +02:00
|
|
|
if err != nil {
|
|
|
|
if err == storage.ErrObjectNotExist {
|
|
|
|
return nil, nil
|
|
|
|
} else {
|
2017-09-07 13:16:53 +02:00
|
|
|
return nil, fmt.Errorf("Failed to open state file at %v: %v", c.stateFileURL(), err)
|
2017-07-19 11:07:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
defer stateFileReader.Close()
|
|
|
|
|
|
|
|
stateFileContents, err := ioutil.ReadAll(stateFileReader)
|
|
|
|
if err != nil {
|
2017-09-07 13:16:53 +02:00
|
|
|
return nil, fmt.Errorf("Failed to read state file from %v: %v", c.stateFileURL(), err)
|
2017-07-19 11:07:24 +02:00
|
|
|
}
|
|
|
|
|
2017-09-07 13:16:53 +02:00
|
|
|
stateFileAttrs, err := c.stateFile().Attrs(c.storageContext)
|
2017-07-19 11:07:24 +02:00
|
|
|
if err != nil {
|
2017-09-07 13:16:53 +02:00
|
|
|
return nil, fmt.Errorf("Failed to read state file attrs from %v: %v", c.stateFileURL(), err)
|
2017-07-19 11:07:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
result := &remote.Payload{
|
|
|
|
Data: stateFileContents,
|
|
|
|
MD5: stateFileAttrs.MD5,
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RemoteClient) Put(data []byte) error {
|
2017-09-07 13:16:53 +02:00
|
|
|
stateFileWriter := c.stateFile().NewWriter(c.storageContext)
|
2017-07-19 11:07:24 +02:00
|
|
|
|
|
|
|
stateFileWriter.Write(data)
|
|
|
|
err := stateFileWriter.Close()
|
|
|
|
|
|
|
|
if err != nil {
|
2017-09-07 12:56:17 +02:00
|
|
|
return fmt.Errorf("Failed to upload state to %v: %v", c.stateFileURL(), err)
|
2017-07-19 11:07:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RemoteClient) Delete() error {
|
2017-09-07 13:16:53 +02:00
|
|
|
err := c.stateFile().Delete(c.storageContext)
|
2017-07-19 11:07:24 +02:00
|
|
|
|
|
|
|
if err != nil {
|
2017-09-07 12:56:17 +02:00
|
|
|
return fmt.Errorf("Failed to delete state file %v: %v", c.stateFileURL(), err)
|
2017-07-19 11:07:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RemoteClient) Lock(info *state.LockInfo) (string, error) {
|
|
|
|
if info.ID == "" {
|
|
|
|
lockID, err := uuid.GenerateUUID()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
info.ID = lockID
|
|
|
|
}
|
|
|
|
|
2017-09-07 12:56:17 +02:00
|
|
|
info.Path = c.lockFileURL()
|
2017-07-19 11:07:24 +02:00
|
|
|
|
|
|
|
infoJson, err := json.Marshal(info)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2017-09-07 13:16:53 +02:00
|
|
|
writer := c.lockFile().If(storage.Conditions{DoesNotExist: true}).NewWriter(c.storageContext)
|
2017-07-19 11:07:24 +02:00
|
|
|
writer.Write(infoJson)
|
|
|
|
if err := writer.Close(); err != nil {
|
|
|
|
return "", fmt.Errorf("Error while saving lock file (%v): %v", info.Path, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return info.ID, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RemoteClient) Unlock(id string) error {
|
|
|
|
lockErr := &state.LockError{}
|
|
|
|
|
2017-09-07 13:16:53 +02:00
|
|
|
lockFileReader, err := c.lockFile().NewReader(c.storageContext)
|
2017-07-19 11:07:24 +02:00
|
|
|
if err != nil {
|
2017-09-07 13:16:53 +02:00
|
|
|
lockErr.Err = fmt.Errorf("Failed to retrieve lock info (%v): %v", c.lockFileURL(), err)
|
2017-07-19 11:07:24 +02:00
|
|
|
return lockErr
|
|
|
|
}
|
|
|
|
defer lockFileReader.Close()
|
|
|
|
|
|
|
|
lockFileContents, err := ioutil.ReadAll(lockFileReader)
|
|
|
|
if err != nil {
|
2017-09-07 13:16:53 +02:00
|
|
|
lockErr.Err = fmt.Errorf("Failed to retrieve lock info (%v): %v", c.lockFileURL(), err)
|
2017-07-19 11:07:24 +02:00
|
|
|
return lockErr
|
|
|
|
}
|
|
|
|
|
|
|
|
lockInfo := &state.LockInfo{}
|
|
|
|
err = json.Unmarshal(lockFileContents, lockInfo)
|
|
|
|
if err != nil {
|
2017-09-07 13:16:53 +02:00
|
|
|
lockErr.Err = fmt.Errorf("Failed to unmarshal lock info (%v): %v", c.lockFileURL(), err)
|
2017-07-19 11:07:24 +02:00
|
|
|
return lockErr
|
|
|
|
}
|
|
|
|
|
|
|
|
lockErr.Info = lockInfo
|
|
|
|
|
|
|
|
if lockInfo.ID != id {
|
|
|
|
lockErr.Err = fmt.Errorf("Lock id %q does not match existing lock", id)
|
|
|
|
return lockErr
|
|
|
|
}
|
|
|
|
|
|
|
|
lockFileAttrs, err := lockFile.Attrs(c.storageContext)
|
|
|
|
if err != nil {
|
2017-09-07 13:16:53 +02:00
|
|
|
lockErr.Err = fmt.Errorf("Failed to fetch lock file attrs (%v): %v", c.lockFileURL(), err)
|
2017-07-19 11:07:24 +02:00
|
|
|
return lockErr
|
|
|
|
}
|
|
|
|
|
|
|
|
err = lockFile.If(storage.Conditions{GenerationMatch: lockFileAttrs.Generation}).Delete(c.storageContext)
|
|
|
|
if err != nil {
|
2017-09-07 13:16:53 +02:00
|
|
|
lockErr.Err = fmt.Errorf("Failed to delete lock file (%v): %v", c.lockFileURL(), err)
|
2017-07-19 11:07:24 +02:00
|
|
|
return lockErr
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-09-07 13:16:53 +02:00
|
|
|
func (c *RemoteClient) stateFile() *storage.ObjectHandle {
|
|
|
|
return c.storageClient.Bucket(c.bucketName).Object(c.stateFilePath)
|
|
|
|
}
|
|
|
|
|
2017-09-07 12:56:17 +02:00
|
|
|
func (c *RemoteClient) stateFileURL() string {
|
2017-07-19 11:07:24 +02:00
|
|
|
return fmt.Sprintf("gs://%v/%v", c.bucketName, c.stateFilePath)
|
|
|
|
}
|
|
|
|
|
2017-09-07 13:16:53 +02:00
|
|
|
func (c *RemoteClient) lockFile() *storage.ObjectHandle {
|
|
|
|
return c.storageClient.Bucket(c.bucketName).Object(c.lockFilePath)
|
|
|
|
}
|
|
|
|
|
2017-09-07 12:56:17 +02:00
|
|
|
func (c *RemoteClient) lockFileURL() string {
|
2017-07-19 11:07:24 +02:00
|
|
|
return fmt.Sprintf("gs://%v/%v", c.bucketName, c.lockFilePath)
|
|
|
|
}
|