github.com/rjeczalik/terraform@v0.6.7-0.20160812060014-e251d5c7bd39/builtin/providers/aws/resource_aws_s3_bucket_object.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "log" 8 "os" 9 "sort" 10 "strings" 11 12 "github.com/hashicorp/terraform/helper/schema" 13 "github.com/mitchellh/go-homedir" 14 15 "github.com/aws/aws-sdk-go/aws" 16 "github.com/aws/aws-sdk-go/aws/awserr" 17 "github.com/aws/aws-sdk-go/service/s3" 18 ) 19 20 func resourceAwsS3BucketObject() *schema.Resource { 21 return &schema.Resource{ 22 Create: resourceAwsS3BucketObjectPut, 23 Read: resourceAwsS3BucketObjectRead, 24 Update: resourceAwsS3BucketObjectPut, 25 Delete: resourceAwsS3BucketObjectDelete, 26 27 Schema: map[string]*schema.Schema{ 28 "bucket": &schema.Schema{ 29 Type: schema.TypeString, 30 Required: true, 31 ForceNew: true, 32 }, 33 34 "acl": &schema.Schema{ 35 Type: schema.TypeString, 36 Default: "private", 37 Optional: true, 38 ValidateFunc: validateS3BucketObjectAclType, 39 }, 40 41 "cache_control": &schema.Schema{ 42 Type: schema.TypeString, 43 Optional: true, 44 }, 45 46 "content_disposition": &schema.Schema{ 47 Type: schema.TypeString, 48 Optional: true, 49 }, 50 51 "content_encoding": &schema.Schema{ 52 Type: schema.TypeString, 53 Optional: true, 54 }, 55 56 "content_language": &schema.Schema{ 57 Type: schema.TypeString, 58 Optional: true, 59 }, 60 61 "content_type": &schema.Schema{ 62 Type: schema.TypeString, 63 Optional: true, 64 Computed: true, 65 }, 66 67 "key": &schema.Schema{ 68 Type: schema.TypeString, 69 Required: true, 70 ForceNew: true, 71 }, 72 73 "source": &schema.Schema{ 74 Type: schema.TypeString, 75 Optional: true, 76 ConflictsWith: []string{"content"}, 77 }, 78 79 "content": &schema.Schema{ 80 Type: schema.TypeString, 81 Optional: true, 82 ConflictsWith: []string{"source"}, 83 }, 84 85 "kms_key_id": &schema.Schema{ 86 Type: schema.TypeString, 87 Optional: true, 88 }, 89 90 "etag": &schema.Schema{ 91 Type: schema.TypeString, 92 // This will conflict with SSE-C and SSE-KMS encryption and multi-part upload 93 // if/when it's actually implemented. The Etag then won't match raw-file MD5. 94 // See http://docs.aws.amazon.com/AmazonS3/latest/API/RESTCommonResponseHeaders.html 95 Optional: true, 96 Computed: true, 97 }, 98 99 "version_id": &schema.Schema{ 100 Type: schema.TypeString, 101 Computed: true, 102 }, 103 }, 104 } 105 } 106 107 func resourceAwsS3BucketObjectPut(d *schema.ResourceData, meta interface{}) error { 108 s3conn := meta.(*AWSClient).s3conn 109 110 bucket := d.Get("bucket").(string) 111 key := d.Get("key").(string) 112 acl := d.Get("acl").(string) 113 var body io.ReadSeeker 114 115 if v, ok := d.GetOk("source"); ok { 116 source := v.(string) 117 path, err := homedir.Expand(source) 118 if err != nil { 119 return fmt.Errorf("Error expanding homedir in source (%s): %s", source, err) 120 } 121 file, err := os.Open(path) 122 if err != nil { 123 return fmt.Errorf("Error opening S3 bucket object source (%s): %s", source, err) 124 } 125 126 body = file 127 } else if v, ok := d.GetOk("content"); ok { 128 content := v.(string) 129 body = bytes.NewReader([]byte(content)) 130 } else { 131 return fmt.Errorf("Must specify \"source\" or \"content\" field") 132 } 133 134 if _, ok := d.GetOk("kms_key_id"); ok { 135 if _, ok := d.GetOk("etag"); ok { 136 return fmt.Errorf("Unable to specify 'kms_key_id' and 'etag' together because 'etag' wouldn't equal the MD5 digest of the raw object data") 137 } 138 } 139 140 putInput := &s3.PutObjectInput{ 141 Bucket: aws.String(bucket), 142 Key: aws.String(key), 143 ACL: aws.String(acl), 144 Body: body, 145 } 146 147 if v, ok := d.GetOk("cache_control"); ok { 148 putInput.CacheControl = aws.String(v.(string)) 149 } 150 151 if v, ok := d.GetOk("content_type"); ok { 152 putInput.ContentType = aws.String(v.(string)) 153 } 154 155 if v, ok := d.GetOk("content_encoding"); ok { 156 putInput.ContentEncoding = aws.String(v.(string)) 157 } 158 159 if v, ok := d.GetOk("content_language"); ok { 160 putInput.ContentLanguage = aws.String(v.(string)) 161 } 162 163 if v, ok := d.GetOk("content_disposition"); ok { 164 putInput.ContentDisposition = aws.String(v.(string)) 165 } 166 167 if v, ok := d.GetOk("kms_key_id"); ok { 168 putInput.SSEKMSKeyId = aws.String(v.(string)) 169 putInput.ServerSideEncryption = aws.String("aws:kms") 170 } 171 172 resp, err := s3conn.PutObject(putInput) 173 if err != nil { 174 return fmt.Errorf("Error putting object in S3 bucket (%s): %s", bucket, err) 175 } 176 177 // See https://forums.aws.amazon.com/thread.jspa?threadID=44003 178 d.Set("etag", strings.Trim(*resp.ETag, `"`)) 179 180 d.Set("version_id", resp.VersionId) 181 d.SetId(key) 182 return resourceAwsS3BucketObjectRead(d, meta) 183 } 184 185 func resourceAwsS3BucketObjectRead(d *schema.ResourceData, meta interface{}) error { 186 s3conn := meta.(*AWSClient).s3conn 187 188 bucket := d.Get("bucket").(string) 189 key := d.Get("key").(string) 190 etag := d.Get("etag").(string) 191 192 resp, err := s3conn.HeadObject( 193 &s3.HeadObjectInput{ 194 Bucket: aws.String(bucket), 195 Key: aws.String(key), 196 IfMatch: aws.String(etag), 197 }) 198 199 if err != nil { 200 // If S3 returns a 404 Request Failure, mark the object as destroyed 201 if awsErr, ok := err.(awserr.RequestFailure); ok && awsErr.StatusCode() == 404 { 202 d.SetId("") 203 log.Printf("[WARN] Error Reading Object (%s), object not found (HTTP status 404)", key) 204 return nil 205 } 206 return err 207 } 208 209 d.Set("cache_control", resp.CacheControl) 210 d.Set("content_disposition", resp.ContentDisposition) 211 d.Set("content_encoding", resp.ContentEncoding) 212 d.Set("content_language", resp.ContentLanguage) 213 d.Set("content_type", resp.ContentType) 214 d.Set("version_id", resp.VersionId) 215 d.Set("kms_key_id", resp.SSEKMSKeyId) 216 217 log.Printf("[DEBUG] Reading S3 Bucket Object meta: %s", resp) 218 return nil 219 } 220 221 func resourceAwsS3BucketObjectDelete(d *schema.ResourceData, meta interface{}) error { 222 s3conn := meta.(*AWSClient).s3conn 223 224 bucket := d.Get("bucket").(string) 225 key := d.Get("key").(string) 226 227 if _, ok := d.GetOk("version_id"); ok { 228 // Bucket is versioned, we need to delete all versions 229 vInput := s3.ListObjectVersionsInput{ 230 Bucket: aws.String(bucket), 231 Prefix: aws.String(key), 232 } 233 out, err := s3conn.ListObjectVersions(&vInput) 234 if err != nil { 235 return fmt.Errorf("Failed listing S3 object versions: %s", err) 236 } 237 238 for _, v := range out.Versions { 239 input := s3.DeleteObjectInput{ 240 Bucket: aws.String(bucket), 241 Key: aws.String(key), 242 VersionId: v.VersionId, 243 } 244 _, err := s3conn.DeleteObject(&input) 245 if err != nil { 246 return fmt.Errorf("Error deleting S3 object version of %s:\n %s:\n %s", 247 key, v, err) 248 } 249 } 250 } else { 251 // Just delete the object 252 input := s3.DeleteObjectInput{ 253 Bucket: aws.String(bucket), 254 Key: aws.String(key), 255 } 256 _, err := s3conn.DeleteObject(&input) 257 if err != nil { 258 return fmt.Errorf("Error deleting S3 bucket object: %s", err) 259 } 260 } 261 262 return nil 263 } 264 265 func validateS3BucketObjectAclType(v interface{}, k string) (ws []string, errors []error) { 266 value := v.(string) 267 268 cannedAcls := map[string]bool{ 269 s3.ObjectCannedACLPrivate: true, 270 s3.ObjectCannedACLPublicRead: true, 271 s3.ObjectCannedACLPublicReadWrite: true, 272 s3.ObjectCannedACLAuthenticatedRead: true, 273 s3.ObjectCannedACLAwsExecRead: true, 274 s3.ObjectCannedACLBucketOwnerRead: true, 275 s3.ObjectCannedACLBucketOwnerFullControl: true, 276 } 277 278 sentenceJoin := func(m map[string]bool) string { 279 keys := make([]string, 0, len(m)) 280 for k := range m { 281 keys = append(keys, fmt.Sprintf("%q", k)) 282 } 283 sort.Strings(keys) 284 285 length := len(keys) 286 words := make([]string, length) 287 copy(words, keys) 288 289 words[length-1] = fmt.Sprintf("or %s", words[length-1]) 290 return strings.Join(words, ", ") 291 } 292 293 if _, ok := cannedAcls[value]; !ok { 294 errors = append(errors, fmt.Errorf( 295 "%q contains an invalid canned ACL type %q. Valid types are either %s", 296 k, value, sentenceJoin(cannedAcls))) 297 } 298 return 299 }