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  }