202 lines
5.0 KiB
Go
202 lines
5.0 KiB
Go
|
package manta
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"context"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"log"
|
||
|
"path"
|
||
|
|
||
|
"strings"
|
||
|
|
||
|
uuid "github.com/hashicorp/go-uuid"
|
||
|
"github.com/hashicorp/terraform/state"
|
||
|
"github.com/hashicorp/terraform/state/remote"
|
||
|
"github.com/joyent/triton-go/storage"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
mantaDefaultRootStore = "/stor"
|
||
|
lockFileName = "tflock"
|
||
|
)
|
||
|
|
||
|
type RemoteClient struct {
|
||
|
storageClient *storage.StorageClient
|
||
|
directoryName string
|
||
|
keyName string
|
||
|
statePath string
|
||
|
}
|
||
|
|
||
|
func (c *RemoteClient) Get() (*remote.Payload, error) {
|
||
|
output, err := c.storageClient.Objects().Get(context.Background(), &storage.GetObjectInput{
|
||
|
ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, c.keyName),
|
||
|
})
|
||
|
if err != nil {
|
||
|
if strings.Contains(err.Error(), "ResourceNotFound") {
|
||
|
return nil, nil
|
||
|
}
|
||
|
return nil, err
|
||
|
}
|
||
|
defer output.ObjectReader.Close()
|
||
|
|
||
|
buf := bytes.NewBuffer(nil)
|
||
|
if _, err := io.Copy(buf, output.ObjectReader); err != nil {
|
||
|
return nil, fmt.Errorf("Failed to read remote state: %s", err)
|
||
|
}
|
||
|
|
||
|
payload := &remote.Payload{
|
||
|
Data: buf.Bytes(),
|
||
|
}
|
||
|
|
||
|
// If there was no data, then return nil
|
||
|
if len(payload.Data) == 0 {
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
return payload, nil
|
||
|
|
||
|
}
|
||
|
|
||
|
func (c *RemoteClient) Put(data []byte) error {
|
||
|
contentType := "application/json"
|
||
|
contentLength := int64(len(data))
|
||
|
|
||
|
params := &storage.PutObjectInput{
|
||
|
ContentType: contentType,
|
||
|
ContentLength: uint64(contentLength),
|
||
|
ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, c.keyName),
|
||
|
ObjectReader: bytes.NewReader(data),
|
||
|
}
|
||
|
|
||
|
log.Printf("[DEBUG] Uploading remote state to Manta: %#v", params)
|
||
|
err := c.storageClient.Objects().Put(context.Background(), params)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *RemoteClient) Delete() error {
|
||
|
err := c.storageClient.Objects().Delete(context.Background(), &storage.DeleteObjectInput{
|
||
|
ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, c.keyName),
|
||
|
})
|
||
|
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func (c *RemoteClient) Lock(info *state.LockInfo) (string, error) {
|
||
|
|
||
|
//At Joyent, we want to make sure that the State directory exists before we interact with it
|
||
|
//We don't expect users to have to create it in advance
|
||
|
//The order of operations of Backend State as follows:
|
||
|
// * Get - if this doesn't exist then we continue as though it's new
|
||
|
// * Lock - we make sure that the state directory exists as it's the entrance to writing to Manta
|
||
|
// * Put - put the state up there
|
||
|
// * Unlock - unlock the directory
|
||
|
//We can always guarantee that the user can put their state in the specified location because of this
|
||
|
err := c.storageClient.Dir().Put(context.Background(), &storage.PutDirectoryInput{
|
||
|
DirectoryName: path.Join(mantaDefaultRootStore, c.directoryName),
|
||
|
})
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
//firstly we want to check that a lock doesn't already exist
|
||
|
lockErr := &state.LockError{}
|
||
|
lockInfo, err := c.getLockInfo()
|
||
|
if err != nil {
|
||
|
if !strings.Contains(err.Error(), "ResourceNotFound") {
|
||
|
lockErr.Err = fmt.Errorf("failed to retrieve lock info: %s", err)
|
||
|
return "", lockErr
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if lockInfo != nil {
|
||
|
lockErr := &state.LockError{
|
||
|
Err: fmt.Errorf("A lock is already acquired"),
|
||
|
Info: lockInfo,
|
||
|
}
|
||
|
return "", lockErr
|
||
|
}
|
||
|
|
||
|
info.Path = path.Join(c.directoryName, lockFileName)
|
||
|
|
||
|
if info.ID == "" {
|
||
|
lockID, err := uuid.GenerateUUID()
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
info.ID = lockID
|
||
|
}
|
||
|
|
||
|
data := info.Marshal()
|
||
|
|
||
|
contentType := "application/json"
|
||
|
contentLength := int64(len(data))
|
||
|
|
||
|
params := &storage.PutObjectInput{
|
||
|
ContentType: contentType,
|
||
|
ContentLength: uint64(contentLength),
|
||
|
ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, lockFileName),
|
||
|
ObjectReader: bytes.NewReader(data),
|
||
|
}
|
||
|
|
||
|
log.Printf("[DEBUG] Creating manta state lock: %#v", params)
|
||
|
err = c.storageClient.Objects().Put(context.Background(), params)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
return info.ID, nil
|
||
|
}
|
||
|
|
||
|
func (c *RemoteClient) Unlock(id string) error {
|
||
|
lockErr := &state.LockError{}
|
||
|
|
||
|
lockInfo, err := c.getLockInfo()
|
||
|
if err != nil {
|
||
|
lockErr.Err = fmt.Errorf("failed to retrieve lock info: %s", err)
|
||
|
return lockErr
|
||
|
}
|
||
|
lockErr.Info = lockInfo
|
||
|
|
||
|
if lockInfo.ID != id {
|
||
|
lockErr.Err = fmt.Errorf("lock id %q does not match existing lock", id)
|
||
|
return lockErr
|
||
|
}
|
||
|
|
||
|
err = c.storageClient.Objects().Delete(context.Background(), &storage.DeleteObjectInput{
|
||
|
ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, lockFileName),
|
||
|
})
|
||
|
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func (c *RemoteClient) getLockInfo() (*state.LockInfo, error) {
|
||
|
output, err := c.storageClient.Objects().Get(context.Background(), &storage.GetObjectInput{
|
||
|
ObjectPath: path.Join(mantaDefaultRootStore, c.directoryName, lockFileName),
|
||
|
})
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
defer output.ObjectReader.Close()
|
||
|
|
||
|
buf := bytes.NewBuffer(nil)
|
||
|
if _, err := io.Copy(buf, output.ObjectReader); err != nil {
|
||
|
return nil, fmt.Errorf("Failed to read lock info: %s", err)
|
||
|
}
|
||
|
|
||
|
lockInfo := &state.LockInfo{}
|
||
|
err = json.Unmarshal(buf.Bytes(), lockInfo)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return lockInfo, nil
|
||
|
}
|