github.com/jrasell/terraform@v0.6.17-0.20160523115548-2652f5232949/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 }