github.com/hooklift/terraform@v0.11.0-beta1.0.20171117000744-6786c1361ffe/backend/remote-state/manta/client.go (about)

     1  package manta
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  	"path"
    11  
    12  	"strings"
    13  
    14  	uuid "github.com/hashicorp/go-uuid"
    15  	"github.com/hashicorp/terraform/state"
    16  	"github.com/hashicorp/terraform/state/remote"
    17  	"github.com/joyent/triton-go/storage"
    18  )
    19  
    20  const (
    21  	mantaDefaultRootStore = "/stor"
    22  	lockFileName          = "tflock"
    23  )
    24  
    25  type RemoteClient struct {
    26  	storageClient *storage.StorageClient
    27  	directoryName string
    28  	keyName       string
    29  	statePath     string
    30  }
    31  
    32  func (c *RemoteClient) Get() (*remote.Payload, error) {
    33  	output, err := c.storageClient.Objects().Get(context.Background(), &storage.GetObjectInput{
    34  		ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, c.keyName),
    35  	})
    36  	if err != nil {
    37  		if strings.Contains(err.Error(), "ResourceNotFound") {
    38  			return nil, nil
    39  		}
    40  		return nil, err
    41  	}
    42  	defer output.ObjectReader.Close()
    43  
    44  	buf := bytes.NewBuffer(nil)
    45  	if _, err := io.Copy(buf, output.ObjectReader); err != nil {
    46  		return nil, fmt.Errorf("Failed to read remote state: %s", err)
    47  	}
    48  
    49  	payload := &remote.Payload{
    50  		Data: buf.Bytes(),
    51  	}
    52  
    53  	// If there was no data, then return nil
    54  	if len(payload.Data) == 0 {
    55  		return nil, nil
    56  	}
    57  
    58  	return payload, nil
    59  
    60  }
    61  
    62  func (c *RemoteClient) Put(data []byte) error {
    63  	contentType := "application/json"
    64  	contentLength := int64(len(data))
    65  
    66  	params := &storage.PutObjectInput{
    67  		ContentType:   contentType,
    68  		ContentLength: uint64(contentLength),
    69  		ObjectPath:    path.Join(mantaDefaultRootStore, c.directoryName, c.keyName),
    70  		ObjectReader:  bytes.NewReader(data),
    71  	}
    72  
    73  	log.Printf("[DEBUG] Uploading remote state to Manta: %#v", params)
    74  	err := c.storageClient.Objects().Put(context.Background(), params)
    75  	if err != nil {
    76  		return err
    77  	}
    78  
    79  	return nil
    80  }
    81  
    82  func (c *RemoteClient) Delete() error {
    83  	err := c.storageClient.Objects().Delete(context.Background(), &storage.DeleteObjectInput{
    84  		ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, c.keyName),
    85  	})
    86  
    87  	return err
    88  }
    89  
    90  func (c *RemoteClient) Lock(info *state.LockInfo) (string, error) {
    91  
    92  	//At Joyent, we want to make sure that the State directory exists before we interact with it
    93  	//We don't expect users to have to create it in advance
    94  	//The order of operations of Backend State as follows:
    95  	// * Get - if this doesn't exist then we continue as though it's new
    96  	// * Lock - we make sure that the state directory exists as it's the entrance to writing to Manta
    97  	// * Put - put the state up there
    98  	// * Unlock - unlock the directory
    99  	//We can always guarantee that the user can put their state in the specified location because of this
   100  	err := c.storageClient.Dir().Put(context.Background(), &storage.PutDirectoryInput{
   101  		DirectoryName: path.Join(mantaDefaultRootStore, c.directoryName),
   102  	})
   103  	if err != nil {
   104  		return "", err
   105  	}
   106  
   107  	//firstly we want to check that a lock doesn't already exist
   108  	lockErr := &state.LockError{}
   109  	lockInfo, err := c.getLockInfo()
   110  	if err != nil {
   111  		if !strings.Contains(err.Error(), "ResourceNotFound") {
   112  			lockErr.Err = fmt.Errorf("failed to retrieve lock info: %s", err)
   113  			return "", lockErr
   114  		}
   115  	}
   116  
   117  	if lockInfo != nil {
   118  		lockErr := &state.LockError{
   119  			Err:  fmt.Errorf("A lock is already acquired"),
   120  			Info: lockInfo,
   121  		}
   122  		return "", lockErr
   123  	}
   124  
   125  	info.Path = path.Join(c.directoryName, lockFileName)
   126  
   127  	if info.ID == "" {
   128  		lockID, err := uuid.GenerateUUID()
   129  		if err != nil {
   130  			return "", err
   131  		}
   132  
   133  		info.ID = lockID
   134  	}
   135  
   136  	data := info.Marshal()
   137  
   138  	contentType := "application/json"
   139  	contentLength := int64(len(data))
   140  
   141  	params := &storage.PutObjectInput{
   142  		ContentType:   contentType,
   143  		ContentLength: uint64(contentLength),
   144  		ObjectPath:    path.Join(mantaDefaultRootStore, c.directoryName, lockFileName),
   145  		ObjectReader:  bytes.NewReader(data),
   146  	}
   147  
   148  	log.Printf("[DEBUG] Creating manta state lock: %#v", params)
   149  	err = c.storageClient.Objects().Put(context.Background(), params)
   150  	if err != nil {
   151  		return "", err
   152  	}
   153  
   154  	return info.ID, nil
   155  }
   156  
   157  func (c *RemoteClient) Unlock(id string) error {
   158  	lockErr := &state.LockError{}
   159  
   160  	lockInfo, err := c.getLockInfo()
   161  	if err != nil {
   162  		lockErr.Err = fmt.Errorf("failed to retrieve lock info: %s", err)
   163  		return lockErr
   164  	}
   165  	lockErr.Info = lockInfo
   166  
   167  	if lockInfo.ID != id {
   168  		lockErr.Err = fmt.Errorf("lock id %q does not match existing lock", id)
   169  		return lockErr
   170  	}
   171  
   172  	err = c.storageClient.Objects().Delete(context.Background(), &storage.DeleteObjectInput{
   173  		ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, lockFileName),
   174  	})
   175  
   176  	return err
   177  }
   178  
   179  func (c *RemoteClient) getLockInfo() (*state.LockInfo, error) {
   180  	output, err := c.storageClient.Objects().Get(context.Background(), &storage.GetObjectInput{
   181  		ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, lockFileName),
   182  	})
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  
   187  	defer output.ObjectReader.Close()
   188  
   189  	buf := bytes.NewBuffer(nil)
   190  	if _, err := io.Copy(buf, output.ObjectReader); err != nil {
   191  		return nil, fmt.Errorf("Failed to read lock info: %s", err)
   192  	}
   193  
   194  	lockInfo := &state.LockInfo{}
   195  	err = json.Unmarshal(buf.Bytes(), lockInfo)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  
   200  	return lockInfo, nil
   201  }