github.com/paulmey/terraform@v0.5.2-0.20150519145237-046e9b4c884d/builtin/providers/aws/resource_aws_s3_bucket.go (about) 1 package aws 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "log" 7 8 "github.com/hashicorp/terraform/helper/schema" 9 10 "github.com/awslabs/aws-sdk-go/aws" 11 "github.com/awslabs/aws-sdk-go/service/s3" 12 ) 13 14 func resourceAwsS3Bucket() *schema.Resource { 15 return &schema.Resource{ 16 Create: resourceAwsS3BucketCreate, 17 Read: resourceAwsS3BucketRead, 18 Update: resourceAwsS3BucketUpdate, 19 Delete: resourceAwsS3BucketDelete, 20 21 Schema: map[string]*schema.Schema{ 22 "bucket": &schema.Schema{ 23 Type: schema.TypeString, 24 Required: true, 25 ForceNew: true, 26 }, 27 28 "acl": &schema.Schema{ 29 Type: schema.TypeString, 30 Default: "private", 31 Optional: true, 32 ForceNew: true, 33 }, 34 35 "policy": &schema.Schema{ 36 Type: schema.TypeString, 37 Optional: true, 38 StateFunc: normalizeJson, 39 }, 40 41 "website": &schema.Schema{ 42 Type: schema.TypeList, 43 Optional: true, 44 Elem: &schema.Resource{ 45 Schema: map[string]*schema.Schema{ 46 "index_document": &schema.Schema{ 47 Type: schema.TypeString, 48 Optional: true, 49 }, 50 51 "error_document": &schema.Schema{ 52 Type: schema.TypeString, 53 Optional: true, 54 }, 55 56 "redirect_all_requests_to": &schema.Schema{ 57 Type: schema.TypeString, 58 ConflictsWith: []string{ 59 "website.0.index_document", 60 "website.0.error_document", 61 }, 62 Optional: true, 63 }, 64 }, 65 }, 66 }, 67 68 "hosted_zone_id": &schema.Schema{ 69 Type: schema.TypeString, 70 Optional: true, 71 Computed: true, 72 }, 73 74 "region": &schema.Schema{ 75 Type: schema.TypeString, 76 Optional: true, 77 Computed: true, 78 }, 79 80 "website_endpoint": &schema.Schema{ 81 Type: schema.TypeString, 82 Optional: true, 83 Computed: true, 84 }, 85 86 "tags": tagsSchema(), 87 }, 88 } 89 } 90 91 func resourceAwsS3BucketCreate(d *schema.ResourceData, meta interface{}) error { 92 s3conn := meta.(*AWSClient).s3conn 93 awsRegion := meta.(*AWSClient).region 94 95 // Get the bucket and acl 96 bucket := d.Get("bucket").(string) 97 acl := d.Get("acl").(string) 98 99 log.Printf("[DEBUG] S3 bucket create: %s, ACL: %s", bucket, acl) 100 101 req := &s3.CreateBucketInput{ 102 Bucket: aws.String(bucket), 103 ACL: aws.String(acl), 104 } 105 106 // Special case us-east-1 region and do not set the LocationConstraint. 107 // See "Request Elements: http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUT.html 108 if awsRegion != "us-east-1" { 109 req.CreateBucketConfiguration = &s3.CreateBucketConfiguration{ 110 LocationConstraint: aws.String(awsRegion), 111 } 112 } 113 114 _, err := s3conn.CreateBucket(req) 115 if err != nil { 116 return fmt.Errorf("Error creating S3 bucket: %s", err) 117 } 118 119 // Assign the bucket name as the resource ID 120 d.SetId(bucket) 121 122 return resourceAwsS3BucketUpdate(d, meta) 123 } 124 125 func resourceAwsS3BucketUpdate(d *schema.ResourceData, meta interface{}) error { 126 s3conn := meta.(*AWSClient).s3conn 127 if err := setTagsS3(s3conn, d); err != nil { 128 return err 129 } 130 131 if d.HasChange("policy") { 132 if err := resourceAwsS3BucketPolicyUpdate(s3conn, d); err != nil { 133 return err 134 } 135 } 136 137 if d.HasChange("website") { 138 if err := resourceAwsS3BucketWebsiteUpdate(s3conn, d); err != nil { 139 return err 140 } 141 } 142 143 return resourceAwsS3BucketRead(d, meta) 144 } 145 146 func resourceAwsS3BucketRead(d *schema.ResourceData, meta interface{}) error { 147 s3conn := meta.(*AWSClient).s3conn 148 149 _, err := s3conn.HeadBucket(&s3.HeadBucketInput{ 150 Bucket: aws.String(d.Id()), 151 }) 152 if err != nil { 153 if awsError, ok := err.(aws.APIError); ok && awsError.StatusCode == 404 { 154 d.SetId("") 155 } else { 156 // some of the AWS SDK's errors can be empty strings, so let's add 157 // some additional context. 158 return fmt.Errorf("error reading S3 bucket \"%s\": %s", d.Id(), err) 159 } 160 } 161 162 // Read the policy 163 pol, err := s3conn.GetBucketPolicy(&s3.GetBucketPolicyInput{ 164 Bucket: aws.String(d.Id()), 165 }) 166 log.Printf("[DEBUG] S3 bucket: %s, read policy: %v", d.Id(), pol) 167 if err != nil { 168 if err := d.Set("policy", ""); err != nil { 169 return err 170 } 171 } else { 172 if v := pol.Policy; v == nil { 173 if err := d.Set("policy", ""); err != nil { 174 return err 175 } 176 } else if err := d.Set("policy", normalizeJson(*v)); err != nil { 177 return err 178 } 179 } 180 181 // Read the website configuration 182 ws, err := s3conn.GetBucketWebsite(&s3.GetBucketWebsiteInput{ 183 Bucket: aws.String(d.Id()), 184 }) 185 var websites []map[string]interface{} 186 if err == nil { 187 w := make(map[string]interface{}) 188 189 if v := ws.IndexDocument; v != nil { 190 w["index_document"] = *v.Suffix 191 } 192 193 if v := ws.ErrorDocument; v != nil { 194 w["error_document"] = *v.Key 195 } 196 197 if v := ws.RedirectAllRequestsTo; v != nil { 198 w["redirect_all_requests_to"] = *v.HostName 199 } 200 201 websites = append(websites, w) 202 } 203 if err := d.Set("website", websites); err != nil { 204 return err 205 } 206 207 // Add the region as an attribute 208 location, err := s3conn.GetBucketLocation( 209 &s3.GetBucketLocationInput{ 210 Bucket: aws.String(d.Id()), 211 }, 212 ) 213 if err != nil { 214 return err 215 } 216 var region string 217 if location.LocationConstraint != nil { 218 region = *location.LocationConstraint 219 } 220 region = normalizeRegion(region) 221 if err := d.Set("region", region); err != nil { 222 return err 223 } 224 225 // Add the hosted zone ID for this bucket's region as an attribute 226 hostedZoneID := HostedZoneIDForRegion(region) 227 if err := d.Set("hosted_zone_id", hostedZoneID); err != nil { 228 return err 229 } 230 231 // Add website_endpoint as an attribute 232 endpoint, err := websiteEndpoint(s3conn, d) 233 if err != nil { 234 return err 235 } 236 if err := d.Set("website_endpoint", endpoint); err != nil { 237 return err 238 } 239 240 tagSet, err := getTagSetS3(s3conn, d.Id()) 241 if err != nil { 242 return err 243 } 244 245 if err := d.Set("tags", tagsToMapS3(tagSet)); err != nil { 246 return err 247 } 248 249 return nil 250 } 251 252 func resourceAwsS3BucketDelete(d *schema.ResourceData, meta interface{}) error { 253 s3conn := meta.(*AWSClient).s3conn 254 255 log.Printf("[DEBUG] S3 Delete Bucket: %s", d.Id()) 256 _, err := s3conn.DeleteBucket(&s3.DeleteBucketInput{ 257 Bucket: aws.String(d.Id()), 258 }) 259 if err != nil { 260 return err 261 } 262 return nil 263 } 264 265 func resourceAwsS3BucketPolicyUpdate(s3conn *s3.S3, d *schema.ResourceData) error { 266 bucket := d.Get("bucket").(string) 267 policy := d.Get("policy").(string) 268 269 if policy != "" { 270 log.Printf("[DEBUG] S3 bucket: %s, put policy: %s", bucket, policy) 271 272 _, err := s3conn.PutBucketPolicy(&s3.PutBucketPolicyInput{ 273 Bucket: aws.String(bucket), 274 Policy: aws.String(policy), 275 }) 276 277 if err != nil { 278 return fmt.Errorf("Error putting S3 policy: %s", err) 279 } 280 } else { 281 log.Printf("[DEBUG] S3 bucket: %s, delete policy: %s", bucket, policy) 282 _, err := s3conn.DeleteBucketPolicy(&s3.DeleteBucketPolicyInput{ 283 Bucket: aws.String(bucket), 284 }) 285 286 if err != nil { 287 return fmt.Errorf("Error deleting S3 policy: %s", err) 288 } 289 } 290 291 return nil 292 } 293 294 func resourceAwsS3BucketWebsiteUpdate(s3conn *s3.S3, d *schema.ResourceData) error { 295 ws := d.Get("website").([]interface{}) 296 297 if len(ws) == 1 { 298 w := ws[0].(map[string]interface{}) 299 return resourceAwsS3BucketWebsitePut(s3conn, d, w) 300 } else if len(ws) == 0 { 301 return resourceAwsS3BucketWebsiteDelete(s3conn, d) 302 } else { 303 return fmt.Errorf("Cannot specify more than one website.") 304 } 305 } 306 307 func resourceAwsS3BucketWebsitePut(s3conn *s3.S3, d *schema.ResourceData, website map[string]interface{}) error { 308 bucket := d.Get("bucket").(string) 309 310 indexDocument := website["index_document"].(string) 311 errorDocument := website["error_document"].(string) 312 redirectAllRequestsTo := website["redirect_all_requests_to"].(string) 313 314 if indexDocument == "" && redirectAllRequestsTo == "" { 315 return fmt.Errorf("Must specify either index_document or redirect_all_requests_to.") 316 } 317 318 websiteConfiguration := &s3.WebsiteConfiguration{} 319 320 if indexDocument != "" { 321 websiteConfiguration.IndexDocument = &s3.IndexDocument{Suffix: aws.String(indexDocument)} 322 } 323 324 if errorDocument != "" { 325 websiteConfiguration.ErrorDocument = &s3.ErrorDocument{Key: aws.String(errorDocument)} 326 } 327 328 if redirectAllRequestsTo != "" { 329 websiteConfiguration.RedirectAllRequestsTo = &s3.RedirectAllRequestsTo{HostName: aws.String(redirectAllRequestsTo)} 330 } 331 332 putInput := &s3.PutBucketWebsiteInput{ 333 Bucket: aws.String(bucket), 334 WebsiteConfiguration: websiteConfiguration, 335 } 336 337 log.Printf("[DEBUG] S3 put bucket website: %#v", putInput) 338 339 _, err := s3conn.PutBucketWebsite(putInput) 340 if err != nil { 341 return fmt.Errorf("Error putting S3 website: %s", err) 342 } 343 344 return nil 345 } 346 347 func resourceAwsS3BucketWebsiteDelete(s3conn *s3.S3, d *schema.ResourceData) error { 348 bucket := d.Get("bucket").(string) 349 deleteInput := &s3.DeleteBucketWebsiteInput{Bucket: aws.String(bucket)} 350 351 log.Printf("[DEBUG] S3 delete bucket website: %#v", deleteInput) 352 353 _, err := s3conn.DeleteBucketWebsite(deleteInput) 354 if err != nil { 355 return fmt.Errorf("Error deleting S3 website: %s", err) 356 } 357 358 return nil 359 } 360 361 func websiteEndpoint(s3conn *s3.S3, d *schema.ResourceData) (string, error) { 362 // If the bucket doesn't have a website configuration, return an empty 363 // endpoint 364 if _, ok := d.GetOk("website"); !ok { 365 return "", nil 366 } 367 368 bucket := d.Get("bucket").(string) 369 370 // Lookup the region for this bucket 371 location, err := s3conn.GetBucketLocation( 372 &s3.GetBucketLocationInput{ 373 Bucket: aws.String(bucket), 374 }, 375 ) 376 if err != nil { 377 return "", err 378 } 379 var region string 380 if location.LocationConstraint != nil { 381 region = *location.LocationConstraint 382 } 383 384 return WebsiteEndpointUrl(bucket, region), nil 385 } 386 387 func WebsiteEndpointUrl(bucket string, region string) string { 388 region = normalizeRegion(region) 389 return fmt.Sprintf("%s.s3-website-%s.amazonaws.com", bucket, region) 390 } 391 392 func normalizeJson(jsonString interface{}) string { 393 if jsonString == nil { 394 return "" 395 } 396 j := make(map[string]interface{}) 397 err := json.Unmarshal([]byte(jsonString.(string)), &j) 398 if err != nil { 399 return fmt.Sprintf("Error parsing JSON: %s", err) 400 } 401 b, _ := json.Marshal(j) 402 return string(b[:]) 403 } 404 405 func normalizeRegion(region string) string { 406 // Default to us-east-1 if the bucket doesn't have a region: 407 // http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGETlocation.html 408 if region == "" { 409 region = "us-east-1" 410 } 411 412 return region 413 }