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 }