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  }