github.com/mponton/terratest@v0.44.0/modules/aws/s3.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "fmt" 6 "strings" 7 8 "github.com/aws/aws-sdk-go/aws" 9 "github.com/aws/aws-sdk-go/service/s3" 10 "github.com/aws/aws-sdk-go/service/s3/s3manager" 11 "github.com/mponton/terratest/modules/logger" 12 "github.com/mponton/terratest/modules/testing" 13 "github.com/stretchr/testify/require" 14 ) 15 16 // FindS3BucketWithTag finds the name of the S3 bucket in the given region with the given tag key=value. 17 func FindS3BucketWithTag(t testing.TestingT, awsRegion string, key string, value string) string { 18 bucket, err := FindS3BucketWithTagE(t, awsRegion, key, value) 19 require.NoError(t, err) 20 21 return bucket 22 } 23 24 // FindS3BucketWithTagE finds the name of the S3 bucket in the given region with the given tag key=value. 25 func FindS3BucketWithTagE(t testing.TestingT, awsRegion string, key string, value string) (string, error) { 26 s3Client, err := NewS3ClientE(t, awsRegion) 27 if err != nil { 28 return "", err 29 } 30 31 resp, err := s3Client.ListBuckets(&s3.ListBucketsInput{}) 32 if err != nil { 33 return "", err 34 } 35 36 for _, bucket := range resp.Buckets { 37 tagResponse, err := s3Client.GetBucketTagging(&s3.GetBucketTaggingInput{Bucket: bucket.Name}) 38 39 if err != nil { 40 if strings.Contains(err.Error(), "NoSuchBucket") { 41 // Occasionally, the ListBuckets call will return a bucket that has been deleted by S3 42 // but hasn't yet been actually removed from the backend. Listing tags on that bucket 43 // will return this error. If the bucket has been deleted, it can't be the one to find, 44 // so just ignore this error, and keep checking the other buckets. 45 continue 46 } 47 if !strings.Contains(err.Error(), "AuthorizationHeaderMalformed") && 48 !strings.Contains(err.Error(), "BucketRegionError") && 49 !strings.Contains(err.Error(), "NoSuchTagSet") { 50 return "", err 51 } 52 } 53 54 for _, tag := range tagResponse.TagSet { 55 if *tag.Key == key && *tag.Value == value { 56 logger.Logf(t, "Found S3 bucket %s with tag %s=%s", *bucket.Name, key, value) 57 return *bucket.Name, nil 58 } 59 } 60 } 61 62 return "", nil 63 } 64 65 // GetS3BucketTags fetches the given bucket's tags and returns them as a string map of strings. 66 func GetS3BucketTags(t testing.TestingT, awsRegion string, bucket string) map[string]string { 67 tags, err := GetS3BucketTagsE(t, awsRegion, bucket) 68 require.NoError(t, err) 69 70 return tags 71 } 72 73 // GetS3BucketTagsE fetches the given bucket's tags and returns them as a string map of strings. 74 func GetS3BucketTagsE(t testing.TestingT, awsRegion string, bucket string) (map[string]string, error) { 75 s3Client, err := NewS3ClientE(t, awsRegion) 76 if err != nil { 77 return nil, err 78 } 79 80 out, err := s3Client.GetBucketTagging(&s3.GetBucketTaggingInput{ 81 Bucket: &bucket, 82 }) 83 if err != nil { 84 return nil, err 85 } 86 87 tags := map[string]string{} 88 for _, tag := range out.TagSet { 89 tags[aws.StringValue(tag.Key)] = aws.StringValue(tag.Value) 90 } 91 92 return tags, nil 93 } 94 95 // GetS3ObjectContents fetches the contents of the object in the given bucket with the given key and return it as a string. 96 func GetS3ObjectContents(t testing.TestingT, awsRegion string, bucket string, key string) string { 97 contents, err := GetS3ObjectContentsE(t, awsRegion, bucket, key) 98 require.NoError(t, err) 99 100 return contents 101 } 102 103 // GetS3ObjectContentsE fetches the contents of the object in the given bucket with the given key and return it as a string. 104 func GetS3ObjectContentsE(t testing.TestingT, awsRegion string, bucket string, key string) (string, error) { 105 s3Client, err := NewS3ClientE(t, awsRegion) 106 if err != nil { 107 return "", err 108 } 109 110 res, err := s3Client.GetObject(&s3.GetObjectInput{ 111 Bucket: &bucket, 112 Key: &key, 113 }) 114 115 if err != nil { 116 return "", err 117 } 118 119 buf := new(bytes.Buffer) 120 _, err = buf.ReadFrom(res.Body) 121 if err != nil { 122 return "", err 123 } 124 125 contents := buf.String() 126 logger.Logf(t, "Read contents from s3://%s/%s", bucket, key) 127 128 return contents, nil 129 } 130 131 // CreateS3Bucket creates an S3 bucket in the given region with the given name. Note that S3 bucket names must be globally unique. 132 func CreateS3Bucket(t testing.TestingT, region string, name string) { 133 err := CreateS3BucketE(t, region, name) 134 require.NoError(t, err) 135 } 136 137 // CreateS3BucketE creates an S3 bucket in the given region with the given name. Note that S3 bucket names must be globally unique. 138 func CreateS3BucketE(t testing.TestingT, region string, name string) error { 139 logger.Logf(t, "Creating bucket %s in %s", name, region) 140 141 s3Client, err := NewS3ClientE(t, region) 142 if err != nil { 143 return err 144 } 145 146 params := &s3.CreateBucketInput{ 147 Bucket: aws.String(name), 148 // https://github.com/aws/aws-sdk-go/blob/v1.44.122/service/s3/api.go#L41646 149 ObjectOwnership: aws.String(s3.ObjectOwnershipObjectWriter), 150 } 151 _, err = s3Client.CreateBucket(params) 152 return err 153 } 154 155 // PutS3BucketPolicy applies an IAM resource policy to a given S3 bucket to create it's bucket policy 156 func PutS3BucketPolicy(t testing.TestingT, region string, bucketName string, policyJSONString string) { 157 err := PutS3BucketPolicyE(t, region, bucketName, policyJSONString) 158 require.NoError(t, err) 159 } 160 161 // PutS3BucketPolicyE applies an IAM resource policy to a given S3 bucket to create it's bucket policy 162 func PutS3BucketPolicyE(t testing.TestingT, region string, bucketName string, policyJSONString string) error { 163 logger.Logf(t, "Applying bucket policy for bucket %s in %s", bucketName, region) 164 165 s3Client, err := NewS3ClientE(t, region) 166 if err != nil { 167 return err 168 } 169 170 input := &s3.PutBucketPolicyInput{ 171 Bucket: aws.String(bucketName), 172 Policy: aws.String(policyJSONString), 173 } 174 175 _, err = s3Client.PutBucketPolicy(input) 176 return err 177 } 178 179 // PutS3BucketVersioning creates an S3 bucket versioning configuration in the given region against the given bucket name, WITHOUT requiring MFA to remove versioning. 180 func PutS3BucketVersioning(t testing.TestingT, region string, bucketName string) { 181 err := PutS3BucketVersioningE(t, region, bucketName) 182 require.NoError(t, err) 183 } 184 185 // PutS3BucketVersioningE creates an S3 bucket versioning configuration in the given region against the given bucket name, WITHOUT requiring MFA to remove versioning. 186 func PutS3BucketVersioningE(t testing.TestingT, region string, bucketName string) error { 187 logger.Logf(t, "Creating bucket versioning configuration for bucket %s in %s", bucketName, region) 188 189 s3Client, err := NewS3ClientE(t, region) 190 if err != nil { 191 return err 192 } 193 194 input := &s3.PutBucketVersioningInput{ 195 Bucket: aws.String(bucketName), 196 VersioningConfiguration: &s3.VersioningConfiguration{ 197 MFADelete: aws.String("Disabled"), 198 Status: aws.String("Enabled"), 199 }, 200 } 201 202 _, err = s3Client.PutBucketVersioning(input) 203 return err 204 } 205 206 // DeleteS3Bucket destroys the S3 bucket in the given region with the given name. 207 func DeleteS3Bucket(t testing.TestingT, region string, name string) { 208 err := DeleteS3BucketE(t, region, name) 209 require.NoError(t, err) 210 } 211 212 // DeleteS3BucketE destroys the S3 bucket in the given region with the given name. 213 func DeleteS3BucketE(t testing.TestingT, region string, name string) error { 214 logger.Logf(t, "Deleting bucket %s in %s", region, name) 215 216 s3Client, err := NewS3ClientE(t, region) 217 if err != nil { 218 return err 219 } 220 221 params := &s3.DeleteBucketInput{ 222 Bucket: aws.String(name), 223 } 224 _, err = s3Client.DeleteBucket(params) 225 return err 226 } 227 228 // EmptyS3Bucket removes the contents of an S3 bucket in the given region with the given name. 229 func EmptyS3Bucket(t testing.TestingT, region string, name string) { 230 err := EmptyS3BucketE(t, region, name) 231 require.NoError(t, err) 232 } 233 234 // EmptyS3BucketE removes the contents of an S3 bucket in the given region with the given name. 235 func EmptyS3BucketE(t testing.TestingT, region string, name string) error { 236 logger.Logf(t, "Emptying bucket %s in %s", name, region) 237 238 s3Client, err := NewS3ClientE(t, region) 239 if err != nil { 240 return err 241 } 242 243 params := &s3.ListObjectVersionsInput{ 244 Bucket: aws.String(name), 245 } 246 247 for { 248 // Requesting a batch of objects from s3 bucket 249 bucketObjects, err := s3Client.ListObjectVersions(params) 250 if err != nil { 251 return err 252 } 253 254 //Checks if the bucket is already empty 255 if len((*bucketObjects).Versions) == 0 { 256 logger.Logf(t, "Bucket %s is already empty", name) 257 return nil 258 } 259 260 //creating an array of pointers of ObjectIdentifier 261 objectsToDelete := make([]*s3.ObjectIdentifier, 0, 1000) 262 for _, object := range (*bucketObjects).Versions { 263 obj := s3.ObjectIdentifier{ 264 Key: object.Key, 265 VersionId: object.VersionId, 266 } 267 objectsToDelete = append(objectsToDelete, &obj) 268 } 269 270 for _, object := range (*bucketObjects).DeleteMarkers { 271 obj := s3.ObjectIdentifier{ 272 Key: object.Key, 273 VersionId: object.VersionId, 274 } 275 objectsToDelete = append(objectsToDelete, &obj) 276 } 277 278 //Creating JSON payload for bulk delete 279 deleteArray := s3.Delete{Objects: objectsToDelete} 280 deleteParams := &s3.DeleteObjectsInput{ 281 Bucket: aws.String(name), 282 Delete: &deleteArray, 283 } 284 285 //Running the Bulk delete job (limit 1000) 286 _, err = s3Client.DeleteObjects(deleteParams) 287 if err != nil { 288 return err 289 } 290 291 if *(*bucketObjects).IsTruncated { //if there are more objects in the bucket, IsTruncated = true 292 // params.Marker = (*deleteParams).Delete.Objects[len((*deleteParams).Delete.Objects)-1].Key 293 params.KeyMarker = bucketObjects.NextKeyMarker 294 logger.Logf(t, "Requesting next batch | %s", *(params.KeyMarker)) 295 } else { //if all objects in the bucket have been cleaned up. 296 break 297 } 298 } 299 logger.Logf(t, "Bucket %s is now empty", name) 300 return err 301 } 302 303 // GetS3BucketLoggingTarget fetches the given bucket's logging target bucket and returns it as a string 304 func GetS3BucketLoggingTarget(t testing.TestingT, awsRegion string, bucket string) string { 305 loggingTarget, err := GetS3BucketLoggingTargetE(t, awsRegion, bucket) 306 require.NoError(t, err) 307 308 return loggingTarget 309 } 310 311 // GetS3BucketLoggingTargetE fetches the given bucket's logging target bucket and returns it as the following string: 312 // `TargetBucket` of the `LoggingEnabled` property for an S3 bucket 313 func GetS3BucketLoggingTargetE(t testing.TestingT, awsRegion string, bucket string) (string, error) { 314 s3Client, err := NewS3ClientE(t, awsRegion) 315 if err != nil { 316 return "", err 317 } 318 319 res, err := s3Client.GetBucketLogging(&s3.GetBucketLoggingInput{ 320 Bucket: &bucket, 321 }) 322 323 if err != nil { 324 return "", err 325 } 326 327 if res.LoggingEnabled == nil { 328 return "", S3AccessLoggingNotEnabledErr{bucket, awsRegion} 329 } 330 331 return aws.StringValue(res.LoggingEnabled.TargetBucket), nil 332 } 333 334 // GetS3BucketLoggingTargetPrefix fetches the given bucket's logging object prefix and returns it as a string 335 func GetS3BucketLoggingTargetPrefix(t testing.TestingT, awsRegion string, bucket string) string { 336 loggingObjectTargetPrefix, err := GetS3BucketLoggingTargetPrefixE(t, awsRegion, bucket) 337 require.NoError(t, err) 338 339 return loggingObjectTargetPrefix 340 } 341 342 // GetS3BucketLoggingTargetPrefixE fetches the given bucket's logging object prefix and returns it as the following string: 343 // `TargetPrefix` of the `LoggingEnabled` property for an S3 bucket 344 func GetS3BucketLoggingTargetPrefixE(t testing.TestingT, awsRegion string, bucket string) (string, error) { 345 s3Client, err := NewS3ClientE(t, awsRegion) 346 if err != nil { 347 return "", err 348 } 349 350 res, err := s3Client.GetBucketLogging(&s3.GetBucketLoggingInput{ 351 Bucket: &bucket, 352 }) 353 354 if err != nil { 355 return "", err 356 } 357 358 if res.LoggingEnabled == nil { 359 return "", S3AccessLoggingNotEnabledErr{bucket, awsRegion} 360 } 361 362 return aws.StringValue(res.LoggingEnabled.TargetPrefix), nil 363 } 364 365 // GetS3BucketVersioning fetches the given bucket's versioning configuration status and returns it as a string 366 func GetS3BucketVersioning(t testing.TestingT, awsRegion string, bucket string) string { 367 versioningStatus, err := GetS3BucketVersioningE(t, awsRegion, bucket) 368 require.NoError(t, err) 369 370 return versioningStatus 371 } 372 373 // GetS3BucketVersioningE fetches the given bucket's versioning configuration status and returns it as a string 374 func GetS3BucketVersioningE(t testing.TestingT, awsRegion string, bucket string) (string, error) { 375 s3Client, err := NewS3ClientE(t, awsRegion) 376 if err != nil { 377 return "", err 378 } 379 380 res, err := s3Client.GetBucketVersioning(&s3.GetBucketVersioningInput{ 381 Bucket: &bucket, 382 }) 383 if err != nil { 384 return "", err 385 } 386 387 return aws.StringValue(res.Status), nil 388 } 389 390 // GetS3BucketPolicy fetches the given bucket's resource policy and returns it as a string 391 func GetS3BucketPolicy(t testing.TestingT, awsRegion string, bucket string) string { 392 bucketPolicy, err := GetS3BucketPolicyE(t, awsRegion, bucket) 393 require.NoError(t, err) 394 395 return bucketPolicy 396 } 397 398 // GetS3BucketPolicyE fetches the given bucket's resource policy and returns it as a string 399 func GetS3BucketPolicyE(t testing.TestingT, awsRegion string, bucket string) (string, error) { 400 s3Client, err := NewS3ClientE(t, awsRegion) 401 if err != nil { 402 return "", err 403 } 404 405 res, err := s3Client.GetBucketPolicy(&s3.GetBucketPolicyInput{ 406 Bucket: &bucket, 407 }) 408 if err != nil { 409 return "", err 410 } 411 412 return aws.StringValue(res.Policy), nil 413 } 414 415 // AssertS3BucketExists checks if the given S3 bucket exists in the given region and fail the test if it does not. 416 func AssertS3BucketExists(t testing.TestingT, region string, name string) { 417 err := AssertS3BucketExistsE(t, region, name) 418 require.NoError(t, err) 419 } 420 421 // AssertS3BucketExistsE checks if the given S3 bucket exists in the given region and return an error if it does not. 422 func AssertS3BucketExistsE(t testing.TestingT, region string, name string) error { 423 s3Client, err := NewS3ClientE(t, region) 424 if err != nil { 425 return err 426 } 427 428 params := &s3.HeadBucketInput{ 429 Bucket: aws.String(name), 430 } 431 _, err = s3Client.HeadBucket(params) 432 return err 433 } 434 435 // AssertS3BucketVersioningExists checks if the given S3 bucket has a versioning configuration enabled and returns an error if it does not. 436 func AssertS3BucketVersioningExists(t testing.TestingT, region string, bucketName string) { 437 err := AssertS3BucketVersioningExistsE(t, region, bucketName) 438 require.NoError(t, err) 439 } 440 441 // AssertS3BucketVersioningExistsE checks if the given S3 bucket has a versioning configuration enabled and returns an error if it does not. 442 func AssertS3BucketVersioningExistsE(t testing.TestingT, region string, bucketName string) error { 443 status, err := GetS3BucketVersioningE(t, region, bucketName) 444 if err != nil { 445 return err 446 } 447 448 if status == "Enabled" { 449 return nil 450 } 451 return NewBucketVersioningNotEnabledError(bucketName, region, status) 452 } 453 454 // AssertS3BucketPolicyExists checks if the given S3 bucket has a resource policy attached and returns an error if it does not 455 func AssertS3BucketPolicyExists(t testing.TestingT, region string, bucketName string) { 456 err := AssertS3BucketPolicyExistsE(t, region, bucketName) 457 require.NoError(t, err) 458 } 459 460 // AssertS3BucketPolicyExistsE checks if the given S3 bucket has a resource policy attached and returns an error if it does not 461 func AssertS3BucketPolicyExistsE(t testing.TestingT, region string, bucketName string) error { 462 policy, err := GetS3BucketPolicyE(t, region, bucketName) 463 if err != nil { 464 return err 465 } 466 467 if policy == "" { 468 return NewNoBucketPolicyError(bucketName, region, policy) 469 } 470 return nil 471 } 472 473 // NewS3Client creates an S3 client. 474 func NewS3Client(t testing.TestingT, region string) *s3.S3 { 475 client, err := NewS3ClientE(t, region) 476 require.NoError(t, err) 477 478 return client 479 } 480 481 // NewS3ClientE creates an S3 client. 482 func NewS3ClientE(t testing.TestingT, region string) (*s3.S3, error) { 483 sess, err := NewAuthenticatedSession(region) 484 if err != nil { 485 return nil, err 486 } 487 488 return s3.New(sess), nil 489 } 490 491 // NewS3Uploader creates an S3 Uploader. 492 func NewS3Uploader(t testing.TestingT, region string) *s3manager.Uploader { 493 uploader, err := NewS3UploaderE(t, region) 494 require.NoError(t, err) 495 return uploader 496 } 497 498 // NewS3UploaderE creates an S3 Uploader. 499 func NewS3UploaderE(t testing.TestingT, region string) (*s3manager.Uploader, error) { 500 sess, err := NewAuthenticatedSession(region) 501 if err != nil { 502 return nil, err 503 } 504 505 return s3manager.NewUploader(sess), nil 506 } 507 508 // S3AccessLoggingNotEnabledErr is a custom error that occurs when acess logging hasn't been enabled on the S3 Bucket 509 type S3AccessLoggingNotEnabledErr struct { 510 OriginBucket string 511 Region string 512 } 513 514 func (err S3AccessLoggingNotEnabledErr) Error() string { 515 return fmt.Sprintf("Server Acess Logging hasn't been enabled for S3 Bucket %s in region %s", err.OriginBucket, err.Region) 516 }