Remove custom mock and add repository for S3

main
Elie 2021-02-16 18:42:46 +01:00
parent 80f45375f7
commit 76d98ed8e7
No known key found for this signature in database
GPG Key ID: 399AF69092C727B6
20 changed files with 1503 additions and 709 deletions

View File

@ -0,0 +1,37 @@
// Code generated by mockery v2.3.0. DO NOT EDIT.
package client
import (
aws "github.com/aws/aws-sdk-go/aws"
mock "github.com/stretchr/testify/mock"
s3iface "github.com/aws/aws-sdk-go/service/s3/s3iface"
)
// MockAwsClientFactoryInterface is an autogenerated mock type for the AwsClientFactoryInterface type
type MockAwsClientFactoryInterface struct {
mock.Mock
}
// GetS3Client provides a mock function with given fields: configs
func (_m *MockAwsClientFactoryInterface) GetS3Client(configs ...*aws.Config) s3iface.S3API {
_va := make([]interface{}, len(configs))
for _i := range configs {
_va[_i] = configs[_i]
}
var _ca []interface{}
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 s3iface.S3API
if rf, ok := ret.Get(0).(func(...*aws.Config) s3iface.S3API); ok {
r0 = rf(configs...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(s3iface.S3API)
}
}
return r0
}

View File

@ -1,4 +1,4 @@
package aws
package client
import (
"github.com/aws/aws-sdk-go/aws"
@ -15,6 +15,10 @@ type AwsClientFactory struct {
config client.ConfigProvider
}
func NewAWSClientFactory(config client.ConfigProvider) *AwsClientFactory {
return &AwsClientFactory{config}
}
func (s AwsClientFactory) GetS3Client(configs ...*aws.Config) s3iface.S3API {
return s3.New(s.config, configs...)
}

View File

@ -2,6 +2,8 @@ package aws
import (
"github.com/cloudskiff/driftctl/pkg/alerter"
"github.com/cloudskiff/driftctl/pkg/remote/aws/client"
"github.com/cloudskiff/driftctl/pkg/remote/aws/repository"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/terraform"
)
@ -22,16 +24,16 @@ func Init(alerter *alerter.Alerter, providerLibrary *terraform.ProviderLibrary,
return err
}
factory := AwsClientFactory{config: provider.session}
s3Repository := repository.NewS3Repository(client.NewAWSClientFactory(provider.session))
providerLibrary.AddProvider(terraform.AWS, provider)
supplierLibrary.AddSupplier(NewS3BucketSupplier(provider, factory))
supplierLibrary.AddSupplier(NewS3BucketAnalyticSupplier(provider, factory))
supplierLibrary.AddSupplier(NewS3BucketInventorySupplier(provider, factory))
supplierLibrary.AddSupplier(NewS3BucketMetricSupplier(provider, factory))
supplierLibrary.AddSupplier(NewS3BucketNotificationSupplier(provider, factory))
supplierLibrary.AddSupplier(NewS3BucketPolicySupplier(provider, factory))
supplierLibrary.AddSupplier(NewS3BucketSupplier(provider, s3Repository))
supplierLibrary.AddSupplier(NewS3BucketAnalyticSupplier(provider, s3Repository))
supplierLibrary.AddSupplier(NewS3BucketInventorySupplier(provider, s3Repository))
supplierLibrary.AddSupplier(NewS3BucketMetricSupplier(provider, s3Repository))
supplierLibrary.AddSupplier(NewS3BucketNotificationSupplier(provider, s3Repository))
supplierLibrary.AddSupplier(NewS3BucketPolicySupplier(provider, s3Repository))
supplierLibrary.AddSupplier(NewEC2EipSupplier(provider))
supplierLibrary.AddSupplier(NewEC2EipAssociationSupplier(provider))
supplierLibrary.AddSupplier(NewEC2EbsVolumeSupplier(provider))

View File

@ -0,0 +1,126 @@
// Code generated by mockery v2.3.0. DO NOT EDIT.
package repository
import (
s3 "github.com/aws/aws-sdk-go/service/s3"
mock "github.com/stretchr/testify/mock"
)
// MockS3Repository is an autogenerated mock type for the S3Repository type
type MockS3Repository struct {
mock.Mock
}
// GetBucketLocation provides a mock function with given fields: bucket
func (_m *MockS3Repository) GetBucketLocation(bucket *s3.Bucket) (string, error) {
ret := _m.Called(bucket)
var r0 string
if rf, ok := ret.Get(0).(func(*s3.Bucket) string); ok {
r0 = rf(bucket)
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func(*s3.Bucket) error); ok {
r1 = rf(bucket)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListAllBuckets provides a mock function with given fields:
func (_m *MockS3Repository) ListAllBuckets() ([]*s3.Bucket, error) {
ret := _m.Called()
var r0 []*s3.Bucket
if rf, ok := ret.Get(0).(func() []*s3.Bucket); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*s3.Bucket)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListBucketAnalyticsConfigurations provides a mock function with given fields: bucket, region
func (_m *MockS3Repository) ListBucketAnalyticsConfigurations(bucket *s3.Bucket, region string) ([]*s3.AnalyticsConfiguration, error) {
ret := _m.Called(bucket, region)
var r0 []*s3.AnalyticsConfiguration
if rf, ok := ret.Get(0).(func(*s3.Bucket, string) []*s3.AnalyticsConfiguration); ok {
r0 = rf(bucket, region)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*s3.AnalyticsConfiguration)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*s3.Bucket, string) error); ok {
r1 = rf(bucket, region)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListBucketInventoryConfigurations provides a mock function with given fields: bucket, region
func (_m *MockS3Repository) ListBucketInventoryConfigurations(bucket *s3.Bucket, region string) ([]*s3.InventoryConfiguration, error) {
ret := _m.Called(bucket, region)
var r0 []*s3.InventoryConfiguration
if rf, ok := ret.Get(0).(func(*s3.Bucket, string) []*s3.InventoryConfiguration); ok {
r0 = rf(bucket, region)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*s3.InventoryConfiguration)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*s3.Bucket, string) error); ok {
r1 = rf(bucket, region)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListBucketMetricsConfigurations provides a mock function with given fields: bucket, region
func (_m *MockS3Repository) ListBucketMetricsConfigurations(bucket *s3.Bucket, region string) ([]*s3.MetricsConfiguration, error) {
ret := _m.Called(bucket, region)
var r0 []*s3.MetricsConfiguration
if rf, ok := ret.Get(0).(func(*s3.Bucket, string) []*s3.MetricsConfiguration); ok {
r0 = rf(bucket, region)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*s3.MetricsConfiguration)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*s3.Bucket, string) error); ok {
r1 = rf(bucket, region)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@ -0,0 +1,148 @@
package repository
import (
awssdk "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/cloudskiff/driftctl/pkg/remote/aws/client"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
type S3Repository interface {
ListAllBuckets() ([]*s3.Bucket, error)
ListBucketInventoryConfigurations(bucket *s3.Bucket, region string) ([]*s3.InventoryConfiguration, error)
ListBucketMetricsConfigurations(bucket *s3.Bucket, region string) ([]*s3.MetricsConfiguration, error)
ListBucketAnalyticsConfigurations(bucket *s3.Bucket, region string) ([]*s3.AnalyticsConfiguration, error)
GetBucketLocation(bucket *s3.Bucket) (string, error)
}
type s3Repository struct {
clientFactory client.AwsClientFactoryInterface
}
func NewS3Repository(factory client.AwsClientFactoryInterface) *s3Repository {
return &s3Repository{
factory,
}
}
func (s *s3Repository) ListAllBuckets() ([]*s3.Bucket, error) {
out, err := s.clientFactory.GetS3Client(nil).ListBuckets(&s3.ListBucketsInput{})
if err != nil {
return nil, err
}
return out.Buckets, nil
}
func (s *s3Repository) ListBucketInventoryConfigurations(bucket *s3.Bucket, region string) ([]*s3.InventoryConfiguration, error) {
inventoryConfigurations := make([]*s3.InventoryConfiguration, 0)
client := s.clientFactory.GetS3Client(&awssdk.Config{Region: &region})
request := &s3.ListBucketInventoryConfigurationsInput{
Bucket: bucket.Name,
ContinuationToken: nil,
}
for {
configurations, err := client.ListBucketInventoryConfigurations(request)
if err != nil {
return nil, errors.Wrapf(
err,
"Error listing bucket inventory configuration %s",
*bucket.Name,
)
}
inventoryConfigurations = append(inventoryConfigurations, configurations.InventoryConfigurationList...)
if configurations.IsTruncated != nil && *configurations.IsTruncated {
request.ContinuationToken = configurations.NextContinuationToken
} else {
break
}
}
return inventoryConfigurations, nil
}
func (s *s3Repository) ListBucketMetricsConfigurations(bucket *s3.Bucket, region string) ([]*s3.MetricsConfiguration, error) {
metricsConfigurationList := make([]*s3.MetricsConfiguration, 0)
client := s.clientFactory.GetS3Client(&awssdk.Config{Region: &region})
request := &s3.ListBucketMetricsConfigurationsInput{
Bucket: bucket.Name,
ContinuationToken: nil,
}
for {
configurations, err := client.ListBucketMetricsConfigurations(request)
if err != nil {
return nil, errors.Wrapf(
err,
"Error listing bucket metrics configuration %s",
*bucket.Name,
)
}
metricsConfigurationList = append(metricsConfigurationList, configurations.MetricsConfigurationList...)
if configurations.IsTruncated != nil && *configurations.IsTruncated {
request.ContinuationToken = configurations.NextContinuationToken
} else {
break
}
}
return metricsConfigurationList, nil
}
func (s *s3Repository) ListBucketAnalyticsConfigurations(bucket *s3.Bucket, region string) ([]*s3.AnalyticsConfiguration, error) {
analyticsConfigurationList := make([]*s3.AnalyticsConfiguration, 0)
client := s.clientFactory.GetS3Client(&awssdk.Config{Region: &region})
request := &s3.ListBucketAnalyticsConfigurationsInput{
Bucket: bucket.Name,
ContinuationToken: nil,
}
for {
configurations, err := client.ListBucketAnalyticsConfigurations(request)
if err != nil {
return nil, errors.Wrapf(
err,
"Error listing bucket analytics configuration %s",
*bucket.Name,
)
}
analyticsConfigurationList = append(analyticsConfigurationList, configurations.AnalyticsConfigurationList...)
if configurations.IsTruncated != nil && *configurations.IsTruncated {
request.ContinuationToken = configurations.NextContinuationToken
} else {
break
}
}
return analyticsConfigurationList, nil
}
func (s *s3Repository) GetBucketLocation(bucket *s3.Bucket) (string, error) {
bucketLocationRequest := s3.GetBucketLocationInput{Bucket: bucket.Name}
bucketLocationResponse, err := s.clientFactory.GetS3Client(nil).GetBucketLocation(&bucketLocationRequest)
if err != nil {
awsErr, ok := err.(awserr.Error)
if ok && awsErr.Code() == s3.ErrCodeNoSuchBucket {
logrus.WithFields(logrus.Fields{
"bucket": *bucket.Name,
}).Warning("Unable to retrieve bucket region, this may be an inconsistency in S3 api for fresh deleted bucket, skipping ...")
return "", nil
}
return "", err
}
// Buckets in Region us-east-1 have a LocationConstraint of null.
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLocation.html#API_GetBucketLocation_ResponseSyntax
if bucketLocationResponse.LocationConstraint == nil {
return "us-east-1", err
}
if *bucketLocationResponse.LocationConstraint == "EU" {
return "eu-west-1", err
}
return *bucketLocationResponse.LocationConstraint, nil
}

View File

@ -0,0 +1,540 @@
package repository
import (
"strings"
"testing"
"github.com/aws/aws-sdk-go/aws"
awssdk "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/cloudskiff/driftctl/mocks"
"github.com/cloudskiff/driftctl/pkg/remote/aws/client"
"github.com/pkg/errors"
"github.com/r3labs/diff/v2"
"github.com/stretchr/testify/assert"
)
func Test_s3Repository_ListAllBuckets(t *testing.T) {
tests := []struct {
name string
mocks func(client *mocks.FakeS3)
want []*s3.Bucket
wantErr error
}{
{
name: "List buckets",
mocks: func(client *mocks.FakeS3) {
client.On("ListBuckets", &s3.ListBucketsInput{}).Return(
&s3.ListBucketsOutput{
Buckets: []*s3.Bucket{
{Name: aws.String("bucket1")},
{Name: aws.String("bucket2")},
{Name: aws.String("bucket3")},
},
},
nil,
)
},
want: []*s3.Bucket{
{Name: aws.String("bucket1")},
{Name: aws.String("bucket2")},
{Name: aws.String("bucket3")},
},
},
{
name: "Error listing buckets",
mocks: func(client *mocks.FakeS3) {
client.On("ListBuckets", &s3.ListBucketsInput{}).Return(
nil,
awserr.NewRequestFailure(nil, 403, ""),
)
},
want: nil,
wantErr: awserr.NewRequestFailure(nil, 403, ""),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockedClient := &mocks.FakeS3{}
tt.mocks(mockedClient)
factory := client.MockAwsClientFactoryInterface{}
factory.On("GetS3Client", (*aws.Config)(nil)).Return(mockedClient).Once()
r := NewS3Repository(&factory)
got, err := r.ListAllBuckets()
factory.AssertExpectations(t)
assert.Equal(t, tt.wantErr, err)
changelog, err := diff.Diff(got, tt.want)
assert.Nil(t, err)
if len(changelog) > 0 {
for _, change := range changelog {
t.Errorf("%s: %s -> %s", strings.Join(change.Path, "."), change.From, change.To)
}
t.Fail()
}
})
}
}
func Test_s3Repository_ListBucketInventoryConfigurations(t *testing.T) {
tests := []struct {
name string
input struct {
bucket s3.Bucket
region string
}
mocks func(client *mocks.FakeS3)
want []*s3.InventoryConfiguration
wantErr string
}{
{
name: "List inventory configs",
input: struct {
bucket s3.Bucket
region string
}{
bucket: s3.Bucket{
Name: awssdk.String("test-bucket"),
},
region: "us-east-1",
},
mocks: func(client *mocks.FakeS3) {
client.On(
"ListBucketInventoryConfigurations",
&s3.ListBucketInventoryConfigurationsInput{
Bucket: awssdk.String("test-bucket"),
ContinuationToken: nil,
},
).Return(
&s3.ListBucketInventoryConfigurationsOutput{
InventoryConfigurationList: []*s3.InventoryConfiguration{
{Id: awssdk.String("config1")},
{Id: awssdk.String("config2")},
{Id: awssdk.String("config3")},
},
IsTruncated: awssdk.Bool(true),
NextContinuationToken: awssdk.String("nexttoken"),
},
nil,
)
client.On(
"ListBucketInventoryConfigurations",
&s3.ListBucketInventoryConfigurationsInput{
Bucket: awssdk.String("test-bucket"),
ContinuationToken: awssdk.String("nexttoken"),
},
).Return(
&s3.ListBucketInventoryConfigurationsOutput{
InventoryConfigurationList: []*s3.InventoryConfiguration{
{Id: awssdk.String("config4")},
{Id: awssdk.String("config5")},
{Id: awssdk.String("config6")},
},
IsTruncated: awssdk.Bool(false),
},
nil,
)
},
want: []*s3.InventoryConfiguration{
{Id: awssdk.String("config1")},
{Id: awssdk.String("config2")},
{Id: awssdk.String("config3")},
{Id: awssdk.String("config4")},
{Id: awssdk.String("config5")},
{Id: awssdk.String("config6")},
},
},
{
name: "Error listing inventory configs",
input: struct {
bucket s3.Bucket
region string
}{
bucket: s3.Bucket{
Name: awssdk.String("test-bucket"),
},
region: "us-east-1",
},
mocks: func(client *mocks.FakeS3) {
client.On(
"ListBucketInventoryConfigurations",
&s3.ListBucketInventoryConfigurationsInput{
Bucket: awssdk.String("test-bucket"),
},
).Return(
nil,
errors.New("aws error"),
)
},
want: nil,
wantErr: "Error listing bucket inventory configuration test-bucket: aws error",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockedClient := &mocks.FakeS3{}
tt.mocks(mockedClient)
factory := client.MockAwsClientFactoryInterface{}
factory.On("GetS3Client", &aws.Config{Region: awssdk.String(tt.input.region)}).Return(mockedClient).Once()
r := NewS3Repository(&factory)
got, err := r.ListBucketInventoryConfigurations(&tt.input.bucket, tt.input.region)
factory.AssertExpectations(t)
if err != nil && tt.wantErr == "" {
t.Fatalf("Unexpected error %+v", err)
}
if err != nil {
assert.Equal(t, tt.wantErr, err.Error())
}
changelog, err := diff.Diff(got, tt.want)
assert.Nil(t, err)
if len(changelog) > 0 {
for _, change := range changelog {
t.Errorf("%s: %s -> %s", strings.Join(change.Path, "."), change.From, change.To)
}
t.Fail()
}
})
}
}
func Test_s3Repository_ListBucketMetricsConfigurations(t *testing.T) {
tests := []struct {
name string
input struct {
bucket s3.Bucket
region string
}
mocks func(client *mocks.FakeS3)
want []*s3.MetricsConfiguration
wantErr string
}{
{
name: "List metrics configs",
input: struct {
bucket s3.Bucket
region string
}{
bucket: s3.Bucket{
Name: awssdk.String("test-bucket"),
},
region: "us-east-1",
},
mocks: func(client *mocks.FakeS3) {
client.On(
"ListBucketMetricsConfigurations",
&s3.ListBucketMetricsConfigurationsInput{
Bucket: awssdk.String("test-bucket"),
ContinuationToken: nil,
},
).Return(
&s3.ListBucketMetricsConfigurationsOutput{
MetricsConfigurationList: []*s3.MetricsConfiguration{
{Id: awssdk.String("metric1")},
{Id: awssdk.String("metric2")},
{Id: awssdk.String("metric3")},
},
IsTruncated: awssdk.Bool(true),
NextContinuationToken: awssdk.String("nexttoken"),
},
nil,
)
client.On(
"ListBucketMetricsConfigurations",
&s3.ListBucketMetricsConfigurationsInput{
Bucket: awssdk.String("test-bucket"),
ContinuationToken: awssdk.String("nexttoken"),
},
).Return(
&s3.ListBucketMetricsConfigurationsOutput{
MetricsConfigurationList: []*s3.MetricsConfiguration{
{Id: awssdk.String("metric4")},
{Id: awssdk.String("metric5")},
{Id: awssdk.String("metric6")},
},
IsTruncated: awssdk.Bool(false),
},
nil,
)
},
want: []*s3.MetricsConfiguration{
{Id: awssdk.String("metric1")},
{Id: awssdk.String("metric2")},
{Id: awssdk.String("metric3")},
{Id: awssdk.String("metric4")},
{Id: awssdk.String("metric5")},
{Id: awssdk.String("metric6")},
},
},
{
name: "Error listing metrics configs",
input: struct {
bucket s3.Bucket
region string
}{
bucket: s3.Bucket{
Name: awssdk.String("test-bucket"),
},
region: "us-east-1",
},
mocks: func(client *mocks.FakeS3) {
client.On(
"ListBucketMetricsConfigurations",
&s3.ListBucketMetricsConfigurationsInput{
Bucket: awssdk.String("test-bucket"),
},
).Return(
nil,
errors.New("aws error"),
)
},
want: nil,
wantErr: "Error listing bucket metrics configuration test-bucket: aws error",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockedClient := &mocks.FakeS3{}
tt.mocks(mockedClient)
factory := client.MockAwsClientFactoryInterface{}
factory.On("GetS3Client", &aws.Config{Region: awssdk.String(tt.input.region)}).Return(mockedClient).Once()
r := NewS3Repository(&factory)
got, err := r.ListBucketMetricsConfigurations(&tt.input.bucket, tt.input.region)
factory.AssertExpectations(t)
if err != nil && tt.wantErr == "" {
t.Fatalf("Unexpected error %+v", err)
}
if err != nil {
assert.Equal(t, tt.wantErr, err.Error())
}
changelog, err := diff.Diff(got, tt.want)
assert.Nil(t, err)
if len(changelog) > 0 {
for _, change := range changelog {
t.Errorf("%s: %s -> %s", strings.Join(change.Path, "."), change.From, change.To)
}
t.Fail()
}
})
}
}
func Test_s3Repository_ListBucketAnalyticsConfigurations(t *testing.T) {
tests := []struct {
name string
input struct {
bucket s3.Bucket
region string
}
mocks func(client *mocks.FakeS3)
want []*s3.AnalyticsConfiguration
wantErr string
}{
{
name: "List analytics configs",
input: struct {
bucket s3.Bucket
region string
}{
bucket: s3.Bucket{
Name: awssdk.String("test-bucket"),
},
region: "us-east-1",
},
mocks: func(client *mocks.FakeS3) {
client.On(
"ListBucketAnalyticsConfigurations",
&s3.ListBucketAnalyticsConfigurationsInput{
Bucket: awssdk.String("test-bucket"),
ContinuationToken: nil,
},
).Return(
&s3.ListBucketAnalyticsConfigurationsOutput{
AnalyticsConfigurationList: []*s3.AnalyticsConfiguration{
{Id: awssdk.String("analytic1")},
{Id: awssdk.String("analytic2")},
{Id: awssdk.String("analytic3")},
},
IsTruncated: awssdk.Bool(true),
NextContinuationToken: awssdk.String("nexttoken"),
},
nil,
)
client.On(
"ListBucketAnalyticsConfigurations",
&s3.ListBucketAnalyticsConfigurationsInput{
Bucket: awssdk.String("test-bucket"),
ContinuationToken: awssdk.String("nexttoken"),
},
).Return(
&s3.ListBucketAnalyticsConfigurationsOutput{
AnalyticsConfigurationList: []*s3.AnalyticsConfiguration{
{Id: awssdk.String("analytic4")},
{Id: awssdk.String("analytic5")},
{Id: awssdk.String("analytic6")},
},
IsTruncated: awssdk.Bool(false),
},
nil,
)
},
want: []*s3.AnalyticsConfiguration{
{Id: awssdk.String("analytic1")},
{Id: awssdk.String("analytic2")},
{Id: awssdk.String("analytic3")},
{Id: awssdk.String("analytic4")},
{Id: awssdk.String("analytic5")},
{Id: awssdk.String("analytic6")},
},
},
{
name: "Error listing analytics configs",
input: struct {
bucket s3.Bucket
region string
}{
bucket: s3.Bucket{
Name: awssdk.String("test-bucket"),
},
region: "us-east-1",
},
mocks: func(client *mocks.FakeS3) {
client.On(
"ListBucketAnalyticsConfigurations",
&s3.ListBucketAnalyticsConfigurationsInput{
Bucket: awssdk.String("test-bucket"),
},
).Return(
nil,
errors.New("aws error"),
)
},
want: nil,
wantErr: "Error listing bucket analytics configuration test-bucket: aws error",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockedClient := &mocks.FakeS3{}
tt.mocks(mockedClient)
factory := client.MockAwsClientFactoryInterface{}
factory.On("GetS3Client", &aws.Config{Region: awssdk.String(tt.input.region)}).Return(mockedClient).Once()
r := NewS3Repository(&factory)
got, err := r.ListBucketAnalyticsConfigurations(&tt.input.bucket, tt.input.region)
factory.AssertExpectations(t)
if err != nil && tt.wantErr == "" {
t.Fatalf("Unexpected error %+v", err)
}
if err != nil {
assert.Equal(t, tt.wantErr, err.Error())
}
changelog, err := diff.Diff(got, tt.want)
assert.Nil(t, err)
if len(changelog) > 0 {
for _, change := range changelog {
t.Errorf("%s: %s -> %s", strings.Join(change.Path, "."), change.From, change.To)
}
t.Fail()
}
})
}
}
func Test_s3Repository_GetBucketLocation(t *testing.T) {
tests := []struct {
name string
bucket *s3.Bucket
mocks func(client *mocks.FakeS3)
want string
wantErr string
}{
{
name: "get bucket location",
bucket: &s3.Bucket{
Name: awssdk.String("test-bucket"),
},
mocks: func(client *mocks.FakeS3) {
client.On("GetBucketLocation", &s3.GetBucketLocationInput{
Bucket: awssdk.String("test-bucket"),
}).Return(
&s3.GetBucketLocationOutput{
LocationConstraint: awssdk.String("eu-east-1"),
},
nil,
)
},
want: "eu-east-1",
},
{
name: "get bucket location for us-east-2",
bucket: &s3.Bucket{
Name: awssdk.String("test-bucket"),
},
mocks: func(client *mocks.FakeS3) {
client.On("GetBucketLocation", &s3.GetBucketLocationInput{
Bucket: awssdk.String("test-bucket"),
}).Return(
&s3.GetBucketLocationOutput{},
nil,
)
},
want: "us-east-1",
},
{
name: "get bucket location when no such bucket",
bucket: &s3.Bucket{
Name: awssdk.String("test-bucket"),
},
mocks: func(client *mocks.FakeS3) {
client.On("GetBucketLocation", &s3.GetBucketLocationInput{
Bucket: awssdk.String("test-bucket"),
}).Return(
nil,
awserr.New(s3.ErrCodeNoSuchBucket, "", nil),
)
},
want: "",
},
{
name: "get bucket location when error",
bucket: &s3.Bucket{
Name: awssdk.String("test-bucket"),
},
mocks: func(client *mocks.FakeS3) {
client.On("GetBucketLocation", &s3.GetBucketLocationInput{
Bucket: awssdk.String("test-bucket"),
}).Return(
nil,
awserr.New("UnknownError", "aws error", nil),
)
},
wantErr: "UnknownError: aws error",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockedClient := &mocks.FakeS3{}
tt.mocks(mockedClient)
factory := client.MockAwsClientFactoryInterface{}
factory.On("GetS3Client", (*aws.Config)(nil)).Return(mockedClient).Once()
r := NewS3Repository(&factory)
got, err := r.GetBucketLocation(tt.bucket)
factory.AssertExpectations(t)
if err != nil && tt.wantErr == "" {
t.Fatalf("Unexpected error %+v", err)
}
if err != nil {
assert.Equal(t, tt.wantErr, err.Error())
}
changelog, err := diff.Diff(got, tt.want)
assert.Nil(t, err)
if len(changelog) > 0 {
for _, change := range changelog {
t.Errorf("%s: %s -> %s", strings.Join(change.Path, "."), change.From, change.To)
}
t.Fail()
}
})
}
}

View File

@ -3,55 +3,49 @@ package aws
import (
"fmt"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
awssdk "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/cloudskiff/driftctl/pkg/remote/aws/repository"
"github.com/cloudskiff/driftctl/pkg/remote/deserializer"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/resource/aws"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/sirupsen/logrus"
"github.com/zclconf/go-cty/cty"
)
type S3BucketAnalyticSupplier struct {
reader terraform.ResourceReader
deserializer deserializer.CTYDeserializer
factory AwsClientFactoryInterface
repository repository.S3Repository
runner *terraform.ParallelResourceReader
}
func NewS3BucketAnalyticSupplier(provider *AWSTerraformProvider, factory AwsClientFactoryInterface) *S3BucketAnalyticSupplier {
func NewS3BucketAnalyticSupplier(provider *AWSTerraformProvider, repository repository.S3Repository) *S3BucketAnalyticSupplier {
return &S3BucketAnalyticSupplier{
provider,
awsdeserializer.NewS3BucketAnalyticDeserializer(),
factory,
repository,
terraform.NewParallelResourceReader(provider.Runner().SubRunner()),
}
}
func (s *S3BucketAnalyticSupplier) Resources() ([]resource.Resource, error) {
input := &s3.ListBucketsInput{}
client := s.factory.GetS3Client(nil)
response, err := client.ListBuckets(input)
buckets, err := s.repository.ListAllBuckets()
if err != nil {
return nil, remoteerror.NewResourceEnumerationErrorWithType(err, aws.AwsS3BucketAnalyticsConfigurationResourceType, aws.AwsS3BucketResourceType)
}
for _, bucket := range response.Buckets {
name := *bucket.Name
region, err := readBucketRegion(&client, name)
for _, bucket := range buckets {
bucket := *bucket
region, err := s.repository.GetBucketLocation(&bucket)
if err != nil {
return nil, err
}
if region == "" {
continue
}
if err := s.listBucketAnalyticConfiguration(*bucket.Name, region); err != nil {
if err := s.listBucketAnalyticConfiguration(&bucket, region); err != nil {
return nil, remoteerror.NewResourceEnumerationError(err, aws.AwsS3BucketAnalyticsConfigurationResourceType)
}
}
@ -63,31 +57,15 @@ func (s *S3BucketAnalyticSupplier) Resources() ([]resource.Resource, error) {
return s.deserializer.Deserialize(ctyVals)
}
func (s *S3BucketAnalyticSupplier) listBucketAnalyticConfiguration(name, region string) error {
request := &s3.ListBucketAnalyticsConfigurationsInput{
Bucket: &name,
ContinuationToken: nil,
}
analyticsConfigurationList := make([]*s3.AnalyticsConfiguration, 0)
client := s.factory.GetS3Client(&awssdk.Config{Region: &region})
func (s *S3BucketAnalyticSupplier) listBucketAnalyticConfiguration(bucket *s3.Bucket, region string) error {
for {
configurations, err := client.ListBucketAnalyticsConfigurations(request)
if err != nil {
logrus.Warnf("Error listing bucket analytics configuration %s: %+v", name, err)
return err
}
analyticsConfigurationList = append(analyticsConfigurationList, configurations.AnalyticsConfigurationList...)
if configurations.IsTruncated != nil && *configurations.IsTruncated {
request.ContinuationToken = configurations.NextContinuationToken
} else {
break
}
analyticsConfigurationList, err := s.repository.ListBucketAnalyticsConfigurations(bucket, region)
if err != nil {
return err
}
for _, analytics := range analyticsConfigurationList {
id := fmt.Sprintf("%s:%s", name, *analytics.Id)
id := fmt.Sprintf("%s:%s", *bucket.Name, *analytics.Id)
s.runner.Run(func() (cty.Value, error) {
s3BucketAnalytic, err := s.reader.ReadResource(
terraform.ReadResourceArgs{

View File

@ -4,103 +4,141 @@ import (
"context"
"testing"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
resourceaws "github.com/cloudskiff/driftctl/pkg/resource/aws"
"github.com/stretchr/testify/assert"
awssdk "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/cloudskiff/driftctl/pkg/parallel"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
"github.com/cloudskiff/driftctl/test/goldenfile"
"github.com/cloudskiff/driftctl/pkg/remote/aws/client"
"github.com/cloudskiff/driftctl/pkg/remote/aws/repository"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/cloudskiff/driftctl/pkg/resource"
resourceaws "github.com/cloudskiff/driftctl/pkg/resource/aws"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/cloudskiff/driftctl/test"
"github.com/cloudskiff/driftctl/test/goldenfile"
"github.com/cloudskiff/driftctl/test/mocks"
"github.com/stretchr/testify/assert"
)
func TestS3BucketAnalyticSupplier_Resources(t *testing.T) {
tests := []struct {
test string
dirName string
bucketsIDs []string
bucketLocation map[string]string
analyticsIDs map[string][]string
listError error
wantErr error
test string
dirName string
mocks func(repository *repository.MockS3Repository)
listError error
wantErr error
}{
{
test: "multiple bucket with multiple analytics", dirName: "s3_bucket_analytics_multiple",
bucketsIDs: []string{
"bucket-martin-test-drift",
"bucket-martin-test-drift2",
"bucket-martin-test-drift3",
},
bucketLocation: map[string]string{
"bucket-martin-test-drift": "eu-west-1",
"bucket-martin-test-drift2": "eu-west-3",
"bucket-martin-test-drift3": "ap-northeast-1",
},
analyticsIDs: map[string][]string{
"bucket-martin-test-drift": {
"Analytics_Bucket1",
"Analytics2_Bucket1",
},
"bucket-martin-test-drift2": {
"Analytics_Bucket2",
"Analytics2_Bucket2",
},
"bucket-martin-test-drift3": {
"Analytics_Bucket3",
"Analytics2_Bucket3",
},
test: "multiple bucket with multiple analytics",
dirName: "s3_bucket_analytics_multiple",
mocks: func(repository *repository.MockS3Repository) {
repository.On(
"ListAllBuckets",
).Return([]*s3.Bucket{
{Name: awssdk.String("bucket-martin-test-drift")},
{Name: awssdk.String("bucket-martin-test-drift2")},
{Name: awssdk.String("bucket-martin-test-drift3")},
}, nil)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift")},
).Return(
"eu-west-1",
nil,
)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift2")},
).Return(
"eu-west-3",
nil,
)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift3")},
).Return(
"ap-northeast-1",
nil,
)
repository.On(
"ListBucketAnalyticsConfigurations",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift")},
"eu-west-1",
).Return(
[]*s3.AnalyticsConfiguration{
{Id: awssdk.String("Analytics_Bucket1")},
{Id: awssdk.String("Analytics2_Bucket1")},
},
nil,
)
repository.On(
"ListBucketAnalyticsConfigurations",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift2")},
"eu-west-3",
).Return(
[]*s3.AnalyticsConfiguration{
{Id: awssdk.String("Analytics_Bucket2")},
{Id: awssdk.String("Analytics2_Bucket2")},
},
nil,
)
repository.On(
"ListBucketAnalyticsConfigurations",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift3")},
"ap-northeast-1",
).Return(
[]*s3.AnalyticsConfiguration{
{Id: awssdk.String("Analytics_Bucket3")},
{Id: awssdk.String("Analytics2_Bucket3")},
},
nil,
)
},
},
{
test: "cannot list bucket", dirName: "s3_bucket_analytics_list_bucket",
bucketsIDs: nil,
listError: awserr.NewRequestFailure(nil, 403, ""),
bucketLocation: map[string]string{
"bucket-martin-test-drift": "eu-west-1",
"bucket-martin-test-drift2": "eu-west-3",
"bucket-martin-test-drift3": "ap-northeast-1",
},
analyticsIDs: map[string][]string{
"bucket-martin-test-drift": {
"Analytics_Bucket1",
"Analytics2_Bucket1",
},
"bucket-martin-test-drift2": {
"Analytics_Bucket2",
"Analytics2_Bucket2",
},
"bucket-martin-test-drift3": {
"Analytics_Bucket3",
"Analytics2_Bucket3",
},
mocks: func(repository *repository.MockS3Repository) {
repository.On("ListAllBuckets").Return(nil, awserr.NewRequestFailure(nil, 403, ""))
},
wantErr: remoteerror.NewResourceEnumerationErrorWithType(awserr.NewRequestFailure(nil, 403, ""), resourceaws.AwsS3BucketAnalyticsConfigurationResourceType, resourceaws.AwsS3BucketResourceType),
},
{
test: "cannot list Analytics", dirName: "s3_bucket_analytics_list_analytics",
bucketsIDs: []string{
"bucket-martin-test-drift",
"bucket-martin-test-drift2",
"bucket-martin-test-drift3",
mocks: func(repository *repository.MockS3Repository) {
repository.On("ListAllBuckets").Return(
[]*s3.Bucket{
{Name: awssdk.String("bucket-martin-test-drift")},
},
nil,
)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift")},
).Return(
"eu-west-1",
nil,
)
repository.On(
"ListBucketAnalyticsConfigurations",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift")},
"eu-west-1",
).Return(
nil,
awserr.NewRequestFailure(nil, 403, ""),
)
},
bucketLocation: map[string]string{
"bucket-martin-test-drift": "eu-west-1",
"bucket-martin-test-drift2": "eu-west-3",
"bucket-martin-test-drift3": "ap-northeast-1",
},
analyticsIDs: nil,
listError: awserr.NewRequestFailure(nil, 403, ""),
wantErr: remoteerror.NewResourceEnumerationError(awserr.NewRequestFailure(nil, 403, ""), resourceaws.AwsS3BucketAnalyticsConfigurationResourceType),
wantErr: remoteerror.NewResourceEnumerationError(awserr.NewRequestFailure(nil, 403, ""), resourceaws.AwsS3BucketAnalyticsConfigurationResourceType),
},
}
for _, tt := range tests {
@ -114,22 +152,22 @@ func TestS3BucketAnalyticSupplier_Resources(t *testing.T) {
if err != nil {
t.Fatal(err)
}
factory := AwsClientFactory{config: provider.session}
supplierLibrary.AddSupplier(NewS3BucketAnalyticSupplier(provider, factory))
repository := repository.NewS3Repository(client.NewAWSClientFactory(provider.session))
supplierLibrary.AddSupplier(NewS3BucketAnalyticSupplier(provider, repository))
}
t.Run(tt.test, func(t *testing.T) {
mock := mocks.NewMockAWSS3Client(tt.bucketsIDs, tt.analyticsIDs, nil, nil, tt.bucketLocation, tt.listError)
mock := repository.MockS3Repository{}
tt.mocks(&mock)
provider := mocks.NewMockedGoldenTFProvider(tt.dirName, providerLibrary.Provider(terraform.AWS), shouldUpdate)
factory := mocks.NewMockAwsClientFactory(mock)
deserializer := awsdeserializer.NewS3BucketAnalyticDeserializer()
s := &S3BucketAnalyticSupplier{
provider,
deserializer,
factory,
&mock,
terraform.NewParallelResourceReader(parallel.NewParallelRunner(context.TODO(), 10)),
}
got, err := s.Resources()

View File

@ -3,55 +3,49 @@ package aws
import (
"fmt"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
awssdk "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/cloudskiff/driftctl/pkg/remote/aws/repository"
"github.com/cloudskiff/driftctl/pkg/remote/deserializer"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/resource/aws"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/sirupsen/logrus"
"github.com/zclconf/go-cty/cty"
)
type S3BucketInventorySupplier struct {
reader terraform.ResourceReader
deserializer deserializer.CTYDeserializer
factory AwsClientFactoryInterface
repository repository.S3Repository
runner *terraform.ParallelResourceReader
}
func NewS3BucketInventorySupplier(provider *AWSTerraformProvider, factory AwsClientFactoryInterface) *S3BucketInventorySupplier {
func NewS3BucketInventorySupplier(provider *AWSTerraformProvider, repository repository.S3Repository) *S3BucketInventorySupplier {
return &S3BucketInventorySupplier{
provider,
awsdeserializer.NewS3BucketInventoryDeserializer(),
factory,
repository,
terraform.NewParallelResourceReader(provider.Runner().SubRunner()),
}
}
func (s *S3BucketInventorySupplier) Resources() ([]resource.Resource, error) {
input := &s3.ListBucketsInput{}
client := s.factory.GetS3Client(nil)
response, err := client.ListBuckets(input)
buckets, err := s.repository.ListAllBuckets()
if err != nil {
return nil, remoteerror.NewResourceEnumerationErrorWithType(err, aws.AwsS3BucketInventoryResourceType, aws.AwsS3BucketResourceType)
}
for _, bucket := range response.Buckets {
name := *bucket.Name
region, err := readBucketRegion(&client, name)
for _, bucket := range buckets {
bucket := *bucket
region, err := s.repository.GetBucketLocation(&bucket)
if err != nil {
return nil, err
}
if region == "" {
continue
}
if err := s.listBucketInventoryConfiguration(*bucket.Name, region); err != nil {
if err := s.listBucketInventoryConfiguration(&bucket, region); err != nil {
return nil, remoteerror.NewResourceEnumerationError(err, aws.AwsS3BucketInventoryResourceType)
}
}
@ -63,31 +57,13 @@ func (s *S3BucketInventorySupplier) Resources() ([]resource.Resource, error) {
return s.deserializer.Deserialize(ctyVals)
}
func (s *S3BucketInventorySupplier) listBucketInventoryConfiguration(name, region string) error {
request := &s3.ListBucketInventoryConfigurationsInput{
Bucket: &name,
ContinuationToken: nil,
func (s *S3BucketInventorySupplier) listBucketInventoryConfiguration(bucket *s3.Bucket, region string) error {
inventoryConfigurations, err := s.repository.ListBucketInventoryConfigurations(bucket, region)
if err != nil {
return err
}
inventoryConfigurations := make([]*s3.InventoryConfiguration, 0)
client := s.factory.GetS3Client(&awssdk.Config{Region: &region})
for {
configurations, err := client.ListBucketInventoryConfigurations(request)
if err != nil {
logrus.Warnf("Error listing bucket inventory configuration %s: %+v", name, err)
return err
}
inventoryConfigurations = append(inventoryConfigurations, configurations.InventoryConfigurationList...)
if configurations.IsTruncated != nil && *configurations.IsTruncated {
request.ContinuationToken = configurations.NextContinuationToken
} else {
break
}
}
for _, config := range inventoryConfigurations {
id := fmt.Sprintf("%s:%s", name, *config.Id)
id := fmt.Sprintf("%s:%s", *bucket.Name, *config.Id)
s.runner.Run(func() (cty.Value, error) {
s3BucketInventory, err := s.reader.ReadResource(
terraform.ReadResourceArgs{

View File

@ -4,102 +4,137 @@ import (
"context"
"testing"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
resourceaws "github.com/cloudskiff/driftctl/pkg/resource/aws"
"github.com/stretchr/testify/assert"
awssdk "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/cloudskiff/driftctl/pkg/parallel"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
"github.com/cloudskiff/driftctl/test/goldenfile"
"github.com/cloudskiff/driftctl/pkg/remote/aws/client"
"github.com/cloudskiff/driftctl/pkg/remote/aws/repository"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/cloudskiff/driftctl/pkg/resource"
resourceaws "github.com/cloudskiff/driftctl/pkg/resource/aws"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/cloudskiff/driftctl/test"
"github.com/cloudskiff/driftctl/test/goldenfile"
"github.com/cloudskiff/driftctl/test/mocks"
"github.com/stretchr/testify/assert"
)
func TestS3BucketInventorySupplier_Resources(t *testing.T) {
tests := []struct {
test string
dirName string
bucketsIDs []string
bucketLocation map[string]string
inventoriesIDs map[string][]string
listError error
wantErr error
test string
dirName string
mocks func(repository *repository.MockS3Repository)
wantErr error
}{
{
test: "multiple bucket with multiple inventories", dirName: "s3_bucket_inventories_multiple",
bucketsIDs: []string{
"bucket-martin-test-drift",
"bucket-martin-test-drift2",
"bucket-martin-test-drift3",
},
bucketLocation: map[string]string{
"bucket-martin-test-drift": "eu-west-1",
"bucket-martin-test-drift2": "eu-west-3",
"bucket-martin-test-drift3": "ap-northeast-1",
},
inventoriesIDs: map[string][]string{
"bucket-martin-test-drift": {
"Inventory_Bucket1",
"Inventory2_Bucket1",
},
"bucket-martin-test-drift2": {
"Inventory_Bucket2",
"Inventory2_Bucket2",
},
"bucket-martin-test-drift3": {
"Inventory_Bucket3",
"Inventory2_Bucket3",
},
mocks: func(repository *repository.MockS3Repository) {
repository.On(
"ListAllBuckets",
).Return([]*s3.Bucket{
{Name: awssdk.String("bucket-martin-test-drift")},
{Name: awssdk.String("bucket-martin-test-drift2")},
{Name: awssdk.String("bucket-martin-test-drift3")},
}, nil)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift")},
).Return(
"eu-west-1",
nil,
)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift2")},
).Return(
"eu-west-3",
nil,
)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift3")},
).Return(
"eu-west-1",
nil,
)
repository.On(
"ListBucketInventoryConfigurations",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift")},
"eu-west-1",
).Return(
[]*s3.InventoryConfiguration{
{Id: awssdk.String("Inventory_Bucket1")},
{Id: awssdk.String("Inventory2_Bucket1")},
},
nil,
)
repository.On(
"ListBucketInventoryConfigurations",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift2")},
"eu-west-3",
).Return(
[]*s3.InventoryConfiguration{
{Id: awssdk.String("Inventory_Bucket2")},
{Id: awssdk.String("Inventory2_Bucket2")},
},
nil,
)
repository.On(
"ListBucketInventoryConfigurations",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift3")},
"eu-west-1",
).Return(
[]*s3.InventoryConfiguration{
{Id: awssdk.String("Inventory_Bucket3")},
{Id: awssdk.String("Inventory2_Bucket3")},
},
nil,
)
},
},
{
test: "cannot list bucket", dirName: "s3_bucket_inventories_list_bucket",
bucketsIDs: nil,
listError: awserr.NewRequestFailure(nil, 403, ""),
bucketLocation: map[string]string{
"bucket-martin-test-drift": "eu-west-1",
"bucket-martin-test-drift2": "eu-west-3",
"bucket-martin-test-drift3": "ap-northeast-1",
},
inventoriesIDs: map[string][]string{
"bucket-martin-test-drift": {
"Inventory_Bucket1",
"Inventory2_Bucket1",
},
"bucket-martin-test-drift2": {
"Inventory_Bucket2",
"Inventory2_Bucket2",
},
"bucket-martin-test-drift3": {
"Inventory_Bucket3",
"Inventory2_Bucket3",
},
mocks: func(repository *repository.MockS3Repository) {
repository.On("ListAllBuckets").Return(nil, awserr.NewRequestFailure(nil, 403, ""))
},
wantErr: remoteerror.NewResourceEnumerationErrorWithType(awserr.NewRequestFailure(nil, 403, ""), resourceaws.AwsS3BucketInventoryResourceType, resourceaws.AwsS3BucketResourceType),
},
{
test: "cannot list bucket inventories", dirName: "s3_bucket_inventories_list_inventories",
bucketsIDs: []string{
"bucket-martin-test-drift",
"bucket-martin-test-drift2",
"bucket-martin-test-drift3",
mocks: func(repository *repository.MockS3Repository) {
repository.On("ListAllBuckets").Return(
[]*s3.Bucket{
{Name: awssdk.String("bucket-martin-test-drift")},
},
nil,
)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift")},
).Return(
"eu-west-1",
nil,
)
repository.On(
"ListBucketInventoryConfigurations",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift")},
"eu-west-1",
).Return(
nil,
awserr.NewRequestFailure(nil, 403, ""),
)
},
bucketLocation: map[string]string{
"bucket-martin-test-drift": "eu-west-1",
"bucket-martin-test-drift2": "eu-west-3",
"bucket-martin-test-drift3": "ap-northeast-1",
},
inventoriesIDs: nil,
listError: awserr.NewRequestFailure(nil, 403, ""),
wantErr: remoteerror.NewResourceEnumerationError(awserr.NewRequestFailure(nil, 403, ""), resourceaws.AwsS3BucketInventoryResourceType),
wantErr: remoteerror.NewResourceEnumerationError(awserr.NewRequestFailure(nil, 403, ""), resourceaws.AwsS3BucketInventoryResourceType),
},
}
for _, tt := range tests {
@ -114,21 +149,21 @@ func TestS3BucketInventorySupplier_Resources(t *testing.T) {
t.Fatal(err)
}
factory := AwsClientFactory{config: provider.session}
supplierLibrary.AddSupplier(NewS3BucketInventorySupplier(provider, factory))
repository := repository.NewS3Repository(client.NewAWSClientFactory(provider.session))
supplierLibrary.AddSupplier(NewS3BucketInventorySupplier(provider, repository))
}
t.Run(tt.test, func(t *testing.T) {
mock := mocks.NewMockAWSS3Client(tt.bucketsIDs, nil, tt.inventoriesIDs, nil, tt.bucketLocation, tt.listError)
factory := mocks.NewMockAwsClientFactory(mock)
mock := repository.MockS3Repository{}
tt.mocks(&mock)
provider := mocks.NewMockedGoldenTFProvider(tt.dirName, providerLibrary.Provider(terraform.AWS), shouldUpdate)
deserializer := awsdeserializer.NewS3BucketInventoryDeserializer()
s := &S3BucketInventorySupplier{
provider,
deserializer,
factory,
&mock,
terraform.NewParallelResourceReader(parallel.NewParallelRunner(context.TODO(), 10)),
}
got, err := s.Resources()

View File

@ -4,102 +4,138 @@ import (
"context"
"testing"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
resourceaws "github.com/cloudskiff/driftctl/pkg/resource/aws"
awssdk "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/cloudskiff/driftctl/pkg/parallel"
"github.com/stretchr/testify/assert"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
"github.com/cloudskiff/driftctl/test/goldenfile"
"github.com/cloudskiff/driftctl/pkg/remote/aws/client"
"github.com/cloudskiff/driftctl/pkg/remote/aws/repository"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/cloudskiff/driftctl/pkg/resource"
resourceaws "github.com/cloudskiff/driftctl/pkg/resource/aws"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/cloudskiff/driftctl/test"
"github.com/cloudskiff/driftctl/test/goldenfile"
"github.com/cloudskiff/driftctl/test/mocks"
"github.com/stretchr/testify/assert"
)
func TestS3BucketMetricSupplier_Resources(t *testing.T) {
tests := []struct {
test string
dirName string
bucketsIDs []string
bucketLocation map[string]string
metricsIDs map[string][]string
listError error
wantErr error
test string
dirName string
mocks func(repository *repository.MockS3Repository)
wantErr error
}{
{
test: "multiple bucket with multiple metrics", dirName: "s3_bucket_metrics_multiple",
bucketsIDs: []string{
"bucket-martin-test-drift",
"bucket-martin-test-drift2",
"bucket-martin-test-drift3",
},
bucketLocation: map[string]string{
"bucket-martin-test-drift": "eu-west-1",
"bucket-martin-test-drift2": "eu-west-3",
"bucket-martin-test-drift3": "ap-northeast-1",
},
metricsIDs: map[string][]string{
"bucket-martin-test-drift": {
"Metrics_Bucket1",
"Metrics2_Bucket1",
},
"bucket-martin-test-drift2": {
"Metrics_Bucket2",
"Metrics2_Bucket2",
},
"bucket-martin-test-drift3": {
"Metrics_Bucket3",
"Metrics2_Bucket3",
},
mocks: func(repository *repository.MockS3Repository) {
repository.On(
"ListAllBuckets",
).Return([]*s3.Bucket{
{Name: awssdk.String("bucket-martin-test-drift")},
{Name: awssdk.String("bucket-martin-test-drift2")},
{Name: awssdk.String("bucket-martin-test-drift3")},
}, nil)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift")},
).Return(
"eu-west-1",
nil,
)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift2")},
).Return(
"eu-west-3",
nil,
)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift3")},
).Return(
"ap-northeast-1",
nil,
)
repository.On(
"ListBucketMetricsConfigurations",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift")},
"eu-west-1",
).Return(
[]*s3.MetricsConfiguration{
{Id: awssdk.String("Metrics_Bucket1")},
{Id: awssdk.String("Metrics2_Bucket1")},
},
nil,
)
repository.On(
"ListBucketMetricsConfigurations",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift2")},
"eu-west-3",
).Return(
[]*s3.MetricsConfiguration{
{Id: awssdk.String("Metrics_Bucket2")},
{Id: awssdk.String("Metrics2_Bucket2")},
},
nil,
)
repository.On(
"ListBucketMetricsConfigurations",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift3")},
"ap-northeast-1",
).Return(
[]*s3.MetricsConfiguration{
{Id: awssdk.String("Metrics_Bucket3")},
{Id: awssdk.String("Metrics2_Bucket3")},
},
nil,
)
},
},
{
test: "cannot list bucket", dirName: "s3_bucket_metrics_list_bucket",
bucketsIDs: nil,
listError: awserr.NewRequestFailure(nil, 403, ""),
bucketLocation: map[string]string{
"bucket-martin-test-drift": "eu-west-1",
"bucket-martin-test-drift2": "eu-west-3",
"bucket-martin-test-drift3": "ap-northeast-1",
},
metricsIDs: map[string][]string{
"bucket-martin-test-drift": {
"Metrics_Bucket1",
"Metrics2_Bucket1",
},
"bucket-martin-test-drift2": {
"Metrics_Bucket2",
"Metrics2_Bucket2",
},
"bucket-martin-test-drift3": {
"Metrics_Bucket3",
"Metrics2_Bucket3",
},
mocks: func(repository *repository.MockS3Repository) {
repository.On("ListAllBuckets").Return(nil, awserr.NewRequestFailure(nil, 403, ""))
},
wantErr: remoteerror.NewResourceEnumerationErrorWithType(awserr.NewRequestFailure(nil, 403, ""), resourceaws.AwsS3BucketMetricResourceType, resourceaws.AwsS3BucketResourceType),
},
{
test: "cannot list metrics", dirName: "s3_bucket_metrics_list_metrics",
bucketsIDs: []string{
"bucket-martin-test-drift",
"bucket-martin-test-drift2",
"bucket-martin-test-drift3",
mocks: func(repository *repository.MockS3Repository) {
repository.On("ListAllBuckets").Return(
[]*s3.Bucket{
{Name: awssdk.String("bucket-martin-test-drift")},
},
nil,
)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift")},
).Return(
"eu-west-1",
nil,
)
repository.On(
"ListBucketMetricsConfigurations",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift")},
"eu-west-1",
).Return(
nil,
awserr.NewRequestFailure(nil, 403, ""),
)
},
bucketLocation: map[string]string{
"bucket-martin-test-drift": "eu-west-1",
"bucket-martin-test-drift2": "eu-west-3",
"bucket-martin-test-drift3": "ap-northeast-1",
},
metricsIDs: nil,
listError: awserr.NewRequestFailure(nil, 403, ""),
wantErr: remoteerror.NewResourceEnumerationError(awserr.NewRequestFailure(nil, 403, ""), resourceaws.AwsS3BucketMetricResourceType),
wantErr: remoteerror.NewResourceEnumerationError(awserr.NewRequestFailure(nil, 403, ""), resourceaws.AwsS3BucketMetricResourceType),
},
}
for _, tt := range tests {
@ -113,21 +149,21 @@ func TestS3BucketMetricSupplier_Resources(t *testing.T) {
if err != nil {
t.Fatal(err)
}
factory := AwsClientFactory{config: provider.session}
supplierLibrary.AddSupplier(NewS3BucketMetricSupplier(provider, factory))
repository := repository.NewS3Repository(client.NewAWSClientFactory(provider.session))
supplierLibrary.AddSupplier(NewS3BucketMetricSupplier(provider, repository))
}
t.Run(tt.test, func(t *testing.T) {
mock := mocks.NewMockAWSS3Client(tt.bucketsIDs, nil, nil, tt.metricsIDs, tt.bucketLocation, tt.listError)
factory := mocks.NewMockAwsClientFactory(mock)
mock := repository.MockS3Repository{}
tt.mocks(&mock)
provider := mocks.NewMockedGoldenTFProvider(tt.dirName, providerLibrary.Provider(terraform.AWS), shouldUpdate)
deserializer := awsdeserializer.NewS3BucketMetricDeserializer()
s := &S3BucketMetricSupplier{
provider,
deserializer,
factory,
&mock,
terraform.NewParallelResourceReader(parallel.NewParallelRunner(context.TODO(), 10)),
}
got, err := s.Resources()

View File

@ -3,55 +3,49 @@ package aws
import (
"fmt"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
awssdk "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/cloudskiff/driftctl/pkg/remote/aws/repository"
"github.com/cloudskiff/driftctl/pkg/remote/deserializer"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/resource/aws"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/sirupsen/logrus"
"github.com/zclconf/go-cty/cty"
)
type S3BucketMetricSupplier struct {
reader terraform.ResourceReader
deserializer deserializer.CTYDeserializer
factory AwsClientFactoryInterface
repository repository.S3Repository
runner *terraform.ParallelResourceReader
}
func NewS3BucketMetricSupplier(provider *AWSTerraformProvider, factory AwsClientFactoryInterface) *S3BucketMetricSupplier {
func NewS3BucketMetricSupplier(provider *AWSTerraformProvider, repository repository.S3Repository) *S3BucketMetricSupplier {
return &S3BucketMetricSupplier{
provider,
awsdeserializer.NewS3BucketMetricDeserializer(),
factory,
repository,
terraform.NewParallelResourceReader(provider.Runner().SubRunner()),
}
}
func (s *S3BucketMetricSupplier) Resources() ([]resource.Resource, error) {
input := &s3.ListBucketsInput{}
client := s.factory.GetS3Client(nil)
response, err := client.ListBuckets(input)
buckets, err := s.repository.ListAllBuckets()
if err != nil {
return nil, remoteerror.NewResourceEnumerationErrorWithType(err, aws.AwsS3BucketMetricResourceType, aws.AwsS3BucketResourceType)
}
for _, bucket := range response.Buckets {
name := *bucket.Name
region, err := readBucketRegion(&client, name)
for _, bucket := range buckets {
bucket := *bucket
region, err := s.repository.GetBucketLocation(&bucket)
if err != nil {
return nil, err
}
if region == "" {
continue
}
if err := s.listBucketMetricConfiguration(*bucket.Name, region); err != nil {
if err := s.listBucketMetricConfiguration(&bucket, region); err != nil {
return nil, remoteerror.NewResourceEnumerationError(err, aws.AwsS3BucketMetricResourceType)
}
}
@ -63,31 +57,15 @@ func (s *S3BucketMetricSupplier) Resources() ([]resource.Resource, error) {
return s.deserializer.Deserialize(ctyVals)
}
func (s *S3BucketMetricSupplier) listBucketMetricConfiguration(name, region string) error {
request := &s3.ListBucketMetricsConfigurationsInput{
Bucket: &name,
ContinuationToken: nil,
}
func (s *S3BucketMetricSupplier) listBucketMetricConfiguration(bucket *s3.Bucket, region string) error {
metricsConfigurationList := make([]*s3.MetricsConfiguration, 0)
client := s.factory.GetS3Client(&awssdk.Config{Region: &region})
for {
configurations, err := client.ListBucketMetricsConfigurations(request)
if err != nil {
logrus.Warnf("Error listing bucket analytics configuration %s: %+v", name, err)
return err
}
metricsConfigurationList = append(metricsConfigurationList, configurations.MetricsConfigurationList...)
if configurations.IsTruncated != nil && *configurations.IsTruncated {
request.ContinuationToken = configurations.NextContinuationToken
} else {
break
}
metricsConfigurationList, err := s.repository.ListBucketMetricsConfigurations(bucket, region)
if err != nil {
return err
}
for _, config := range metricsConfigurationList {
id := fmt.Sprintf("%s:%s", name, *config.Id)
id := fmt.Sprintf("%s:%s", *bucket.Name, *config.Id)
s.runner.Run(func() (cty.Value, error) {
s3BucketMetric, err := s.reader.ReadResource(
terraform.ReadResourceArgs{

View File

@ -1,10 +1,9 @@
package aws
import (
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/cloudskiff/driftctl/pkg/remote/aws/repository"
"github.com/cloudskiff/driftctl/pkg/remote/deserializer"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/resource/aws"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
@ -15,37 +14,47 @@ import (
type S3BucketNotificationSupplier struct {
reader terraform.ResourceReader
deserializer deserializer.CTYDeserializer
factory AwsClientFactoryInterface
repository repository.S3Repository
runner *terraform.ParallelResourceReader
}
func NewS3BucketNotificationSupplier(provider *AWSTerraformProvider, factory AwsClientFactoryInterface) *S3BucketNotificationSupplier {
func NewS3BucketNotificationSupplier(provider *AWSTerraformProvider, repository repository.S3Repository) *S3BucketNotificationSupplier {
return &S3BucketNotificationSupplier{
provider,
awsdeserializer.NewS3BucketNotificationDeserializer(),
factory, terraform.NewParallelResourceReader(provider.Runner().SubRunner()),
repository,
terraform.NewParallelResourceReader(provider.Runner().SubRunner()),
}
}
func (s *S3BucketNotificationSupplier) Resources() ([]resource.Resource, error) {
input := &s3.ListBucketsInput{}
client := s.factory.GetS3Client(nil)
response, err := client.ListBuckets(input)
buckets, err := s.repository.ListAllBuckets()
if err != nil {
return nil, remoteerror.NewResourceEnumerationErrorWithType(err, aws.AwsS3BucketNotificationResourceType, aws.AwsS3BucketResourceType)
}
for _, bucket := range response.Buckets {
name := *bucket.Name
region, err := readBucketRegion(&client, name)
for _, bucket := range buckets {
bucket := *bucket
region, err := s.repository.GetBucketLocation(&bucket)
if err != nil {
return nil, err
}
if region == "" {
continue
}
s.listBucketNotificationConfiguration(*bucket.Name, region)
s.runner.Run(func() (cty.Value, error) {
s3BucketPolicy, err := s.reader.ReadResource(terraform.ReadResourceArgs{
Ty: aws.AwsS3BucketNotificationResourceType,
ID: *bucket.Name,
Attributes: map[string]string{
"alias": region,
},
})
if err != nil {
return cty.NilVal, err
}
return *s3BucketPolicy, err
})
}
ctyVals, err := s.runner.Wait()
if err != nil {
@ -68,19 +77,3 @@ func (s *S3BucketNotificationSupplier) Resources() ([]resource.Resource, error)
}
return results, nil
}
func (s *S3BucketNotificationSupplier) listBucketNotificationConfiguration(name, region string) {
s.runner.Run(func() (cty.Value, error) {
s3BucketPolicy, err := s.reader.ReadResource(terraform.ReadResourceArgs{
Ty: aws.AwsS3BucketNotificationResourceType,
ID: name,
Attributes: map[string]string{
"alias": region,
},
})
if err != nil {
return cty.NilVal, err
}
return *s3BucketPolicy, err
})
}

View File

@ -4,65 +4,90 @@ import (
"context"
"testing"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
resourceaws "github.com/cloudskiff/driftctl/pkg/resource/aws"
awssdk "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/cloudskiff/driftctl/pkg/parallel"
"github.com/stretchr/testify/assert"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
"github.com/cloudskiff/driftctl/test/goldenfile"
"github.com/cloudskiff/driftctl/pkg/remote/aws/client"
"github.com/cloudskiff/driftctl/pkg/remote/aws/repository"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/cloudskiff/driftctl/pkg/resource"
resourceaws "github.com/cloudskiff/driftctl/pkg/resource/aws"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/cloudskiff/driftctl/test"
"github.com/cloudskiff/driftctl/test/goldenfile"
"github.com/cloudskiff/driftctl/test/mocks"
"github.com/stretchr/testify/assert"
)
func TestS3BucketNotificationSupplier_Resources(t *testing.T) {
tests := []struct {
test string
dirName string
bucketsIDs []string
bucketLocation map[string]string
listError error
wantErr error
test string
dirName string
mocks func(repository *repository.MockS3Repository)
wantErr error
}{
{
test: "single bucket without notifications",
dirName: "s3_bucket_notifications_no_notif",
bucketsIDs: []string{
"dritftctl-test-no-notifications",
},
bucketLocation: map[string]string{
"dritftctl-test-no-notifications": "eu-west-3",
mocks: func(repository *repository.MockS3Repository) {
repository.On(
"ListAllBuckets",
).Return([]*s3.Bucket{
{Name: awssdk.String("dritftctl-test-no-notifications")},
}, nil)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("dritftctl-test-no-notifications")},
).Return(
"eu-west-3",
nil,
)
},
},
{
test: "multiple bucket with notifications", dirName: "s3_bucket_notifications_multiple",
bucketsIDs: []string{
"bucket-martin-test-drift",
"bucket-martin-test-drift2",
"bucket-martin-test-drift3",
},
bucketLocation: map[string]string{
"bucket-martin-test-drift": "eu-west-1",
"bucket-martin-test-drift2": "eu-west-3",
"bucket-martin-test-drift3": "ap-northeast-1",
mocks: func(repository *repository.MockS3Repository) {
repository.On(
"ListAllBuckets",
).Return([]*s3.Bucket{
{Name: awssdk.String("bucket-martin-test-drift")},
{Name: awssdk.String("bucket-martin-test-drift2")},
{Name: awssdk.String("bucket-martin-test-drift3")},
}, nil)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift")},
).Return(
"eu-west-1",
nil,
)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift2")},
).Return(
"eu-west-3",
nil,
)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift3")},
).Return(
"ap-northeast-1",
nil,
)
},
},
{
test: "Cannot list bucket", dirName: "s3_bucket_notifications_list_bucket",
listError: awserr.NewRequestFailure(nil, 403, ""),
bucketLocation: map[string]string{
"bucket-martin-test-drift": "eu-west-1",
"bucket-martin-test-drift2": "eu-west-3",
"bucket-martin-test-drift3": "ap-northeast-1",
mocks: func(repository *repository.MockS3Repository) {
repository.On("ListAllBuckets").Return(nil, awserr.NewRequestFailure(nil, 403, ""))
},
wantErr: remoteerror.NewResourceEnumerationErrorWithType(awserr.NewRequestFailure(nil, 403, ""), resourceaws.AwsS3BucketNotificationResourceType, resourceaws.AwsS3BucketResourceType),
},
@ -78,21 +103,21 @@ func TestS3BucketNotificationSupplier_Resources(t *testing.T) {
if err != nil {
t.Fatal(err)
}
factory := AwsClientFactory{config: provider.session}
supplierLibrary.AddSupplier(NewS3BucketNotificationSupplier(provider, factory))
repository := repository.NewS3Repository(client.NewAWSClientFactory(provider.session))
supplierLibrary.AddSupplier(NewS3BucketNotificationSupplier(provider, repository))
}
t.Run(tt.test, func(t *testing.T) {
mock := mocks.NewMockAWSS3Client(tt.bucketsIDs, nil, nil, nil, tt.bucketLocation, tt.listError)
factory := mocks.NewMockAwsClientFactory(mock)
mock := repository.MockS3Repository{}
tt.mocks(&mock)
provider := mocks.NewMockedGoldenTFProvider(tt.dirName, providerLibrary.Provider(terraform.AWS), shouldUpdate)
deserializer := awsdeserializer.NewS3BucketNotificationDeserializer()
s := &S3BucketNotificationSupplier{
provider,
deserializer,
factory,
&mock,
terraform.NewParallelResourceReader(parallel.NewParallelRunner(context.TODO(), 10)),
}
got, err := s.Resources()

View File

@ -1,10 +1,9 @@
package aws
import (
"github.com/aws/aws-sdk-go/service/s3"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/cloudskiff/driftctl/pkg/remote/aws/repository"
"github.com/cloudskiff/driftctl/pkg/remote/deserializer"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/resource/aws"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
@ -15,38 +14,50 @@ import (
type S3BucketPolicySupplier struct {
reader terraform.ResourceReader
deserializer deserializer.CTYDeserializer
factory AwsClientFactoryInterface
repository repository.S3Repository
runner *terraform.ParallelResourceReader
}
func NewS3BucketPolicySupplier(provider *AWSTerraformProvider, factory AwsClientFactoryInterface) *S3BucketPolicySupplier {
func NewS3BucketPolicySupplier(provider *AWSTerraformProvider, repository repository.S3Repository) *S3BucketPolicySupplier {
return &S3BucketPolicySupplier{
provider,
awsdeserializer.NewS3BucketPolicyDeserializer(),
factory,
repository,
terraform.NewParallelResourceReader(provider.Runner().SubRunner()),
}
}
func (s *S3BucketPolicySupplier) Resources() ([]resource.Resource, error) {
input := &s3.ListBucketsInput{}
client := s.factory.GetS3Client(nil)
response, err := client.ListBuckets(input)
buckets, err := s.repository.ListAllBuckets()
if err != nil {
return nil, remoteerror.NewResourceEnumerationErrorWithType(err, aws.AwsS3BucketPolicyResourceType, aws.AwsS3BucketResourceType)
}
for _, bucket := range response.Buckets {
name := *bucket.Name
region, err := readBucketRegion(&client, name)
for _, bucket := range buckets {
bucket := *bucket
region, err := s.repository.GetBucketLocation(&bucket)
if err != nil {
return nil, err
}
if region == "" {
continue
}
s.listBucketPolicyConfiguration(*bucket.Name, region)
s.runner.Run(func() (cty.Value, error) {
s3BucketNotification, err := s.reader.ReadResource(
terraform.ReadResourceArgs{
Ty: aws.AwsS3BucketPolicyResourceType,
ID: *bucket.Name,
Attributes: map[string]string{
"alias": region,
},
},
)
if err != nil {
return cty.NilVal, err
}
return *s3BucketNotification, err
})
}
ctyVals, err := s.runner.Wait()
if err != nil {
@ -68,22 +79,3 @@ func (s *S3BucketPolicySupplier) Resources() ([]resource.Resource, error) {
}
return results, nil
}
func (s *S3BucketPolicySupplier) listBucketPolicyConfiguration(name, region string) {
s.runner.Run(func() (cty.Value, error) {
s3BucketNotification, err := s.reader.ReadResource(
terraform.ReadResourceArgs{
Ty: aws.AwsS3BucketPolicyResourceType,
ID: name,
Attributes: map[string]string{
"alias": region,
},
},
)
if err != nil {
return cty.NilVal, err
}
return *s3BucketNotification, err
})
}

View File

@ -4,65 +4,90 @@ import (
"context"
"testing"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
resourceaws "github.com/cloudskiff/driftctl/pkg/resource/aws"
"github.com/stretchr/testify/assert"
awssdk "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/cloudskiff/driftctl/pkg/parallel"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
"github.com/cloudskiff/driftctl/test/goldenfile"
"github.com/cloudskiff/driftctl/pkg/remote/aws/client"
"github.com/cloudskiff/driftctl/pkg/remote/aws/repository"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/cloudskiff/driftctl/pkg/resource"
resourceaws "github.com/cloudskiff/driftctl/pkg/resource/aws"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/cloudskiff/driftctl/test"
"github.com/cloudskiff/driftctl/test/goldenfile"
"github.com/cloudskiff/driftctl/test/mocks"
"github.com/stretchr/testify/assert"
)
func TestS3BucketPolicySupplier_Resources(t *testing.T) {
tests := []struct {
test string
dirName string
bucketsIDs []string
bucketLocation map[string]string
listError error
wantErr error
test string
dirName string
mocks func(repository *repository.MockS3Repository)
wantErr error
}{
{
test: "single bucket without policy",
dirName: "s3_bucket_policy_no_policy",
bucketsIDs: []string{
"dritftctl-test-no-policy",
},
bucketLocation: map[string]string{
"dritftctl-test-no-policy": "eu-west-3",
mocks: func(repository *repository.MockS3Repository) {
repository.On(
"ListAllBuckets",
).Return([]*s3.Bucket{
{Name: awssdk.String("dritftctl-test-no-policy")},
}, nil)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("dritftctl-test-no-policy")},
).Return(
"eu-west-3",
nil,
)
},
},
{
test: "multiple bucket with policies", dirName: "s3_bucket_policies_multiple",
bucketsIDs: []string{
"bucket-martin-test-drift",
"bucket-martin-test-drift2",
"bucket-martin-test-drift3",
},
bucketLocation: map[string]string{
"bucket-martin-test-drift": "eu-west-1",
"bucket-martin-test-drift2": "eu-west-3",
"bucket-martin-test-drift3": "ap-northeast-1",
mocks: func(repository *repository.MockS3Repository) {
repository.On(
"ListAllBuckets",
).Return([]*s3.Bucket{
{Name: awssdk.String("bucket-martin-test-drift")},
{Name: awssdk.String("bucket-martin-test-drift2")},
{Name: awssdk.String("bucket-martin-test-drift3")},
}, nil)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift")},
).Return(
"eu-west-1",
nil,
)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift2")},
).Return(
"eu-west-3",
nil,
)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift3")},
).Return(
"ap-northeast-1",
nil,
)
},
},
{
test: "cannot list bucket", dirName: "s3_bucket_policies_list_bucket",
bucketsIDs: nil,
listError: awserr.NewRequestFailure(nil, 403, ""),
bucketLocation: map[string]string{
"bucket-martin-test-drift": "eu-west-1",
"bucket-martin-test-drift2": "eu-west-3",
"bucket-martin-test-drift3": "ap-northeast-1",
mocks: func(repository *repository.MockS3Repository) {
repository.On("ListAllBuckets").Return(nil, awserr.NewRequestFailure(nil, 403, ""))
},
wantErr: remoteerror.NewResourceEnumerationErrorWithType(awserr.NewRequestFailure(nil, 403, ""), resourceaws.AwsS3BucketPolicyResourceType, resourceaws.AwsS3BucketResourceType),
},
@ -79,21 +104,21 @@ func TestS3BucketPolicySupplier_Resources(t *testing.T) {
if err != nil {
t.Fatal(err)
}
factory := AwsClientFactory{config: provider.session}
supplierLibrary.AddSupplier(NewS3BucketPolicySupplier(provider, factory))
repository := repository.NewS3Repository(client.NewAWSClientFactory(provider.session))
supplierLibrary.AddSupplier(NewS3BucketPolicySupplier(provider, repository))
}
t.Run(tt.test, func(t *testing.T) {
mock := mocks.NewMockAWSS3Client(tt.bucketsIDs, nil, nil, nil, tt.bucketLocation, tt.listError)
factory := mocks.NewMockAwsClientFactory(mock)
mock := repository.MockS3Repository{}
tt.mocks(&mock)
provider := mocks.NewMockedGoldenTFProvider(tt.dirName, providerLibrary.Provider(terraform.AWS), shouldUpdate)
deserializer := awsdeserializer.NewS3BucketPolicyDeserializer()
s := &S3BucketPolicySupplier{
provider,
deserializer,
factory,
&mock,
terraform.NewParallelResourceReader(parallel.NewParallelRunner(context.TODO(), 10)),
}
got, err := s.Resources()

View File

@ -1,97 +1,55 @@
package aws
import (
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3iface"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/cloudskiff/driftctl/pkg/remote/aws/repository"
"github.com/cloudskiff/driftctl/pkg/remote/deserializer"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/resource/aws"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/sirupsen/logrus"
"github.com/zclconf/go-cty/cty"
)
type S3BucketSupplier struct {
reader terraform.ResourceReader
deserializer deserializer.CTYDeserializer
awsClientFactory AwsClientFactoryInterface
runner *terraform.ParallelResourceReader
reader terraform.ResourceReader
deserializer deserializer.CTYDeserializer
repository repository.S3Repository
runner *terraform.ParallelResourceReader
}
func NewS3BucketSupplier(provider *AWSTerraformProvider, factory AwsClientFactoryInterface) *S3BucketSupplier {
func NewS3BucketSupplier(provider *AWSTerraformProvider, repository repository.S3Repository) *S3BucketSupplier {
return &S3BucketSupplier{
provider,
awsdeserializer.NewS3BucketDeserializer(),
factory,
repository,
terraform.NewParallelResourceReader(provider.Runner().SubRunner()),
}
}
func (s S3BucketSupplier) Resources() ([]resource.Resource, error) {
retrieve, err := s.list()
if err != nil {
return nil, err
}
return s.deserializer.Deserialize(retrieve)
}
func (s *S3BucketSupplier) list() ([]cty.Value, error) {
input := &s3.ListBucketsInput{}
s3Client := s.awsClientFactory.GetS3Client(nil)
response, err := s3Client.ListBuckets(input)
buckets, err := s.repository.ListAllBuckets()
if err != nil {
return nil, remoteerror.NewResourceEnumerationError(err, aws.AwsS3BucketResourceType)
}
for _, bucket := range response.Buckets {
for _, bucket := range buckets {
b := *bucket
s.runner.Run(func() (cty.Value, error) {
return s.readBucket(b, &s3Client)
return s.readBucket(b)
})
}
return s.runner.Wait()
}
func readBucketRegion(client *s3iface.S3API, name string) (string, error) {
bucketLocationRequest := s3.GetBucketLocationInput{Bucket: &name}
bucketLocationResponse, err := (*client).GetBucketLocation(&bucketLocationRequest)
values, err := s.runner.Wait()
if err != nil {
awsErr, ok := err.(awserr.Error)
if ok && awsErr.Code() == s3.ErrCodeNoSuchBucket {
logrus.WithFields(logrus.Fields{
"bucket": name,
}).Warning("Unable to retrieve bucket region, this may be an inconsistency in S3 api for fresh deleted bucket, skipping ...")
return "", nil
}
return "", err
return nil, err
}
// Buckets in Region us-east-1 have a LocationConstraint of null.
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLocation.html#API_GetBucketLocation_ResponseSyntax
if bucketLocationResponse.LocationConstraint == nil {
return "us-east-1", err
}
if *bucketLocationResponse.LocationConstraint == "EU" {
return "eu-west-1", err
}
return *bucketLocationResponse.LocationConstraint, nil
return s.deserializer.Deserialize(values)
}
func (s *S3BucketSupplier) readBucket(bucket s3.Bucket, client *s3iface.S3API) (cty.Value, error) {
name := *bucket.Name
region, err := readBucketRegion(client, name)
func (s *S3BucketSupplier) readBucket(bucket s3.Bucket) (cty.Value, error) {
region, err := s.repository.GetBucketLocation(&bucket)
if err != nil {
return cty.NilVal, err
}
@ -101,7 +59,7 @@ func (s *S3BucketSupplier) readBucket(bucket s3.Bucket, client *s3iface.S3API) (
s3Bucket, err := s.reader.ReadResource(terraform.ReadResourceArgs{
Ty: aws.AwsS3BucketResourceType,
ID: name,
ID: *bucket.Name,
Attributes: map[string]string{
"alias": region,
},

View File

@ -4,55 +4,71 @@ import (
"context"
"testing"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
resourceaws "github.com/cloudskiff/driftctl/pkg/resource/aws"
"github.com/stretchr/testify/assert"
awssdk "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/cloudskiff/driftctl/pkg/parallel"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
"github.com/cloudskiff/driftctl/test/goldenfile"
"github.com/cloudskiff/driftctl/pkg/remote/aws/client"
"github.com/cloudskiff/driftctl/pkg/remote/aws/repository"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/cloudskiff/driftctl/pkg/resource"
resourceaws "github.com/cloudskiff/driftctl/pkg/resource/aws"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/cloudskiff/driftctl/test"
"github.com/cloudskiff/driftctl/test/goldenfile"
"github.com/cloudskiff/driftctl/test/mocks"
"github.com/stretchr/testify/assert"
)
func TestS3BucketSupplier_Resources(t *testing.T) {
tests := []struct {
test string
dirName string
bucketsIDs []string
bucketLocation map[string]string
listError error
wantErr error
test string
dirName string
mocks func(repository *repository.MockS3Repository)
wantErr error
}{
{
test: "multiple bucket", dirName: "s3_bucket_multiple",
bucketsIDs: []string{
"bucket-martin-test-drift",
"bucket-martin-test-drift2",
"bucket-martin-test-drift3",
},
bucketLocation: map[string]string{
"bucket-martin-test-drift": "eu-west-1",
"bucket-martin-test-drift2": "eu-west-3",
"bucket-martin-test-drift3": "ap-northeast-1",
mocks: func(repository *repository.MockS3Repository) {
repository.On(
"ListAllBuckets",
).Return([]*s3.Bucket{
{Name: awssdk.String("bucket-martin-test-drift")},
{Name: awssdk.String("bucket-martin-test-drift2")},
{Name: awssdk.String("bucket-martin-test-drift3")},
}, nil)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift")},
).Return(
"eu-west-1",
nil,
)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift2")},
).Return(
"eu-west-3",
nil,
)
repository.On(
"GetBucketLocation",
&s3.Bucket{Name: awssdk.String("bucket-martin-test-drift3")},
).Return(
"ap-northeast-1",
nil,
)
},
},
{
test: "cannot list bucket", dirName: "s3_bucket_list",
bucketsIDs: nil,
listError: awserr.NewRequestFailure(nil, 403, ""),
bucketLocation: map[string]string{
"bucket-martin-test-drift": "eu-west-1",
"bucket-martin-test-drift2": "eu-west-3",
"bucket-martin-test-drift3": "ap-northeast-1",
mocks: func(repository *repository.MockS3Repository) {
repository.On("ListAllBuckets").Return(nil, awserr.NewRequestFailure(nil, 403, ""))
},
wantErr: remoteerror.NewResourceEnumerationError(awserr.NewRequestFailure(nil, 403, ""), resourceaws.AwsS3BucketResourceType),
},
@ -68,20 +84,21 @@ func TestS3BucketSupplier_Resources(t *testing.T) {
if err != nil {
t.Fatal(err)
}
factory := AwsClientFactory{config: provider.session}
supplierLibrary.AddSupplier(NewS3BucketSupplier(provider, factory))
repository := repository.NewS3Repository(client.NewAWSClientFactory(provider.session))
supplierLibrary.AddSupplier(NewS3BucketSupplier(provider, repository))
}
t.Run(tt.test, func(t *testing.T) {
factory := mocks.NewMockAwsClientFactory(mocks.NewMockAWSS3Client(tt.bucketsIDs, nil, nil, nil, tt.bucketLocation, tt.listError))
mock := repository.MockS3Repository{}
tt.mocks(&mock)
provider := mocks.NewMockedGoldenTFProvider(tt.dirName, providerLibrary.Provider(terraform.AWS), shouldUpdate)
deserializer := awsdeserializer.NewS3BucketDeserializer()
s := &S3BucketSupplier{
provider,
deserializer,
factory,
&mock,
terraform.NewParallelResourceReader(parallel.NewParallelRunner(context.TODO(), 10)),
}
got, err := s.Resources()

View File

@ -1,18 +0,0 @@
package mocks
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3/s3iface"
)
type MockAwsClientFactory struct {
client s3iface.S3API
}
func NewMockAwsClientFactory(client s3iface.S3API) MockAwsClientFactory {
return MockAwsClientFactory{client}
}
func (s MockAwsClientFactory) GetS3Client(configs ...*aws.Config) s3iface.S3API {
return s.client
}

View File

@ -1,96 +0,0 @@
package mocks
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3iface"
)
type MockAWSS3Client struct {
s3iface.S3API
bucketsIDs []string
analyticsIDs map[string][]string
inventoriesIDs map[string][]string
metricsIDs map[string][]string
bucketLocations map[string]string
err error
}
func NewMockAWSS3Client(bucketsIDs []string, analyticsIDs map[string][]string, inventoriesIDs map[string][]string, metricsIDs map[string][]string, bucketLocations map[string]string, err error) *MockAWSS3Client {
return &MockAWSS3Client{bucketsIDs: bucketsIDs, analyticsIDs: analyticsIDs, inventoriesIDs: inventoriesIDs, metricsIDs: metricsIDs, bucketLocations: bucketLocations, err: err}
}
func (m MockAWSS3Client) ListBucketAnalyticsConfigurations(in *s3.ListBucketAnalyticsConfigurationsInput) (*s3.ListBucketAnalyticsConfigurationsOutput, error) {
if m.analyticsIDs == nil && m.err != nil {
return nil, m.err
}
var configurations []*s3.AnalyticsConfiguration
for _, id := range m.analyticsIDs[*in.Bucket] {
configurations = append(configurations, &s3.AnalyticsConfiguration{
Id: aws.String(id),
})
}
return &s3.ListBucketAnalyticsConfigurationsOutput{
AnalyticsConfigurationList: configurations,
}, nil
}
func (m MockAWSS3Client) ListBucketInventoryConfigurations(in *s3.ListBucketInventoryConfigurationsInput) (*s3.ListBucketInventoryConfigurationsOutput, error) {
if m.inventoriesIDs == nil && m.err != nil {
return nil, m.err
}
var configurations []*s3.InventoryConfiguration
for _, id := range m.inventoriesIDs[*in.Bucket] {
configurations = append(configurations, &s3.InventoryConfiguration{
Id: aws.String(id),
})
}
return &s3.ListBucketInventoryConfigurationsOutput{
InventoryConfigurationList: configurations,
}, nil
}
func (m MockAWSS3Client) ListBucketMetricsConfigurations(in *s3.ListBucketMetricsConfigurationsInput) (*s3.ListBucketMetricsConfigurationsOutput, error) {
if m.metricsIDs == nil && m.err != nil {
return nil, m.err
}
var configurations []*s3.MetricsConfiguration
for _, id := range m.metricsIDs[*in.Bucket] {
configurations = append(configurations, &s3.MetricsConfiguration{
Id: aws.String(id),
})
}
return &s3.ListBucketMetricsConfigurationsOutput{
MetricsConfigurationList: configurations,
}, nil
}
func (m MockAWSS3Client) ListBuckets(*s3.ListBucketsInput) (*s3.ListBucketsOutput, error) {
if m.bucketsIDs == nil && m.err != nil {
return nil, m.err
}
var buckets []*s3.Bucket
for _, id := range m.bucketsIDs {
buckets = append(buckets, &s3.Bucket{
Name: aws.String(id),
})
}
return &s3.ListBucketsOutput{
Buckets: buckets,
Owner: nil,
}, nil
}
func (m MockAWSS3Client) GetBucketLocation(input *s3.GetBucketLocationInput) (*s3.GetBucketLocationOutput, error) {
location, exists := m.bucketLocations[*input.Bucket]
if !exists {
panic(fmt.Sprintf("no region provided for bucket %s", *input.Bucket))
}
return &s3.GetBucketLocationOutput{
LocationConstraint: &location,
}, nil
}