2015-04-30 18:21:49 +02:00
|
|
|
package remote
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2015-09-14 11:38:29 +02:00
|
|
|
"log"
|
2015-04-30 18:21:49 +02:00
|
|
|
"os"
|
2015-06-25 18:23:12 +02:00
|
|
|
"strconv"
|
2015-04-30 18:21:49 +02:00
|
|
|
|
2015-06-03 20:36:57 +02:00
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
|
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
2015-10-30 00:52:10 +01:00
|
|
|
"github.com/aws/aws-sdk-go/aws/session"
|
2015-06-03 20:36:57 +02:00
|
|
|
"github.com/aws/aws-sdk-go/service/s3"
|
2015-10-22 20:03:25 +02:00
|
|
|
"github.com/hashicorp/go-cleanhttp"
|
2016-05-06 18:52:18 +02:00
|
|
|
"github.com/hashicorp/go-multierror"
|
|
|
|
terraformAws "github.com/hashicorp/terraform/builtin/providers/aws"
|
2015-04-30 18:21:49 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
func s3Factory(conf map[string]string) (Client, error) {
|
|
|
|
bucketName, ok := conf["bucket"]
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("missing 'bucket' configuration")
|
|
|
|
}
|
|
|
|
|
|
|
|
keyName, ok := conf["key"]
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("missing 'key' configuration")
|
|
|
|
}
|
|
|
|
|
2016-01-11 14:28:52 +01:00
|
|
|
endpoint, ok := conf["endpoint"]
|
|
|
|
if !ok {
|
|
|
|
endpoint = os.Getenv("AWS_S3_ENDPOINT")
|
|
|
|
}
|
|
|
|
|
2015-04-30 18:21:49 +02:00
|
|
|
regionName, ok := conf["region"]
|
|
|
|
if !ok {
|
|
|
|
regionName = os.Getenv("AWS_DEFAULT_REGION")
|
|
|
|
if regionName == "" {
|
2015-05-05 20:30:35 +02:00
|
|
|
return nil, fmt.Errorf(
|
|
|
|
"missing 'region' configuration or AWS_DEFAULT_REGION environment variable")
|
2015-04-30 18:21:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-19 20:33:03 +02:00
|
|
|
serverSideEncryption := false
|
2015-06-25 18:23:12 +02:00
|
|
|
if raw, ok := conf["encrypt"]; ok {
|
|
|
|
v, err := strconv.ParseBool(raw)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf(
|
|
|
|
"'encrypt' field couldn't be parsed as bool: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
serverSideEncryption = v
|
2015-06-19 20:33:03 +02:00
|
|
|
}
|
|
|
|
|
2015-09-14 11:38:29 +02:00
|
|
|
acl := ""
|
|
|
|
if raw, ok := conf["acl"]; ok {
|
|
|
|
acl = raw
|
|
|
|
}
|
2015-10-07 16:09:03 +02:00
|
|
|
kmsKeyID := conf["kms_key_id"]
|
2015-09-14 11:38:29 +02:00
|
|
|
|
2016-05-06 18:52:18 +02:00
|
|
|
var errs []error
|
|
|
|
creds := terraformAws.GetCredentials(conf["access_key"], conf["secret_key"], conf["token"], conf["profile"], conf["shared_credentials_file"])
|
|
|
|
// Call Get to check for credential provider. If nothing found, we'll get an
|
|
|
|
// error, and we can present it nicely to the user
|
|
|
|
_, err := creds.Get()
|
2015-04-30 18:21:49 +02:00
|
|
|
if err != nil {
|
2016-05-06 18:52:18 +02:00
|
|
|
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
|
|
|
|
errs = append(errs, fmt.Errorf(`No valid credential sources found for AWS S3 remote.
|
|
|
|
Please see https://www.terraform.io/docs/state/remote/s3.html for more information on
|
|
|
|
providing credentials for the AWS S3 remote`))
|
|
|
|
} else {
|
|
|
|
errs = append(errs, fmt.Errorf("Error loading credentials for AWS S3 remote: %s", err))
|
|
|
|
}
|
|
|
|
return nil, &multierror.Error{Errors: errs}
|
2015-04-30 18:21:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
awsConfig := &aws.Config{
|
2016-05-06 18:52:18 +02:00
|
|
|
Credentials: creds,
|
2016-01-11 14:28:52 +01:00
|
|
|
Endpoint: aws.String(endpoint),
|
2015-07-28 22:29:46 +02:00
|
|
|
Region: aws.String(regionName),
|
2015-10-22 20:03:25 +02:00
|
|
|
HTTPClient: cleanhttp.DefaultClient(),
|
2015-04-30 18:21:49 +02:00
|
|
|
}
|
2015-10-30 00:52:10 +01:00
|
|
|
sess := session.New(awsConfig)
|
|
|
|
nativeClient := s3.New(sess)
|
2015-04-30 18:21:49 +02:00
|
|
|
|
|
|
|
return &S3Client{
|
2015-06-19 20:33:03 +02:00
|
|
|
nativeClient: nativeClient,
|
|
|
|
bucketName: bucketName,
|
|
|
|
keyName: keyName,
|
|
|
|
serverSideEncryption: serverSideEncryption,
|
2015-09-14 11:38:29 +02:00
|
|
|
acl: acl,
|
2015-07-31 09:09:28 +02:00
|
|
|
kmsKeyID: kmsKeyID,
|
2015-04-30 18:21:49 +02:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type S3Client struct {
|
2015-06-19 20:33:03 +02:00
|
|
|
nativeClient *s3.S3
|
|
|
|
bucketName string
|
|
|
|
keyName string
|
|
|
|
serverSideEncryption bool
|
2015-09-14 11:38:29 +02:00
|
|
|
acl string
|
2015-07-31 09:09:28 +02:00
|
|
|
kmsKeyID string
|
2015-04-30 18:21:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *S3Client) Get() (*Payload, error) {
|
|
|
|
output, err := c.nativeClient.GetObject(&s3.GetObjectInput{
|
|
|
|
Bucket: &c.bucketName,
|
|
|
|
Key: &c.keyName,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
2015-05-20 22:20:30 +02:00
|
|
|
if awserr := err.(awserr.Error); awserr != nil {
|
2015-05-20 13:21:23 +02:00
|
|
|
if awserr.Code() == "NoSuchKey" {
|
2015-04-30 18:21:49 +02:00
|
|
|
return nil, nil
|
|
|
|
} else {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
defer output.Body.Close()
|
|
|
|
|
|
|
|
buf := bytes.NewBuffer(nil)
|
|
|
|
if _, err := io.Copy(buf, output.Body); err != nil {
|
|
|
|
return nil, fmt.Errorf("Failed to read remote state: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
payload := &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 *S3Client) Put(data []byte) error {
|
2015-10-02 00:14:55 +02:00
|
|
|
contentType := "application/json"
|
2015-04-30 18:21:49 +02:00
|
|
|
contentLength := int64(len(data))
|
|
|
|
|
2015-06-19 20:33:03 +02:00
|
|
|
i := &s3.PutObjectInput{
|
2015-04-30 18:21:49 +02:00
|
|
|
ContentType: &contentType,
|
|
|
|
ContentLength: &contentLength,
|
|
|
|
Body: bytes.NewReader(data),
|
|
|
|
Bucket: &c.bucketName,
|
|
|
|
Key: &c.keyName,
|
2015-06-19 20:33:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if c.serverSideEncryption {
|
2015-07-31 09:09:28 +02:00
|
|
|
if c.kmsKeyID != "" {
|
2015-10-07 16:39:08 +02:00
|
|
|
i.SSEKMSKeyId = &c.kmsKeyID
|
2015-07-31 09:09:28 +02:00
|
|
|
i.ServerSideEncryption = aws.String("aws:kms")
|
|
|
|
} else {
|
|
|
|
i.ServerSideEncryption = aws.String("AES256")
|
|
|
|
}
|
2015-06-19 20:33:03 +02:00
|
|
|
}
|
|
|
|
|
2015-09-14 11:38:29 +02:00
|
|
|
if c.acl != "" {
|
|
|
|
i.ACL = aws.String(c.acl)
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("[DEBUG] Uploading remote state to S3: %#v", i)
|
|
|
|
|
2015-06-25 18:23:12 +02:00
|
|
|
if _, err := c.nativeClient.PutObject(i); err == nil {
|
2015-04-30 18:21:49 +02:00
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("Failed to upload state: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *S3Client) Delete() error {
|
|
|
|
_, err := c.nativeClient.DeleteObject(&s3.DeleteObjectInput{
|
|
|
|
Bucket: &c.bucketName,
|
|
|
|
Key: &c.keyName,
|
|
|
|
})
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|