From c660cb16dcc5fb21a8c53e70a22833f615154617 Mon Sep 17 00:00:00 2001 From: Elie Date: Mon, 4 Oct 2021 11:15:32 +0200 Subject: [PATCH 1/8] Fix crash when no lockfile are found (Azure) --- pkg/remote/azurerm/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/remote/azurerm/init.go b/pkg/remote/azurerm/init.go index c2d0a978..aaaf36d6 100644 --- a/pkg/remote/azurerm/init.go +++ b/pkg/remote/azurerm/init.go @@ -50,7 +50,7 @@ func Init( remoteLibrary.AddEnumerator(NewAzurermStorageContainerEnumerator(storageAccountRepo, factory)) remoteLibrary.AddEnumerator(NewAzurermVirtualNetworkEnumerator(networkRepo, factory)) - err = resourceSchemaRepository.Init(terraform.AZURE, version, provider.Schema()) + err = resourceSchemaRepository.Init(terraform.AZURE, provider.Version(), provider.Schema()) if err != nil { return err } From ce3760b24b3c20c9a9a6f4ca49c54efdd977595e Mon Sep 17 00:00:00 2001 From: Elie Date: Mon, 4 Oct 2021 11:14:29 +0200 Subject: [PATCH 2/8] Add azurerm_route_table --- .../azurerm/azurerm_route_table_enumerator.go | 48 ++++++ pkg/remote/azurerm/init.go | 1 + .../repository/mock_NetworkRepository.go | 25 ++- .../repository/mock_routeTablesClient.go | 29 ++++ .../mock_routeTablesListAllPager.go | 58 +++++++ pkg/remote/azurerm/repository/network.go | 44 +++++ pkg/remote/azurerm/repository/network_test.go | 153 ++++++++++++++++++ pkg/remote/azurerm_network_scanner_test.go | 93 +++++++++++ pkg/resource/azurerm/azurerm_route_table.go | 18 +++ .../azurerm/azurerm_route_table_test.go | 31 ++++ pkg/resource/azurerm/metadata.go | 1 + .../azurerm_route_table/.terraform.lock.hcl | 21 +++ .../acc/azurerm_route_table/terraform.tf | 22 +++ pkg/resource/resource_types.go | 1 + 14 files changed, 544 insertions(+), 1 deletion(-) create mode 100644 pkg/remote/azurerm/azurerm_route_table_enumerator.go create mode 100644 pkg/remote/azurerm/repository/mock_routeTablesClient.go create mode 100644 pkg/remote/azurerm/repository/mock_routeTablesListAllPager.go create mode 100644 pkg/resource/azurerm/azurerm_route_table.go create mode 100644 pkg/resource/azurerm/azurerm_route_table_test.go create mode 100644 pkg/resource/azurerm/testdata/acc/azurerm_route_table/.terraform.lock.hcl create mode 100644 pkg/resource/azurerm/testdata/acc/azurerm_route_table/terraform.tf diff --git a/pkg/remote/azurerm/azurerm_route_table_enumerator.go b/pkg/remote/azurerm/azurerm_route_table_enumerator.go new file mode 100644 index 00000000..7708a998 --- /dev/null +++ b/pkg/remote/azurerm/azurerm_route_table_enumerator.go @@ -0,0 +1,48 @@ +package azurerm + +import ( + "github.com/cloudskiff/driftctl/pkg/remote/azurerm/repository" + remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error" + "github.com/cloudskiff/driftctl/pkg/resource" + "github.com/cloudskiff/driftctl/pkg/resource/azurerm" +) + +type AzurermRouteTableEnumerator struct { + repository repository.NetworkRepository + factory resource.ResourceFactory +} + +func NewAzurermRouteTableEnumerator(repo repository.NetworkRepository, factory resource.ResourceFactory) *AzurermRouteTableEnumerator { + return &AzurermRouteTableEnumerator{ + repository: repo, + factory: factory, + } +} + +func (e *AzurermRouteTableEnumerator) SupportedType() resource.ResourceType { + return azurerm.AzureRouteTableResourceType +} + +func (e *AzurermRouteTableEnumerator) Enumerate() ([]*resource.Resource, error) { + resources, err := e.repository.ListAllRouteTables() + if err != nil { + return nil, remoteerror.NewResourceListingError(err, string(e.SupportedType())) + } + + results := make([]*resource.Resource, len(resources)) + + for _, res := range resources { + results = append( + results, + e.factory.CreateAbstractResource( + string(e.SupportedType()), + *res.ID, + map[string]interface{}{ + "name": *res.Name, + }, + ), + ) + } + + return results, err +} diff --git a/pkg/remote/azurerm/init.go b/pkg/remote/azurerm/init.go index aaaf36d6..0647d42d 100644 --- a/pkg/remote/azurerm/init.go +++ b/pkg/remote/azurerm/init.go @@ -49,6 +49,7 @@ func Init( remoteLibrary.AddEnumerator(NewAzurermStorageAccountEnumerator(storageAccountRepo, factory)) remoteLibrary.AddEnumerator(NewAzurermStorageContainerEnumerator(storageAccountRepo, factory)) remoteLibrary.AddEnumerator(NewAzurermVirtualNetworkEnumerator(networkRepo, factory)) + remoteLibrary.AddEnumerator(NewAzurermRouteTableEnumerator(networkRepo, factory)) err = resourceSchemaRepository.Init(terraform.AZURE, provider.Version(), provider.Schema()) if err != nil { diff --git a/pkg/remote/azurerm/repository/mock_NetworkRepository.go b/pkg/remote/azurerm/repository/mock_NetworkRepository.go index 0a979bfd..c5de4eff 100644 --- a/pkg/remote/azurerm/repository/mock_NetworkRepository.go +++ b/pkg/remote/azurerm/repository/mock_NetworkRepository.go @@ -12,7 +12,30 @@ type MockNetworkRepository struct { mock.Mock } -// ListAllVirtualNetwork provides a mock function with given fields: +// ListAllRouteTables provides a mock function with given fields: +func (_m *MockNetworkRepository) ListAllRouteTables() ([]*armnetwork.RouteTable, error) { + ret := _m.Called() + + var r0 []*armnetwork.RouteTable + if rf, ok := ret.Get(0).(func() []*armnetwork.RouteTable); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*armnetwork.RouteTable) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListAllVirtualNetworks provides a mock function with given fields: func (_m *MockNetworkRepository) ListAllVirtualNetworks() ([]*armnetwork.VirtualNetwork, error) { ret := _m.Called() diff --git a/pkg/remote/azurerm/repository/mock_routeTablesClient.go b/pkg/remote/azurerm/repository/mock_routeTablesClient.go new file mode 100644 index 00000000..70df302a --- /dev/null +++ b/pkg/remote/azurerm/repository/mock_routeTablesClient.go @@ -0,0 +1,29 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package repository + +import ( + armnetwork "github.com/Azure/azure-sdk-for-go/sdk/network/armnetwork" + mock "github.com/stretchr/testify/mock" +) + +// mockRouteTablesClient is an autogenerated mock type for the routeTablesClient type +type mockRouteTablesClient struct { + mock.Mock +} + +// ListAll provides a mock function with given fields: options +func (_m *mockRouteTablesClient) ListAll(options *armnetwork.RouteTablesListAllOptions) routeTablesListAllPager { + ret := _m.Called(options) + + var r0 routeTablesListAllPager + if rf, ok := ret.Get(0).(func(*armnetwork.RouteTablesListAllOptions) routeTablesListAllPager); ok { + r0 = rf(options) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(routeTablesListAllPager) + } + } + + return r0 +} diff --git a/pkg/remote/azurerm/repository/mock_routeTablesListAllPager.go b/pkg/remote/azurerm/repository/mock_routeTablesListAllPager.go new file mode 100644 index 00000000..20760aec --- /dev/null +++ b/pkg/remote/azurerm/repository/mock_routeTablesListAllPager.go @@ -0,0 +1,58 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package repository + +import ( + context "context" + + armnetwork "github.com/Azure/azure-sdk-for-go/sdk/network/armnetwork" + + mock "github.com/stretchr/testify/mock" +) + +// mockRouteTablesListAllPager is an autogenerated mock type for the routeTablesListAllPager type +type mockRouteTablesListAllPager struct { + mock.Mock +} + +// Err provides a mock function with given fields: +func (_m *mockRouteTablesListAllPager) Err() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NextPage provides a mock function with given fields: ctx +func (_m *mockRouteTablesListAllPager) NextPage(ctx context.Context) bool { + ret := _m.Called(ctx) + + var r0 bool + if rf, ok := ret.Get(0).(func(context.Context) bool); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// PageResponse provides a mock function with given fields: +func (_m *mockRouteTablesListAllPager) PageResponse() armnetwork.RouteTablesListAllResponse { + ret := _m.Called() + + var r0 armnetwork.RouteTablesListAllResponse + if rf, ok := ret.Get(0).(func() armnetwork.RouteTablesListAllResponse); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(armnetwork.RouteTablesListAllResponse) + } + + return r0 +} diff --git a/pkg/remote/azurerm/repository/network.go b/pkg/remote/azurerm/repository/network.go index 853d5911..2322c7ed 100644 --- a/pkg/remote/azurerm/repository/network.go +++ b/pkg/remote/azurerm/repository/network.go @@ -11,6 +11,7 @@ import ( type NetworkRepository interface { ListAllVirtualNetworks() ([]*armnetwork.VirtualNetwork, error) + ListAllRouteTables() ([]*armnetwork.RouteTable, error) } type virtualNetworksListAllPager interface { @@ -30,14 +31,33 @@ func (c virtualNetworksClientImpl) ListAll(options *armnetwork.VirtualNetworksLi return c.client.ListAll(options) } +type routeTablesClient interface { + ListAll(options *armnetwork.RouteTablesListAllOptions) routeTablesListAllPager +} + +type routeTablesListAllPager interface { + pager + PageResponse() armnetwork.RouteTablesListAllResponse +} + +type routeTablesClientImpl struct { + client *armnetwork.RouteTablesClient +} + +func (c routeTablesClientImpl) ListAll(options *armnetwork.RouteTablesListAllOptions) routeTablesListAllPager { + return c.client.ListAll(options) +} + type networkRepository struct { virtualNetworksClient virtualNetworksClient + routeTableClient routeTablesClient cache cache.Cache } func NewNetworkRepository(con *arm.Connection, config common.AzureProviderConfig, cache cache.Cache) *networkRepository { return &networkRepository{ &virtualNetworksClientImpl{client: armnetwork.NewVirtualNetworksClient(con, config.SubscriptionID)}, + &routeTablesClientImpl{client: armnetwork.NewRouteTablesClient(con, config.SubscriptionID)}, cache, } } @@ -66,3 +86,27 @@ func (s *networkRepository) ListAllVirtualNetworks() ([]*armnetwork.VirtualNetwo return results, nil } + +func (s *networkRepository) ListAllRouteTables() ([]*armnetwork.RouteTable, error) { + if v := s.cache.Get("ListAllRouteTables"); v != nil { + return v.([]*armnetwork.RouteTable), nil + } + + pager := s.routeTableClient.ListAll(nil) + results := make([]*armnetwork.RouteTable, 0) + for pager.NextPage(context.Background()) { + resp := pager.PageResponse() + if err := pager.Err(); err != nil { + return nil, err + } + results = append(results, resp.RouteTablesListAllResult.RouteTableListResult.Value...) + } + + if err := pager.Err(); err != nil { + return nil, err + } + + s.cache.Put("ListAllRouteTables", results) + + return results, nil +} diff --git a/pkg/remote/azurerm/repository/network_test.go b/pkg/remote/azurerm/repository/network_test.go index 6c9251b3..f045fac9 100644 --- a/pkg/remote/azurerm/repository/network_test.go +++ b/pkg/remote/azurerm/repository/network_test.go @@ -186,3 +186,156 @@ func Test_ListAllVirtualNetwork_Error(t *testing.T) { assert.Equal(t, expectedErr, err) assert.Nil(t, got) } + +func Test_ListAllRouteTables_MultiplesResults(t *testing.T) { + + expected := []*armnetwork.RouteTable{ + { + Resource: armnetwork.Resource{ + ID: to.StringPtr("table1"), + }, + }, + { + Resource: armnetwork.Resource{ + ID: to.StringPtr("table2"), + }, + }, + { + Resource: armnetwork.Resource{ + ID: to.StringPtr("table3"), + }, + }, + { + Resource: armnetwork.Resource{ + ID: to.StringPtr("table4"), + }, + }, + } + + fakeClient := &mockRouteTablesClient{} + + mockPager := &mockRouteTablesListAllPager{} + mockPager.On("Err").Return(nil).Times(3) + mockPager.On("NextPage", mock.Anything).Return(true).Times(2) + mockPager.On("NextPage", mock.Anything).Return(false).Times(1) + mockPager.On("PageResponse").Return(armnetwork.RouteTablesListAllResponse{ + RouteTablesListAllResult: armnetwork.RouteTablesListAllResult{ + RouteTableListResult: armnetwork.RouteTableListResult{ + Value: expected[:2], + }, + }, + }).Times(1) + mockPager.On("PageResponse").Return(armnetwork.RouteTablesListAllResponse{ + RouteTablesListAllResult: armnetwork.RouteTablesListAllResult{ + RouteTableListResult: armnetwork.RouteTableListResult{ + Value: expected[2:], + }, + }, + }).Times(1) + + fakeClient.On("ListAll", mock.Anything).Return(mockPager) + + c := &cache.MockCache{} + c.On("Get", "ListAllRouteTables").Return(nil).Times(1) + c.On("Put", "ListAllRouteTables", expected).Return(true).Times(1) + s := &networkRepository{ + routeTableClient: fakeClient, + cache: c, + } + got, err := s.ListAllRouteTables() + if err != nil { + t.Errorf("ListAllRouteTables() error = %v", err) + return + } + + mockPager.AssertExpectations(t) + fakeClient.AssertExpectations(t) + c.AssertExpectations(t) + + if !reflect.DeepEqual(got, expected) { + t.Errorf("ListAllRouteTables() got = %v, want %v", got, expected) + } +} + +func Test_ListAllRouteTables_MultiplesResults_WithCache(t *testing.T) { + + expected := []*armnetwork.RouteTable{ + { + Resource: armnetwork.Resource{ + ID: to.StringPtr("table1"), + }, + }, + } + + fakeClient := &mockRouteTablesClient{} + + c := &cache.MockCache{} + c.On("Get", "ListAllRouteTables").Return(expected).Times(1) + s := &networkRepository{ + routeTableClient: fakeClient, + cache: c, + } + got, err := s.ListAllRouteTables() + if err != nil { + t.Errorf("ListAllRouteTables() error = %v", err) + return + } + + fakeClient.AssertExpectations(t) + c.AssertExpectations(t) + + if !reflect.DeepEqual(got, expected) { + t.Errorf("ListAllRouteTables() got = %v, want %v", got, expected) + } +} + +func Test_ListAllRouteTables_Error_OnPageResponse(t *testing.T) { + + fakeClient := &mockRouteTablesClient{} + + expectedErr := errors.New("unexpected error") + + mockPager := &mockRouteTablesListAllPager{} + mockPager.On("Err").Return(expectedErr).Times(1) + mockPager.On("NextPage", mock.Anything).Return(true).Times(1) + mockPager.On("PageResponse").Return(armnetwork.RouteTablesListAllResponse{}).Times(1) + + fakeClient.On("ListAll", mock.Anything).Return(mockPager) + + s := &networkRepository{ + routeTableClient: fakeClient, + cache: cache.New(0), + } + got, err := s.ListAllRouteTables() + + mockPager.AssertExpectations(t) + fakeClient.AssertExpectations(t) + + assert.Equal(t, expectedErr, err) + assert.Nil(t, got) +} + +func Test_ListAllRouteTables_Error(t *testing.T) { + + fakeClient := &mockRouteTablesClient{} + + expectedErr := errors.New("unexpected error") + + mockPager := &mockRouteTablesListAllPager{} + mockPager.On("Err").Return(expectedErr).Times(1) + mockPager.On("NextPage", mock.Anything).Return(false).Times(1) + + fakeClient.On("ListAll", mock.Anything).Return(mockPager) + + s := &networkRepository{ + routeTableClient: fakeClient, + cache: cache.New(0), + } + got, err := s.ListAllRouteTables() + + mockPager.AssertExpectations(t) + fakeClient.AssertExpectations(t) + + assert.Equal(t, expectedErr, err) + assert.Nil(t, got) +} diff --git a/pkg/remote/azurerm_network_scanner_test.go b/pkg/remote/azurerm_network_scanner_test.go index debdc0f8..1894d967 100644 --- a/pkg/remote/azurerm_network_scanner_test.go +++ b/pkg/remote/azurerm_network_scanner_test.go @@ -112,3 +112,96 @@ func TestAzurermVirtualNetwork(t *testing.T) { }) } } + +func TestAzurermRouteTables(t *testing.T) { + + dummyError := errors.New("this is an error") + + tests := []struct { + test string + mocks func(*repository.MockNetworkRepository, *mocks.AlerterInterface) + assertExpected func(t *testing.T, got []*resource.Resource) + wantErr error + }{ + { + test: "no route tables", + mocks: func(repository *repository.MockNetworkRepository, alerter *mocks.AlerterInterface) { + repository.On("ListAllRouteTables").Return([]*armnetwork.RouteTable{}, nil) + }, + assertExpected: func(t *testing.T, got []*resource.Resource) { + assert.Len(t, got, 0) + }, + }, + { + test: "error listing route tables", + mocks: func(repository *repository.MockNetworkRepository, alerter *mocks.AlerterInterface) { + repository.On("ListAllRouteTables").Return(nil, dummyError) + }, + wantErr: error2.NewResourceListingError(dummyError, resourceazure.AzureRouteTableResourceType), + }, + { + test: "multiple route tables", + mocks: func(repository *repository.MockNetworkRepository, alerter *mocks.AlerterInterface) { + repository.On("ListAllRouteTables").Return([]*armnetwork.RouteTable{ + { + Resource: armnetwork.Resource{ + ID: to.StringPtr("route1"), + Name: to.StringPtr("route1"), + }, + }, + { + Resource: armnetwork.Resource{ + ID: to.StringPtr("route2"), + Name: to.StringPtr("route2"), + }, + }, + }, nil) + }, + assertExpected: func(t *testing.T, got []*resource.Resource) { + assert.Len(t, got, 2) + + assert.Equal(t, got[0].ResourceId(), "route1") + assert.Equal(t, got[0].ResourceType(), resourceazure.AzureRouteTableResourceType) + + assert.Equal(t, got[1].ResourceId(), "route2") + assert.Equal(t, got[1].ResourceType(), resourceazure.AzureRouteTableResourceType) + }, + }, + } + + providerVersion := "2.71.0" + schemaRepository := testresource.InitFakeSchemaRepository("azurerm", providerVersion) + resourceazure.InitResourcesMetadata(schemaRepository) + factory := terraform.NewTerraformResourceFactory(schemaRepository) + + for _, c := range tests { + t.Run(c.test, func(tt *testing.T) { + + scanOptions := ScannerOptions{} + remoteLibrary := common.NewRemoteLibrary() + + // Initialize mocks + alerter := &mocks.AlerterInterface{} + fakeRepo := &repository.MockNetworkRepository{} + c.mocks(fakeRepo, alerter) + + var repo repository.NetworkRepository = fakeRepo + + remoteLibrary.AddEnumerator(azurerm.NewAzurermRouteTableEnumerator(repo, factory)) + + testFilter := &filter.MockFilter{} + testFilter.On("IsTypeIgnored", mock.Anything).Return(false) + + s := NewScanner(remoteLibrary, alerter, scanOptions, testFilter) + got, err := s.Resources() + assert.Equal(tt, c.wantErr, err) + if err != nil { + return + } + + c.assertExpected(tt, got) + alerter.AssertExpectations(tt) + fakeRepo.AssertExpectations(tt) + }) + } +} diff --git a/pkg/resource/azurerm/azurerm_route_table.go b/pkg/resource/azurerm/azurerm_route_table.go new file mode 100644 index 00000000..ba6b3fc4 --- /dev/null +++ b/pkg/resource/azurerm/azurerm_route_table.go @@ -0,0 +1,18 @@ +package azurerm + +import ( + "github.com/cloudskiff/driftctl/pkg/resource" +) + +const AzureRouteTableResourceType = "azurerm_route_table" + +func initAzureRouteTableMetaData(resourceSchemaRepository resource.SchemaRepositoryInterface) { + resourceSchemaRepository.SetHumanReadableAttributesFunc(AzureRouteTableResourceType, func(res *resource.Resource) map[string]string { + attrs := make(map[string]string) + + if v := res.Attributes().GetString("name"); v != nil && *v != "" { + attrs["Name"] = *v + } + return attrs + }) +} diff --git a/pkg/resource/azurerm/azurerm_route_table_test.go b/pkg/resource/azurerm/azurerm_route_table_test.go new file mode 100644 index 00000000..96322567 --- /dev/null +++ b/pkg/resource/azurerm/azurerm_route_table_test.go @@ -0,0 +1,31 @@ +package azurerm_test + +import ( + "testing" + + "github.com/cloudskiff/driftctl/test" + "github.com/cloudskiff/driftctl/test/acceptance" +) + +func TestAcc_Azure_RouteTable(t *testing.T) { + acceptance.Run(t, acceptance.AccTestCase{ + TerraformVersion: "0.15.5", + Paths: []string{"./testdata/acc/azurerm_route_table"}, + Args: []string{ + "scan", + "--to", "azure+tf", + "--filter", "Type=='azurerm_route_table'", + }, + Checks: []acceptance.AccCheck{ + { + Check: func(result *test.ScanResult, stdout string, err error) { + if err != nil { + t.Fatal(err) + } + result.AssertInfrastructureIsInSync() + result.AssertManagedCount(1) + }, + }, + }, + }) +} diff --git a/pkg/resource/azurerm/metadata.go b/pkg/resource/azurerm/metadata.go index 9f05ca50..babdc8f2 100644 --- a/pkg/resource/azurerm/metadata.go +++ b/pkg/resource/azurerm/metadata.go @@ -4,4 +4,5 @@ import "github.com/cloudskiff/driftctl/pkg/resource" func InitResourcesMetadata(resourceSchemaRepository resource.SchemaRepositoryInterface) { initAzureVirtualNetworkMetaData(resourceSchemaRepository) + initAzureRouteTableMetaData(resourceSchemaRepository) } diff --git a/pkg/resource/azurerm/testdata/acc/azurerm_route_table/.terraform.lock.hcl b/pkg/resource/azurerm/testdata/acc/azurerm_route_table/.terraform.lock.hcl new file mode 100644 index 00000000..67ef8eaf --- /dev/null +++ b/pkg/resource/azurerm/testdata/acc/azurerm_route_table/.terraform.lock.hcl @@ -0,0 +1,21 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "2.71.0" + constraints = "~> 2.71.0" + hashes = [ + "h1:RiFIxNI4Yr9CqleqEdgg1ydLAZ5JiYiz6l5iTD3WcuU=", + "zh:2b9d8a703a0222f72cbceb8d2bdb580066afdcd7f28b6ad65d5ed935319b5433", + "zh:332988f4c1747bcc8ebd32734bf8de2bea4c13a6fbd08d7eb97d0c43d335b15e", + "zh:3a902470276ba48e23ad4dd6baff16a9ce3b60b29c0b07064dbe96ce4640a31c", + "zh:5eaa0d0c2c6554913421be10fbf4bb6a9ef98fbbd750d3d1f02c99798aae2c22", + "zh:67859f40ed2f770f33ace9d3911e8b9c9be505947b38a0578e6d097f5db1d4bf", + "zh:7cd9bf4899fe383fc7eeede03cad138d637244878cd295a7a1044ca20ca0652c", + "zh:afcb82c1382a1a9d63a41137321e077144aad768e4e46057a7ea604d067b4181", + "zh:c6e358759ed00a628dcfe7adb0906b2c98576ac3056fdd70930786d404e1da66", + "zh:cb3390c34f6790ad656929d0268ab3bc082678e8cbe2add0a177cf7896068844", + "zh:cc213dbf59cf41506e86b83492ccfef6ef5f34d4d00d9e49fc8a01fee253f4ee", + "zh:d1e8c9b507e2d187ea2447ae156028ba3f76db2164674761987c14217d04fee5", + ] +} diff --git a/pkg/resource/azurerm/testdata/acc/azurerm_route_table/terraform.tf b/pkg/resource/azurerm/testdata/acc/azurerm_route_table/terraform.tf new file mode 100644 index 00000000..f2855c0a --- /dev/null +++ b/pkg/resource/azurerm/testdata/acc/azurerm_route_table/terraform.tf @@ -0,0 +1,22 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 2.71.0" + } + } +} + +provider "azurerm" { + features {} +} + +data "azurerm_resource_group" "qa1" { + name = "driftctl-qa-1" +} + +resource "azurerm_route_table" "table1" { + name = "table1" + location = data.azurerm_resource_group.qa1.location + resource_group_name = data.azurerm_resource_group.qa1.name +} diff --git a/pkg/resource/resource_types.go b/pkg/resource/resource_types.go index 8f16fecd..a83be32f 100644 --- a/pkg/resource/resource_types.go +++ b/pkg/resource/resource_types.go @@ -83,6 +83,7 @@ var supportedTypes = map[string]struct{}{ "azurerm_storage_account": {}, "azurerm_storage_container": {}, "azurerm_virtual_network": {}, + "azurerm_route_table": {}, } func IsResourceTypeSupported(ty string) bool { From 9350edc28a717343d6d0cbb27f352d21be910991 Mon Sep 17 00:00:00 2001 From: sundowndev Date: Mon, 4 Oct 2021 12:40:55 +0200 Subject: [PATCH 3/8] feat: add azurerm_resource_group --- go.mod | 1 + go.sum | 3 + .../azurerm_resource_group_enumerator.go | 47 ++++++ pkg/remote/azurerm/init.go | 2 + .../repository/mock_ResourcesRepository.go | 36 +++++ .../repository/mock_resourcesClient.go | 29 ++++ .../repository/mock_resourcesListPager.go | 58 +++++++ pkg/remote/azurerm/repository/resources.go | 67 ++++++++ .../azurerm/repository/resources_test.go | 152 ++++++++++++++++++ pkg/remote/azurerm_resources_scanner_test.go | 110 +++++++++++++ .../azurerm/azurerm_resource_group.go | 16 ++ .../azurerm/azurerm_resource_group_test.go | 31 ++++ pkg/resource/azurerm/metadata.go | 1 + .../.terraform.lock.hcl | 21 +++ .../acc/azurerm_resource_group/terraform.tf | 22 +++ pkg/resource/resource_types.go | 1 + 16 files changed, 597 insertions(+) create mode 100644 pkg/remote/azurerm/azurerm_resource_group_enumerator.go create mode 100644 pkg/remote/azurerm/repository/mock_ResourcesRepository.go create mode 100644 pkg/remote/azurerm/repository/mock_resourcesClient.go create mode 100644 pkg/remote/azurerm/repository/mock_resourcesListPager.go create mode 100644 pkg/remote/azurerm/repository/resources.go create mode 100644 pkg/remote/azurerm/repository/resources_test.go create mode 100644 pkg/remote/azurerm_resources_scanner_test.go create mode 100644 pkg/resource/azurerm/azurerm_resource_group.go create mode 100644 pkg/resource/azurerm/azurerm_resource_group_test.go create mode 100755 pkg/resource/azurerm/testdata/acc/azurerm_resource_group/.terraform.lock.hcl create mode 100644 pkg/resource/azurerm/testdata/acc/azurerm_resource_group/terraform.tf diff --git a/go.mod b/go.mod index b6f023f1..226a6cdb 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0 github.com/Azure/azure-sdk-for-go/sdk/network/armnetwork v0.3.0 + github.com/Azure/azure-sdk-for-go/sdk/resources/armresources v0.3.0 github.com/Azure/azure-sdk-for-go/sdk/storage/armstorage v0.2.0 github.com/Azure/go-autorest/autorest v0.11.3 github.com/aws/aws-sdk-go v1.38.68 diff --git a/go.sum b/go.sum index 0f4b5d21..821008ae 100644 --- a/go.sum +++ b/go.sum @@ -48,6 +48,7 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/azure-sdk-for-go v45.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v57.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v57.1.0+incompatible h1:TKQ3ieyB0vVKkF6t9dsWbMjq56O1xU3eh3Ec09v6ajM= github.com/Azure/azure-sdk-for-go v57.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0 h1:lhSJz9RMbJcTgxifR1hUNJnn6CNYtbgEDtQV22/9RBA= @@ -59,6 +60,8 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0 h1:v9p9TfTbf7AwNb5NYQt7hI4 github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= github.com/Azure/azure-sdk-for-go/sdk/network/armnetwork v0.3.0 h1:3ICM5L/XRaknp4DUNqdcNtiOzs6Mc3VKeyQp81+JS2Y= github.com/Azure/azure-sdk-for-go/sdk/network/armnetwork v0.3.0/go.mod h1:YSO+0IW+22kuLybFl2GAYaTDh1VWxNid83hqY/DkpGQ= +github.com/Azure/azure-sdk-for-go/sdk/resources/armresources v0.3.0 h1:I1cONUC2nKiGU3JXm2jRB4+QIs06lGqkplVpwy4ie9o= +github.com/Azure/azure-sdk-for-go/sdk/resources/armresources v0.3.0/go.mod h1:LdmyxRi5+2XPnbuv0X9c6ymGle+UkoNvqsBvG+oG53M= github.com/Azure/azure-sdk-for-go/sdk/storage/armstorage v0.2.0 h1:LOq4ZG6rMgTAZTyGbYHyxL1EVfZdngpUDRY/KvBToMs= github.com/Azure/azure-sdk-for-go/sdk/storage/armstorage v0.2.0/go.mod h1:mIFJgQ93RCQPBsN2jBDzDOfwJpLacGwXIxmirNQMiq4= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= diff --git a/pkg/remote/azurerm/azurerm_resource_group_enumerator.go b/pkg/remote/azurerm/azurerm_resource_group_enumerator.go new file mode 100644 index 00000000..c4e4f082 --- /dev/null +++ b/pkg/remote/azurerm/azurerm_resource_group_enumerator.go @@ -0,0 +1,47 @@ +package azurerm + +import ( + "github.com/cloudskiff/driftctl/pkg/remote/azurerm/repository" + remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error" + "github.com/cloudskiff/driftctl/pkg/resource" + "github.com/cloudskiff/driftctl/pkg/resource/azurerm" +) + +type AzurermResourceGroupEnumerator struct { + repository repository.ResourcesRepository + factory resource.ResourceFactory +} + +func NewAzurermResourceGroupEnumerator(repo repository.ResourcesRepository, factory resource.ResourceFactory) *AzurermResourceGroupEnumerator { + return &AzurermResourceGroupEnumerator{ + repository: repo, + factory: factory, + } +} + +func (e *AzurermResourceGroupEnumerator) SupportedType() resource.ResourceType { + return azurerm.AzureResourceGroupResourceType +} + +func (e *AzurermResourceGroupEnumerator) Enumerate() ([]*resource.Resource, error) { + groups, err := e.repository.ListAllResourceGroups() + if err != nil { + return nil, remoteerror.NewResourceListingError(err, string(e.SupportedType())) + } + + results := make([]*resource.Resource, 0) + for _, group := range groups { + results = append( + results, + e.factory.CreateAbstractResource( + string(e.SupportedType()), + *group.ID, + map[string]interface{}{ + "name": *group.Name, + }, + ), + ) + } + + return results, err +} diff --git a/pkg/remote/azurerm/init.go b/pkg/remote/azurerm/init.go index 0647d42d..76c59c9c 100644 --- a/pkg/remote/azurerm/init.go +++ b/pkg/remote/azurerm/init.go @@ -43,6 +43,7 @@ func Init( storageAccountRepo := repository.NewStorageRepository(con, providerConfig, c) networkRepo := repository.NewNetworkRepository(con, providerConfig, c) + armResourcesRepo := repository.NewResourcesRepository(con, providerConfig, c) providerLibrary.AddProvider(terraform.AZURE, provider) @@ -50,6 +51,7 @@ func Init( remoteLibrary.AddEnumerator(NewAzurermStorageContainerEnumerator(storageAccountRepo, factory)) remoteLibrary.AddEnumerator(NewAzurermVirtualNetworkEnumerator(networkRepo, factory)) remoteLibrary.AddEnumerator(NewAzurermRouteTableEnumerator(networkRepo, factory)) + remoteLibrary.AddEnumerator(NewAzurermResourceGroupEnumerator(armResourcesRepo, factory)) err = resourceSchemaRepository.Init(terraform.AZURE, provider.Version(), provider.Schema()) if err != nil { diff --git a/pkg/remote/azurerm/repository/mock_ResourcesRepository.go b/pkg/remote/azurerm/repository/mock_ResourcesRepository.go new file mode 100644 index 00000000..154c5ad3 --- /dev/null +++ b/pkg/remote/azurerm/repository/mock_ResourcesRepository.go @@ -0,0 +1,36 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package repository + +import ( + armresources "github.com/Azure/azure-sdk-for-go/sdk/resources/armresources" + mock "github.com/stretchr/testify/mock" +) + +// MockResourcesRepository is an autogenerated mock type for the ResourcesRepository type +type MockResourcesRepository struct { + mock.Mock +} + +// ListAllResourceGroups provides a mock function with given fields: +func (_m *MockResourcesRepository) ListAllResourceGroups() ([]*armresources.ResourceGroup, error) { + ret := _m.Called() + + var r0 []*armresources.ResourceGroup + if rf, ok := ret.Get(0).(func() []*armresources.ResourceGroup); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*armresources.ResourceGroup) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/pkg/remote/azurerm/repository/mock_resourcesClient.go b/pkg/remote/azurerm/repository/mock_resourcesClient.go new file mode 100644 index 00000000..0681eefa --- /dev/null +++ b/pkg/remote/azurerm/repository/mock_resourcesClient.go @@ -0,0 +1,29 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package repository + +import ( + armresources "github.com/Azure/azure-sdk-for-go/sdk/resources/armresources" + mock "github.com/stretchr/testify/mock" +) + +// mockResourcesClient is an autogenerated mock type for the resourcesClient type +type mockResourcesClient struct { + mock.Mock +} + +// List provides a mock function with given fields: options +func (_m *mockResourcesClient) List(options *armresources.ResourceGroupsListOptions) resourcesListPager { + ret := _m.Called(options) + + var r0 resourcesListPager + if rf, ok := ret.Get(0).(func(*armresources.ResourceGroupsListOptions) resourcesListPager); ok { + r0 = rf(options) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(resourcesListPager) + } + } + + return r0 +} diff --git a/pkg/remote/azurerm/repository/mock_resourcesListPager.go b/pkg/remote/azurerm/repository/mock_resourcesListPager.go new file mode 100644 index 00000000..2e31f32a --- /dev/null +++ b/pkg/remote/azurerm/repository/mock_resourcesListPager.go @@ -0,0 +1,58 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package repository + +import ( + context "context" + + armresources "github.com/Azure/azure-sdk-for-go/sdk/resources/armresources" + + mock "github.com/stretchr/testify/mock" +) + +// mockResourcesListPager is an autogenerated mock type for the resourcesListPager type +type mockResourcesListPager struct { + mock.Mock +} + +// Err provides a mock function with given fields: +func (_m *mockResourcesListPager) Err() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NextPage provides a mock function with given fields: ctx +func (_m *mockResourcesListPager) NextPage(ctx context.Context) bool { + ret := _m.Called(ctx) + + var r0 bool + if rf, ok := ret.Get(0).(func(context.Context) bool); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// PageResponse provides a mock function with given fields: +func (_m *mockResourcesListPager) PageResponse() armresources.ResourceGroupsListResponse { + ret := _m.Called() + + var r0 armresources.ResourceGroupsListResponse + if rf, ok := ret.Get(0).(func() armresources.ResourceGroupsListResponse); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(armresources.ResourceGroupsListResponse) + } + + return r0 +} diff --git a/pkg/remote/azurerm/repository/resources.go b/pkg/remote/azurerm/repository/resources.go new file mode 100644 index 00000000..155efb04 --- /dev/null +++ b/pkg/remote/azurerm/repository/resources.go @@ -0,0 +1,67 @@ +package repository + +import ( + "context" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" + "github.com/Azure/azure-sdk-for-go/sdk/resources/armresources" + "github.com/cloudskiff/driftctl/pkg/remote/azurerm/common" + "github.com/cloudskiff/driftctl/pkg/remote/cache" +) + +type ResourcesRepository interface { + ListAllResourceGroups() ([]*armresources.ResourceGroup, error) +} + +type resourcesListPager interface { + pager + PageResponse() armresources.ResourceGroupsListResponse +} + +type resourcesClient interface { + List(options *armresources.ResourceGroupsListOptions) resourcesListPager +} + +type resourcesClientImpl struct { + client *armresources.ResourceGroupsClient +} + +func (c resourcesClientImpl) List(options *armresources.ResourceGroupsListOptions) resourcesListPager { + return c.client.List(options) +} + +type resourcesRepository struct { + client resourcesClient + cache cache.Cache +} + +func NewResourcesRepository(con *arm.Connection, config common.AzureProviderConfig, cache cache.Cache) *resourcesRepository { + return &resourcesRepository{ + &resourcesClientImpl{armresources.NewResourceGroupsClient(con, config.SubscriptionID)}, + cache, + } +} + +func (s *resourcesRepository) ListAllResourceGroups() ([]*armresources.ResourceGroup, error) { + cacheKey := "resourcesListAllResourceGroups" + if v := s.cache.Get(cacheKey); v != nil { + return v.([]*armresources.ResourceGroup), nil + } + + pager := s.client.List(nil) + results := make([]*armresources.ResourceGroup, 0) + for pager.NextPage(context.Background()) { + resp := pager.PageResponse() + if err := pager.Err(); err != nil { + return nil, err + } + results = append(results, resp.ResourceGroupsListResult.Value...) + } + if err := pager.Err(); err != nil { + return nil, err + } + + s.cache.Put(cacheKey, results) + + return results, nil +} diff --git a/pkg/remote/azurerm/repository/resources_test.go b/pkg/remote/azurerm/repository/resources_test.go new file mode 100644 index 00000000..f7a79f08 --- /dev/null +++ b/pkg/remote/azurerm/repository/resources_test.go @@ -0,0 +1,152 @@ +package repository + +import ( + "reflect" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/resources/armresources" + "github.com/cloudskiff/driftctl/pkg/remote/cache" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func Test_ArmResources_ListAllResourceGroups(t *testing.T) { + expectedResults := []*armresources.ResourceGroup{ + { + ID: to.StringPtr("/subscriptions/008b5f48-1b66-4d92-a6b6-d215b4c9b473/resourceGroups/elie-dev"), + Name: to.StringPtr("elie-dev"), + }, + { + ID: to.StringPtr("/subscriptions/008b5f48-1b66-4d92-a6b6-d215b4c9b473/resourceGroups/william-dev"), + Name: to.StringPtr("william-dev"), + }, + { + ID: to.StringPtr("/subscriptions/008b5f48-1b66-4d92-a6b6-d215b4c9b473/resourceGroups/driftctl-sj-tests"), + Name: to.StringPtr("driftctl-sj-tests"), + }, + } + + testcases := []struct { + name string + mocks func(*mockResourcesListPager, *cache.MockCache) + expected []*armresources.ResourceGroup + wantErr string + }{ + { + name: "should return resource groups", + mocks: func(mockPager *mockResourcesListPager, mockCache *cache.MockCache) { + mockPager.On("Err").Return(nil).Times(3) + mockPager.On("NextPage", mock.Anything).Return(true).Times(2) + mockPager.On("NextPage", mock.Anything).Return(false).Times(1) + mockPager.On("PageResponse").Return(armresources.ResourceGroupsListResponse{ + ResourceGroupsListResult: armresources.ResourceGroupsListResult{ + ResourceGroupListResult: armresources.ResourceGroupListResult{ + Value: []*armresources.ResourceGroup{ + { + ID: to.StringPtr("/subscriptions/008b5f48-1b66-4d92-a6b6-d215b4c9b473/resourceGroups/elie-dev"), + Name: to.StringPtr("elie-dev"), + }, + { + ID: to.StringPtr("/subscriptions/008b5f48-1b66-4d92-a6b6-d215b4c9b473/resourceGroups/william-dev"), + Name: to.StringPtr("william-dev"), + }, + }, + }, + }, + }).Times(1) + mockPager.On("PageResponse").Return(armresources.ResourceGroupsListResponse{ + ResourceGroupsListResult: armresources.ResourceGroupsListResult{ + ResourceGroupListResult: armresources.ResourceGroupListResult{ + Value: []*armresources.ResourceGroup{ + { + ID: to.StringPtr("/subscriptions/008b5f48-1b66-4d92-a6b6-d215b4c9b473/resourceGroups/driftctl-sj-tests"), + Name: to.StringPtr("driftctl-sj-tests"), + }, + }, + }, + }, + }).Times(1) + + mockCache.On("Get", "resourcesListAllResourceGroups").Return(nil).Times(1) + mockCache.On("Put", "resourcesListAllResourceGroups", expectedResults).Return(true).Times(1) + }, + expected: expectedResults, + }, + { + name: "should hit cache and return resource groups", + mocks: func(mockPager *mockResourcesListPager, mockCache *cache.MockCache) { + mockCache.On("Get", "resourcesListAllResourceGroups").Return(expectedResults).Times(1) + }, + expected: expectedResults, + }, + { + name: "should return remote error", + mocks: func(mockPager *mockResourcesListPager, mockCache *cache.MockCache) { + mockPager.On("NextPage", mock.Anything).Return(true).Times(1) + mockPager.On("PageResponse").Return(armresources.ResourceGroupsListResponse{ + ResourceGroupsListResult: armresources.ResourceGroupsListResult{ + ResourceGroupListResult: armresources.ResourceGroupListResult{ + Value: []*armresources.ResourceGroup{}, + }, + }, + }).Times(1) + mockPager.On("Err").Return(errors.New("remote error")).Times(1) + + mockCache.On("Get", "resourcesListAllResourceGroups").Return(nil).Times(1) + }, + wantErr: "remote error", + }, + { + name: "should return remote error after fetching all pages", + mocks: func(mockPager *mockResourcesListPager, mockCache *cache.MockCache) { + mockPager.On("NextPage", mock.Anything).Return(true).Times(1) + mockPager.On("NextPage", mock.Anything).Return(false).Times(1) + mockPager.On("PageResponse").Return(armresources.ResourceGroupsListResponse{ + ResourceGroupsListResult: armresources.ResourceGroupsListResult{ + ResourceGroupListResult: armresources.ResourceGroupListResult{ + Value: []*armresources.ResourceGroup{}, + }, + }, + }).Times(1) + mockPager.On("Err").Return(nil).Times(1) + mockPager.On("Err").Return(errors.New("remote error")).Times(1) + + mockCache.On("Get", "resourcesListAllResourceGroups").Return(nil).Times(1) + }, + wantErr: "remote error", + }, + } + + for _, tt := range testcases { + t.Run(tt.name, func(t *testing.T) { + fakeClient := &mockResourcesClient{} + mockPager := &mockResourcesListPager{} + mockCache := &cache.MockCache{} + + fakeClient.On("List", mock.Anything).Maybe().Return(mockPager) + + tt.mocks(mockPager, mockCache) + + s := &resourcesRepository{ + client: fakeClient, + cache: mockCache, + } + got, err := s.ListAllResourceGroups() + if tt.wantErr != "" { + assert.EqualError(t, err, tt.wantErr) + } else { + assert.Nil(t, err) + } + + fakeClient.AssertExpectations(t) + mockPager.AssertExpectations(t) + mockCache.AssertExpectations(t) + + if !reflect.DeepEqual(got, tt.expected) { + t.Errorf("ListAllResourceGroups() got = %v, want %v", got, tt.expected) + } + }) + } +} diff --git a/pkg/remote/azurerm_resources_scanner_test.go b/pkg/remote/azurerm_resources_scanner_test.go new file mode 100644 index 00000000..bade6a63 --- /dev/null +++ b/pkg/remote/azurerm_resources_scanner_test.go @@ -0,0 +1,110 @@ +package remote + +import ( + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/resources/armresources" + "github.com/cloudskiff/driftctl/mocks" + "github.com/cloudskiff/driftctl/pkg/filter" + "github.com/cloudskiff/driftctl/pkg/remote/azurerm" + "github.com/cloudskiff/driftctl/pkg/remote/azurerm/repository" + "github.com/cloudskiff/driftctl/pkg/remote/common" + error2 "github.com/cloudskiff/driftctl/pkg/remote/error" + "github.com/cloudskiff/driftctl/pkg/resource" + resourceazure "github.com/cloudskiff/driftctl/pkg/resource/azurerm" + "github.com/cloudskiff/driftctl/pkg/terraform" + testresource "github.com/cloudskiff/driftctl/test/resource" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestAzurermResourceGroups(t *testing.T) { + + dummyError := errors.New("this is an error") + + tests := []struct { + test string + mocks func(*repository.MockResourcesRepository, *mocks.AlerterInterface) + assertExpected func(t *testing.T, got []*resource.Resource) + wantErr error + }{ + { + test: "no resource group", + mocks: func(repository *repository.MockResourcesRepository, alerter *mocks.AlerterInterface) { + repository.On("ListAllResourceGroups").Return([]*armresources.ResourceGroup{}, nil) + }, + assertExpected: func(t *testing.T, got []*resource.Resource) { + assert.Len(t, got, 0) + }, + }, + { + test: "error listing resource groups", + mocks: func(repository *repository.MockResourcesRepository, alerter *mocks.AlerterInterface) { + repository.On("ListAllResourceGroups").Return(nil, dummyError) + }, + wantErr: error2.NewResourceListingError(dummyError, resourceazure.AzureResourceGroupResourceType), + }, + { + test: "multiple resource groups", + mocks: func(repository *repository.MockResourcesRepository, alerter *mocks.AlerterInterface) { + repository.On("ListAllResourceGroups").Return([]*armresources.ResourceGroup{ + { + ID: to.StringPtr("group1"), + Name: to.StringPtr("group1"), + }, + { + ID: to.StringPtr("group2"), + Name: to.StringPtr("group2"), + }, + }, nil) + }, + assertExpected: func(t *testing.T, got []*resource.Resource) { + assert.Len(t, got, 2) + + assert.Equal(t, got[0].ResourceId(), "group1") + assert.Equal(t, got[0].ResourceType(), resourceazure.AzureResourceGroupResourceType) + + assert.Equal(t, got[1].ResourceId(), "group2") + assert.Equal(t, got[1].ResourceType(), resourceazure.AzureResourceGroupResourceType) + }, + }, + } + + providerVersion := "2.71.0" + schemaRepository := testresource.InitFakeSchemaRepository("azurerm", providerVersion) + resourceazure.InitResourcesMetadata(schemaRepository) + factory := terraform.NewTerraformResourceFactory(schemaRepository) + + for _, c := range tests { + t.Run(c.test, func(tt *testing.T) { + + scanOptions := ScannerOptions{} + remoteLibrary := common.NewRemoteLibrary() + + // Initialize mocks + alerter := &mocks.AlerterInterface{} + fakeRepo := &repository.MockResourcesRepository{} + c.mocks(fakeRepo, alerter) + + var repo repository.ResourcesRepository = fakeRepo + + remoteLibrary.AddEnumerator(azurerm.NewAzurermResourceGroupEnumerator(repo, factory)) + + testFilter := &filter.MockFilter{} + testFilter.On("IsTypeIgnored", mock.Anything).Return(false) + + s := NewScanner(remoteLibrary, alerter, scanOptions, testFilter) + got, err := s.Resources() + assert.Equal(tt, c.wantErr, err) + if err != nil { + return + } + + c.assertExpected(tt, got) + alerter.AssertExpectations(tt) + fakeRepo.AssertExpectations(tt) + }) + } +} diff --git a/pkg/resource/azurerm/azurerm_resource_group.go b/pkg/resource/azurerm/azurerm_resource_group.go new file mode 100644 index 00000000..85a0a86e --- /dev/null +++ b/pkg/resource/azurerm/azurerm_resource_group.go @@ -0,0 +1,16 @@ +package azurerm + +import "github.com/cloudskiff/driftctl/pkg/resource" + +const AzureResourceGroupResourceType = "azurerm_resource_group" + +func initAzureResourceGroupMetadata(resourceSchemaRepository resource.SchemaRepositoryInterface) { + resourceSchemaRepository.SetHumanReadableAttributesFunc(AzureResourceGroupResourceType, func(res *resource.Resource) map[string]string { + val := res.Attrs + attrs := make(map[string]string) + if name := val.GetString("name"); name != nil && *name != "" { + attrs["Name"] = *name + } + return attrs + }) +} diff --git a/pkg/resource/azurerm/azurerm_resource_group_test.go b/pkg/resource/azurerm/azurerm_resource_group_test.go new file mode 100644 index 00000000..8df4938a --- /dev/null +++ b/pkg/resource/azurerm/azurerm_resource_group_test.go @@ -0,0 +1,31 @@ +package azurerm_test + +import ( + "testing" + + "github.com/cloudskiff/driftctl/test" + "github.com/cloudskiff/driftctl/test/acceptance" +) + +func TestAcc_Azure_ResourceGroup(t *testing.T) { + acceptance.Run(t, acceptance.AccTestCase{ + TerraformVersion: "0.15.5", + Paths: []string{"./testdata/acc/azurerm_resource_group"}, + Args: []string{ + "scan", + "--to", "azure+tf", + "--filter", "Type=='azurerm_resource_group' && contains(Id, 'acc-test-res-group-')", + }, + Checks: []acceptance.AccCheck{ + { + Check: func(result *test.ScanResult, stdout string, err error) { + if err != nil { + t.Fatal(err) + } + result.AssertInfrastructureIsInSync() + result.AssertManagedCount(2) + }, + }, + }, + }) +} diff --git a/pkg/resource/azurerm/metadata.go b/pkg/resource/azurerm/metadata.go index babdc8f2..a2176118 100644 --- a/pkg/resource/azurerm/metadata.go +++ b/pkg/resource/azurerm/metadata.go @@ -5,4 +5,5 @@ import "github.com/cloudskiff/driftctl/pkg/resource" func InitResourcesMetadata(resourceSchemaRepository resource.SchemaRepositoryInterface) { initAzureVirtualNetworkMetaData(resourceSchemaRepository) initAzureRouteTableMetaData(resourceSchemaRepository) + initAzureResourceGroupMetadata(resourceSchemaRepository) } diff --git a/pkg/resource/azurerm/testdata/acc/azurerm_resource_group/.terraform.lock.hcl b/pkg/resource/azurerm/testdata/acc/azurerm_resource_group/.terraform.lock.hcl new file mode 100755 index 00000000..67ef8eaf --- /dev/null +++ b/pkg/resource/azurerm/testdata/acc/azurerm_resource_group/.terraform.lock.hcl @@ -0,0 +1,21 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "2.71.0" + constraints = "~> 2.71.0" + hashes = [ + "h1:RiFIxNI4Yr9CqleqEdgg1ydLAZ5JiYiz6l5iTD3WcuU=", + "zh:2b9d8a703a0222f72cbceb8d2bdb580066afdcd7f28b6ad65d5ed935319b5433", + "zh:332988f4c1747bcc8ebd32734bf8de2bea4c13a6fbd08d7eb97d0c43d335b15e", + "zh:3a902470276ba48e23ad4dd6baff16a9ce3b60b29c0b07064dbe96ce4640a31c", + "zh:5eaa0d0c2c6554913421be10fbf4bb6a9ef98fbbd750d3d1f02c99798aae2c22", + "zh:67859f40ed2f770f33ace9d3911e8b9c9be505947b38a0578e6d097f5db1d4bf", + "zh:7cd9bf4899fe383fc7eeede03cad138d637244878cd295a7a1044ca20ca0652c", + "zh:afcb82c1382a1a9d63a41137321e077144aad768e4e46057a7ea604d067b4181", + "zh:c6e358759ed00a628dcfe7adb0906b2c98576ac3056fdd70930786d404e1da66", + "zh:cb3390c34f6790ad656929d0268ab3bc082678e8cbe2add0a177cf7896068844", + "zh:cc213dbf59cf41506e86b83492ccfef6ef5f34d4d00d9e49fc8a01fee253f4ee", + "zh:d1e8c9b507e2d187ea2447ae156028ba3f76db2164674761987c14217d04fee5", + ] +} diff --git a/pkg/resource/azurerm/testdata/acc/azurerm_resource_group/terraform.tf b/pkg/resource/azurerm/testdata/acc/azurerm_resource_group/terraform.tf new file mode 100644 index 00000000..ddb18018 --- /dev/null +++ b/pkg/resource/azurerm/testdata/acc/azurerm_resource_group/terraform.tf @@ -0,0 +1,22 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 2.71.0" + } + } +} + +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test-1" { + name = "acc-test-res-group-1" + location = "West Europe" +} + +resource "azurerm_resource_group" "test-2" { + name = "acc-test-res-group-2" + location = "West Europe" +} diff --git a/pkg/resource/resource_types.go b/pkg/resource/resource_types.go index a83be32f..7ce95386 100644 --- a/pkg/resource/resource_types.go +++ b/pkg/resource/resource_types.go @@ -84,6 +84,7 @@ var supportedTypes = map[string]struct{}{ "azurerm_storage_container": {}, "azurerm_virtual_network": {}, "azurerm_route_table": {}, + "azurerm_resource_group": {}, } func IsResourceTypeSupported(ty string) bool { From 0cf7f8b4a0a98a13c5d81c171f2c327bcf615999 Mon Sep 17 00:00:00 2001 From: sundowndev Date: Mon, 4 Oct 2021 13:59:13 +0200 Subject: [PATCH 4/8] refactor(azurerm): remove arm from resources repo --- pkg/remote/azurerm/init.go | 4 ++-- pkg/remote/azurerm/repository/resources_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/remote/azurerm/init.go b/pkg/remote/azurerm/init.go index 76c59c9c..e3fc8e8f 100644 --- a/pkg/remote/azurerm/init.go +++ b/pkg/remote/azurerm/init.go @@ -43,7 +43,7 @@ func Init( storageAccountRepo := repository.NewStorageRepository(con, providerConfig, c) networkRepo := repository.NewNetworkRepository(con, providerConfig, c) - armResourcesRepo := repository.NewResourcesRepository(con, providerConfig, c) + resourcesRepo := repository.NewResourcesRepository(con, providerConfig, c) providerLibrary.AddProvider(terraform.AZURE, provider) @@ -51,7 +51,7 @@ func Init( remoteLibrary.AddEnumerator(NewAzurermStorageContainerEnumerator(storageAccountRepo, factory)) remoteLibrary.AddEnumerator(NewAzurermVirtualNetworkEnumerator(networkRepo, factory)) remoteLibrary.AddEnumerator(NewAzurermRouteTableEnumerator(networkRepo, factory)) - remoteLibrary.AddEnumerator(NewAzurermResourceGroupEnumerator(armResourcesRepo, factory)) + remoteLibrary.AddEnumerator(NewAzurermResourceGroupEnumerator(resourcesRepo, factory)) err = resourceSchemaRepository.Init(terraform.AZURE, provider.Version(), provider.Schema()) if err != nil { diff --git a/pkg/remote/azurerm/repository/resources_test.go b/pkg/remote/azurerm/repository/resources_test.go index f7a79f08..07b9cd07 100644 --- a/pkg/remote/azurerm/repository/resources_test.go +++ b/pkg/remote/azurerm/repository/resources_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/mock" ) -func Test_ArmResources_ListAllResourceGroups(t *testing.T) { +func Test_Resources_ListAllResourceGroups(t *testing.T) { expectedResults := []*armresources.ResourceGroup{ { ID: to.StringPtr("/subscriptions/008b5f48-1b66-4d92-a6b6-d215b4c9b473/resourceGroups/elie-dev"), From e0ae993c1c9bdf35ff35fefe008b4480e332974b Mon Sep 17 00:00:00 2001 From: Elie Date: Fri, 1 Oct 2021 10:28:16 +0200 Subject: [PATCH 5/8] Add azurerm_subnet --- pkg/driftctl.go | 1 + pkg/middlewares/azurerm_subnet_expander.go | 62 +++++ .../azurerm_subnet_expander_test.go | 174 ++++++++++++++ .../azurerm/azurerm_subnets_enumerator.go | 51 ++++ pkg/remote/azurerm/init.go | 1 + .../repository/mock_NetworkRepository.go | 24 ++ .../azurerm/repository/mock_subnetsClient.go | 29 +++ .../repository/mock_subnetsListPager.go | 58 +++++ pkg/remote/azurerm/repository/network.go | 62 ++++- pkg/remote/azurerm/repository/network_test.go | 226 ++++++++++++++++++ pkg/remote/azurerm_network_scanner_test.go | 133 +++++++++++ pkg/resource/azurerm/azurerm_subnet.go | 3 + pkg/resource/azurerm/azurerm_subnet_test.go | 32 +++ .../acc/azurerm_subnet/.terraform.lock.hcl | 21 ++ .../testdata/acc/azurerm_subnet/terraform.tf | 36 +++ pkg/resource/resource_types.go | 1 + 16 files changed, 910 insertions(+), 4 deletions(-) create mode 100644 pkg/middlewares/azurerm_subnet_expander.go create mode 100644 pkg/middlewares/azurerm_subnet_expander_test.go create mode 100644 pkg/remote/azurerm/azurerm_subnets_enumerator.go create mode 100644 pkg/remote/azurerm/repository/mock_subnetsClient.go create mode 100644 pkg/remote/azurerm/repository/mock_subnetsListPager.go create mode 100644 pkg/resource/azurerm/azurerm_subnet.go create mode 100644 pkg/resource/azurerm/azurerm_subnet_test.go create mode 100644 pkg/resource/azurerm/testdata/acc/azurerm_subnet/.terraform.lock.hcl create mode 100644 pkg/resource/azurerm/testdata/acc/azurerm_subnet/terraform.tf diff --git a/pkg/driftctl.go b/pkg/driftctl.go index d6903adb..4ff7ae97 100644 --- a/pkg/driftctl.go +++ b/pkg/driftctl.go @@ -111,6 +111,7 @@ func (d DriftCTL) Run() (*analyser.Analysis, error) { middlewares.NewEipAssociationExpander(d.resourceFactory), middlewares.NewRDSClusterInstanceExpander(d.resourceFactory), middlewares.NewGoogleLegacyBucketIAMBindings(), + middlewares.NewAzurermSubnetExpander(d.resourceFactory), ) if !d.opts.StrictMode { diff --git a/pkg/middlewares/azurerm_subnet_expander.go b/pkg/middlewares/azurerm_subnet_expander.go new file mode 100644 index 00000000..07f86c6c --- /dev/null +++ b/pkg/middlewares/azurerm_subnet_expander.go @@ -0,0 +1,62 @@ +package middlewares + +import ( + "github.com/cloudskiff/driftctl/pkg/resource" + "github.com/cloudskiff/driftctl/pkg/resource/azurerm" +) + +// Explodes subnet found in azurerm_virtual_network.subnet from state resources to dedicated resources +type AzurermSubnetExpander struct { + resourceFactory resource.ResourceFactory +} + +func NewAzurermSubnetExpander(resourceFactory resource.ResourceFactory) AzurermSubnetExpander { + return AzurermSubnetExpander{ + resourceFactory: resourceFactory, + } +} + +func (m AzurermSubnetExpander) Execute(_, resourcesFromState *[]*resource.Resource) error { + newList := make([]*resource.Resource, 0) + for _, res := range *resourcesFromState { + newList = append(newList, res) + + // Ignore all resources other than azurerm_virtual_network + if res.ResourceType() != azurerm.AzureVirtualNetworkResourceType { + continue + } + + subnets, exist := res.Attributes().Get("subnet") + if !exist || subnets == nil { + continue + } + + for _, subnet := range subnets.([]interface{}) { + subnet := subnet.(map[string]interface{}) + id := subnet["id"].(string) + exist := false + for _, resFromState := range *resourcesFromState { + if resFromState.ResourceType() == azurerm.AzureSubnetResourceType && + resFromState.ResourceId() == id { + exist = true + break + } + } + if exist { + continue + } + res := m.resourceFactory.CreateAbstractResource( + azurerm.AzureSubnetResourceType, + id, + map[string]interface{}{}, + ) + + newList = append(newList, res) + + } + + res.Attrs.SafeDelete([]string{"subnet"}) + } + *resourcesFromState = newList + return nil +} diff --git a/pkg/middlewares/azurerm_subnet_expander_test.go b/pkg/middlewares/azurerm_subnet_expander_test.go new file mode 100644 index 00000000..c6f4a135 --- /dev/null +++ b/pkg/middlewares/azurerm_subnet_expander_test.go @@ -0,0 +1,174 @@ +package middlewares + +import ( + "strings" + "testing" + + "github.com/aws/aws-sdk-go/aws/awsutil" + "github.com/cloudskiff/driftctl/pkg/resource" + "github.com/cloudskiff/driftctl/pkg/resource/azurerm" + "github.com/cloudskiff/driftctl/pkg/terraform" + "github.com/r3labs/diff/v2" +) + +func TestAzurermSubnetExpander_Execute(t *testing.T) { + tests := []struct { + name string + input []*resource.Resource + expected []*resource.Resource + mock func(factory *terraform.MockResourceFactory) + }{ + { + name: "test with nil subnet attribute", + input: []*resource.Resource{ + { + Id: "network1", + Type: azurerm.AzureVirtualNetworkResourceType, + Attrs: &resource.Attributes{ + "subnet": nil, + }, + }, + }, + expected: []*resource.Resource{ + { + Id: "network1", + Type: azurerm.AzureVirtualNetworkResourceType, + Attrs: &resource.Attributes{ + "subnet": nil, + }, + }, + }, + }, + { + name: "test with empty subnet attributes", + input: []*resource.Resource{ + { + Id: "network1", + Type: azurerm.AzureVirtualNetworkResourceType, + Attrs: &resource.Attributes{ + "subnet": []interface{}{}, + }, + }, + }, + expected: []*resource.Resource{ + { + Id: "network1", + Type: azurerm.AzureVirtualNetworkResourceType, + Attrs: &resource.Attributes{}, + }, + }, + }, + { + name: "test that resource will not be expanded if it already exist", + input: []*resource.Resource{ + { + Id: "exist", + Type: azurerm.AzureSubnetResourceType, + Attrs: &resource.Attributes{}, + }, + { + Id: "network1", + Type: azurerm.AzureVirtualNetworkResourceType, + Attrs: &resource.Attributes{ + "subnet": []interface{}{ + map[string]interface{}{ + "id": "exist", + }, + }, + }, + }, + }, + expected: []*resource.Resource{ + { + Id: "exist", + Type: azurerm.AzureSubnetResourceType, + Attrs: &resource.Attributes{}, + }, + { + Id: "network1", + Type: azurerm.AzureVirtualNetworkResourceType, + Attrs: &resource.Attributes{}, + }, + }, + }, + { + name: "test subnet are expanded", + input: []*resource.Resource{ + { + Id: "fake_resource", + }, + { + Id: "network1", + Type: azurerm.AzureVirtualNetworkResourceType, + Attrs: &resource.Attributes{ + "subnet": []interface{}{ + map[string]interface{}{ + "id": "subnet1", + }, + map[string]interface{}{ + "id": "subnet2", + }, + }, + }, + }, + }, + expected: []*resource.Resource{ + { + Id: "fake_resource", + }, + { + Id: "network1", + Type: azurerm.AzureVirtualNetworkResourceType, + Attrs: &resource.Attributes{}, + }, + { + Id: "subnet1", + Type: azurerm.AzureSubnetResourceType, + Attrs: &resource.Attributes{}, + }, + { + Id: "subnet2", + Type: azurerm.AzureSubnetResourceType, + Attrs: &resource.Attributes{}, + }, + }, + mock: func(factory *terraform.MockResourceFactory) { + factory.On("CreateAbstractResource", azurerm.AzureSubnetResourceType, "subnet1", map[string]interface{}{}).Times(1).Return(&resource.Resource{ + Id: "subnet1", + Type: azurerm.AzureSubnetResourceType, + Attrs: &resource.Attributes{}, + }, nil) + factory.On("CreateAbstractResource", azurerm.AzureSubnetResourceType, "subnet2", map[string]interface{}{}).Times(1).Return(&resource.Resource{ + Id: "subnet2", + Type: azurerm.AzureSubnetResourceType, + Attrs: &resource.Attributes{}, + }, nil) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + factory := &terraform.MockResourceFactory{} + if tt.mock != nil { + tt.mock(factory) + } + + m := NewAzurermSubnetExpander(factory) + err := m.Execute(&[]*resource.Resource{}, &tt.input) + if err != nil { + t.Fatal(err) + } + + changelog, err := diff.Diff(tt.expected, tt.input) + if err != nil { + t.Fatal(err) + } + if len(changelog) > 0 { + for _, change := range changelog { + t.Errorf("%s got = %v, want %v", strings.Join(change.Path, "."), awsutil.Prettify(change.From), awsutil.Prettify(change.To)) + } + } + + }) + } +} diff --git a/pkg/remote/azurerm/azurerm_subnets_enumerator.go b/pkg/remote/azurerm/azurerm_subnets_enumerator.go new file mode 100644 index 00000000..4c0b8205 --- /dev/null +++ b/pkg/remote/azurerm/azurerm_subnets_enumerator.go @@ -0,0 +1,51 @@ +package azurerm + +import ( + "github.com/cloudskiff/driftctl/pkg/remote/azurerm/repository" + remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error" + "github.com/cloudskiff/driftctl/pkg/resource" + "github.com/cloudskiff/driftctl/pkg/resource/azurerm" +) + +type AzurermSubnetEnumerator struct { + repository repository.NetworkRepository + factory resource.ResourceFactory +} + +func NewAzurermSubnetEnumerator(repo repository.NetworkRepository, factory resource.ResourceFactory) *AzurermSubnetEnumerator { + return &AzurermSubnetEnumerator{ + repository: repo, + factory: factory, + } +} + +func (e *AzurermSubnetEnumerator) SupportedType() resource.ResourceType { + return azurerm.AzureSubnetResourceType +} + +func (e *AzurermSubnetEnumerator) Enumerate() ([]*resource.Resource, error) { + networks, err := e.repository.ListAllVirtualNetworks() + if err != nil { + return nil, remoteerror.NewResourceListingErrorWithType(err, string(e.SupportedType()), azurerm.AzureVirtualNetworkResourceType) + } + + results := make([]*resource.Resource, 0) + for _, network := range networks { + resources, err := e.repository.ListAllSubnets(network) + if err != nil { + return nil, remoteerror.NewResourceListingError(err, string(e.SupportedType())) + } + for _, res := range resources { + results = append( + results, + e.factory.CreateAbstractResource( + string(e.SupportedType()), + *res.ID, + map[string]interface{}{}, + ), + ) + } + } + + return results, err +} diff --git a/pkg/remote/azurerm/init.go b/pkg/remote/azurerm/init.go index e3fc8e8f..a6385b80 100644 --- a/pkg/remote/azurerm/init.go +++ b/pkg/remote/azurerm/init.go @@ -52,6 +52,7 @@ func Init( remoteLibrary.AddEnumerator(NewAzurermVirtualNetworkEnumerator(networkRepo, factory)) remoteLibrary.AddEnumerator(NewAzurermRouteTableEnumerator(networkRepo, factory)) remoteLibrary.AddEnumerator(NewAzurermResourceGroupEnumerator(resourcesRepo, factory)) + remoteLibrary.AddEnumerator(NewAzurermSubnetEnumerator(networkRepo, factory)) err = resourceSchemaRepository.Init(terraform.AZURE, provider.Version(), provider.Schema()) if err != nil { diff --git a/pkg/remote/azurerm/repository/mock_NetworkRepository.go b/pkg/remote/azurerm/repository/mock_NetworkRepository.go index c5de4eff..ab25f9bf 100644 --- a/pkg/remote/azurerm/repository/mock_NetworkRepository.go +++ b/pkg/remote/azurerm/repository/mock_NetworkRepository.go @@ -36,6 +36,30 @@ func (_m *MockNetworkRepository) ListAllRouteTables() ([]*armnetwork.RouteTable, } // ListAllVirtualNetworks provides a mock function with given fields: +// ListAllSubnets provides a mock function with given fields: virtualNetwork +func (_m *MockNetworkRepository) ListAllSubnets(virtualNetwork *armnetwork.VirtualNetwork) ([]*armnetwork.Subnet, error) { + ret := _m.Called(virtualNetwork) + + var r0 []*armnetwork.Subnet + if rf, ok := ret.Get(0).(func(*armnetwork.VirtualNetwork) []*armnetwork.Subnet); ok { + r0 = rf(virtualNetwork) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*armnetwork.Subnet) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(*armnetwork.VirtualNetwork) error); ok { + r1 = rf(virtualNetwork) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListAllVirtualNetwork provides a mock function with given fields: func (_m *MockNetworkRepository) ListAllVirtualNetworks() ([]*armnetwork.VirtualNetwork, error) { ret := _m.Called() diff --git a/pkg/remote/azurerm/repository/mock_subnetsClient.go b/pkg/remote/azurerm/repository/mock_subnetsClient.go new file mode 100644 index 00000000..150f2e59 --- /dev/null +++ b/pkg/remote/azurerm/repository/mock_subnetsClient.go @@ -0,0 +1,29 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package repository + +import ( + armnetwork "github.com/Azure/azure-sdk-for-go/sdk/network/armnetwork" + mock "github.com/stretchr/testify/mock" +) + +// mockSubnetsClient is an autogenerated mock type for the subnetsClient type +type mockSubnetsClient struct { + mock.Mock +} + +// List provides a mock function with given fields: resourceGroupName, virtualNetworkName, options +func (_m *mockSubnetsClient) List(resourceGroupName string, virtualNetworkName string, options *armnetwork.SubnetsListOptions) subnetsListPager { + ret := _m.Called(resourceGroupName, virtualNetworkName, options) + + var r0 subnetsListPager + if rf, ok := ret.Get(0).(func(string, string, *armnetwork.SubnetsListOptions) subnetsListPager); ok { + r0 = rf(resourceGroupName, virtualNetworkName, options) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(subnetsListPager) + } + } + + return r0 +} diff --git a/pkg/remote/azurerm/repository/mock_subnetsListPager.go b/pkg/remote/azurerm/repository/mock_subnetsListPager.go new file mode 100644 index 00000000..997d7698 --- /dev/null +++ b/pkg/remote/azurerm/repository/mock_subnetsListPager.go @@ -0,0 +1,58 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package repository + +import ( + context "context" + + armnetwork "github.com/Azure/azure-sdk-for-go/sdk/network/armnetwork" + + mock "github.com/stretchr/testify/mock" +) + +// mockSubnetsListPager is an autogenerated mock type for the subnetsListPager type +type mockSubnetsListPager struct { + mock.Mock +} + +// Err provides a mock function with given fields: +func (_m *mockSubnetsListPager) Err() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NextPage provides a mock function with given fields: ctx +func (_m *mockSubnetsListPager) NextPage(ctx context.Context) bool { + ret := _m.Called(ctx) + + var r0 bool + if rf, ok := ret.Get(0).(func(context.Context) bool); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// PageResponse provides a mock function with given fields: +func (_m *mockSubnetsListPager) PageResponse() armnetwork.SubnetsListResponse { + ret := _m.Called() + + var r0 armnetwork.SubnetsListResponse + if rf, ok := ret.Get(0).(func() armnetwork.SubnetsListResponse); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(armnetwork.SubnetsListResponse) + } + + return r0 +} diff --git a/pkg/remote/azurerm/repository/network.go b/pkg/remote/azurerm/repository/network.go index 2322c7ed..4f609016 100644 --- a/pkg/remote/azurerm/repository/network.go +++ b/pkg/remote/azurerm/repository/network.go @@ -2,9 +2,11 @@ package repository import ( "context" + "fmt" "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" "github.com/Azure/azure-sdk-for-go/sdk/network/armnetwork" + "github.com/Azure/go-autorest/autorest/azure" "github.com/cloudskiff/driftctl/pkg/remote/azurerm/common" "github.com/cloudskiff/driftctl/pkg/remote/cache" ) @@ -12,6 +14,28 @@ import ( type NetworkRepository interface { ListAllVirtualNetworks() ([]*armnetwork.VirtualNetwork, error) ListAllRouteTables() ([]*armnetwork.RouteTable, error) + ListAllSubnets(virtualNetwork *armnetwork.VirtualNetwork) ([]*armnetwork.Subnet, error) +} + +type subnetsListPager interface { + pager + PageResponse() armnetwork.SubnetsListResponse +} + +type subnetsClient interface { + List(resourceGroupName, virtualNetworkName string, options *armnetwork.SubnetsListOptions) subnetsListPager +} + +type subnetsClientImpl struct { + client *armnetwork.SubnetsClient +} + +func (s subnetsClientImpl) List(resourceGroupName, virtualNetworkName string, options *armnetwork.SubnetsListOptions) subnetsListPager { + return s.client.List(resourceGroupName, virtualNetworkName, options) +} + +type virtualNetworksClient interface { + ListAll(options *armnetwork.VirtualNetworksListAllOptions) virtualNetworksListAllPager } type virtualNetworksListAllPager interface { @@ -19,10 +43,6 @@ type virtualNetworksListAllPager interface { PageResponse() armnetwork.VirtualNetworksListAllResponse } -type virtualNetworksClient interface { - ListAll(options *armnetwork.VirtualNetworksListAllOptions) virtualNetworksListAllPager -} - type virtualNetworksClientImpl struct { client *armnetwork.VirtualNetworksClient } @@ -51,6 +71,7 @@ func (c routeTablesClientImpl) ListAll(options *armnetwork.RouteTablesListAllOpt type networkRepository struct { virtualNetworksClient virtualNetworksClient routeTableClient routeTablesClient + subnetsClient subnetsClient cache cache.Cache } @@ -58,6 +79,7 @@ func NewNetworkRepository(con *arm.Connection, config common.AzureProviderConfig return &networkRepository{ &virtualNetworksClientImpl{client: armnetwork.NewVirtualNetworksClient(con, config.SubscriptionID)}, &routeTablesClientImpl{client: armnetwork.NewRouteTablesClient(con, config.SubscriptionID)}, + &subnetsClientImpl{client: armnetwork.NewSubnetsClient(con, config.SubscriptionID)}, cache, } } @@ -110,3 +132,35 @@ func (s *networkRepository) ListAllRouteTables() ([]*armnetwork.RouteTable, erro return results, nil } + +func (s *networkRepository) ListAllSubnets(virtualNetwork *armnetwork.VirtualNetwork) ([]*armnetwork.Subnet, error) { + + cacheKey := fmt.Sprintf("ListAllSubnets_%s", *virtualNetwork.ID) + + if v := s.cache.Get(cacheKey); v != nil { + return v.([]*armnetwork.Subnet), nil + } + + res, err := azure.ParseResourceID(*virtualNetwork.ID) + if err != nil { + return nil, err + } + + pager := s.subnetsClient.List(res.ResourceGroup, *virtualNetwork.Name, nil) + results := make([]*armnetwork.Subnet, 0) + for pager.NextPage(context.Background()) { + resp := pager.PageResponse() + if err := pager.Err(); err != nil { + return nil, err + } + results = append(results, resp.SubnetsListResult.SubnetListResult.Value...) + } + + if err := pager.Err(); err != nil { + return nil, err + } + + s.cache.Put(cacheKey, results) + + return results, nil +} diff --git a/pkg/remote/azurerm/repository/network_test.go b/pkg/remote/azurerm/repository/network_test.go index f045fac9..fd3fa3f8 100644 --- a/pkg/remote/azurerm/repository/network_test.go +++ b/pkg/remote/azurerm/repository/network_test.go @@ -1,6 +1,7 @@ package repository import ( + "fmt" "reflect" "testing" @@ -339,3 +340,228 @@ func Test_ListAllRouteTables_Error(t *testing.T) { assert.Equal(t, expectedErr, err) assert.Nil(t, got) } + +func Test_ListAllSubnets_MultiplesResults(t *testing.T) { + + network := &armnetwork.VirtualNetwork{ + Resource: armnetwork.Resource{ + Name: to.StringPtr("network1"), + ID: to.StringPtr("/subscriptions/7bfb2c5c-0000-0000-0000-fffa356eb406/resourceGroups/test-dev/providers/Microsoft.Network/virtualNetworks/network1"), + }, + } + + expected := []*armnetwork.Subnet{ + { + SubResource: armnetwork.SubResource{ + ID: to.StringPtr("subnet1"), + }, + }, + { + SubResource: armnetwork.SubResource{ + ID: to.StringPtr("subnet2"), + }, + }, + { + SubResource: armnetwork.SubResource{ + ID: to.StringPtr("subnet3"), + }, + }, + { + SubResource: armnetwork.SubResource{ + ID: to.StringPtr("subnet4"), + }, + }, + } + + fakeClient := &mockSubnetsClient{} + + mockPager := &mockSubnetsListPager{} + mockPager.On("Err").Return(nil).Times(3) + mockPager.On("NextPage", mock.Anything).Return(true).Times(2) + mockPager.On("NextPage", mock.Anything).Return(false).Times(1) + mockPager.On("PageResponse").Return(armnetwork.SubnetsListResponse{ + SubnetsListResult: armnetwork.SubnetsListResult{ + SubnetListResult: armnetwork.SubnetListResult{ + Value: []*armnetwork.Subnet{ + { + SubResource: armnetwork.SubResource{ + ID: to.StringPtr("subnet1"), + }, + }, + { + SubResource: armnetwork.SubResource{ + ID: to.StringPtr("subnet2"), + }, + }, + }, + }, + }, + }).Times(1) + mockPager.On("PageResponse").Return(armnetwork.SubnetsListResponse{ + SubnetsListResult: armnetwork.SubnetsListResult{ + SubnetListResult: armnetwork.SubnetListResult{ + Value: []*armnetwork.Subnet{ + { + SubResource: armnetwork.SubResource{ + ID: to.StringPtr("subnet3"), + }, + }, + { + SubResource: armnetwork.SubResource{ + ID: to.StringPtr("subnet4"), + }, + }, + }, + }, + }, + }).Times(1) + + fakeClient.On("List", "test-dev", "network1", mock.Anything).Return(mockPager) + + c := &cache.MockCache{} + cacheKey := fmt.Sprintf("ListAllSubnets_%s", *network.ID) + c.On("Get", cacheKey).Return(nil).Times(1) + c.On("Put", cacheKey, expected).Return(true).Times(1) + s := &networkRepository{ + subnetsClient: fakeClient, + cache: c, + } + got, err := s.ListAllSubnets(network) + if err != nil { + t.Errorf("ListAllSubnets() error = %v", err) + return + } + + mockPager.AssertExpectations(t) + fakeClient.AssertExpectations(t) + c.AssertExpectations(t) + + if !reflect.DeepEqual(got, expected) { + t.Errorf("ListAllSubnets() got = %v, want %v", got, expected) + } +} + +func Test_ListAllSubnets_MultiplesResults_WithCache(t *testing.T) { + + network := &armnetwork.VirtualNetwork{ + Resource: armnetwork.Resource{ + ID: to.StringPtr("networkID"), + }, + } + + expected := []*armnetwork.Subnet{ + { + Name: to.StringPtr("network1"), + }, + } + fakeClient := &mockSubnetsClient{} + + c := &cache.MockCache{} + c.On("Get", "ListAllSubnets_networkID").Return(expected).Times(1) + s := &networkRepository{ + subnetsClient: fakeClient, + cache: c, + } + got, err := s.ListAllSubnets(network) + if err != nil { + t.Errorf("ListAllSubnets() error = %v", err) + return + } + + fakeClient.AssertExpectations(t) + c.AssertExpectations(t) + + if !reflect.DeepEqual(got, expected) { + t.Errorf("ListAllSubnets() got = %v, want %v", got, expected) + } +} + +func Test_ListAllSubnets_Error_OnPageResponse(t *testing.T) { + + network := &armnetwork.VirtualNetwork{ + Resource: armnetwork.Resource{ + Name: to.StringPtr("network1"), + ID: to.StringPtr("/subscriptions/7bfb2c5c-0000-0000-0000-fffa356eb406/resourceGroups/test-dev/providers/Microsoft.Network/virtualNetworks/network1"), + }, + } + + fakeClient := &mockSubnetsClient{} + + expectedErr := errors.New("unexpected error") + + mockPager := &mockSubnetsListPager{} + mockPager.On("Err").Return(expectedErr).Times(1) + mockPager.On("NextPage", mock.Anything).Return(true).Times(1) + mockPager.On("PageResponse").Return(armnetwork.SubnetsListResponse{}).Times(1) + + fakeClient.On("List", "test-dev", "network1", mock.Anything).Return(mockPager) + + s := &networkRepository{ + subnetsClient: fakeClient, + cache: cache.New(0), + } + got, err := s.ListAllSubnets(network) + + mockPager.AssertExpectations(t) + fakeClient.AssertExpectations(t) + + assert.Equal(t, expectedErr, err) + assert.Nil(t, got) +} + +func Test_ListAllSubnets_Error(t *testing.T) { + + network := &armnetwork.VirtualNetwork{ + Resource: armnetwork.Resource{ + Name: to.StringPtr("network1"), + ID: to.StringPtr("/subscriptions/7bfb2c5c-0000-0000-0000-fffa356eb406/resourceGroups/test-dev/providers/Microsoft.Network/virtualNetworks/network1"), + }, + } + + fakeClient := &mockSubnetsClient{} + + expectedErr := errors.New("unexpected error") + + mockPager := &mockSubnetsListPager{} + mockPager.On("Err").Return(expectedErr).Times(1) + mockPager.On("NextPage", mock.Anything).Return(false).Times(1) + + fakeClient.On("List", "test-dev", "network1", mock.Anything).Return(mockPager) + + s := &networkRepository{ + subnetsClient: fakeClient, + cache: cache.New(0), + } + got, err := s.ListAllSubnets(network) + + mockPager.AssertExpectations(t) + fakeClient.AssertExpectations(t) + + assert.Equal(t, expectedErr, err) + assert.Nil(t, got) +} + +func Test_ListAllSubnets_ErrorOnInvalidNetworkID(t *testing.T) { + + network := &armnetwork.VirtualNetwork{ + Resource: armnetwork.Resource{ + Name: to.StringPtr("network1"), + ID: to.StringPtr("foobar"), + }, + } + + fakeClient := &mockSubnetsClient{} + + expectedErr := errors.New("parsing failed for foobar. Invalid resource Id format") + + s := &networkRepository{ + subnetsClient: fakeClient, + cache: cache.New(0), + } + got, err := s.ListAllSubnets(network) + + fakeClient.AssertExpectations(t) + + assert.Equal(t, expectedErr.Error(), err.Error()) + assert.Nil(t, got) +} diff --git a/pkg/remote/azurerm_network_scanner_test.go b/pkg/remote/azurerm_network_scanner_test.go index 1894d967..80e33c72 100644 --- a/pkg/remote/azurerm_network_scanner_test.go +++ b/pkg/remote/azurerm_network_scanner_test.go @@ -205,3 +205,136 @@ func TestAzurermRouteTables(t *testing.T) { }) } } + +func TestAzurermSubnets(t *testing.T) { + + dummyError := errors.New("this is an error") + + networks := []*armnetwork.VirtualNetwork{ + { + Resource: armnetwork.Resource{ + ID: to.StringPtr("network1"), + }, + }, + { + Resource: armnetwork.Resource{ + ID: to.StringPtr("network2"), + }, + }, + } + + tests := []struct { + test string + mocks func(*repository.MockNetworkRepository, *mocks.AlerterInterface) + assertExpected func(t *testing.T, got []*resource.Resource) + wantErr error + }{ + { + test: "no subnets", + mocks: func(repository *repository.MockNetworkRepository, alerter *mocks.AlerterInterface) { + repository.On("ListAllVirtualNetworks").Return(networks, nil) + repository.On("ListAllSubnets", networks[0]).Return([]*armnetwork.Subnet{}, nil).Times(1) + repository.On("ListAllSubnets", networks[1]).Return([]*armnetwork.Subnet{}, nil).Times(1) + }, + assertExpected: func(t *testing.T, got []*resource.Resource) { + assert.Len(t, got, 0) + }, + }, + { + test: "error listing virtual network", + mocks: func(repository *repository.MockNetworkRepository, alerter *mocks.AlerterInterface) { + repository.On("ListAllVirtualNetworks").Return(nil, dummyError) + }, + wantErr: error2.NewResourceListingErrorWithType(dummyError, resourceazure.AzureSubnetResourceType, resourceazure.AzureVirtualNetworkResourceType), + }, + { + test: "error listing subnets", + mocks: func(repository *repository.MockNetworkRepository, alerter *mocks.AlerterInterface) { + repository.On("ListAllVirtualNetworks").Return(networks, nil) + repository.On("ListAllSubnets", networks[0]).Return(nil, dummyError).Times(1) + }, + wantErr: error2.NewResourceListingError(dummyError, resourceazure.AzureSubnetResourceType), + }, + { + test: "multiple subnets", + mocks: func(repository *repository.MockNetworkRepository, alerter *mocks.AlerterInterface) { + repository.On("ListAllVirtualNetworks").Return(networks, nil) + repository.On("ListAllSubnets", networks[0]).Return([]*armnetwork.Subnet{ + { + SubResource: armnetwork.SubResource{ + ID: to.StringPtr("subnet1"), + }, + }, + { + SubResource: armnetwork.SubResource{ + ID: to.StringPtr("subnet2"), + }, + }, + }, nil).Times(1) + repository.On("ListAllSubnets", networks[1]).Return([]*armnetwork.Subnet{ + { + SubResource: armnetwork.SubResource{ + ID: to.StringPtr("subnet3"), + }, + }, + { + SubResource: armnetwork.SubResource{ + ID: to.StringPtr("subnet4"), + }, + }, + }, nil).Times(1) + }, + assertExpected: func(t *testing.T, got []*resource.Resource) { + assert.Len(t, got, 4) + + assert.Equal(t, got[0].ResourceId(), "subnet1") + assert.Equal(t, got[0].ResourceType(), resourceazure.AzureSubnetResourceType) + + assert.Equal(t, got[1].ResourceId(), "subnet2") + assert.Equal(t, got[1].ResourceType(), resourceazure.AzureSubnetResourceType) + + assert.Equal(t, got[2].ResourceId(), "subnet3") + assert.Equal(t, got[2].ResourceType(), resourceazure.AzureSubnetResourceType) + + assert.Equal(t, got[3].ResourceId(), "subnet4") + assert.Equal(t, got[3].ResourceType(), resourceazure.AzureSubnetResourceType) + }, + }, + } + + providerVersion := "2.71.0" + schemaRepository := testresource.InitFakeSchemaRepository("azurerm", providerVersion) + resourceazure.InitResourcesMetadata(schemaRepository) + factory := terraform.NewTerraformResourceFactory(schemaRepository) + + for _, c := range tests { + t.Run(c.test, func(tt *testing.T) { + + scanOptions := ScannerOptions{} + remoteLibrary := common.NewRemoteLibrary() + + // Initialize mocks + alerter := &mocks.AlerterInterface{} + fakeRepo := &repository.MockNetworkRepository{} + c.mocks(fakeRepo, alerter) + + var repo repository.NetworkRepository = fakeRepo + + remoteLibrary.AddEnumerator(azurerm.NewAzurermSubnetEnumerator(repo, factory)) + + testFilter := &filter.MockFilter{} + testFilter.On("IsTypeIgnored", mock.Anything).Return(false) + + s := NewScanner(remoteLibrary, alerter, scanOptions, testFilter) + got, err := s.Resources() + assert.Equal(tt, c.wantErr, err) + if err != nil { + return + } + + c.assertExpected(tt, got) + alerter.AssertExpectations(tt) + fakeRepo.AssertExpectations(tt) + }) + } +} diff --git a/pkg/resource/azurerm/azurerm_subnet.go b/pkg/resource/azurerm/azurerm_subnet.go new file mode 100644 index 00000000..bcb43335 --- /dev/null +++ b/pkg/resource/azurerm/azurerm_subnet.go @@ -0,0 +1,3 @@ +package azurerm + +const AzureSubnetResourceType = "azurerm_subnet" diff --git a/pkg/resource/azurerm/azurerm_subnet_test.go b/pkg/resource/azurerm/azurerm_subnet_test.go new file mode 100644 index 00000000..908175bf --- /dev/null +++ b/pkg/resource/azurerm/azurerm_subnet_test.go @@ -0,0 +1,32 @@ +package azurerm_test + +import ( + "testing" + + "github.com/cloudskiff/driftctl/test" + "github.com/cloudskiff/driftctl/test/acceptance" +) + +func TestAcc_Azure_Subnet(t *testing.T) { + acceptance.Run(t, acceptance.AccTestCase{ + TerraformVersion: "0.15.5", + Paths: []string{"./testdata/acc/azurerm_subnet"}, + Args: []string{ + "scan", + "--to", "azure+tf", + "--filter", "Type=='azurerm_subnet' || Type=='azurerm_virtual_network'", + }, + Checks: []acceptance.AccCheck{ + { + Check: func(result *test.ScanResult, stdout string, err error) { + if err != nil { + t.Fatal(err) + } + result.AssertInfrastructureIsInSync() + // We should have one azurerm_virtual_network and two azurerm_subnet + result.AssertManagedCount(3) + }, + }, + }, + }) +} diff --git a/pkg/resource/azurerm/testdata/acc/azurerm_subnet/.terraform.lock.hcl b/pkg/resource/azurerm/testdata/acc/azurerm_subnet/.terraform.lock.hcl new file mode 100644 index 00000000..67ef8eaf --- /dev/null +++ b/pkg/resource/azurerm/testdata/acc/azurerm_subnet/.terraform.lock.hcl @@ -0,0 +1,21 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "2.71.0" + constraints = "~> 2.71.0" + hashes = [ + "h1:RiFIxNI4Yr9CqleqEdgg1ydLAZ5JiYiz6l5iTD3WcuU=", + "zh:2b9d8a703a0222f72cbceb8d2bdb580066afdcd7f28b6ad65d5ed935319b5433", + "zh:332988f4c1747bcc8ebd32734bf8de2bea4c13a6fbd08d7eb97d0c43d335b15e", + "zh:3a902470276ba48e23ad4dd6baff16a9ce3b60b29c0b07064dbe96ce4640a31c", + "zh:5eaa0d0c2c6554913421be10fbf4bb6a9ef98fbbd750d3d1f02c99798aae2c22", + "zh:67859f40ed2f770f33ace9d3911e8b9c9be505947b38a0578e6d097f5db1d4bf", + "zh:7cd9bf4899fe383fc7eeede03cad138d637244878cd295a7a1044ca20ca0652c", + "zh:afcb82c1382a1a9d63a41137321e077144aad768e4e46057a7ea604d067b4181", + "zh:c6e358759ed00a628dcfe7adb0906b2c98576ac3056fdd70930786d404e1da66", + "zh:cb3390c34f6790ad656929d0268ab3bc082678e8cbe2add0a177cf7896068844", + "zh:cc213dbf59cf41506e86b83492ccfef6ef5f34d4d00d9e49fc8a01fee253f4ee", + "zh:d1e8c9b507e2d187ea2447ae156028ba3f76db2164674761987c14217d04fee5", + ] +} diff --git a/pkg/resource/azurerm/testdata/acc/azurerm_subnet/terraform.tf b/pkg/resource/azurerm/testdata/acc/azurerm_subnet/terraform.tf new file mode 100644 index 00000000..27c113d9 --- /dev/null +++ b/pkg/resource/azurerm/testdata/acc/azurerm_subnet/terraform.tf @@ -0,0 +1,36 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 2.71.0" + } + } +} + +provider "azurerm" { + features {} +} + +data "azurerm_resource_group" "qa1" { + name = "elie-dev" +} + +resource "azurerm_virtual_network" "test" { + name = "network1" + location = data.azurerm_resource_group.qa1.location + resource_group_name = data.azurerm_resource_group.qa1.name + address_space = ["10.0.0.0/16"] + dns_servers = ["10.0.0.4", "10.0.0.5"] + + subnet { + address_prefix = "10.0.2.0/24" + name = "nested-subnet" + } +} + +resource "azurerm_subnet" "example" { + name = "non-nested-subnet" + resource_group_name = data.azurerm_resource_group.qa1.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefixes = ["10.0.1.0/24"] +} diff --git a/pkg/resource/resource_types.go b/pkg/resource/resource_types.go index 7ce95386..fce22964 100644 --- a/pkg/resource/resource_types.go +++ b/pkg/resource/resource_types.go @@ -85,6 +85,7 @@ var supportedTypes = map[string]struct{}{ "azurerm_virtual_network": {}, "azurerm_route_table": {}, "azurerm_resource_group": {}, + "azurerm_subnet": {}, } func IsResourceTypeSupported(ty string) bool { From 688cf89909a891b58e07847c311a0adcdacec880 Mon Sep 17 00:00:00 2001 From: Elie Date: Mon, 4 Oct 2021 17:02:11 +0200 Subject: [PATCH 6/8] use codecov orb --- .circleci/config.yml | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7298ba8a..c2fdebc6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,7 @@ version: 2.1 orbs: go: circleci/go@1.5.0 + codecov: codecov/codecov@3.1.0 jobs: test_acc: parameters: @@ -31,6 +32,9 @@ jobs: name: Run acceptance tests command: make acc no_output_timeout: 30m + - codecov/upload: + flags: << parameters.pattern >> + file: cover-acc.out - run: name: Discord notification when: on_fail @@ -72,17 +76,8 @@ jobs: -w /app\ golang:1.16\ bash -c 'make install-tools && make test' - - run: - name: Check Codecov - command: | - curl -s -o codecov https://codecov.io/bash \ - && VERSION=$(grep 'VERSION=\".*\"' codecov | cut -d'"' -f2) \ - && shasum -a 512 -c --ignore-missing <(curl -s https://raw.githubusercontent.com/codecov/codecov-bash/${VERSION}/SHA512SUM) - - run: - name: Codecov upload - command: | - chmod +x codecov - ./codecov + - codecov/upload: + flags: unit - store_test_results: path: ./ release: From 88e23cf8f07cf56a70854e82ccde08056d038c31 Mon Sep 17 00:00:00 2001 From: Elie Date: Tue, 5 Oct 2021 09:59:15 +0200 Subject: [PATCH 7/8] Fix azure ACC tests --- pkg/resource/azurerm/testdata/acc/azurerm_subnet/terraform.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/resource/azurerm/testdata/acc/azurerm_subnet/terraform.tf b/pkg/resource/azurerm/testdata/acc/azurerm_subnet/terraform.tf index 27c113d9..c3ab6b16 100644 --- a/pkg/resource/azurerm/testdata/acc/azurerm_subnet/terraform.tf +++ b/pkg/resource/azurerm/testdata/acc/azurerm_subnet/terraform.tf @@ -12,7 +12,7 @@ provider "azurerm" { } data "azurerm_resource_group" "qa1" { - name = "elie-dev" + name = "driftctl-qa-1" } resource "azurerm_virtual_network" "test" { From 4066e58ce0063716423d47a737269a8339181288 Mon Sep 17 00:00:00 2001 From: Elie Date: Tue, 5 Oct 2021 10:20:31 +0200 Subject: [PATCH 8/8] Fix GCP Acceptance test --- pkg/resource/google/google_compute_firewall_test.go | 3 +-- .../testdata/acc/google_compute_instance/terraform.tf | 6 +++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/resource/google/google_compute_firewall_test.go b/pkg/resource/google/google_compute_firewall_test.go index c28fe611..f71021dc 100644 --- a/pkg/resource/google/google_compute_firewall_test.go +++ b/pkg/resource/google/google_compute_firewall_test.go @@ -23,9 +23,8 @@ func TestAcc_Google_ComputeFirewall(t *testing.T) { if err != nil { t.Fatal(err) } + result.AssertInfrastructureIsInSync() result.AssertManagedCount(3) - result.AssertDriftCountTotal(0) - result.AssertUnmanagedCount(4) // Default VPCs }, }, }, diff --git a/pkg/resource/google/testdata/acc/google_compute_instance/terraform.tf b/pkg/resource/google/testdata/acc/google_compute_instance/terraform.tf index 76acecc8..2420351f 100644 --- a/pkg/resource/google/testdata/acc/google_compute_instance/terraform.tf +++ b/pkg/resource/google/testdata/acc/google_compute_instance/terraform.tf @@ -9,6 +9,10 @@ terraform { } } +resource "google_compute_network" "driftctl-unittest-instance" { + name = "driftctl-unittest-instance" +} + resource "google_compute_instance" "default" { name = "test" machine_type = "e2-medium" @@ -21,6 +25,6 @@ resource "google_compute_instance" "default" { } network_interface { - network = "default" + network = google_compute_network.driftctl-unittest-instance.name } }