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  }