Merge pull request #788 from cloudskiff/subnet_supplier

Split subnet_supplier
main
Elie 2021-07-05 16:36:23 +02:00 committed by GitHub
commit bb61b2b2ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 358 additions and 343761 deletions

View File

@ -0,0 +1,46 @@
package aws
import (
"github.com/cloudskiff/driftctl/pkg/remote/aws/repository"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/resource/aws"
)
type EC2DefaultSubnetEnumerator struct {
repository repository.EC2Repository
factory resource.ResourceFactory
}
func NewEC2DefaultSubnetEnumerator(repo repository.EC2Repository, factory resource.ResourceFactory) *EC2DefaultSubnetEnumerator {
return &EC2DefaultSubnetEnumerator{
repository: repo,
factory: factory,
}
}
func (e *EC2DefaultSubnetEnumerator) SupportedType() resource.ResourceType {
return aws.AwsDefaultSubnetResourceType
}
func (e *EC2DefaultSubnetEnumerator) Enumerate() ([]resource.Resource, error) {
_, defaultSubnets, err := e.repository.ListAllSubnets()
if err != nil {
return nil, remoteerror.NewResourceEnumerationError(err, string(e.SupportedType()))
}
results := make([]resource.Resource, len(defaultSubnets))
for _, subnet := range defaultSubnets {
results = append(
results,
e.factory.CreateAbstractResource(
string(e.SupportedType()),
*subnet.SubnetId,
map[string]interface{}{},
),
)
}
return results, err
}

View File

@ -0,0 +1,46 @@
package aws
import (
"github.com/cloudskiff/driftctl/pkg/remote/aws/repository"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/resource/aws"
)
type EC2SubnetEnumerator struct {
repository repository.EC2Repository
factory resource.ResourceFactory
}
func NewEC2SubnetEnumerator(repo repository.EC2Repository, factory resource.ResourceFactory) *EC2SubnetEnumerator {
return &EC2SubnetEnumerator{
repository: repo,
factory: factory,
}
}
func (e *EC2SubnetEnumerator) SupportedType() resource.ResourceType {
return aws.AwsSubnetResourceType
}
func (e *EC2SubnetEnumerator) Enumerate() ([]resource.Resource, error) {
subnets, _, err := e.repository.ListAllSubnets()
if err != nil {
return nil, remoteerror.NewResourceEnumerationError(err, string(e.SupportedType()))
}
results := make([]resource.Resource, len(subnets))
for _, subnet := range subnets {
results = append(
results,
e.factory.CreateAbstractResource(
string(e.SupportedType()),
*subnet.SubnetId,
map[string]interface{}{},
),
)
}
return results, err
}

View File

@ -89,6 +89,10 @@ func Init(version string, alerter *alerter.Alerter,
remoteLibrary.AddDetailsFetcher(aws.AwsDefaultVpcResourceType, common.NewGenericDetailsFetcher(aws.AwsDefaultVpcResourceType, provider, deserializer))
remoteLibrary.AddEnumerator(NewEC2RouteTableAssociationEnumerator(ec2repository, factory))
remoteLibrary.AddDetailsFetcher(aws.AwsRouteTableAssociationResourceType, NewEC2RouteTableAssociationDetailsFetcher(provider, deserializer))
remoteLibrary.AddEnumerator(NewEC2SubnetEnumerator(ec2repository, factory))
remoteLibrary.AddDetailsFetcher(aws.AwsSubnetResourceType, common.NewGenericDetailsFetcher(aws.AwsSubnetResourceType, provider, deserializer))
remoteLibrary.AddEnumerator(NewEC2DefaultSubnetEnumerator(ec2repository, factory))
remoteLibrary.AddDetailsFetcher(aws.AwsDefaultSubnetResourceType, common.NewGenericDetailsFetcher(aws.AwsDefaultSubnetResourceType, provider, deserializer))
remoteLibrary.AddEnumerator(NewKMSKeyEnumerator(kmsRepository, factory))
remoteLibrary.AddDetailsFetcher(aws.AwsKmsKeyResourceType, common.NewGenericDetailsFetcher(aws.AwsKmsKeyResourceType, provider, deserializer))
@ -115,7 +119,6 @@ func Init(version string, alerter *alerter.Alerter,
supplierLibrary.AddSupplier(NewIamRolePolicySupplier(provider, deserializer, iamRepository))
supplierLibrary.AddSupplier(NewIamRolePolicyAttachmentSupplier(provider, deserializer, iamRepository))
supplierLibrary.AddSupplier(NewVPCSecurityGroupRuleSupplier(provider, deserializer, ec2repository))
supplierLibrary.AddSupplier(NewSubnetSupplier(provider, deserializer, ec2repository))
supplierLibrary.AddSupplier(NewRouteTableSupplier(provider, deserializer, ec2repository))
supplierLibrary.AddSupplier(NewRouteSupplier(provider, deserializer, ec2repository))
supplierLibrary.AddSupplier(NewNatGatewaySupplier(provider, deserializer, ec2repository))

View File

@ -1,99 +0,0 @@
package aws
import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/cloudskiff/driftctl/pkg/remote/aws/repository"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/cloudskiff/driftctl/pkg/resource/aws"
"github.com/zclconf/go-cty/cty"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/sirupsen/logrus"
)
type SubnetSupplier struct {
reader terraform.ResourceReader
deserializer *resource.Deserializer
repository repository.EC2Repository
defaultSubnetRunner *terraform.ParallelResourceReader
subnetRunner *terraform.ParallelResourceReader
}
func NewSubnetSupplier(provider *AWSTerraformProvider, deserializer *resource.Deserializer, repository repository.EC2Repository) *SubnetSupplier {
return &SubnetSupplier{
provider,
deserializer,
repository,
terraform.NewParallelResourceReader(provider.Runner().SubRunner()),
terraform.NewParallelResourceReader(provider.Runner().SubRunner()),
}
}
func (s *SubnetSupplier) Resources() ([]resource.Resource, error) {
subnets, defaultSubnets, err := s.repository.ListAllSubnets()
if err != nil {
return nil, remoteerror.NewResourceEnumerationError(err, aws.AwsSubnetResourceType)
}
for _, item := range subnets {
res := *item
s.subnetRunner.Run(func() (cty.Value, error) {
return s.readSubnet(res)
})
}
subnetResources, err := s.subnetRunner.Wait()
if err != nil {
return nil, err
}
for _, item := range defaultSubnets {
res := *item
s.defaultSubnetRunner.Run(func() (cty.Value, error) {
return s.readSubnet(res)
})
}
// Retrieve results from terraform provider
defaultSubnetResources, err := s.defaultSubnetRunner.Wait()
if err != nil {
return nil, err
}
// Deserialize
deserializedDefaultSubnets, err := s.deserializer.Deserialize(aws.AwsDefaultSubnetResourceType, defaultSubnetResources)
if err != nil {
return nil, err
}
deserializedSubnets, err := s.deserializer.Deserialize(aws.AwsSubnetResourceType, subnetResources)
if err != nil {
return nil, err
}
resources := make([]resource.Resource, 0, len(subnetResources)+len(deserializedDefaultSubnets))
resources = append(resources, deserializedDefaultSubnets...)
resources = append(resources, deserializedSubnets...)
return resources, nil
}
func (s *SubnetSupplier) readSubnet(subnet ec2.Subnet) (cty.Value, error) {
var Ty resource.ResourceType = aws.AwsSubnetResourceType
if subnet.DefaultForAz != nil && *subnet.DefaultForAz {
Ty = aws.AwsDefaultSubnetResourceType
}
val, err := s.reader.ReadResource(terraform.ReadResourceArgs{
ID: *subnet.SubnetId,
Ty: Ty,
})
if err != nil {
logrus.Error(err)
return cty.NilVal, err
}
return *val, nil
}

View File

@ -1,127 +0,0 @@
package aws
import (
"context"
"testing"
"github.com/cloudskiff/driftctl/pkg/remote/aws/repository"
"github.com/cloudskiff/driftctl/pkg/remote/cache"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
testresource "github.com/cloudskiff/driftctl/test/resource"
resourceaws "github.com/cloudskiff/driftctl/pkg/resource/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/cloudskiff/driftctl/pkg/parallel"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/aws"
"github.com/cloudskiff/driftctl/test/goldenfile"
mocks2 "github.com/cloudskiff/driftctl/test/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/cloudskiff/driftctl/test"
)
func TestSubnetSupplier_Resources(t *testing.T) {
cases := []struct {
test string
dirName string
mocks func(repo *repository.MockEC2Repository)
err error
}{
{
test: "no Subnet",
dirName: "subnet_empty",
mocks: func(repo *repository.MockEC2Repository) {
repo.On("ListAllSubnets").Once().Return([]*ec2.Subnet{}, []*ec2.Subnet{}, nil)
},
err: nil,
},
{
test: "mixed default Subnet and Subnet",
dirName: "subnet",
mocks: func(repo *repository.MockEC2Repository) {
repo.On("ListAllSubnets").Once().Return([]*ec2.Subnet{
{
SubnetId: aws.String("subnet-05810d3f933925f6d"), // subnet1
DefaultForAz: aws.Bool(false),
},
{
SubnetId: aws.String("subnet-0b13f1e0eacf67424"), // subnet2
DefaultForAz: aws.Bool(false),
},
{
SubnetId: aws.String("subnet-0c9b78001fe186e22"), // subnet3
DefaultForAz: aws.Bool(false),
},
}, []*ec2.Subnet{
{
SubnetId: aws.String("subnet-44fe0c65"), // us-east-1a
DefaultForAz: aws.Bool(true),
},
{
SubnetId: aws.String("subnet-65e16628"), // us-east-1b
DefaultForAz: aws.Bool(true),
},
{
SubnetId: aws.String("subnet-afa656f0"), // us-east-1c
DefaultForAz: aws.Bool(true),
},
}, nil)
},
err: nil,
},
{
test: "cannot list Subnet",
dirName: "subnet_empty",
mocks: func(repo *repository.MockEC2Repository) {
repo.On("ListAllSubnets").Once().Return(nil, nil, awserr.NewRequestFailure(nil, 403, ""))
},
err: remoteerror.NewResourceEnumerationError(awserr.NewRequestFailure(nil, 403, ""), resourceaws.AwsSubnetResourceType),
},
}
for _, c := range cases {
shouldUpdate := c.dirName == *goldenfile.Update
providerLibrary := terraform.NewProviderLibrary()
supplierLibrary := resource.NewSupplierLibrary()
repo := testresource.InitFakeSchemaRepository("aws", "3.19.0")
resourceaws.InitResourcesMetadata(repo)
factory := terraform.NewTerraformResourceFactory(repo)
deserializer := resource.NewDeserializer(factory)
if shouldUpdate {
provider, err := InitTestAwsProvider(providerLibrary)
if err != nil {
t.Fatal(err)
}
supplierLibrary.AddSupplier(NewSubnetSupplier(provider, deserializer, repository.NewEC2Repository(provider.session, cache.New(0))))
}
t.Run(c.test, func(tt *testing.T) {
fakeEC2 := repository.MockEC2Repository{}
c.mocks(&fakeEC2)
provider := mocks2.NewMockedGoldenTFProvider(c.dirName, providerLibrary.Provider(terraform.AWS), shouldUpdate)
s := &SubnetSupplier{
provider,
deserializer,
&fakeEC2,
terraform.NewParallelResourceReader(parallel.NewParallelRunner(context.TODO(), 10)),
terraform.NewParallelResourceReader(parallel.NewParallelRunner(context.TODO(), 10)),
}
got, err := s.Resources()
assert.Equal(tt, c.err, err)
mock.AssertExpectationsForObjects(tt)
test.CtyTestDiff(got, c.dirName, provider, deserializer, shouldUpdate, tt)
})
}
}

View File

@ -1,20 +0,0 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/aws" {
version = "3.19.0"
constraints = "3.19.0"
hashes = [
"h1:+7Vi7p13+cnrxjXbfJiTimGSFR97xCaQwkkvWcreLns=",
"zh:185a5259153eb9ee4699d4be43b3d509386b473683392034319beee97d470c3b",
"zh:2d9a0a01f93e8d16539d835c02b8b6e1927b7685f4076e96cb07f7dd6944bc6c",
"zh:703f6da36b1b5f3497baa38fccaa7765fb8a2b6440344e4c97172516b49437dd",
"zh:770855565462abadbbddd98cb357d2f1a8f30f68a358cb37cbd5c072cb15b377",
"zh:8008db43149fe4345301f81e15e6d9ddb47aa5e7a31648f9b290af96ad86e92a",
"zh:8cdd27d375da6dcb7687f1fed126b7c04efce1671066802ee876dbbc9c66ec79",
"zh:be22ae185005690d1a017c1b909e0d80ab567e239b4f06ecacdba85080667c1c",
"zh:d2d02e72dbd80f607636cd6237a6c862897caabc635c7b50c0cb243d11246723",
"zh:d8f125b66a1eda2555c0f9bbdf12036a5f8d073499a22ca9e4812b68067fea31",
"zh:f5a98024c64d5d2973ff15b093725a074c0cb4afde07ef32c542e69f17ac90bc",
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1099,3 +1099,225 @@ func TestEC2RouteTableAssociation(t *testing.T) {
})
}
}
func TestEC2Subnet(t *testing.T) {
tests := []struct {
test string
dirName string
mocks func(repository *repository.MockEC2Repository)
wantErr error
}{
{
test: "no subnets",
dirName: "aws_ec2_subnet_empty",
mocks: func(repository *repository.MockEC2Repository) {
repository.On("ListAllSubnets").Return([]*ec2.Subnet{}, []*ec2.Subnet{}, nil)
},
},
{
test: "multiple subnets",
dirName: "aws_ec2_subnet_multiple",
mocks: func(repository *repository.MockEC2Repository) {
repository.On("ListAllSubnets").Return([]*ec2.Subnet{
{
SubnetId: awssdk.String("subnet-05810d3f933925f6d"), // subnet1
DefaultForAz: awssdk.Bool(false),
},
{
SubnetId: awssdk.String("subnet-0b13f1e0eacf67424"), // subnet2
DefaultForAz: awssdk.Bool(false),
},
{
SubnetId: awssdk.String("subnet-0c9b78001fe186e22"), // subnet3
DefaultForAz: awssdk.Bool(false),
},
}, []*ec2.Subnet{
{
SubnetId: awssdk.String("subnet-44fe0c65"), // us-east-1a
DefaultForAz: awssdk.Bool(true),
},
{
SubnetId: awssdk.String("subnet-65e16628"), // us-east-1b
DefaultForAz: awssdk.Bool(true),
},
{
SubnetId: awssdk.String("subnet-afa656f0"), // us-east-1c
DefaultForAz: awssdk.Bool(true),
},
}, nil)
},
},
{
test: "cannot list subnets",
dirName: "aws_ec2_subnet_list",
mocks: func(repository *repository.MockEC2Repository) {
repository.On("ListAllSubnets").Return(nil, nil, awserr.NewRequestFailure(nil, 403, ""))
},
wantErr: remoteerror.NewResourceEnumerationError(awserr.NewRequestFailure(nil, 403, ""), resourceaws.AwsSubnetResourceType),
},
}
schemaRepository := testresource.InitFakeSchemaRepository("aws", "3.19.0")
resourceaws.InitResourcesMetadata(schemaRepository)
factory := terraform.NewTerraformResourceFactory(schemaRepository)
deserializer := resource.NewDeserializer(factory)
alerter := &mocks.AlerterInterface{}
for _, c := range tests {
t.Run(c.test, func(tt *testing.T) {
shouldUpdate := c.dirName == *goldenfile.Update
sess := session.Must(session.NewSessionWithOptions(session.Options{
SharedConfigState: session.SharedConfigEnable,
}))
scanOptions := ScannerOptions{Deep: true}
providerLibrary := terraform.NewProviderLibrary()
remoteLibrary := common.NewRemoteLibrary()
// Initialize mocks
fakeRepo := &repository.MockEC2Repository{}
c.mocks(fakeRepo)
var repo repository.EC2Repository = fakeRepo
providerVersion := "3.19.0"
realProvider, err := terraform2.InitTestAwsProvider(providerLibrary, providerVersion)
if err != nil {
t.Fatal(err)
}
provider := terraform2.NewFakeTerraformProvider(realProvider)
provider.WithResponse(c.dirName)
// Replace mock by real resources if we are in update mode
if shouldUpdate {
err := realProvider.Init()
if err != nil {
t.Fatal(err)
}
provider.ShouldUpdate()
repo = repository.NewEC2Repository(sess, cache.New(0))
}
remoteLibrary.AddEnumerator(aws.NewEC2SubnetEnumerator(repo, factory))
remoteLibrary.AddDetailsFetcher(resourceaws.AwsSubnetResourceType, common.NewGenericDetailsFetcher(resourceaws.AwsSubnetResourceType, provider, deserializer))
s := NewScanner(nil, remoteLibrary, alerter, scanOptions)
got, err := s.Resources()
assert.Equal(tt, err, c.wantErr)
if err != nil {
return
}
test.TestAgainstGoldenFile(got, resourceaws.AwsSubnetResourceType, c.dirName, provider, deserializer, shouldUpdate, tt)
})
}
}
func TestEC2DefaultSubnet(t *testing.T) {
tests := []struct {
test string
dirName string
mocks func(repository *repository.MockEC2Repository)
wantErr error
}{
{
test: "no default subnets",
dirName: "aws_ec2_default_subnet_empty",
mocks: func(repository *repository.MockEC2Repository) {
repository.On("ListAllSubnets").Return([]*ec2.Subnet{}, []*ec2.Subnet{}, nil)
},
},
{
test: "multiple default subnets",
dirName: "aws_ec2_default_subnet_multiple",
mocks: func(repository *repository.MockEC2Repository) {
repository.On("ListAllSubnets").Return([]*ec2.Subnet{
{
SubnetId: awssdk.String("subnet-05810d3f933925f6d"), // subnet1
DefaultForAz: awssdk.Bool(false),
},
{
SubnetId: awssdk.String("subnet-0b13f1e0eacf67424"), // subnet2
DefaultForAz: awssdk.Bool(false),
},
{
SubnetId: awssdk.String("subnet-0c9b78001fe186e22"), // subnet3
DefaultForAz: awssdk.Bool(false),
},
}, []*ec2.Subnet{
{
SubnetId: awssdk.String("subnet-44fe0c65"), // us-east-1a
DefaultForAz: awssdk.Bool(true),
},
{
SubnetId: awssdk.String("subnet-65e16628"), // us-east-1b
DefaultForAz: awssdk.Bool(true),
},
{
SubnetId: awssdk.String("subnet-afa656f0"), // us-east-1c
DefaultForAz: awssdk.Bool(true),
},
}, nil)
},
},
{
test: "cannot list default subnets",
dirName: "aws_ec2_default_subnet_list",
mocks: func(repository *repository.MockEC2Repository) {
repository.On("ListAllSubnets").Return(nil, nil, awserr.NewRequestFailure(nil, 403, ""))
},
wantErr: remoteerror.NewResourceEnumerationError(awserr.NewRequestFailure(nil, 403, ""), resourceaws.AwsDefaultSubnetResourceType),
},
}
schemaRepository := testresource.InitFakeSchemaRepository("aws", "3.19.0")
resourceaws.InitResourcesMetadata(schemaRepository)
factory := terraform.NewTerraformResourceFactory(schemaRepository)
deserializer := resource.NewDeserializer(factory)
alerter := &mocks.AlerterInterface{}
for _, c := range tests {
t.Run(c.test, func(tt *testing.T) {
shouldUpdate := c.dirName == *goldenfile.Update
sess := session.Must(session.NewSessionWithOptions(session.Options{
SharedConfigState: session.SharedConfigEnable,
}))
scanOptions := ScannerOptions{Deep: true}
providerLibrary := terraform.NewProviderLibrary()
remoteLibrary := common.NewRemoteLibrary()
// Initialize mocks
fakeRepo := &repository.MockEC2Repository{}
c.mocks(fakeRepo)
var repo repository.EC2Repository = fakeRepo
providerVersion := "3.19.0"
realProvider, err := terraform2.InitTestAwsProvider(providerLibrary, providerVersion)
if err != nil {
t.Fatal(err)
}
provider := terraform2.NewFakeTerraformProvider(realProvider)
provider.WithResponse(c.dirName)
// Replace mock by real resources if we are in update mode
if shouldUpdate {
err := realProvider.Init()
if err != nil {
t.Fatal(err)
}
provider.ShouldUpdate()
repo = repository.NewEC2Repository(sess, cache.New(0))
}
remoteLibrary.AddEnumerator(aws.NewEC2DefaultSubnetEnumerator(repo, factory))
remoteLibrary.AddDetailsFetcher(resourceaws.AwsDefaultSubnetResourceType, common.NewGenericDetailsFetcher(resourceaws.AwsDefaultSubnetResourceType, provider, deserializer))
s := NewScanner(nil, remoteLibrary, alerter, scanOptions)
got, err := s.Resources()
assert.Equal(tt, err, c.wantErr)
if err != nil {
return
}
test.TestAgainstGoldenFile(got, resourceaws.AwsDefaultSubnetResourceType, c.dirName, provider, deserializer, shouldUpdate, tt)
})
}
}

View File

@ -0,0 +1,40 @@
provider "aws" {
region = "us-east-1"
}
terraform {
required_providers {
aws = "3.19.0"
}
}
resource "aws_vpc" "vpc_for_subnets" {
cidr_block = "10.0.0.0/16"
}
resource "aws_default_subnet" "default-a" {
availability_zone = "us-east-1a"
}
resource "aws_default_subnet" "default-b" {
availability_zone = "us-east-1b"
}
resource "aws_default_subnet" "default-c" {
availability_zone = "us-east-1c"
}
resource "aws_subnet" "subnet1" {
vpc_id = aws_vpc.vpc_for_subnets.id
cidr_block = "10.0.0.0/24"
}
resource "aws_subnet" "subnet2" {
vpc_id = aws_vpc.vpc_for_subnets.id
cidr_block = "10.0.1.0/24"
}
resource "aws_subnet" "subnet3" {
vpc_id = aws_vpc.vpc_for_subnets.id
cidr_block = "10.0.2.0/24"
}