github.com/minamijoyo/terraform@v0.7.8-0.20161029001309-18b3736ba44b/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  			"storage_class": &schema.Schema{
    86  				Type:         schema.TypeString,
    87  				Optional:     true,
    88  				Computed:     true,
    89  				ValidateFunc: validateS3BucketObjectStorageClassType,
    90  			},
    91  
    92  			"kms_key_id": &schema.Schema{
    93  				Type:     schema.TypeString,
    94  				Optional: true,
    95  			},
    96  
    97  			"etag": &schema.Schema{
    98  				Type: schema.TypeString,
    99  				// This will conflict with SSE-C and SSE-KMS encryption and multi-part upload
   100  				// if/when it's actually implemented. The Etag then won't match raw-file MD5.
   101  				// See http://docs.aws.amazon.com/AmazonS3/latest/API/RESTCommonResponseHeaders.html
   102  				Optional:      true,
   103  				Computed:      true,
   104  				ConflictsWith: []string{"kms_key_id"},
   105  			},
   106  
   107  			"version_id": &schema.Schema{
   108  				Type:     schema.TypeString,
   109  				Computed: true,
   110  			},
   111  		},
   112  	}
   113  }
   114  
   115  func resourceAwsS3BucketObjectPut(d *schema.ResourceData, meta interface{}) error {
   116  	s3conn := meta.(*AWSClient).s3conn
   117  
   118  	var body io.ReadSeeker
   119  
   120  	if v, ok := d.GetOk("source"); ok {
   121  		source := v.(string)
   122  		path, err := homedir.Expand(source)
   123  		if err != nil {
   124  			return fmt.Errorf("Error expanding homedir in source (%s): %s", source, err)
   125  		}
   126  		file, err := os.Open(path)
   127  		if err != nil {
   128  			return fmt.Errorf("Error opening S3 bucket object source (%s): %s", source, err)
   129  		}
   130  
   131  		body = file
   132  	} else if v, ok := d.GetOk("content"); ok {
   133  		content := v.(string)
   134  		body = bytes.NewReader([]byte(content))
   135  	} else {
   136  		return fmt.Errorf("Must specify \"source\" or \"content\" field")
   137  	}
   138  
   139  	bucket := d.Get("bucket").(string)
   140  	key := d.Get("key").(string)
   141  
   142  	putInput := &s3.PutObjectInput{
   143  		Bucket: aws.String(bucket),
   144  		Key:    aws.String(key),
   145  		ACL:    aws.String(d.Get("acl").(string)),
   146  		Body:   body,
   147  	}
   148  
   149  	if v, ok := d.GetOk("storage_class"); ok {
   150  		putInput.StorageClass = aws.String(v.(string))
   151  	}
   152  
   153  	if v, ok := d.GetOk("cache_control"); ok {
   154  		putInput.CacheControl = aws.String(v.(string))
   155  	}
   156  
   157  	if v, ok := d.GetOk("content_type"); ok {
   158  		putInput.ContentType = aws.String(v.(string))
   159  	}
   160  
   161  	if v, ok := d.GetOk("content_encoding"); ok {
   162  		putInput.ContentEncoding = aws.String(v.(string))
   163  	}
   164  
   165  	if v, ok := d.GetOk("content_language"); ok {
   166  		putInput.ContentLanguage = aws.String(v.(string))
   167  	}
   168  
   169  	if v, ok := d.GetOk("content_disposition"); ok {
   170  		putInput.ContentDisposition = aws.String(v.(string))
   171  	}
   172  
   173  	if v, ok := d.GetOk("kms_key_id"); ok {
   174  		putInput.SSEKMSKeyId = aws.String(v.(string))
   175  		putInput.ServerSideEncryption = aws.String("aws:kms")
   176  	}
   177  
   178  	resp, err := s3conn.PutObject(putInput)
   179  	if err != nil {
   180  		return fmt.Errorf("Error putting object in S3 bucket (%s): %s", bucket, err)
   181  	}
   182  
   183  	// See https://forums.aws.amazon.com/thread.jspa?threadID=44003
   184  	d.Set("etag", strings.Trim(*resp.ETag, `"`))
   185  
   186  	d.Set("version_id", resp.VersionId)
   187  	d.SetId(key)
   188  	return resourceAwsS3BucketObjectRead(d, meta)
   189  }
   190  
   191  func resourceAwsS3BucketObjectRead(d *schema.ResourceData, meta interface{}) error {
   192  	s3conn := meta.(*AWSClient).s3conn
   193  
   194  	bucket := d.Get("bucket").(string)
   195  	key := d.Get("key").(string)
   196  
   197  	resp, err := s3conn.HeadObject(
   198  		&s3.HeadObjectInput{
   199  			Bucket: aws.String(bucket),
   200  			Key:    aws.String(key),
   201  		})
   202  
   203  	if err != nil {
   204  		// If S3 returns a 404 Request Failure, mark the object as destroyed
   205  		if awsErr, ok := err.(awserr.RequestFailure); ok && awsErr.StatusCode() == 404 {
   206  			d.SetId("")
   207  			log.Printf("[WARN] Error Reading Object (%s), object not found (HTTP status 404)", key)
   208  			return nil
   209  		}
   210  		return err
   211  	}
   212  	log.Printf("[DEBUG] Reading S3 Bucket Object meta: %s", resp)
   213  
   214  	d.Set("cache_control", resp.CacheControl)
   215  	d.Set("content_disposition", resp.ContentDisposition)
   216  	d.Set("content_encoding", resp.ContentEncoding)
   217  	d.Set("content_language", resp.ContentLanguage)
   218  	d.Set("content_type", resp.ContentType)
   219  	d.Set("version_id", resp.VersionId)
   220  	d.Set("kms_key_id", resp.SSEKMSKeyId)
   221  	d.Set("etag", strings.Trim(*resp.ETag, `"`))
   222  
   223  	// The "STANDARD" (which is also the default) storage
   224  	// class when set would not be included in the results.
   225  	d.Set("storage_class", s3.StorageClassStandard)
   226  	if resp.StorageClass != nil {
   227  		d.Set("storage_class", resp.StorageClass)
   228  	}
   229  
   230  	return nil
   231  }
   232  
   233  func resourceAwsS3BucketObjectDelete(d *schema.ResourceData, meta interface{}) error {
   234  	s3conn := meta.(*AWSClient).s3conn
   235  
   236  	bucket := d.Get("bucket").(string)
   237  	key := d.Get("key").(string)
   238  
   239  	if _, ok := d.GetOk("version_id"); ok {
   240  		// Bucket is versioned, we need to delete all versions
   241  		vInput := s3.ListObjectVersionsInput{
   242  			Bucket: aws.String(bucket),
   243  			Prefix: aws.String(key),
   244  		}
   245  		out, err := s3conn.ListObjectVersions(&vInput)
   246  		if err != nil {
   247  			return fmt.Errorf("Failed listing S3 object versions: %s", err)
   248  		}
   249  
   250  		for _, v := range out.Versions {
   251  			input := s3.DeleteObjectInput{
   252  				Bucket:    aws.String(bucket),
   253  				Key:       aws.String(key),
   254  				VersionId: v.VersionId,
   255  			}
   256  			_, err := s3conn.DeleteObject(&input)
   257  			if err != nil {
   258  				return fmt.Errorf("Error deleting S3 object version of %s:\n %s:\n %s",
   259  					key, v, err)
   260  			}
   261  		}
   262  	} else {
   263  		// Just delete the object
   264  		input := s3.DeleteObjectInput{
   265  			Bucket: aws.String(bucket),
   266  			Key:    aws.String(key),
   267  		}
   268  		_, err := s3conn.DeleteObject(&input)
   269  		if err != nil {
   270  			return fmt.Errorf("Error deleting S3 bucket object: %s", err)
   271  		}
   272  	}
   273  
   274  	return nil
   275  }
   276  
   277  func validateS3BucketObjectAclType(v interface{}, k string) (ws []string, errors []error) {
   278  	value := v.(string)
   279  
   280  	cannedAcls := map[string]bool{
   281  		s3.ObjectCannedACLPrivate:                true,
   282  		s3.ObjectCannedACLPublicRead:             true,
   283  		s3.ObjectCannedACLPublicReadWrite:        true,
   284  		s3.ObjectCannedACLAuthenticatedRead:      true,
   285  		s3.ObjectCannedACLAwsExecRead:            true,
   286  		s3.ObjectCannedACLBucketOwnerRead:        true,
   287  		s3.ObjectCannedACLBucketOwnerFullControl: true,
   288  	}
   289  
   290  	sentenceJoin := func(m map[string]bool) string {
   291  		keys := make([]string, 0, len(m))
   292  		for k := range m {
   293  			keys = append(keys, fmt.Sprintf("%q", k))
   294  		}
   295  		sort.Strings(keys)
   296  
   297  		length := len(keys)
   298  		words := make([]string, length)
   299  		copy(words, keys)
   300  
   301  		words[length-1] = fmt.Sprintf("or %s", words[length-1])
   302  		return strings.Join(words, ", ")
   303  	}
   304  
   305  	if _, ok := cannedAcls[value]; !ok {
   306  		errors = append(errors, fmt.Errorf(
   307  			"%q contains an invalid canned ACL type %q. Valid types are either %s",
   308  			k, value, sentenceJoin(cannedAcls)))
   309  	}
   310  	return
   311  }
   312  
   313  func validateS3BucketObjectStorageClassType(v interface{}, k string) (ws []string, errors []error) {
   314  	value := v.(string)
   315  
   316  	storageClass := map[string]bool{
   317  		s3.StorageClassStandard:          true,
   318  		s3.StorageClassReducedRedundancy: true,
   319  		s3.StorageClassStandardIa:        true,
   320  	}
   321  
   322  	if _, ok := storageClass[value]; !ok {
   323  		errors = append(errors, fmt.Errorf(
   324  			"%q contains an invalid Storage Class type %q. Valid types are either %q, %q, or %q",
   325  			k, value, s3.StorageClassStandard, s3.StorageClassReducedRedundancy,
   326  			s3.StorageClassStandardIa))
   327  	}
   328  	return
   329  }