terraform/state/remote/s3.go

158 lines
3.5 KiB
Go

package remote
import (
"bytes"
"fmt"
"io"
"os"
"strconv"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
"github.com/aws/aws-sdk-go/service/s3"
)
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")
}
regionName, ok := conf["region"]
if !ok {
regionName = os.Getenv("AWS_DEFAULT_REGION")
if regionName == "" {
return nil, fmt.Errorf(
"missing 'region' configuration or AWS_DEFAULT_REGION environment variable")
}
}
serverSideEncryption := false
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
}
accessKeyId := conf["access_key"]
secretAccessKey := conf["secret_key"]
credentialsProvider := credentials.NewChainCredentials([]credentials.Provider{
&credentials.StaticProvider{Value: credentials.Value{
AccessKeyID: accessKeyId,
SecretAccessKey: secretAccessKey,
SessionToken: "",
}},
&credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{Filename: "", Profile: ""},
&ec2rolecreds.EC2RoleProvider{},
})
// Make sure we got some sort of working credentials.
_, err := credentialsProvider.Get()
if err != nil {
return nil, fmt.Errorf("Unable to determine AWS credentials. Set the AWS_ACCESS_KEY_ID and "+
"AWS_SECRET_ACCESS_KEY environment variables.\n(error was: %s)", err)
}
awsConfig := &aws.Config{
Credentials: credentialsProvider,
Region: aws.String(regionName),
}
nativeClient := s3.New(awsConfig)
return &S3Client{
nativeClient: nativeClient,
bucketName: bucketName,
keyName: keyName,
serverSideEncryption: serverSideEncryption,
}, nil
}
type S3Client struct {
nativeClient *s3.S3
bucketName string
keyName string
serverSideEncryption bool
}
func (c *S3Client) Get() (*Payload, error) {
output, err := c.nativeClient.GetObject(&s3.GetObjectInput{
Bucket: &c.bucketName,
Key: &c.keyName,
})
if err != nil {
if awserr := err.(awserr.Error); awserr != nil {
if awserr.Code() == "NoSuchKey" {
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 {
contentType := "application/octet-stream"
contentLength := int64(len(data))
i := &s3.PutObjectInput{
ContentType: &contentType,
ContentLength: &contentLength,
Body: bytes.NewReader(data),
Bucket: &c.bucketName,
Key: &c.keyName,
}
if c.serverSideEncryption {
i.ServerSideEncryption = aws.String("AES256")
}
if _, err := c.nativeClient.PutObject(i); err == nil {
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
}