github.com/tompao/terraform@v0.6.10-0.20180215233341-e41b29d0961b/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  	infoJson, err := json.Marshal(info)
    84  	if err != nil {
    85  		return "", err
    86  	}
    87  
    88  	lockFile := c.lockFile()
    89  	w := lockFile.If(storage.Conditions{DoesNotExist: true}).NewWriter(c.storageContext)
    90  	err = func() error {
    91  		if _, err := w.Write(infoJson); err != nil {
    92  			return err
    93  		}
    94  		return w.Close()
    95  	}()
    96  	if err != nil {
    97  		return "", c.lockError(fmt.Errorf("writing %q failed: %v", c.lockFileURL(), err))
    98  	}
    99  
   100  	info.ID = strconv.FormatInt(w.Attrs().Generation, 10)
   101  	info.Path = c.lockFileURL()
   102  
   103  	return info.ID, nil
   104  }
   105  
   106  func (c *remoteClient) Unlock(id string) error {
   107  	gen, err := strconv.ParseInt(id, 10, 64)
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	if err := c.lockFile().If(storage.Conditions{GenerationMatch: gen}).Delete(c.storageContext); err != nil {
   113  		return c.lockError(err)
   114  	}
   115  
   116  	return nil
   117  }
   118  
   119  func (c *remoteClient) lockError(err error) *state.LockError {
   120  	lockErr := &state.LockError{
   121  		Err: err,
   122  	}
   123  
   124  	info, infoErr := c.lockInfo()
   125  	if infoErr != nil {
   126  		lockErr.Err = multierror.Append(lockErr.Err, infoErr)
   127  	} else {
   128  		lockErr.Info = info
   129  	}
   130  	return lockErr
   131  }
   132  
   133  // lockInfo reads the lock file, parses its contents and returns the parsed
   134  // LockInfo struct.
   135  func (c *remoteClient) lockInfo() (*state.LockInfo, error) {
   136  	r, err := c.lockFile().NewReader(c.storageContext)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  	defer r.Close()
   141  
   142  	rawData, err := ioutil.ReadAll(r)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	info := &state.LockInfo{}
   148  	if err := json.Unmarshal(rawData, info); err != nil {
   149  		return nil, err
   150  	}
   151  
   152  	return info, nil
   153  }
   154  
   155  func (c *remoteClient) stateFile() *storage.ObjectHandle {
   156  	h := c.storageClient.Bucket(c.bucketName).Object(c.stateFilePath)
   157  	if len(c.encryptionKey) > 0 {
   158  		return h.Key(c.encryptionKey)
   159  	}
   160  	return h
   161  }
   162  
   163  func (c *remoteClient) stateFileURL() string {
   164  	return fmt.Sprintf("gs://%v/%v", c.bucketName, c.stateFilePath)
   165  }
   166  
   167  func (c *remoteClient) lockFile() *storage.ObjectHandle {
   168  	return c.storageClient.Bucket(c.bucketName).Object(c.lockFilePath)
   169  }
   170  
   171  func (c *remoteClient) lockFileURL() string {
   172  	return fmt.Sprintf("gs://%v/%v", c.bucketName, c.lockFilePath)
   173  }