Skip to content

Commit de87469

Browse files
authored
Merge pull request #1473 from snyk/fix/aws_api_gateway_base_path_mapping_reconciler
Import managed api gateway mappings in the state
2 parents 72e1cff + f27d3b9 commit de87469

5 files changed

Lines changed: 271 additions & 0 deletions

File tree

pkg/driftctl.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ func (d DriftCTL) Run() (*analyser.Analysis, error) {
123123
middlewares.NewAwsApiGatewayRestApiPolicyExpander(d.resourceFactory),
124124
middlewares.NewAwsConsoleApiGatewayGatewayResponse(),
125125
middlewares.NewAwsApiGatewayDomainNamesReconciler(),
126+
middlewares.NewAwsApiGatewayBasePathMappingReconciler(),
126127
middlewares.NewAwsEbsEncryptionByDefaultReconciler(d.resourceFactory),
127128
middlewares.NewAwsALBTransformer(d.resourceFactory),
128129

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package middlewares
2+
3+
import (
4+
"github.com/snyk/driftctl/pkg/resource"
5+
"github.com/snyk/driftctl/pkg/resource/aws"
6+
)
7+
8+
// AwsApiGatewayBasePathMappingReconciler is used to reconcile API Gateway base path mapping (v1 and v2)
9+
// from both remote and state resources because v1|v2 AWS SDK list endpoints return all mappings
10+
// without distinction.
11+
type AwsApiGatewayBasePathMappingReconciler struct{}
12+
13+
func NewAwsApiGatewayBasePathMappingReconciler() AwsApiGatewayBasePathMappingReconciler {
14+
return AwsApiGatewayBasePathMappingReconciler{}
15+
}
16+
17+
func (m AwsApiGatewayBasePathMappingReconciler) Execute(remoteResources, resourcesFromState *[]*resource.Resource) error {
18+
newRemoteResources := make([]*resource.Resource, 0)
19+
managedApiMapping := make([]*resource.Resource, 0)
20+
unmanagedApiMapping := make([]*resource.Resource, 0)
21+
for _, res := range *remoteResources {
22+
// Ignore all resources other than aws_api_gateway_base_path_mapping and aws_apigatewayv2_api_mapping
23+
if res.ResourceType() != aws.AwsApiGatewayBasePathMappingResourceType &&
24+
res.ResourceType() != aws.AwsApiGatewayV2MappingResourceType {
25+
newRemoteResources = append(newRemoteResources, res)
26+
continue
27+
}
28+
29+
// Find a matching state resources
30+
existInState := false
31+
for _, stateResource := range *resourcesFromState {
32+
if res.Equal(stateResource) {
33+
existInState = true
34+
break
35+
}
36+
}
37+
38+
// Keep track of the resource if it's managed in IaC
39+
if existInState {
40+
managedApiMapping = append(managedApiMapping, res)
41+
continue
42+
}
43+
44+
// If we're here, it means that we are left with unmanaged path mappings
45+
// in both v1 and v2 format. Let's group real and duplicate path mappings
46+
// in a slice
47+
unmanagedApiMapping = append(unmanagedApiMapping, res)
48+
}
49+
50+
// We only want to show to our end users unmanaged v1 path mappings
51+
// To do that, we're going to loop over unmanaged path mappings to delete duplicates
52+
// and leave after that only v1 path mappings (e.g. remove v2 ones)
53+
deduplicatedUnmanagedMappings := make([]*resource.Resource, 0, len(unmanagedApiMapping))
54+
for _, unmanaged := range unmanagedApiMapping {
55+
// Remove duplicates (e.g. same id, the opposite type)
56+
isDuplicate := false
57+
for _, managed := range managedApiMapping {
58+
if managed.ResourceId() == unmanaged.ResourceId() {
59+
isDuplicate = true
60+
break
61+
}
62+
}
63+
if isDuplicate {
64+
continue
65+
}
66+
67+
// Now keep only v1 path mappings
68+
if unmanaged.ResourceType() == aws.AwsApiGatewayBasePathMappingResourceType {
69+
deduplicatedUnmanagedMappings = append(deduplicatedUnmanagedMappings, unmanaged)
70+
}
71+
}
72+
73+
// Finally, add both managed and unmanaged resources to remote resources
74+
newRemoteResources = append(newRemoteResources, managedApiMapping...)
75+
newRemoteResources = append(newRemoteResources, deduplicatedUnmanagedMappings...)
76+
77+
*remoteResources = newRemoteResources
78+
return nil
79+
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package middlewares
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/aws/aws-sdk-go/aws/awsutil"
8+
"github.com/r3labs/diff/v2"
9+
"github.com/snyk/driftctl/pkg/resource"
10+
"github.com/snyk/driftctl/pkg/resource/aws"
11+
)
12+
13+
func TestAwsApiGatewayBasePathMappingReconciler_Execute(t *testing.T) {
14+
tests := []struct {
15+
name string
16+
resourcesFromState []*resource.Resource
17+
remoteResources []*resource.Resource
18+
expected []*resource.Resource
19+
}{
20+
{
21+
name: "with managed resources",
22+
resourcesFromState: []*resource.Resource{
23+
{
24+
Id: "mapping1",
25+
Type: aws.AwsApiGatewayBasePathMappingResourceType,
26+
},
27+
{
28+
Id: "mapping2",
29+
Type: aws.AwsApiGatewayV2MappingResourceType,
30+
},
31+
},
32+
remoteResources: []*resource.Resource{
33+
{
34+
Id: "mapping1",
35+
Type: aws.AwsApiGatewayBasePathMappingResourceType,
36+
},
37+
{
38+
Id: "mapping1",
39+
Type: aws.AwsApiGatewayV2MappingResourceType,
40+
},
41+
{
42+
Id: "mapping2",
43+
Type: aws.AwsApiGatewayBasePathMappingResourceType,
44+
},
45+
{
46+
Id: "mapping2",
47+
Type: aws.AwsApiGatewayV2MappingResourceType,
48+
},
49+
},
50+
expected: []*resource.Resource{
51+
{
52+
Id: "mapping1",
53+
Type: aws.AwsApiGatewayBasePathMappingResourceType,
54+
},
55+
{
56+
Id: "mapping2",
57+
Type: aws.AwsApiGatewayV2MappingResourceType,
58+
},
59+
},
60+
},
61+
{
62+
name: "with unmanaged resources",
63+
resourcesFromState: []*resource.Resource{},
64+
remoteResources: []*resource.Resource{
65+
{
66+
Id: "mapping1",
67+
Type: aws.AwsApiGatewayBasePathMappingResourceType,
68+
},
69+
{
70+
Id: "mapping1",
71+
Type: aws.AwsApiGatewayV2MappingResourceType,
72+
},
73+
{
74+
Id: "mapping2",
75+
Type: aws.AwsApiGatewayBasePathMappingResourceType,
76+
},
77+
{
78+
Id: "mapping2",
79+
Type: aws.AwsApiGatewayV2MappingResourceType,
80+
},
81+
},
82+
expected: []*resource.Resource{
83+
{
84+
Id: "mapping1",
85+
Type: aws.AwsApiGatewayBasePathMappingResourceType,
86+
},
87+
{
88+
Id: "mapping2",
89+
Type: aws.AwsApiGatewayBasePathMappingResourceType,
90+
},
91+
},
92+
},
93+
{
94+
name: "with deleted resources",
95+
resourcesFromState: []*resource.Resource{
96+
{
97+
Id: "mapping1",
98+
Type: aws.AwsApiGatewayBasePathMappingResourceType,
99+
},
100+
{
101+
Id: "mapping2",
102+
Type: aws.AwsApiGatewayV2MappingResourceType,
103+
},
104+
},
105+
remoteResources: []*resource.Resource{},
106+
expected: []*resource.Resource{},
107+
},
108+
{
109+
name: "with a mix of managed, unmanaged and deleted resources",
110+
resourcesFromState: []*resource.Resource{
111+
{
112+
Id: "mapping1",
113+
Type: aws.AwsApiGatewayBasePathMappingResourceType,
114+
},
115+
{
116+
Id: "mapping2",
117+
Type: aws.AwsApiGatewayV2MappingResourceType,
118+
},
119+
{
120+
Id: "mapping4",
121+
Type: aws.AwsApiGatewayBasePathMappingResourceType,
122+
},
123+
},
124+
remoteResources: []*resource.Resource{
125+
{
126+
Id: "mapping1",
127+
Type: aws.AwsApiGatewayBasePathMappingResourceType,
128+
},
129+
{
130+
Id: "mapping1",
131+
Type: aws.AwsApiGatewayV2MappingResourceType,
132+
},
133+
{
134+
Id: "mapping2",
135+
Type: aws.AwsApiGatewayBasePathMappingResourceType,
136+
},
137+
{
138+
Id: "mapping2",
139+
Type: aws.AwsApiGatewayV2MappingResourceType,
140+
},
141+
{
142+
Id: "mapping3",
143+
Type: aws.AwsApiGatewayBasePathMappingResourceType,
144+
},
145+
{
146+
Id: "mapping3",
147+
Type: aws.AwsApiGatewayV2MappingResourceType,
148+
},
149+
},
150+
expected: []*resource.Resource{
151+
{
152+
Id: "mapping1",
153+
Type: aws.AwsApiGatewayBasePathMappingResourceType,
154+
},
155+
{
156+
Id: "mapping2",
157+
Type: aws.AwsApiGatewayV2MappingResourceType,
158+
},
159+
{
160+
Id: "mapping3",
161+
Type: aws.AwsApiGatewayBasePathMappingResourceType,
162+
},
163+
},
164+
},
165+
}
166+
for _, tt := range tests {
167+
t.Run(tt.name, func(t *testing.T) {
168+
m := NewAwsApiGatewayBasePathMappingReconciler()
169+
err := m.Execute(&tt.remoteResources, &tt.resourcesFromState)
170+
if err != nil {
171+
t.Fatal(err)
172+
}
173+
changelog, err := diff.Diff(tt.expected, tt.remoteResources)
174+
if err != nil {
175+
t.Fatal(err)
176+
}
177+
if len(changelog) > 0 {
178+
for _, change := range changelog {
179+
t.Errorf("%s got = %v, want %v", strings.Join(change.Path, "."), awsutil.Prettify(change.From), awsutil.Prettify(change.To))
180+
}
181+
}
182+
})
183+
}
184+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
*
22
!aws_api_gateway_base_path_mapping
3+
4+
# We include drifts from aws_apigatewayv2_api_mapping as well to avoid regression regarding this bug:
5+
# https://github.com/snyk/driftctl/issues/1442
6+
!aws_apigatewayv2_api_mapping

pkg/resource/aws/testdata/acc/aws_api_gateway_base_path_mapping/.terraform.lock.hcl

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)