github.com/minamijoyo/terraform@v0.7.8-0.20161029001309-18b3736ba44b/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, err := terraformAws.GetCredentials(&terraformAws.Config{ 64 AccessKey: conf["access_key"], 65 SecretKey: conf["secret_key"], 66 Token: conf["token"], 67 Profile: conf["profile"], 68 CredsFilename: conf["shared_credentials_file"], 69 }) 70 // Call Get to check for credential provider. If nothing found, we'll get an 71 // error, and we can present it nicely to the user 72 _, err = creds.Get() 73 if err != nil { 74 if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" { 75 errs = append(errs, fmt.Errorf(`No valid credential sources found for AWS S3 remote. 76 Please see https://www.terraform.io/docs/state/remote/s3.html for more information on 77 providing credentials for the AWS S3 remote`)) 78 } else { 79 errs = append(errs, fmt.Errorf("Error loading credentials for AWS S3 remote: %s", err)) 80 } 81 return nil, &multierror.Error{Errors: errs} 82 } 83 84 awsConfig := &aws.Config{ 85 Credentials: creds, 86 Endpoint: aws.String(endpoint), 87 Region: aws.String(regionName), 88 HTTPClient: cleanhttp.DefaultClient(), 89 } 90 sess := session.New(awsConfig) 91 nativeClient := s3.New(sess) 92 93 return &S3Client{ 94 nativeClient: nativeClient, 95 bucketName: bucketName, 96 keyName: keyName, 97 serverSideEncryption: serverSideEncryption, 98 acl: acl, 99 kmsKeyID: kmsKeyID, 100 }, nil 101 } 102 103 type S3Client struct { 104 nativeClient *s3.S3 105 bucketName string 106 keyName string 107 serverSideEncryption bool 108 acl string 109 kmsKeyID string 110 } 111 112 func (c *S3Client) Get() (*Payload, error) { 113 output, err := c.nativeClient.GetObject(&s3.GetObjectInput{ 114 Bucket: &c.bucketName, 115 Key: &c.keyName, 116 }) 117 118 if err != nil { 119 if awserr := err.(awserr.Error); awserr != nil { 120 if awserr.Code() == "NoSuchKey" { 121 return nil, nil 122 } else { 123 return nil, err 124 } 125 } else { 126 return nil, err 127 } 128 } 129 130 defer output.Body.Close() 131 132 buf := bytes.NewBuffer(nil) 133 if _, err := io.Copy(buf, output.Body); err != nil { 134 return nil, fmt.Errorf("Failed to read remote state: %s", err) 135 } 136 137 payload := &Payload{ 138 Data: buf.Bytes(), 139 } 140 141 // If there was no data, then return nil 142 if len(payload.Data) == 0 { 143 return nil, nil 144 } 145 146 return payload, nil 147 } 148 149 func (c *S3Client) Put(data []byte) error { 150 contentType := "application/json" 151 contentLength := int64(len(data)) 152 153 i := &s3.PutObjectInput{ 154 ContentType: &contentType, 155 ContentLength: &contentLength, 156 Body: bytes.NewReader(data), 157 Bucket: &c.bucketName, 158 Key: &c.keyName, 159 } 160 161 if c.serverSideEncryption { 162 if c.kmsKeyID != "" { 163 i.SSEKMSKeyId = &c.kmsKeyID 164 i.ServerSideEncryption = aws.String("aws:kms") 165 } else { 166 i.ServerSideEncryption = aws.String("AES256") 167 } 168 } 169 170 if c.acl != "" { 171 i.ACL = aws.String(c.acl) 172 } 173 174 log.Printf("[DEBUG] Uploading remote state to S3: %#v", i) 175 176 if _, err := c.nativeClient.PutObject(i); err == nil { 177 return nil 178 } else { 179 return fmt.Errorf("Failed to upload state: %v", err) 180 } 181 } 182 183 func (c *S3Client) Delete() error { 184 _, err := c.nativeClient.DeleteObject(&s3.DeleteObjectInput{ 185 Bucket: &c.bucketName, 186 Key: &c.keyName, 187 }) 188 189 return err 190 }