github.com/jzbruno/terraform@v0.10.3-0.20180104230435-18975d727047/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  	//At Joyent, we want to make sure that the State directory exists before we interact with it
    92  	//We don't expect users to have to create it in advance
    93  	//The order of operations of Backend State as follows:
    94  	// * Get - if this doesn't exist then we continue as though it's new
    95  	// * Lock - we make sure that the state directory exists as it's the entrance to writing to Manta
    96  	// * Put - put the state up there
    97  	// * Unlock - unlock the directory
    98  	//We can always guarantee that the user can put their state in the specified location because of this
    99  	err := c.storageClient.Dir().Put(context.Background(), &storage.PutDirectoryInput{
   100  		DirectoryName: path.Join(mantaDefaultRootStore, c.directoryName),
   101  	})
   102  	if err != nil {
   103  		return "", err
   104  	}
   105  
   106  	//firstly we want to check that a lock doesn't already exist
   107  	lockErr := &state.LockError{}
   108  	lockInfo, err := c.getLockInfo()
   109  	if err != nil {
   110  		if !strings.Contains(err.Error(), "ResourceNotFound") {
   111  			lockErr.Err = fmt.Errorf("failed to retrieve lock info: %s", err)
   112  			return "", lockErr
   113  		}
   114  	}
   115  
   116  	if lockInfo != nil {
   117  		lockErr := &state.LockError{
   118  			Err:  fmt.Errorf("A lock is already acquired"),
   119  			Info: lockInfo,
   120  		}
   121  		return "", lockErr
   122  	}
   123  
   124  	info.Path = path.Join(c.directoryName, lockFileName)
   125  
   126  	if info.ID == "" {
   127  		lockID, err := uuid.GenerateUUID()
   128  		if err != nil {
   129  			return "", err
   130  		}
   131  
   132  		info.ID = lockID
   133  	}
   134  
   135  	data := info.Marshal()
   136  
   137  	contentType := "application/json"
   138  	contentLength := int64(len(data))
   139  
   140  	params := &storage.PutObjectInput{
   141  		ContentType:   contentType,
   142  		ContentLength: uint64(contentLength),
   143  		ObjectPath:    path.Join(mantaDefaultRootStore, c.directoryName, lockFileName),
   144  		ObjectReader:  bytes.NewReader(data),
   145  	}
   146  
   147  	log.Printf("[DEBUG] Creating manta state lock: %#v", params)
   148  	err = c.storageClient.Objects().Put(context.Background(), params)
   149  	if err != nil {
   150  		return "", err
   151  	}
   152  
   153  	return info.ID, nil
   154  }
   155  
   156  func (c *RemoteClient) Unlock(id string) error {
   157  	lockErr := &state.LockError{}
   158  
   159  	lockInfo, err := c.getLockInfo()
   160  	if err != nil {
   161  		lockErr.Err = fmt.Errorf("failed to retrieve lock info: %s", err)
   162  		return lockErr
   163  	}
   164  	lockErr.Info = lockInfo
   165  
   166  	if lockInfo.ID != id {
   167  		lockErr.Err = fmt.Errorf("lock id %q does not match existing lock", id)
   168  		return lockErr
   169  	}
   170  
   171  	err = c.storageClient.Objects().Delete(context.Background(), &storage.DeleteObjectInput{
   172  		ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, lockFileName),
   173  	})
   174  
   175  	return err
   176  }
   177  
   178  func (c *RemoteClient) getLockInfo() (*state.LockInfo, error) {
   179  	output, err := c.storageClient.Objects().Get(context.Background(), &storage.GetObjectInput{
   180  		ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, lockFileName),
   181  	})
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	defer output.ObjectReader.Close()
   187  
   188  	buf := bytes.NewBuffer(nil)
   189  	if _, err := io.Copy(buf, output.ObjectReader); err != nil {
   190  		return nil, fmt.Errorf("Failed to read lock info: %s", err)
   191  	}
   192  
   193  	lockInfo := &state.LockInfo{}
   194  	err = json.Unmarshal(buf.Bytes(), lockInfo)
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  
   199  	return lockInfo, nil
   200  }