github.com/Kevinklinger/open_terraform@v0.11.12-beta1/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  	uuid "github.com/hashicorp/go-uuid"
    13  	"github.com/hashicorp/terraform/state"
    14  	"github.com/hashicorp/terraform/state/remote"
    15  	tritonErrors "github.com/joyent/triton-go/errors"
    16  	"github.com/joyent/triton-go/storage"
    17  )
    18  
    19  const (
    20  	mantaDefaultRootStore = "/stor"
    21  	lockFileName          = "tflock"
    22  )
    23  
    24  type RemoteClient struct {
    25  	storageClient *storage.StorageClient
    26  	directoryName string
    27  	keyName       string
    28  	statePath     string
    29  }
    30  
    31  func (c *RemoteClient) Get() (*remote.Payload, error) {
    32  	output, err := c.storageClient.Objects().Get(context.Background(), &storage.GetObjectInput{
    33  		ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, c.keyName),
    34  	})
    35  	if err != nil {
    36  		if tritonErrors.IsResourceNotFound(err) {
    37  			return nil, nil
    38  		}
    39  		return nil, err
    40  	}
    41  	defer output.ObjectReader.Close()
    42  
    43  	buf := bytes.NewBuffer(nil)
    44  	if _, err := io.Copy(buf, output.ObjectReader); err != nil {
    45  		return nil, fmt.Errorf("Failed to read remote state: %s", err)
    46  	}
    47  
    48  	payload := &remote.Payload{
    49  		Data: buf.Bytes(),
    50  	}
    51  
    52  	// If there was no data, then return nil
    53  	if len(payload.Data) == 0 {
    54  		return nil, nil
    55  	}
    56  
    57  	return payload, nil
    58  
    59  }
    60  
    61  func (c *RemoteClient) Put(data []byte) error {
    62  	contentType := "application/json"
    63  	contentLength := int64(len(data))
    64  
    65  	params := &storage.PutObjectInput{
    66  		ContentType:   contentType,
    67  		ContentLength: uint64(contentLength),
    68  		ObjectPath:    path.Join(mantaDefaultRootStore, c.directoryName, c.keyName),
    69  		ObjectReader:  bytes.NewReader(data),
    70  	}
    71  
    72  	log.Printf("[DEBUG] Uploading remote state to Manta: %#v", params)
    73  	err := c.storageClient.Objects().Put(context.Background(), params)
    74  	if err != nil {
    75  		return err
    76  	}
    77  
    78  	return nil
    79  }
    80  
    81  func (c *RemoteClient) Delete() error {
    82  	err := c.storageClient.Objects().Delete(context.Background(), &storage.DeleteObjectInput{
    83  		ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, c.keyName),
    84  	})
    85  
    86  	return err
    87  }
    88  
    89  func (c *RemoteClient) Lock(info *state.LockInfo) (string, error) {
    90  	//At Joyent, we want to make sure that the State directory exists before we interact with it
    91  	//We don't expect users to have to create it in advance
    92  	//The order of operations of Backend State as follows:
    93  	// * Get - if this doesn't exist then we continue as though it's new
    94  	// * Lock - we make sure that the state directory exists as it's the entrance to writing to Manta
    95  	// * Put - put the state up there
    96  	// * Unlock - unlock the directory
    97  	//We can always guarantee that the user can put their state in the specified location because of this
    98  	err := c.storageClient.Dir().Put(context.Background(), &storage.PutDirectoryInput{
    99  		DirectoryName: path.Join(mantaDefaultRootStore, c.directoryName),
   100  	})
   101  	if err != nil {
   102  		return "", err
   103  	}
   104  
   105  	//firstly we want to check that a lock doesn't already exist
   106  	lockErr := &state.LockError{}
   107  	lockInfo, err := c.getLockInfo()
   108  	if err != nil {
   109  		if !tritonErrors.IsResourceNotFound(err) {
   110  			lockErr.Err = fmt.Errorf("failed to retrieve lock info: %s", err)
   111  			return "", lockErr
   112  		}
   113  	}
   114  
   115  	if lockInfo != nil {
   116  		lockErr := &state.LockError{
   117  			Err:  fmt.Errorf("A lock is already acquired"),
   118  			Info: lockInfo,
   119  		}
   120  		return "", lockErr
   121  	}
   122  
   123  	info.Path = path.Join(c.directoryName, lockFileName)
   124  
   125  	if info.ID == "" {
   126  		lockID, err := uuid.GenerateUUID()
   127  		if err != nil {
   128  			return "", err
   129  		}
   130  
   131  		info.ID = lockID
   132  	}
   133  
   134  	data := info.Marshal()
   135  
   136  	contentType := "application/json"
   137  	contentLength := int64(len(data))
   138  
   139  	params := &storage.PutObjectInput{
   140  		ContentType:   contentType,
   141  		ContentLength: uint64(contentLength),
   142  		ObjectPath:    path.Join(mantaDefaultRootStore, c.directoryName, lockFileName),
   143  		ObjectReader:  bytes.NewReader(data),
   144  		ForceInsert:   true,
   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  }