Skip to content

Commit

Permalink
Merge pull request #1473 from snyk/fix/aws_api_gateway_base_path_mapp…
Browse files Browse the repository at this point in the history
…ing_reconciler

Import managed api gateway mappings in the state
  • Loading branch information
wbeuil authored Apr 15, 2022
2 parents 72e1cff + f27d3b9 commit de87469
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 0 deletions.
1 change: 1 addition & 0 deletions pkg/driftctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),

Expand Down
79 changes: 79 additions & 0 deletions pkg/middlewares/aws_api_gateway_base_path_mapping_reconciler.go
Original file line number Diff line number Diff line change
@@ -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
}
184 changes: 184 additions & 0 deletions pkg/middlewares/aws_api_gateway_base_path_mapping_reconciler_test.go
Original file line number Diff line number Diff line change
@@ -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))
}
}
})
}
}
Original file line number Diff line number Diff line change
@@ -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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit de87469

Please sign in to comment.