github.com/coreos/mantle@v0.13.0/platform/api/aws/s3.go (about)

     1  // Copyright 2016 CoreOS, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package aws
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  	"io/ioutil"
    21  	"net/url"
    22  	"os"
    23  
    24  	"github.com/aws/aws-sdk-go/aws"
    25  	"github.com/aws/aws-sdk-go/aws/awserr"
    26  	"github.com/aws/aws-sdk-go/service/s3"
    27  	"github.com/aws/aws-sdk-go/service/s3/s3manager"
    28  )
    29  
    30  const (
    31  	// ContentTypeJSON is canonical content-type for JSON objects
    32  	ContentTypeJSON = "application/json"
    33  
    34  	// The SDK documentation claims the error code should be `NoSuchKey`, but in
    35  	// practice that's the error for Get and NotFound is the error for Head.
    36  	// https://github.com/aws/aws-sdk-go/blob/b84b5a456f5f281454e9fbe89b38e34d617f4a51/service/s3/api.go#L2618-L2620
    37  	// is just wrong.
    38  	documentedNotFoundErr = "NoSuchKey"
    39  	actualNotFoundErr     = "NotFound"
    40  
    41  	alreadyExistsErr = "BucketAlreadyOwnedByYou"
    42  )
    43  
    44  func s3IsNotFound(err error) bool {
    45  	if awserr, ok := err.(awserr.Error); ok {
    46  		return awserr.Code() == documentedNotFoundErr || awserr.Code() == actualNotFoundErr
    47  	}
    48  	return false
    49  }
    50  
    51  // UploadObject uploads an object to S3
    52  func (a *API) UploadObject(r io.Reader, bucket, path string, force bool, policy string, contentType string) error {
    53  	s3uploader := s3manager.NewUploaderWithClient(a.s3)
    54  
    55  	if !force {
    56  		_, err := a.s3.HeadObject(&s3.HeadObjectInput{
    57  			Bucket: &bucket,
    58  			Key:    &path,
    59  		})
    60  		if err != nil {
    61  			if !s3IsNotFound(err) {
    62  				return fmt.Errorf("unable to head object %v/%v: %v", bucket, path, err)
    63  			}
    64  		} else {
    65  			plog.Infof("skipping upload since object exists and force was not set: s3://%v/%v", bucket, path)
    66  			return nil
    67  		}
    68  	}
    69  
    70  	input := s3manager.UploadInput{
    71  		Body:   r,
    72  		Bucket: aws.String(bucket),
    73  		Key:    aws.String(path),
    74  		ACL:    aws.String(policy),
    75  	}
    76  	if contentType != "" {
    77  		input.ContentType = aws.String(contentType)
    78  	}
    79  
    80  	plog.Infof("uploading s3://%v/%v", bucket, path)
    81  	if _, err := s3uploader.Upload(&input); err != nil {
    82  		return fmt.Errorf("error uploading s3://%v/%v: %v", bucket, path, err)
    83  	}
    84  
    85  	return nil
    86  }
    87  
    88  func (a *API) DeleteObject(bucket, path string) error {
    89  	plog.Infof("deleting s3://%v/%v", bucket, path)
    90  	_, err := a.s3.DeleteObject(&s3.DeleteObjectInput{
    91  		Bucket: aws.String(bucket),
    92  		Key:    aws.String(path),
    93  	})
    94  	if err != nil {
    95  		return fmt.Errorf("error deleting s3://%v/%v: %v", bucket, path, err)
    96  	}
    97  	return err
    98  }
    99  
   100  func (a *API) InitializeBucket(bucket string) error {
   101  	_, err := a.s3.CreateBucket(&s3.CreateBucketInput{
   102  		Bucket: &bucket,
   103  	})
   104  	if err != nil {
   105  		if awserr, ok := err.(awserr.Error); ok {
   106  			if awserr.Code() == alreadyExistsErr {
   107  				return nil
   108  			}
   109  		}
   110  	}
   111  	return err
   112  }
   113  
   114  // This will modify the ACL on Objects to one of the canned ACL policies
   115  func (a *API) PutObjectAcl(bucket, path, policy string) error {
   116  	_, err := a.s3.PutObjectAcl(&s3.PutObjectAclInput{
   117  		ACL:    aws.String(policy),
   118  		Bucket: aws.String(bucket),
   119  		Key:    aws.String(path),
   120  	})
   121  	if err != nil {
   122  		return fmt.Errorf("setting object ACL: %v", err)
   123  	}
   124  	return nil
   125  }
   126  
   127  // Copy an Object to a new location with a given canned ACL policy
   128  func (a *API) CopyObject(srcBucket, srcPath, destBucket, destPath, policy string) error {
   129  	err := a.InitializeBucket(destBucket)
   130  	if err != nil {
   131  		return fmt.Errorf("creating destination bucket: %v", err)
   132  	}
   133  	_, err = a.s3.CopyObject(&s3.CopyObjectInput{
   134  		ACL:        aws.String(policy),
   135  		CopySource: aws.String(url.QueryEscape(fmt.Sprintf("%s/%s", srcBucket, srcPath))),
   136  		Bucket:     aws.String(destBucket),
   137  		Key:        aws.String(destPath),
   138  	})
   139  	if err != nil {
   140  		if awserr, ok := err.(awserr.Error); ok {
   141  			if awserr.Code() == alreadyExistsErr {
   142  				return nil
   143  			}
   144  		}
   145  	}
   146  	return err
   147  }
   148  
   149  // Copies all objects in srcBucket to destBucket with a given canned ACL policy
   150  func (a *API) CopyBucket(srcBucket, prefix, destBucket, policy string) error {
   151  	objects, err := a.s3.ListObjects(&s3.ListObjectsInput{
   152  		Bucket: aws.String(srcBucket),
   153  		Prefix: aws.String(prefix),
   154  	})
   155  	if err != nil {
   156  		return fmt.Errorf("listing bucket: %v", err)
   157  	}
   158  
   159  	err = a.InitializeBucket(destBucket)
   160  	if err != nil {
   161  		return fmt.Errorf("creating destination bucket: %v", err)
   162  	}
   163  
   164  	for _, object := range objects.Contents {
   165  		path := *object.Key
   166  		err = a.CopyObject(srcBucket, path, destBucket, path, policy)
   167  		if err != nil {
   168  			return err
   169  		}
   170  	}
   171  
   172  	return nil
   173  }
   174  
   175  // TODO: bikeshed this name
   176  // modifies the ACL of all objects of a given prefix in srcBucket to a given canned ACL policy
   177  func (a *API) UpdateBucketObjectsACL(srcBucket, prefix, policy string) error {
   178  	objects, err := a.s3.ListObjects(&s3.ListObjectsInput{
   179  		Bucket: aws.String(srcBucket),
   180  		Prefix: aws.String(prefix),
   181  	})
   182  	if err != nil {
   183  		return fmt.Errorf("listing bucket: %v", err)
   184  	}
   185  
   186  	for _, object := range objects.Contents {
   187  		path := *object.Key
   188  		err = a.PutObjectAcl(srcBucket, path, policy)
   189  		if err != nil {
   190  			return err
   191  		}
   192  	}
   193  
   194  	return nil
   195  }
   196  
   197  // Downloads a file from S3 to a temporary file. This file must be closed by the caller.
   198  func (a *API) DownloadFile(srcBucket, srcPath string) (*os.File, error) {
   199  	f, err := ioutil.TempFile("", "mantle-file")
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  	downloader := s3manager.NewDownloader(a.session)
   204  	_, err = downloader.Download(f, &s3.GetObjectInput{
   205  		Bucket: aws.String(srcBucket),
   206  		Key:    aws.String(srcPath),
   207  	})
   208  	if err != nil {
   209  		f.Close()
   210  		return nil, err
   211  	}
   212  	return f, nil
   213  }