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