github.com/pmcatominey/terraform@v0.7.0-rc2.0.20160708105029-1401a52a5cc5/state/remote/s3.go (about)

     1  package remote
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"log"
     8  	"os"
     9  	"strconv"
    10  
    11  	"github.com/aws/aws-sdk-go/aws"
    12  	"github.com/aws/aws-sdk-go/aws/awserr"
    13  	"github.com/aws/aws-sdk-go/aws/session"
    14  	"github.com/aws/aws-sdk-go/service/s3"
    15  	"github.com/hashicorp/go-cleanhttp"
    16  	"github.com/hashicorp/go-multierror"
    17  	terraformAws "github.com/hashicorp/terraform/builtin/providers/aws"
    18  )
    19  
    20  func s3Factory(conf map[string]string) (Client, error) {
    21  	bucketName, ok := conf["bucket"]
    22  	if !ok {
    23  		return nil, fmt.Errorf("missing 'bucket' configuration")
    24  	}
    25  
    26  	keyName, ok := conf["key"]
    27  	if !ok {
    28  		return nil, fmt.Errorf("missing 'key' configuration")
    29  	}
    30  
    31  	endpoint, ok := conf["endpoint"]
    32  	if !ok {
    33  		endpoint = os.Getenv("AWS_S3_ENDPOINT")
    34  	}
    35  
    36  	regionName, ok := conf["region"]
    37  	if !ok {
    38  		regionName = os.Getenv("AWS_DEFAULT_REGION")
    39  		if regionName == "" {
    40  			return nil, fmt.Errorf(
    41  				"missing 'region' configuration or AWS_DEFAULT_REGION environment variable")
    42  		}
    43  	}
    44  
    45  	serverSideEncryption := false
    46  	if raw, ok := conf["encrypt"]; ok {
    47  		v, err := strconv.ParseBool(raw)
    48  		if err != nil {
    49  			return nil, fmt.Errorf(
    50  				"'encrypt' field couldn't be parsed as bool: %s", err)
    51  		}
    52  
    53  		serverSideEncryption = v
    54  	}
    55  
    56  	acl := ""
    57  	if raw, ok := conf["acl"]; ok {
    58  		acl = raw
    59  	}
    60  	kmsKeyID := conf["kms_key_id"]
    61  
    62  	var errs []error
    63  	creds := terraformAws.GetCredentials(conf["access_key"], conf["secret_key"], conf["token"], conf["profile"], conf["shared_credentials_file"])
    64  	// Call Get to check for credential provider. If nothing found, we'll get an
    65  	// error, and we can present it nicely to the user
    66  	_, err := creds.Get()
    67  	if err != nil {
    68  		if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
    69  			errs = append(errs, fmt.Errorf(`No valid credential sources found for AWS S3 remote.
    70  Please see https://www.terraform.io/docs/state/remote/s3.html for more information on
    71  providing credentials for the AWS S3 remote`))
    72  		} else {
    73  			errs = append(errs, fmt.Errorf("Error loading credentials for AWS S3 remote: %s", err))
    74  		}
    75  		return nil, &multierror.Error{Errors: errs}
    76  	}
    77  
    78  	awsConfig := &aws.Config{
    79  		Credentials: creds,
    80  		Endpoint:    aws.String(endpoint),
    81  		Region:      aws.String(regionName),
    82  		HTTPClient:  cleanhttp.DefaultClient(),
    83  	}
    84  	sess := session.New(awsConfig)
    85  	nativeClient := s3.New(sess)
    86  
    87  	return &S3Client{
    88  		nativeClient:         nativeClient,
    89  		bucketName:           bucketName,
    90  		keyName:              keyName,
    91  		serverSideEncryption: serverSideEncryption,
    92  		acl:                  acl,
    93  		kmsKeyID:             kmsKeyID,
    94  	}, nil
    95  }
    96  
    97  type S3Client struct {
    98  	nativeClient         *s3.S3
    99  	bucketName           string
   100  	keyName              string
   101  	serverSideEncryption bool
   102  	acl                  string
   103  	kmsKeyID             string
   104  }
   105  
   106  func (c *S3Client) Get() (*Payload, error) {
   107  	output, err := c.nativeClient.GetObject(&s3.GetObjectInput{
   108  		Bucket: &c.bucketName,
   109  		Key:    &c.keyName,
   110  	})
   111  
   112  	if err != nil {
   113  		if awserr := err.(awserr.Error); awserr != nil {
   114  			if awserr.Code() == "NoSuchKey" {
   115  				return nil, nil
   116  			} else {
   117  				return nil, err
   118  			}
   119  		} else {
   120  			return nil, err
   121  		}
   122  	}
   123  
   124  	defer output.Body.Close()
   125  
   126  	buf := bytes.NewBuffer(nil)
   127  	if _, err := io.Copy(buf, output.Body); err != nil {
   128  		return nil, fmt.Errorf("Failed to read remote state: %s", err)
   129  	}
   130  
   131  	payload := &Payload{
   132  		Data: buf.Bytes(),
   133  	}
   134  
   135  	// If there was no data, then return nil
   136  	if len(payload.Data) == 0 {
   137  		return nil, nil
   138  	}
   139  
   140  	return payload, nil
   141  }
   142  
   143  func (c *S3Client) Put(data []byte) error {
   144  	contentType := "application/json"
   145  	contentLength := int64(len(data))
   146  
   147  	i := &s3.PutObjectInput{
   148  		ContentType:   &contentType,
   149  		ContentLength: &contentLength,
   150  		Body:          bytes.NewReader(data),
   151  		Bucket:        &c.bucketName,
   152  		Key:           &c.keyName,
   153  	}
   154  
   155  	if c.serverSideEncryption {
   156  		if c.kmsKeyID != "" {
   157  			i.SSEKMSKeyId = &c.kmsKeyID
   158  			i.ServerSideEncryption = aws.String("aws:kms")
   159  		} else {
   160  			i.ServerSideEncryption = aws.String("AES256")
   161  		}
   162  	}
   163  
   164  	if c.acl != "" {
   165  		i.ACL = aws.String(c.acl)
   166  	}
   167  
   168  	log.Printf("[DEBUG] Uploading remote state to S3: %#v", i)
   169  
   170  	if _, err := c.nativeClient.PutObject(i); err == nil {
   171  		return nil
   172  	} else {
   173  		return fmt.Errorf("Failed to upload state: %v", err)
   174  	}
   175  }
   176  
   177  func (c *S3Client) Delete() error {
   178  	_, err := c.nativeClient.DeleteObject(&s3.DeleteObjectInput{
   179  		Bucket: &c.bucketName,
   180  		Key:    &c.keyName,
   181  	})
   182  
   183  	return err
   184  }