github.com/SaurabhDubey-Groww/go-cloud@v0.0.0-20221124105541-b26c29285fd8/blob/s3blob/s3blob_test.go (about)

     1  // Copyright 2018 The Go Cloud Development Kit Authors
     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  //     https://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 s3blob
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"net/http"
    22  	"testing"
    23  
    24  	s3managerv2 "github.com/aws/aws-sdk-go-v2/feature/s3/manager"
    25  	s3v2 "github.com/aws/aws-sdk-go-v2/service/s3"
    26  	typesv2 "github.com/aws/aws-sdk-go-v2/service/s3/types"
    27  	"github.com/aws/aws-sdk-go/aws"
    28  	"github.com/aws/aws-sdk-go/aws/awserr"
    29  	"github.com/aws/aws-sdk-go/aws/client"
    30  	"github.com/aws/aws-sdk-go/aws/session"
    31  	"github.com/aws/aws-sdk-go/service/s3"
    32  	"github.com/aws/aws-sdk-go/service/s3/s3manager"
    33  	"github.com/aws/smithy-go"
    34  	"gocloud.dev/blob"
    35  	"gocloud.dev/blob/driver"
    36  	"gocloud.dev/blob/drivertest"
    37  	"gocloud.dev/internal/testing/setup"
    38  )
    39  
    40  // These constants record the region & bucket used for the last --record.
    41  // If you want to use --record mode,
    42  // 1. Create a bucket in your AWS project from the S3 management console.
    43  //
    44  //	https://s3.console.aws.amazon.com/s3/home.
    45  //
    46  // 2. Update this constant to your bucket name.
    47  // TODO(issue #300): Use Terraform to provision a bucket, and get the bucket
    48  //
    49  //	name from the Terraform output instead (saving a copy of it for replay).
    50  const (
    51  	bucketName = "go-cloud-testing"
    52  	region     = "us-west-1"
    53  )
    54  
    55  type harness struct {
    56  	useV2    bool
    57  	session  *session.Session
    58  	clientV2 *s3v2.Client
    59  	opts     *Options
    60  	rt       http.RoundTripper
    61  	closer   func()
    62  }
    63  
    64  func newHarness(ctx context.Context, t *testing.T) (drivertest.Harness, error) {
    65  	sess, rt, done, _ := setup.NewAWSSession(ctx, t, region)
    66  	return &harness{useV2: false, session: sess, opts: nil, rt: rt, closer: done}, nil
    67  }
    68  
    69  func newHarnessUsingLegacyList(ctx context.Context, t *testing.T) (drivertest.Harness, error) {
    70  	sess, rt, done, _ := setup.NewAWSSession(ctx, t, region)
    71  	return &harness{useV2: false, session: sess, opts: &Options{UseLegacyList: true}, rt: rt, closer: done}, nil
    72  }
    73  
    74  func newHarnessV2(ctx context.Context, t *testing.T) (drivertest.Harness, error) {
    75  	cfg, rt, done, _ := setup.NewAWSv2Config(ctx, t, region)
    76  	return &harness{useV2: true, clientV2: s3v2.NewFromConfig(cfg), opts: nil, rt: rt, closer: done}, nil
    77  }
    78  
    79  func newHarnessUsingLegacyListV2(ctx context.Context, t *testing.T) (drivertest.Harness, error) {
    80  	cfg, rt, done, _ := setup.NewAWSv2Config(ctx, t, region)
    81  	return &harness{useV2: true, clientV2: s3v2.NewFromConfig(cfg), opts: &Options{UseLegacyList: true}, rt: rt, closer: done}, nil
    82  }
    83  
    84  func (h *harness) HTTPClient() *http.Client {
    85  	return &http.Client{Transport: h.rt}
    86  }
    87  
    88  func (h *harness) MakeDriver(ctx context.Context) (driver.Bucket, error) {
    89  	return openBucket(ctx, h.useV2, h.session, h.clientV2, bucketName, h.opts)
    90  }
    91  
    92  func (h *harness) MakeDriverForNonexistentBucket(ctx context.Context) (driver.Bucket, error) {
    93  	return openBucket(ctx, h.useV2, h.session, h.clientV2, "go-cdk-bucket-does-not-exist", h.opts)
    94  }
    95  
    96  func (h *harness) Close() {
    97  	h.closer()
    98  }
    99  
   100  func TestConformance(t *testing.T) {
   101  	drivertest.RunConformanceTests(t, newHarness, []drivertest.AsTest{verifyContentLanguage{useV2: false, usingLegacyList: false}})
   102  }
   103  
   104  func TestConformanceUsingLegacyList(t *testing.T) {
   105  	drivertest.RunConformanceTests(t, newHarnessUsingLegacyList, []drivertest.AsTest{verifyContentLanguage{useV2: false, usingLegacyList: true}})
   106  }
   107  
   108  func TestConformanceV2(t *testing.T) {
   109  	drivertest.RunConformanceTests(t, newHarnessV2, []drivertest.AsTest{verifyContentLanguage{useV2: true, usingLegacyList: false}})
   110  }
   111  
   112  func TestConformanceUsingLegacyListV2(t *testing.T) {
   113  	drivertest.RunConformanceTests(t, newHarnessUsingLegacyListV2, []drivertest.AsTest{verifyContentLanguage{useV2: true, usingLegacyList: true}})
   114  }
   115  
   116  func BenchmarkS3blob(b *testing.B) {
   117  	sess, err := session.NewSession(&aws.Config{
   118  		Region: aws.String(region),
   119  	})
   120  	if err != nil {
   121  		b.Fatal(err)
   122  	}
   123  	bkt, err := OpenBucket(context.Background(), sess, bucketName, nil)
   124  	if err != nil {
   125  		b.Fatal(err)
   126  	}
   127  	drivertest.RunBenchmarks(b, bkt)
   128  }
   129  
   130  const language = "nl"
   131  
   132  // verifyContentLanguage uses As to access the underlying GCS types and
   133  // read/write the ContentLanguage field.
   134  type verifyContentLanguage struct {
   135  	useV2           bool
   136  	usingLegacyList bool
   137  }
   138  
   139  func (verifyContentLanguage) Name() string {
   140  	return "verify ContentLanguage can be written and read through As"
   141  }
   142  
   143  func (v verifyContentLanguage) BucketCheck(b *blob.Bucket) error {
   144  	if v.useV2 {
   145  		var client *s3v2.Client
   146  		if !b.As(&client) {
   147  			return errors.New("Bucket.As failed")
   148  		}
   149  		return nil
   150  	}
   151  	var client *s3.S3
   152  	if !b.As(&client) {
   153  		return errors.New("Bucket.As failed")
   154  	}
   155  	return nil
   156  }
   157  
   158  func (v verifyContentLanguage) ErrorCheck(b *blob.Bucket, err error) error {
   159  	if v.useV2 {
   160  		var e smithy.APIError
   161  		if !b.ErrorAs(err, &e) {
   162  			return errors.New("blob.ErrorAs failed")
   163  		}
   164  	} else {
   165  		var e awserr.Error
   166  		if !b.ErrorAs(err, &e) {
   167  			return errors.New("blob.ErrorAs failed")
   168  		}
   169  	}
   170  	return nil
   171  }
   172  
   173  func (v verifyContentLanguage) BeforeRead(as func(interface{}) bool) error {
   174  	if v.useV2 {
   175  		var req *s3v2.GetObjectInput
   176  		if !as(&req) {
   177  			return errors.New("BeforeRead As failed")
   178  		}
   179  		return nil
   180  	}
   181  	var req *s3.GetObjectInput
   182  	if !as(&req) {
   183  		return errors.New("BeforeRead As failed")
   184  	}
   185  	return nil
   186  }
   187  
   188  func (v verifyContentLanguage) BeforeWrite(as func(interface{}) bool) error {
   189  	if v.useV2 {
   190  		var req *s3v2.PutObjectInput
   191  		if !as(&req) {
   192  			return errors.New("Writer.As failed for PutObjectInput")
   193  		}
   194  		req.ContentLanguage = aws.String(language)
   195  		var u *s3managerv2.Uploader
   196  		if !as(&u) {
   197  			return errors.New("Writer.As failed for Uploader")
   198  		}
   199  		return nil
   200  	}
   201  	var req *s3manager.UploadInput
   202  	if !as(&req) {
   203  		return errors.New("Writer.As failed for UploadInput")
   204  	}
   205  	req.ContentLanguage = aws.String(language)
   206  	var u *s3manager.Uploader
   207  	if !as(&u) {
   208  		return errors.New("Writer.As failed for Uploader")
   209  	}
   210  	return nil
   211  }
   212  
   213  func (v verifyContentLanguage) BeforeCopy(as func(interface{}) bool) error {
   214  	if v.useV2 {
   215  		var in *s3v2.CopyObjectInput
   216  		if !as(&in) {
   217  			return errors.New("BeforeCopy.As failed")
   218  		}
   219  		return nil
   220  	}
   221  	var in *s3.CopyObjectInput
   222  	if !as(&in) {
   223  		return errors.New("BeforeCopy.As failed")
   224  	}
   225  	return nil
   226  }
   227  
   228  func (v verifyContentLanguage) BeforeList(as func(interface{}) bool) error {
   229  	if v.useV2 {
   230  		if v.usingLegacyList {
   231  			var req *s3v2.ListObjectsInput
   232  			if !as(&req) {
   233  				return errors.New("List.As failed")
   234  			}
   235  		} else {
   236  			var req *s3v2.ListObjectsV2Input
   237  			if !as(&req) {
   238  				return errors.New("List.As failed")
   239  			}
   240  		}
   241  		return nil
   242  	}
   243  	if v.usingLegacyList {
   244  		var req *s3.ListObjectsInput
   245  		if !as(&req) {
   246  			return errors.New("List.As failed")
   247  		}
   248  	} else {
   249  		var req *s3.ListObjectsV2Input
   250  		if !as(&req) {
   251  			return errors.New("List.As failed")
   252  		}
   253  	}
   254  	return nil
   255  }
   256  
   257  func (v verifyContentLanguage) BeforeSign(as func(interface{}) bool) error {
   258  	if v.useV2 {
   259  		var (
   260  			get *s3v2.GetObjectInput
   261  			put *s3v2.PutObjectInput
   262  			del *s3v2.DeleteObjectInput
   263  		)
   264  		if as(&get) || as(&put) || as(&del) {
   265  			return nil
   266  		}
   267  		return errors.New("BeforeSign.As failed")
   268  	}
   269  	var (
   270  		get *s3.GetObjectInput
   271  		put *s3.PutObjectInput
   272  		del *s3.DeleteObjectInput
   273  	)
   274  	if as(&get) || as(&put) || as(&del) {
   275  		return nil
   276  	}
   277  	return errors.New("BeforeSign.As failed")
   278  }
   279  
   280  func (v verifyContentLanguage) AttributesCheck(attrs *blob.Attributes) error {
   281  	if v.useV2 {
   282  		var hoo s3v2.HeadObjectOutput
   283  		if !attrs.As(&hoo) {
   284  			return errors.New("Attributes.As returned false")
   285  		}
   286  		if got := *hoo.ContentLanguage; got != language {
   287  			return fmt.Errorf("got %q want %q", got, language)
   288  		}
   289  		return nil
   290  	}
   291  	var hoo s3.HeadObjectOutput
   292  	if !attrs.As(&hoo) {
   293  		return errors.New("Attributes.As returned false")
   294  	}
   295  	if got := *hoo.ContentLanguage; got != language {
   296  		return fmt.Errorf("got %q want %q", got, language)
   297  	}
   298  	return nil
   299  }
   300  
   301  func (v verifyContentLanguage) ReaderCheck(r *blob.Reader) error {
   302  	if v.useV2 {
   303  		var goo s3v2.GetObjectOutput
   304  		if !r.As(&goo) {
   305  			return errors.New("Reader.As returned false")
   306  		}
   307  		if got := *goo.ContentLanguage; got != language {
   308  			return fmt.Errorf("got %q want %q", got, language)
   309  		}
   310  		return nil
   311  	}
   312  	var goo s3.GetObjectOutput
   313  	if !r.As(&goo) {
   314  		return errors.New("Reader.As returned false")
   315  	}
   316  	if got := *goo.ContentLanguage; got != language {
   317  		return fmt.Errorf("got %q want %q", got, language)
   318  	}
   319  	return nil
   320  }
   321  
   322  func (v verifyContentLanguage) ListObjectCheck(o *blob.ListObject) error {
   323  	if v.useV2 {
   324  		if o.IsDir {
   325  			var commonPrefix typesv2.CommonPrefix
   326  			if !o.As(&commonPrefix) {
   327  				return errors.New("ListObject.As for directory returned false")
   328  			}
   329  			return nil
   330  		}
   331  		var obj typesv2.Object
   332  		if !o.As(&obj) {
   333  			return errors.New("ListObject.As for object returned false")
   334  		}
   335  		if obj.Key == nil || o.Key != *obj.Key {
   336  			return errors.New("ListObject.As for object returned a different item")
   337  		}
   338  		return nil
   339  	}
   340  	if o.IsDir {
   341  		var commonPrefix s3.CommonPrefix
   342  		if !o.As(&commonPrefix) {
   343  			return errors.New("ListObject.As for directory returned false")
   344  		}
   345  		return nil
   346  	}
   347  	var obj s3.Object
   348  	if !o.As(&obj) {
   349  		return errors.New("ListObject.As for object returned false")
   350  	}
   351  	if obj.Key == nil || o.Key != *obj.Key {
   352  		return errors.New("ListObject.As for object returned a different item")
   353  	}
   354  	return nil
   355  }
   356  
   357  func TestOpenBucket(t *testing.T) {
   358  	tests := []struct {
   359  		description string
   360  		useV2       bool
   361  		bucketName  string
   362  		nilClient   bool
   363  		want        string
   364  		wantErr     bool
   365  	}{
   366  		{
   367  			description: "empty bucket name results in error",
   368  			wantErr:     true,
   369  		},
   370  		{
   371  			description: "empty bucket name results in error V2",
   372  			useV2:       true,
   373  			wantErr:     true,
   374  		},
   375  		{
   376  			description: "nil client results in error",
   377  			bucketName:  "foo",
   378  			nilClient:   true,
   379  			wantErr:     true,
   380  		},
   381  		{
   382  			description: "nil client results in error V2",
   383  			bucketName:  "foo",
   384  			useV2:       true,
   385  			nilClient:   true,
   386  			wantErr:     true,
   387  		},
   388  		{
   389  			description: "success",
   390  			bucketName:  "foo",
   391  			want:        "foo",
   392  		},
   393  		{
   394  			description: "success V2",
   395  			bucketName:  "foo",
   396  			useV2:       true,
   397  			want:        "foo",
   398  		},
   399  	}
   400  
   401  	ctx := context.Background()
   402  	for _, test := range tests {
   403  		t.Run(test.description, func(t *testing.T) {
   404  			var sess client.ConfigProvider
   405  			var clientV2 *s3v2.Client
   406  			if !test.nilClient {
   407  				if test.useV2 {
   408  					cfg, _, done, _ := setup.NewAWSv2Config(ctx, t, region)
   409  					defer done()
   410  					clientV2 = s3v2.NewFromConfig(cfg)
   411  				} else {
   412  					s, _, done, _ := setup.NewAWSSession(ctx, t, region)
   413  					defer done()
   414  					sess = s
   415  				}
   416  			}
   417  
   418  			// Create driver impl.
   419  			drv, err := openBucket(ctx, test.useV2, sess, clientV2, test.bucketName, nil)
   420  			if (err != nil) != test.wantErr {
   421  				t.Errorf("got err %v want error %v", err, test.wantErr)
   422  			}
   423  			if err == nil && drv != nil && drv.name != test.want {
   424  				t.Errorf("got %q want %q", drv.name, test.want)
   425  			}
   426  
   427  			// Create portable type.
   428  			var b *blob.Bucket
   429  			if test.useV2 {
   430  				b, err = OpenBucketV2(ctx, clientV2, test.bucketName, nil)
   431  			} else {
   432  				b, err = OpenBucket(ctx, sess, test.bucketName, nil)
   433  			}
   434  			if b != nil {
   435  				defer b.Close()
   436  			}
   437  			if (err != nil) != test.wantErr {
   438  				t.Errorf("got err %v want error %v", err, test.wantErr)
   439  			}
   440  		})
   441  	}
   442  }
   443  
   444  func TestOpenBucketFromURL(t *testing.T) {
   445  	tests := []struct {
   446  		URL     string
   447  		WantErr bool
   448  	}{
   449  		// OK.
   450  		{"s3://mybucket", false},
   451  		// OK, setting region.
   452  		{"s3://mybucket?region=us-west1", false},
   453  		// OK, setting profile.
   454  		{"s3://mybucket?profile=main", false},
   455  		// OK, setting both profile and region.
   456  		{"s3://mybucket?profile=main&region=us-west-1", false},
   457  		// OK, use V2.
   458  		{"s3://mybucket?awssdk=2", false},
   459  		// Invalid parameter together with a valid one.
   460  		{"s3://mybucket?profile=main&param=value", true},
   461  		// Invalid parameter.
   462  		{"s3://mybucket?param=value", true},
   463  	}
   464  
   465  	ctx := context.Background()
   466  	for _, test := range tests {
   467  		b, err := blob.OpenBucket(ctx, test.URL)
   468  		if b != nil {
   469  			defer b.Close()
   470  		}
   471  		if (err != nil) != test.WantErr {
   472  			t.Errorf("%s: got error %v, want error %v", test.URL, err, test.WantErr)
   473  		}
   474  	}
   475  }