From f27d3b96ee6ffd5c61fb19131a7d3fc24c6246b3 Mon Sep 17 00:00:00 2001 From: sundowndev-snyk Date: Thu, 14 Apr 2022 18:39:41 +0400 Subject: [PATCH] fix: import managed api mappings in the state --- pkg/driftctl.go | 1 + ...pi_gateway_base_path_mapping_reconciler.go | 79 ++++++++ ...teway_base_path_mapping_reconciler_test.go | 184 ++++++++++++++++++ .../.driftignore | 4 + .../.terraform.lock.hcl | 3 + 5 files changed, 271 insertions(+) create mode 100644 pkg/middlewares/aws_api_gateway_base_path_mapping_reconciler.go create mode 100644 pkg/middlewares/aws_api_gateway_base_path_mapping_reconciler_test.go diff --git a/pkg/driftctl.go b/pkg/driftctl.go index e67fefcf7..b527b3bdc 100644 --- a/pkg/driftctl.go +++ b/pkg/driftctl.go @@ -123,6 +123,7 @@ func (d DriftCTL) Run() (*analyser.Analysis, error) { middlewares.NewAwsApiGatewayRestApiPolicyExpander(d.resourceFactory), middlewares.NewAwsConsoleApiGatewayGatewayResponse(), middlewares.NewAwsApiGatewayDomainNamesReconciler(), + middlewares.NewAwsApiGatewayBasePathMappingReconciler(), middlewares.NewAwsEbsEncryptionByDefaultReconciler(d.resourceFactory), middlewares.NewAwsALBTransformer(d.resourceFactory), diff --git a/pkg/middlewares/aws_api_gateway_base_path_mapping_reconciler.go b/pkg/middlewares/aws_api_gateway_base_path_mapping_reconciler.go new file mode 100644 index 000000000..a27ed13dd --- /dev/null +++ b/pkg/middlewares/aws_api_gateway_base_path_mapping_reconciler.go @@ -0,0 +1,79 @@ +package middlewares + +import ( + "github.com/snyk/driftctl/pkg/resource" + "github.com/snyk/driftctl/pkg/resource/aws" +) + +// AwsApiGatewayBasePathMappingReconciler is used to reconcile API Gateway base path mapping (v1 and v2) +// from both remote and state resources because v1|v2 AWS SDK list endpoints return all mappings +// without distinction. +type AwsApiGatewayBasePathMappingReconciler struct{} + +func NewAwsApiGatewayBasePathMappingReconciler() AwsApiGatewayBasePathMappingReconciler { + return AwsApiGatewayBasePathMappingReconciler{} +} + +func (m AwsApiGatewayBasePathMappingReconciler) Execute(remoteResources, resourcesFromState *[]*resource.Resource) error { + newRemoteResources := make([]*resource.Resource, 0) + managedApiMapping := make([]*resource.Resource, 0) + unmanagedApiMapping := make([]*resource.Resource, 0) + for _, res := range *remoteResources { + // Ignore all resources other than aws_api_gateway_base_path_mapping and aws_apigatewayv2_api_mapping + if res.ResourceType() != aws.AwsApiGatewayBasePathMappingResourceType && + res.ResourceType() != aws.AwsApiGatewayV2MappingResourceType { + newRemoteResources = append(newRemoteResources, res) + continue + } + + // Find a matching state resources + existInState := false + for _, stateResource := range *resourcesFromState { + if res.Equal(stateResource) { + existInState = true + break + } + } + + // Keep track of the resource if it's managed in IaC + if existInState { + managedApiMapping = append(managedApiMapping, res) + continue + } + + // If we're here, it means that we are left with unmanaged path mappings + // in both v1 and v2 format. Let's group real and duplicate path mappings + // in a slice + unmanagedApiMapping = append(unmanagedApiMapping, res) + } + + // We only want to show to our end users unmanaged v1 path mappings + // To do that, we're going to loop over unmanaged path mappings to delete duplicates + // and leave after that only v1 path mappings (e.g. remove v2 ones) + deduplicatedUnmanagedMappings := make([]*resource.Resource, 0, len(unmanagedApiMapping)) + for _, unmanaged := range unmanagedApiMapping { + // Remove duplicates (e.g. same id, the opposite type) + isDuplicate := false + for _, managed := range managedApiMapping { + if managed.ResourceId() == unmanaged.ResourceId() { + isDuplicate = true + break + } + } + if isDuplicate { + continue + } + + // Now keep only v1 path mappings + if unmanaged.ResourceType() == aws.AwsApiGatewayBasePathMappingResourceType { + deduplicatedUnmanagedMappings = append(deduplicatedUnmanagedMappings, unmanaged) + } + } + + // Finally, add both managed and unmanaged resources to remote resources + newRemoteResources = append(newRemoteResources, managedApiMapping...) + newRemoteResources = append(newRemoteResources, deduplicatedUnmanagedMappings...) + + *remoteResources = newRemoteResources + return nil +} diff --git a/pkg/middlewares/aws_api_gateway_base_path_mapping_reconciler_test.go b/pkg/middlewares/aws_api_gateway_base_path_mapping_reconciler_test.go new file mode 100644 index 000000000..80e67fbf7 --- /dev/null +++ b/pkg/middlewares/aws_api_gateway_base_path_mapping_reconciler_test.go @@ -0,0 +1,184 @@ +package middlewares + +import ( + "strings" + "testing" + + "github.com/aws/aws-sdk-go/aws/awsutil" + "github.com/r3labs/diff/v2" + "github.com/snyk/driftctl/pkg/resource" + "github.com/snyk/driftctl/pkg/resource/aws" +) + +func TestAwsApiGatewayBasePathMappingReconciler_Execute(t *testing.T) { + tests := []struct { + name string + resourcesFromState []*resource.Resource + remoteResources []*resource.Resource + expected []*resource.Resource + }{ + { + name: "with managed resources", + resourcesFromState: []*resource.Resource{ + { + Id: "mapping1", + Type: aws.AwsApiGatewayBasePathMappingResourceType, + }, + { + Id: "mapping2", + Type: aws.AwsApiGatewayV2MappingResourceType, + }, + }, + remoteResources: []*resource.Resource{ + { + Id: "mapping1", + Type: aws.AwsApiGatewayBasePathMappingResourceType, + }, + { + Id: "mapping1", + Type: aws.AwsApiGatewayV2MappingResourceType, + }, + { + Id: "mapping2", + Type: aws.AwsApiGatewayBasePathMappingResourceType, + }, + { + Id: "mapping2", + Type: aws.AwsApiGatewayV2MappingResourceType, + }, + }, + expected: []*resource.Resource{ + { + Id: "mapping1", + Type: aws.AwsApiGatewayBasePathMappingResourceType, + }, + { + Id: "mapping2", + Type: aws.AwsApiGatewayV2MappingResourceType, + }, + }, + }, + { + name: "with unmanaged resources", + resourcesFromState: []*resource.Resource{}, + remoteResources: []*resource.Resource{ + { + Id: "mapping1", + Type: aws.AwsApiGatewayBasePathMappingResourceType, + }, + { + Id: "mapping1", + Type: aws.AwsApiGatewayV2MappingResourceType, + }, + { + Id: "mapping2", + Type: aws.AwsApiGatewayBasePathMappingResourceType, + }, + { + Id: "mapping2", + Type: aws.AwsApiGatewayV2MappingResourceType, + }, + }, + expected: []*resource.Resource{ + { + Id: "mapping1", + Type: aws.AwsApiGatewayBasePathMappingResourceType, + }, + { + Id: "mapping2", + Type: aws.AwsApiGatewayBasePathMappingResourceType, + }, + }, + }, + { + name: "with deleted resources", + resourcesFromState: []*resource.Resource{ + { + Id: "mapping1", + Type: aws.AwsApiGatewayBasePathMappingResourceType, + }, + { + Id: "mapping2", + Type: aws.AwsApiGatewayV2MappingResourceType, + }, + }, + remoteResources: []*resource.Resource{}, + expected: []*resource.Resource{}, + }, + { + name: "with a mix of managed, unmanaged and deleted resources", + resourcesFromState: []*resource.Resource{ + { + Id: "mapping1", + Type: aws.AwsApiGatewayBasePathMappingResourceType, + }, + { + Id: "mapping2", + Type: aws.AwsApiGatewayV2MappingResourceType, + }, + { + Id: "mapping4", + Type: aws.AwsApiGatewayBasePathMappingResourceType, + }, + }, + remoteResources: []*resource.Resource{ + { + Id: "mapping1", + Type: aws.AwsApiGatewayBasePathMappingResourceType, + }, + { + Id: "mapping1", + Type: aws.AwsApiGatewayV2MappingResourceType, + }, + { + Id: "mapping2", + Type: aws.AwsApiGatewayBasePathMappingResourceType, + }, + { + Id: "mapping2", + Type: aws.AwsApiGatewayV2MappingResourceType, + }, + { + Id: "mapping3", + Type: aws.AwsApiGatewayBasePathMappingResourceType, + }, + { + Id: "mapping3", + Type: aws.AwsApiGatewayV2MappingResourceType, + }, + }, + expected: []*resource.Resource{ + { + Id: "mapping1", + Type: aws.AwsApiGatewayBasePathMappingResourceType, + }, + { + Id: "mapping2", + Type: aws.AwsApiGatewayV2MappingResourceType, + }, + { + Id: "mapping3", + Type: aws.AwsApiGatewayBasePathMappingResourceType, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := NewAwsApiGatewayBasePathMappingReconciler() + err := m.Execute(&tt.remoteResources, &tt.resourcesFromState) + if err != nil { + t.Fatal(err) + } + changelog, err := diff.Diff(tt.expected, tt.remoteResources) + 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/resource/aws/testdata/acc/aws_api_gateway_base_path_mapping/.driftignore b/pkg/resource/aws/testdata/acc/aws_api_gateway_base_path_mapping/.driftignore index 05612a197..6cde1ee71 100644 --- a/pkg/resource/aws/testdata/acc/aws_api_gateway_base_path_mapping/.driftignore +++ b/pkg/resource/aws/testdata/acc/aws_api_gateway_base_path_mapping/.driftignore @@ -1,2 +1,6 @@ * !aws_api_gateway_base_path_mapping + +# We include drifts from aws_apigatewayv2_api_mapping as well to avoid regression regarding this bug: +# https://github.com/snyk/driftctl/issues/1442 +!aws_apigatewayv2_api_mapping diff --git a/pkg/resource/aws/testdata/acc/aws_api_gateway_base_path_mapping/.terraform.lock.hcl b/pkg/resource/aws/testdata/acc/aws_api_gateway_base_path_mapping/.terraform.lock.hcl index 92b26d8a9..a1fae2925 100644 --- a/pkg/resource/aws/testdata/acc/aws_api_gateway_base_path_mapping/.terraform.lock.hcl +++ b/pkg/resource/aws/testdata/acc/aws_api_gateway_base_path_mapping/.terraform.lock.hcl @@ -5,6 +5,7 @@ provider "registry.terraform.io/hashicorp/aws" { version = "3.19.0" constraints = "3.19.0" hashes = [ + "h1:+7Vi7p13+cnrxjXbfJiTimGSFR97xCaQwkkvWcreLns=", "h1:xur9tF49NgsovNnmwmBR8RdpN8Fcg1TD4CKQPJD6n1A=", "zh:185a5259153eb9ee4699d4be43b3d509386b473683392034319beee97d470c3b", "zh:2d9a0a01f93e8d16539d835c02b8b6e1927b7685f4076e96cb07f7dd6944bc6c", @@ -22,6 +23,7 @@ provider "registry.terraform.io/hashicorp/aws" { provider "registry.terraform.io/hashicorp/random" { version = "3.1.0" hashes = [ + "h1:BZMEPucF+pbu9gsPk0G0BHx7YP04+tKdq2MrRDF1EDM=", "h1:rKYu5ZUbXwrLG1w81k7H3nce/Ys6yAxXhWcbtk36HjY=", "zh:2bbb3339f0643b5daa07480ef4397bd23a79963cc364cdfbb4e86354cb7725bc", "zh:3cd456047805bf639fbf2c761b1848880ea703a054f76db51852008b11008626", @@ -41,6 +43,7 @@ provider "registry.terraform.io/hashicorp/tls" { version = "3.1.0" hashes = [ "h1:XTU9f6sGMZHOT8r/+LWCz2BZOPH127FBTPjMMEAAu1U=", + "h1:fUJX8Zxx38e2kBln+zWr1Tl41X+OuiE++REjrEyiOM4=", "zh:3d46616b41fea215566f4a957b6d3a1aa43f1f75c26776d72a98bdba79439db6", "zh:623a203817a6dafa86f1b4141b645159e07ec418c82fe40acd4d2a27543cbaa2", "zh:668217e78b210a6572e7b0ecb4134a6781cc4d738f4f5d09eb756085b082592e",