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  }