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  }