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  }