feat: add aws_lb_listener

main
sundowndev-snyk 2022-04-13 14:59:39 +04:00
parent b217c0f8bd
commit db38ab4cf6
No known key found for this signature in database
GPG Key ID: A4A2BE47AC4C6A68
25 changed files with 172973 additions and 6 deletions

View File

@ -125,6 +125,7 @@ func (d DriftCTL) Run() (*analyser.Analysis, error) {
middlewares.NewAwsApiGatewayDomainNamesReconciler(),
middlewares.NewAwsEbsEncryptionByDefaultReconciler(d.resourceFactory),
middlewares.NewAwsALBTransformer(d.resourceFactory),
middlewares.NewAwsALBListenerTransformer(d.resourceFactory),
middlewares.NewGoogleIAMBindingTransformer(d.resourceFactory),
middlewares.NewGoogleIAMPolicyTransformer(d.resourceFactory),

View File

@ -196,6 +196,7 @@ func TestTerraformStateReader_AWS_Resources(t *testing.T) {
{name: "Launch configuration", dirName: "aws_launch_configuration", wantErr: false},
{name: "EBS encryption by default", dirName: "aws_ebs_encryption_by_default", wantErr: false},
{name: "LoadBalancer", dirName: "aws_lb", wantErr: false},
{name: "Load balancer listener", dirName: "aws_lb_listener", wantErr: false},
{name: "Classic load balancer", dirName: "aws_elb", wantErr: false},
{name: "ElastiCache Cluster", dirName: "aws_elasticache_cluster", wantErr: false},
}

View File

@ -0,0 +1,111 @@
[
{
"Id": "arn:aws:elasticloadbalancing:us-east-1:533948124879:listener/app/test-lb-tf/168f3e312968e275/21a7de7e81a06bda",
"Type": "aws_lb_listener",
"Attrs": {
"arn": "arn:aws:elasticloadbalancing:us-east-1:533948124879:listener/app/test-lb-tf/168f3e312968e275/21a7de7e81a06bda",
"default_action": [
{
"order": 1,
"target_group_arn": "arn:aws:elasticloadbalancing:us-east-1:533948124879:targetgroup/tf-example-lb-tg/9e43d546683d8b35",
"type": "forward"
}
],
"id": "arn:aws:elasticloadbalancing:us-east-1:533948124879:listener/app/test-lb-tf/168f3e312968e275/21a7de7e81a06bda",
"load_balancer_arn": "arn:aws:elasticloadbalancing:us-east-1:533948124879:loadbalancer/app/test-lb-tf/168f3e312968e275",
"port": 80,
"protocol": "HTTP",
"ssl_policy": ""
}
},
{
"Id": "sg-0d8b60865ca657bb3",
"Type": "aws_security_group",
"Attrs": {
"arn": "arn:aws:ec2:us-east-1:533948124879:security-group/sg-0d8b60865ca657bb3",
"description": "Allow TLS inbound traffic",
"id": "sg-0d8b60865ca657bb3",
"name": "allow_tls_lb",
"name_prefix": "",
"owner_id": "533948124879",
"vpc_id": "vpc-069eb1208f4f62f3c"
}
},
{
"Id": "subnet-09b8c4d89ed5f3aeb",
"Type": "aws_subnet",
"Attrs": {
"arn": "arn:aws:ec2:us-east-1:533948124879:subnet/subnet-09b8c4d89ed5f3aeb",
"assign_ipv6_address_on_creation": false,
"availability_zone": "us-east-1a",
"availability_zone_id": "use1-az4",
"cidr_block": "10.100.0.0/24",
"id": "subnet-09b8c4d89ed5f3aeb",
"ipv6_cidr_block": "",
"ipv6_cidr_block_association_id": "",
"map_public_ip_on_launch": false,
"outpost_arn": "",
"owner_id": "533948124879",
"vpc_id": "vpc-069eb1208f4f62f3c"
}
},
{
"Id": "igw-00e1a78d127606c92",
"Type": "aws_internet_gateway",
"Attrs": {
"arn": "arn:aws:ec2:us-east-1:533948124879:internet-gateway/igw-00e1a78d127606c92",
"id": "igw-00e1a78d127606c92",
"owner_id": "533948124879",
"vpc_id": "vpc-069eb1208f4f62f3c"
}
},
{
"Id": "arn:aws:elasticloadbalancing:us-east-1:533948124879:loadbalancer/app/test-lb-tf/168f3e312968e275",
"Type": "aws_lb",
"Attrs": {
"access_logs": [
{
"bucket": "",
"enabled": false,
"prefix": ""
}
],
"arn": "arn:aws:elasticloadbalancing:us-east-1:533948124879:loadbalancer/app/test-lb-tf/168f3e312968e275",
"arn_suffix": "app/test-lb-tf/168f3e312968e275",
"customer_owned_ipv4_pool": "",
"dns_name": "test-lb-tf-2047934243.us-east-1.elb.amazonaws.com",
"drop_invalid_header_fields": false,
"enable_deletion_protection": false,
"enable_http2": true,
"id": "arn:aws:elasticloadbalancing:us-east-1:533948124879:loadbalancer/app/test-lb-tf/168f3e312968e275",
"idle_timeout": 60,
"internal": false,
"ip_address_type": "ipv4",
"load_balancer_type": "application",
"name": "test-lb-tf",
"security_groups": [
"sg-0d8b60865ca657bb3"
],
"subnet_mapping": [
{
"allocation_id": "",
"outpost_id": "",
"private_ipv4_address": "",
"subnet_id": "subnet-00499e85b962e382f"
},
{
"allocation_id": "",
"outpost_id": "",
"private_ipv4_address": "",
"subnet_id": "subnet-09b8c4d89ed5f3aeb"
}
],
"subnets": [
"subnet-00499e85b962e382f",
"subnet-09b8c4d89ed5f3aeb"
],
"vpc_id": "vpc-069eb1208f4f62f3c",
"zone_id": "Z35SXDOTRQ7X7K"
}
}
]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,330 @@
{
"version": 4,
"terraform_version": "1.0.0",
"serial": 575,
"lineage": "9566e18d-6080-4aa8-e9a6-4c38905cf68f",
"outputs": {},
"resources": [
{
"mode": "data",
"type": "azurerm_resource_group",
"name": "raphael-dev",
"provider": "provider[\"registry.terraform.io/hashicorp/azurerm\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "/subscriptions/8cb43347-a79f-4bb2-a8b4-c838b41fa5a5/resourceGroups/raphael-dev",
"location": "eastus",
"name": "raphael-dev",
"tags": {},
"timeouts": null
},
"sensitive_attributes": []
}
]
},
{
"mode": "managed",
"type": "aws_internet_gateway",
"name": "gw",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"arn": "arn:aws:ec2:us-east-1:533948124879:internet-gateway/igw-00e1a78d127606c92",
"id": "igw-00e1a78d127606c92",
"owner_id": "533948124879",
"tags": {},
"tags_all": {},
"vpc_id": "vpc-069eb1208f4f62f3c"
},
"sensitive_attributes": [],
"private": "bnVsbA==",
"dependencies": [
"aws_vpc.main"
]
}
]
},
{
"mode": "managed",
"type": "aws_lb",
"name": "test",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"access_logs": [
{
"bucket": "",
"enabled": false,
"prefix": ""
}
],
"arn": "arn:aws:elasticloadbalancing:us-east-1:533948124879:loadbalancer/app/test-lb-tf/168f3e312968e275",
"arn_suffix": "app/test-lb-tf/168f3e312968e275",
"customer_owned_ipv4_pool": "",
"desync_mitigation_mode": "defensive",
"dns_name": "test-lb-tf-2047934243.us-east-1.elb.amazonaws.com",
"drop_invalid_header_fields": false,
"enable_cross_zone_load_balancing": null,
"enable_deletion_protection": false,
"enable_http2": true,
"enable_waf_fail_open": false,
"id": "arn:aws:elasticloadbalancing:us-east-1:533948124879:loadbalancer/app/test-lb-tf/168f3e312968e275",
"idle_timeout": 60,
"internal": false,
"ip_address_type": "ipv4",
"load_balancer_type": "application",
"name": "test-lb-tf",
"name_prefix": null,
"security_groups": [
"sg-0d8b60865ca657bb3"
],
"subnet_mapping": [
{
"allocation_id": "",
"ipv6_address": "",
"outpost_id": "",
"private_ipv4_address": "",
"subnet_id": "subnet-00499e85b962e382f"
},
{
"allocation_id": "",
"ipv6_address": "",
"outpost_id": "",
"private_ipv4_address": "",
"subnet_id": "subnet-09b8c4d89ed5f3aeb"
}
],
"subnets": [
"subnet-00499e85b962e382f",
"subnet-09b8c4d89ed5f3aeb"
],
"tags": {},
"tags_all": {},
"timeouts": null,
"vpc_id": "vpc-069eb1208f4f62f3c",
"zone_id": "Z35SXDOTRQ7X7K"
},
"sensitive_attributes": [],
"private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6NjAwMDAwMDAwMDAwLCJ1cGRhdGUiOjYwMDAwMDAwMDAwMH19",
"dependencies": [
"aws_security_group.lb_sg",
"aws_subnet.main-1",
"aws_subnet.main-2",
"aws_vpc.main"
]
}
]
},
{
"mode": "managed",
"type": "aws_lb_listener",
"name": "front_end",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"alpn_policy": null,
"arn": "arn:aws:elasticloadbalancing:us-east-1:533948124879:listener/app/test-lb-tf/168f3e312968e275/21a7de7e81a06bda",
"certificate_arn": null,
"default_action": [
{
"authenticate_cognito": [],
"authenticate_oidc": [],
"fixed_response": [],
"forward": [],
"order": 1,
"redirect": [],
"target_group_arn": "arn:aws:elasticloadbalancing:us-east-1:533948124879:targetgroup/tf-example-lb-tg/9e43d546683d8b35",
"type": "forward"
}
],
"id": "arn:aws:elasticloadbalancing:us-east-1:533948124879:listener/app/test-lb-tf/168f3e312968e275/21a7de7e81a06bda",
"load_balancer_arn": "arn:aws:elasticloadbalancing:us-east-1:533948124879:loadbalancer/app/test-lb-tf/168f3e312968e275",
"port": 80,
"protocol": "HTTP",
"ssl_policy": "",
"tags": {},
"tags_all": {},
"timeouts": null
},
"sensitive_attributes": [],
"private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsicmVhZCI6NjAwMDAwMDAwMDAwfX0=",
"dependencies": [
"aws_security_group.lb_sg",
"aws_subnet.main-1",
"aws_subnet.main-2",
"aws_vpc.main",
"aws_lb.test",
"aws_lb_target_group.test"
]
}
]
},
{
"mode": "managed",
"type": "aws_lb_target_group",
"name": "test",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"arn": "arn:aws:elasticloadbalancing:us-east-1:533948124879:targetgroup/tf-example-lb-tg/9e43d546683d8b35",
"arn_suffix": "targetgroup/tf-example-lb-tg/9e43d546683d8b35",
"connection_termination": false,
"deregistration_delay": "300",
"health_check": [
{
"enabled": true,
"healthy_threshold": 5,
"interval": 30,
"matcher": "200",
"path": "/",
"port": "traffic-port",
"protocol": "HTTP",
"timeout": 5,
"unhealthy_threshold": 2
}
],
"id": "arn:aws:elasticloadbalancing:us-east-1:533948124879:targetgroup/tf-example-lb-tg/9e43d546683d8b35",
"lambda_multi_value_headers_enabled": false,
"load_balancing_algorithm_type": "round_robin",
"name": "tf-example-lb-tg",
"name_prefix": null,
"port": 80,
"preserve_client_ip": null,
"protocol": "HTTP",
"protocol_version": "HTTP1",
"proxy_protocol_v2": false,
"slow_start": 0,
"stickiness": [
{
"cookie_duration": 86400,
"cookie_name": "",
"enabled": false,
"type": "lb_cookie"
}
],
"tags": {},
"tags_all": {},
"target_type": "instance",
"vpc_id": "vpc-069eb1208f4f62f3c"
},
"sensitive_attributes": [],
"private": "bnVsbA==",
"dependencies": [
"aws_vpc.main"
]
}
]
},
{
"mode": "managed",
"type": "aws_security_group",
"name": "lb_sg",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"schema_version": 1,
"attributes": {
"arn": "arn:aws:ec2:us-east-1:533948124879:security-group/sg-0d8b60865ca657bb3",
"description": "Allow TLS inbound traffic",
"egress": [
{
"cidr_blocks": [
"0.0.0.0/0"
],
"description": "",
"from_port": 0,
"ipv6_cidr_blocks": [
"::/0"
],
"prefix_list_ids": [],
"protocol": "-1",
"security_groups": [],
"self": false,
"to_port": 0
}
],
"id": "sg-0d8b60865ca657bb3",
"ingress": [
{
"cidr_blocks": [
"10.100.0.0/16"
],
"description": "TLS from VPC",
"from_port": 443,
"ipv6_cidr_blocks": [],
"prefix_list_ids": [],
"protocol": "tcp",
"security_groups": [],
"self": false,
"to_port": 443
}
],
"name": "allow_tls_lb",
"name_prefix": "",
"owner_id": "533948124879",
"revoke_rules_on_delete": false,
"tags": {},
"tags_all": {},
"timeouts": null,
"vpc_id": "vpc-069eb1208f4f62f3c"
},
"sensitive_attributes": [],
"private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6OTAwMDAwMDAwMDAwfSwic2NoZW1hX3ZlcnNpb24iOiIxIn0=",
"dependencies": [
"aws_vpc.main"
]
}
]
},
{
"mode": "managed",
"type": "aws_subnet",
"name": "main-1",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"schema_version": 1,
"attributes": {
"arn": "arn:aws:ec2:us-east-1:533948124879:subnet/subnet-09b8c4d89ed5f3aeb",
"assign_ipv6_address_on_creation": false,
"availability_zone": "us-east-1a",
"availability_zone_id": "use1-az4",
"cidr_block": "10.100.0.0/24",
"customer_owned_ipv4_pool": "",
"enable_dns64": false,
"enable_resource_name_dns_a_record_on_launch": false,
"enable_resource_name_dns_aaaa_record_on_launch": false,
"id": "subnet-09b8c4d89ed5f3aeb",
"ipv6_cidr_block": "",
"ipv6_cidr_block_association_id": "",
"ipv6_native": false,
"map_customer_owned_ip_on_launch": false,
"map_public_ip_on_launch": false,
"outpost_arn": "",
"owner_id": "533948124879",
"private_dns_hostname_type_on_launch": "ip-name",
"tags": {},
"tags_all": {},
"timeouts": null,
"vpc_id": "vpc-069eb1208f4f62f3c"
},
"sensitive_attributes": [],
"private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6MTIwMDAwMDAwMDAwMH0sInNjaGVtYV92ZXJzaW9uIjoiMSJ9",
"dependencies": [
"aws_vpc.main"
]
}
]
}
]
}

View File

@ -0,0 +1,39 @@
package middlewares
import (
"github.com/snyk/driftctl/pkg/resource"
"github.com/snyk/driftctl/pkg/resource/aws"
)
// AwsALBListenerTransformer is a simple middleware to turn all aws_alb_listener resources into aws_lb_listener ones
// Both types provide the same functionality, but we can't know which one was used to provision cloud resources.
// So we use aws_lb_listener as the common type.
type AwsALBListenerTransformer struct {
resourceFactory resource.ResourceFactory
}
func NewAwsALBListenerTransformer(resourceFactory resource.ResourceFactory) AwsALBListenerTransformer {
return AwsALBListenerTransformer{
resourceFactory: resourceFactory,
}
}
func (m AwsALBListenerTransformer) Execute(_, resourcesFromState *[]*resource.Resource) error {
newStateResources := make([]*resource.Resource, 0, len(*resourcesFromState))
for _, res := range *resourcesFromState {
if res.ResourceType() != aws.AwsApplicationLoadBalancerListenerResourceType {
newStateResources = append(newStateResources, res)
continue
}
newStateResources = append(newStateResources, m.resourceFactory.CreateAbstractResource(
aws.AwsLoadBalancerListenerResourceType,
res.ResourceId(),
*res.Attributes(),
))
}
*resourcesFromState = newStateResources
return nil
}

View File

@ -0,0 +1,124 @@
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"
"github.com/snyk/driftctl/pkg/terraform"
)
func TestAwsALBListenerTransformer_Execute(t *testing.T) {
tests := []struct {
name string
resourcesFromState []*resource.Resource
mocks func(*terraform.MockResourceFactory)
expected []*resource.Resource
}{
{
name: "should not transform anything",
mocks: func(factory *terraform.MockResourceFactory) {},
resourcesFromState: []*resource.Resource{
{
Id: "foo",
Type: aws.AwsS3BucketResourceType,
Attrs: &resource.Attributes{},
},
{
Id: "bar",
Type: aws.AwsLoadBalancerListenerResourceType,
Attrs: &resource.Attributes{},
},
},
expected: []*resource.Resource{
{
Id: "foo",
Type: aws.AwsS3BucketResourceType,
Attrs: &resource.Attributes{},
},
{
Id: "bar",
Type: aws.AwsLoadBalancerListenerResourceType,
Attrs: &resource.Attributes{},
},
},
},
{
name: "should transform ALB into LB",
mocks: func(factory *terraform.MockResourceFactory) {
factory.
On("CreateAbstractResource", aws.AwsLoadBalancerListenerResourceType, "alb-test", map[string]interface{}{}).
Return(&resource.Resource{
Id: "alb-test",
Type: aws.AwsLoadBalancerListenerResourceType,
Attrs: &resource.Attributes{},
}).
Once()
},
resourcesFromState: []*resource.Resource{
{
Id: "foo",
Type: aws.AwsApiGatewayRestApiResourceType,
Attrs: &resource.Attributes{
"body": "{\"info\":{\"title\":\"example\",\"version\":\"1.0\"},\"openapi\":\"3.0.1\",\"paths\":{\"/path1\":{\"get\":{\"parameters\":[{\"in\":\"query\",\"name\":\"type\",\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"page\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Pets\"}}},\"description\":\"200 response\",\"headers\":{\"Access-Control-Allow-Origin\":{\"schema\":{\"type\":\"string\"}}}}},\"x-amazon-apigateway-integration\":{\"httpMethod\":\"GET\",\"payloadFormatVersion\":\"1.0\",\"type\":\"HTTP_PROXY\",\"uri\":\"https://ip-ranges.amazonaws.com/ip-ranges.json\",\"responses\":{\"2\\\\d{2}\":{\"responseTemplates\":{\"application/json\":\"#set ($root=$input.path('$')) { \\\"stage\\\": \\\"$root.name\\\", \\\"user-id\\\": \\\"$root.key\\\" }\",\"application/xml\":\"#set ($root=$input.path('$')) \\u003cstage\\u003e$root.name\\u003c/stage\\u003e \"},\"statusCode\":\"200\"}}}}},\"/path1/path2\":{\"get\":{\"x-amazon-apigateway-integration\":{\"httpMethod\":\"GET\",\"payloadFormatVersion\":\"1.0\",\"type\":\"HTTP_PROXY\",\"uri\":\"https://ip-ranges.amazonaws.com/ip-ranges.json\"}}}}}",
},
},
{
Id: "bar",
Type: aws.AwsLoadBalancerListenerResourceType,
Attrs: &resource.Attributes{},
},
{
Id: "alb-test",
Type: aws.AwsApplicationLoadBalancerListenerResourceType,
Attrs: &resource.Attributes{},
},
},
expected: []*resource.Resource{
{
Id: "foo",
Type: aws.AwsApiGatewayRestApiResourceType,
Attrs: &resource.Attributes{
"body": "{\"info\":{\"title\":\"example\",\"version\":\"1.0\"},\"openapi\":\"3.0.1\",\"paths\":{\"/path1\":{\"get\":{\"parameters\":[{\"in\":\"query\",\"name\":\"type\",\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"page\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Pets\"}}},\"description\":\"200 response\",\"headers\":{\"Access-Control-Allow-Origin\":{\"schema\":{\"type\":\"string\"}}}}},\"x-amazon-apigateway-integration\":{\"httpMethod\":\"GET\",\"payloadFormatVersion\":\"1.0\",\"type\":\"HTTP_PROXY\",\"uri\":\"https://ip-ranges.amazonaws.com/ip-ranges.json\",\"responses\":{\"2\\\\d{2}\":{\"responseTemplates\":{\"application/json\":\"#set ($root=$input.path('$')) { \\\"stage\\\": \\\"$root.name\\\", \\\"user-id\\\": \\\"$root.key\\\" }\",\"application/xml\":\"#set ($root=$input.path('$')) \\u003cstage\\u003e$root.name\\u003c/stage\\u003e \"},\"statusCode\":\"200\"}}}}},\"/path1/path2\":{\"get\":{\"x-amazon-apigateway-integration\":{\"httpMethod\":\"GET\",\"payloadFormatVersion\":\"1.0\",\"type\":\"HTTP_PROXY\",\"uri\":\"https://ip-ranges.amazonaws.com/ip-ranges.json\"}}}}}",
},
},
{
Id: "bar",
Type: aws.AwsLoadBalancerListenerResourceType,
Attrs: &resource.Attributes{},
},
{
Id: "alb-test",
Type: aws.AwsLoadBalancerListenerResourceType,
Attrs: &resource.Attributes{},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
factory := &terraform.MockResourceFactory{}
if tt.mocks != nil {
tt.mocks(factory)
}
m := NewAwsALBListenerTransformer(factory)
err := m.Execute(&[]*resource.Resource{}, &tt.resourcesFromState)
if err != nil {
t.Fatal(err)
}
changelog, err := diff.Diff(tt.expected, tt.resourcesFromState)
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))
}
}
})
}
}

View File

@ -235,6 +235,7 @@ func Init(version string, alerter *alerter.Alerter,
remoteLibrary.AddEnumerator(NewLaunchConfigurationEnumerator(autoscalingRepository, factory))
remoteLibrary.AddEnumerator(NewLoadBalancerEnumerator(elbv2Repository, factory))
remoteLibrary.AddEnumerator(NewLoadBalancerListenerEnumerator(elbv2Repository, factory))
remoteLibrary.AddEnumerator(NewClassicLoadBalancerEnumerator(elbRepository, factory))

View File

@ -0,0 +1,53 @@
package aws
import (
"github.com/snyk/driftctl/pkg/remote/aws/repository"
remoteerror "github.com/snyk/driftctl/pkg/remote/error"
"github.com/snyk/driftctl/pkg/resource"
"github.com/snyk/driftctl/pkg/resource/aws"
)
type LoadBalancerListenerEnumerator struct {
repository repository.ELBV2Repository
factory resource.ResourceFactory
}
func NewLoadBalancerListenerEnumerator(repo repository.ELBV2Repository, factory resource.ResourceFactory) *LoadBalancerListenerEnumerator {
return &LoadBalancerListenerEnumerator{
repository: repo,
factory: factory,
}
}
func (e *LoadBalancerListenerEnumerator) SupportedType() resource.ResourceType {
return aws.AwsLoadBalancerListenerResourceType
}
func (e *LoadBalancerListenerEnumerator) Enumerate() ([]*resource.Resource, error) {
loadBalancers, err := e.repository.ListAllLoadBalancers()
if err != nil {
return nil, remoteerror.NewResourceListingErrorWithType(err, string(e.SupportedType()), aws.AwsLoadBalancerResourceType)
}
results := make([]*resource.Resource, 0)
for _, lb := range loadBalancers {
listeners, err := e.repository.ListLoadBalancerListeners(*lb.LoadBalancerArn)
if err != nil {
return nil, remoteerror.NewResourceListingErrorWithType(err, string(e.SupportedType()), aws.AwsLoadBalancerListenerResourceType)
}
for _, listener := range listeners {
results = append(
results,
e.factory.CreateAbstractResource(
string(e.SupportedType()),
*listener.ListenerArn,
map[string]interface{}{},
),
)
}
}
return results, nil
}

View File

@ -9,6 +9,7 @@ import (
type ELBV2Repository interface {
ListAllLoadBalancers() ([]*elbv2.LoadBalancer, error)
ListLoadBalancerListeners(string) ([]*elbv2.Listener, error)
}
type elbv2Repository struct {
@ -24,7 +25,9 @@ func NewELBV2Repository(session *session.Session, c cache.Cache) *elbv2Repositor
}
func (r *elbv2Repository) ListAllLoadBalancers() ([]*elbv2.LoadBalancer, error) {
if v := r.cache.Get("elbv2ListAllLoadBalancers"); v != nil {
cacheKey := "elbv2ListAllLoadBalancers"
defer r.cache.Unlock(cacheKey)
if v := r.cache.GetAndLock(cacheKey); v != nil {
return v.([]*elbv2.LoadBalancer), nil
}
@ -37,6 +40,26 @@ func (r *elbv2Repository) ListAllLoadBalancers() ([]*elbv2.LoadBalancer, error)
if err != nil {
return nil, err
}
r.cache.Put("elbv2ListAllLoadBalancers", results)
r.cache.Put(cacheKey, results)
return results, err
}
func (r *elbv2Repository) ListLoadBalancerListeners(loadBalancerArn string) ([]*elbv2.Listener, error) {
if v := r.cache.Get("elbv2ListLoadBalancerListeners"); v != nil {
return v.([]*elbv2.Listener), nil
}
results := make([]*elbv2.Listener, 0)
input := &elbv2.DescribeListenersInput{
LoadBalancerArn: &loadBalancerArn,
}
err := r.client.DescribeListenersPages(input, func(res *elbv2.DescribeListenersOutput, lastPage bool) bool {
results = append(results, res.Listeners...)
return !lastPage
})
if err != nil {
return nil, err
}
r.cache.Put("elbv2ListLoadBalancerListeners", results)
return results, err
}

View File

@ -40,7 +40,8 @@ func Test_ELBV2Repository_ListAllLoadBalancers(t *testing.T) {
},
}
store.On("Get", "elbv2ListAllLoadBalancers").Return(nil).Once()
store.On("GetAndLock", "elbv2ListAllLoadBalancers").Return(nil).Once()
store.On("Unlock", "elbv2ListAllLoadBalancers").Return().Once()
client.On("DescribeLoadBalancersPages",
&elbv2.DescribeLoadBalancersInput{},
@ -79,7 +80,8 @@ func Test_ELBV2Repository_ListAllLoadBalancers(t *testing.T) {
},
}
store.On("Get", "elbv2ListAllLoadBalancers").Return(output.LoadBalancers).Once()
store.On("GetAndLock", "elbv2ListAllLoadBalancers").Return(output.LoadBalancers).Once()
store.On("Unlock", "elbv2ListAllLoadBalancers").Return().Once()
},
want: []*elbv2.LoadBalancer{
{
@ -91,7 +93,8 @@ func Test_ELBV2Repository_ListAllLoadBalancers(t *testing.T) {
{
name: "error listing load balancers",
mocks: func(client *awstest.MockFakeELBV2, store *cache.MockCache) {
store.On("Get", "elbv2ListAllLoadBalancers").Return(nil).Once()
store.On("GetAndLock", "elbv2ListAllLoadBalancers").Return(nil).Once()
store.On("Unlock", "elbv2ListAllLoadBalancers").Return().Once()
client.On("DescribeLoadBalancersPages",
&elbv2.DescribeLoadBalancersInput{},
@ -126,3 +129,115 @@ func Test_ELBV2Repository_ListAllLoadBalancers(t *testing.T) {
})
}
}
func Test_ELBV2Repository_ListAllLoadBalancerListeners(t *testing.T) {
dummyError := errors.New("dummy error")
tests := []struct {
name string
mocks func(*awstest.MockFakeELBV2, *cache.MockCache)
want []*elbv2.Listener
wantErr error
}{
{
name: "list load balancer listeners",
mocks: func(client *awstest.MockFakeELBV2, store *cache.MockCache) {
results := &elbv2.DescribeListenersOutput{
Listeners: []*elbv2.Listener{
{
LoadBalancerArn: aws.String("test-lb"),
ListenerArn: aws.String("test-lb-listener-1"),
},
{
LoadBalancerArn: aws.String("test-lb"),
ListenerArn: aws.String("test-lb-listener-2"),
},
},
}
store.On("Get", "elbv2ListLoadBalancerListeners").Return(nil).Once()
client.On("DescribeListenersPages",
&elbv2.DescribeListenersInput{LoadBalancerArn: aws.String("test-lb")},
mock.MatchedBy(func(callback func(res *elbv2.DescribeListenersOutput, lastPage bool) bool) bool {
callback(&elbv2.DescribeListenersOutput{Listeners: []*elbv2.Listener{
results.Listeners[0],
}}, false)
callback(&elbv2.DescribeListenersOutput{Listeners: []*elbv2.Listener{
results.Listeners[1],
}}, true)
return true
})).Return(nil).Once()
store.On("Put", "elbv2ListLoadBalancerListeners", results.Listeners).Return(false).Once()
},
want: []*elbv2.Listener{
{
LoadBalancerArn: aws.String("test-lb"),
ListenerArn: aws.String("test-lb-listener-1"),
},
{
LoadBalancerArn: aws.String("test-lb"),
ListenerArn: aws.String("test-lb-listener-2"),
},
},
},
{
name: "list load balancer listeners from cache",
mocks: func(client *awstest.MockFakeELBV2, store *cache.MockCache) {
output := &elbv2.DescribeListenersOutput{
Listeners: []*elbv2.Listener{
{
LoadBalancerArn: aws.String("test-lb"),
ListenerArn: aws.String("test-lb-listener"),
},
},
}
store.On("Get", "elbv2ListLoadBalancerListeners").Return(output.Listeners).Once()
},
want: []*elbv2.Listener{
{
LoadBalancerArn: aws.String("test-lb"),
ListenerArn: aws.String("test-lb-listener"),
},
},
},
{
name: "error listing load balancer listeners",
mocks: func(client *awstest.MockFakeELBV2, store *cache.MockCache) {
store.On("Get", "elbv2ListLoadBalancerListeners").Return(nil).Once()
client.On("DescribeListenersPages",
&elbv2.DescribeListenersInput{LoadBalancerArn: aws.String("test-lb")},
mock.MatchedBy(func(callback func(res *elbv2.DescribeListenersOutput, lastPage bool) bool) bool {
callback(&elbv2.DescribeListenersOutput{Listeners: []*elbv2.Listener{}}, true)
return true
})).Return(dummyError).Once()
},
wantErr: dummyError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
store := &cache.MockCache{}
client := &awstest.MockFakeELBV2{}
tt.mocks(client, store)
r := &elbv2Repository{
client: client,
cache: store,
}
got, err := r.ListLoadBalancerListeners("test-lb")
assert.Equal(t, tt.wantErr, err)
changelog, err := diff.Diff(got, tt.want)
assert.Nil(t, err)
if len(changelog) > 0 {
for _, change := range changelog {
t.Errorf("%s: %v -> %v", strings.Join(change.Path, "."), change.From, change.To)
}
t.Fail()
}
})
}
}

View File

@ -34,3 +34,26 @@ func (_m *MockELBV2Repository) ListAllLoadBalancers() ([]*elbv2.LoadBalancer, er
return r0, r1
}
// ListLoadBalancerListeners provides a mock function with given fields: _a0
func (_m *MockELBV2Repository) ListLoadBalancerListeners(_a0 string) ([]*elbv2.Listener, error) {
ret := _m.Called(_a0)
var r0 []*elbv2.Listener
if rf, ok := ret.Get(0).(func(string) []*elbv2.Listener); ok {
r0 = rf(_a0)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*elbv2.Listener)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@ -115,3 +115,131 @@ func TestELBV2_LoadBalancer(t *testing.T) {
})
}
}
func TestELBV2_LoadBalancerListener(t *testing.T) {
dummyError := errors.New("dummy error")
tests := []struct {
test string
mocks func(*repository.MockELBV2Repository, *mocks.AlerterInterface)
assertExpected func(t *testing.T, got []*resource.Resource)
wantErr error
}{
{
test: "no load balancer listener",
mocks: func(repository *repository.MockELBV2Repository, alerter *mocks.AlerterInterface) {
repository.On("ListAllLoadBalancers").Return([]*elbv2.LoadBalancer{
{
LoadBalancerArn: awssdk.String("test-lb"),
},
}, nil)
repository.On("ListLoadBalancerListeners", "test-lb").Return([]*elbv2.Listener{}, nil)
},
assertExpected: func(t *testing.T, got []*resource.Resource) {
assert.Len(t, got, 0)
},
},
{
test: "should list load balancer listener",
mocks: func(repository *repository.MockELBV2Repository, alerter *mocks.AlerterInterface) {
repository.On("ListAllLoadBalancers").Return([]*elbv2.LoadBalancer{
{
LoadBalancerArn: awssdk.String("test-lb"),
},
}, nil)
repository.On("ListLoadBalancerListeners", "test-lb").Return([]*elbv2.Listener{
{
ListenerArn: awssdk.String("test-lb-listener-1"),
},
}, nil)
},
assertExpected: func(t *testing.T, got []*resource.Resource) {
assert.Len(t, got, 1)
assert.Equal(t, "test-lb-listener-1", got[0].ResourceId())
assert.Equal(t, resourceaws.AwsLoadBalancerListenerResourceType, got[0].ResourceType())
},
},
{
test: "cannot list load balancer listeners (403)",
mocks: func(repository *repository.MockELBV2Repository, alerter *mocks.AlerterInterface) {
repository.On("ListAllLoadBalancers").Return([]*elbv2.LoadBalancer{
{
LoadBalancerArn: awssdk.String("test-lb"),
},
}, nil)
awsError := awserr.NewRequestFailure(awserr.New("AccessDeniedException", "", errors.New("")), 403, "")
repository.On("ListLoadBalancerListeners", "test-lb").Return(nil, awsError)
alerter.On("SendAlert", resourceaws.AwsLoadBalancerListenerResourceType, alerts.NewRemoteAccessDeniedAlert(common.RemoteAWSTerraform, remoteerr.NewResourceListingError(awsError, resourceaws.AwsLoadBalancerListenerResourceType), alerts.EnumerationPhase)).Return()
},
assertExpected: func(t *testing.T, got []*resource.Resource) {
assert.Len(t, got, 0)
},
},
{
test: "cannot list load balancers (403)",
mocks: func(repository *repository.MockELBV2Repository, alerter *mocks.AlerterInterface) {
awsError := awserr.NewRequestFailure(awserr.New("AccessDeniedException", "", errors.New("")), 403, "")
repository.On("ListAllLoadBalancers").Return(nil, awsError)
alerter.On("SendAlert", resourceaws.AwsLoadBalancerListenerResourceType, alerts.NewRemoteAccessDeniedAlert(common.RemoteAWSTerraform, remoteerr.NewResourceListingErrorWithType(awsError, resourceaws.AwsLoadBalancerListenerResourceType, resourceaws.AwsLoadBalancerResourceType), alerts.EnumerationPhase)).Return()
},
assertExpected: func(t *testing.T, got []*resource.Resource) {
assert.Len(t, got, 0)
},
},
{
test: "cannot list load balancer listeners (dummy error)",
mocks: func(repository *repository.MockELBV2Repository, alerter *mocks.AlerterInterface) {
repository.On("ListAllLoadBalancers").Return([]*elbv2.LoadBalancer{
{
LoadBalancerArn: awssdk.String("test-lb"),
},
}, nil)
repository.On("ListLoadBalancerListeners", "test-lb").Return(nil, dummyError)
},
assertExpected: func(t *testing.T, got []*resource.Resource) {
assert.Len(t, got, 0)
},
wantErr: remoteerr.NewResourceScanningError(dummyError, resourceaws.AwsLoadBalancerListenerResourceType, ""),
},
}
schemaRepository := testresource.InitFakeSchemaRepository("aws", "3.19.0")
resourceaws.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.MockELBV2Repository{}
c.mocks(fakeRepo, alerter)
var repo repository.ELBV2Repository = fakeRepo
remoteLibrary.AddEnumerator(aws.NewLoadBalancerListenerEnumerator(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)
})
}
}

View File

@ -0,0 +1,3 @@
package aws
const AwsApplicationLoadBalancerListenerResourceType = "aws_alb_listener"

View File

@ -0,0 +1,30 @@
package aws_test
import (
"testing"
"github.com/snyk/driftctl/test"
"github.com/snyk/driftctl/test/acceptance"
)
func TestAcc_Aws_ApplicationLoadBalancerListener(t *testing.T) {
acceptance.Run(t, acceptance.AccTestCase{
TerraformVersion: "0.15.5",
Paths: []string{"./testdata/acc/aws_alb_listener"},
Args: []string{"scan"},
Checks: []acceptance.AccCheck{
{
Env: map[string]string{
"AWS_REGION": "us-east-1",
},
Check: func(result *test.ScanResult, stdout string, err error) {
if err != nil {
t.Fatal(err)
}
result.AssertInfrastructureIsInSync()
result.AssertManagedCount(1)
},
},
},
})
}

View File

@ -0,0 +1,3 @@
package aws
const AwsLoadBalancerListenerResourceType = "aws_lb_listener"

View File

@ -0,0 +1,30 @@
package aws_test
import (
"testing"
"github.com/snyk/driftctl/test"
"github.com/snyk/driftctl/test/acceptance"
)
func TestAcc_Aws_LoadBalancerListener(t *testing.T) {
acceptance.Run(t, acceptance.AccTestCase{
TerraformVersion: "0.15.5",
Paths: []string{"./testdata/acc/aws_lb_listener"},
Args: []string{"scan"},
Checks: []acceptance.AccCheck{
{
Env: map[string]string{
"AWS_REGION": "us-east-1",
},
Check: func(result *test.ScanResult, stdout string, err error) {
if err != nil {
t.Fatal(err)
}
result.AssertInfrastructureIsInSync()
result.AssertManagedCount(1)
},
},
},
})
}

View File

@ -112,6 +112,7 @@ func TestAWS_Metadata_Flags(t *testing.T) {
AwsLoadBalancerResourceType: {},
AwsApplicationLoadBalancerResourceType: {},
AwsClassicLoadBalancerResourceType: {},
AwsLoadBalancerListenerResourceType: {},
}
schemaRepository := testresource.InitFakeSchemaRepository(tf.AWS, "3.19.0")

View File

@ -0,0 +1,2 @@
*
!aws_lb_listener

View File

@ -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/aws" {
version = "4.9.0"
hashes = [
"h1:GtmIOZMkKmr9tMLWouHWiGXmKEL/diOTNar5XfOVLjs=",
"zh:084b83aef3335ad4f5e4b8323c6fe43c1ff55e17a7647c6a5cad6af519f72b42",
"zh:132e47ce69f14de4523b84b213cedf7173398acda14245b1ffe7747aac50f050",
"zh:2068baef7dfce3613f3b4f27314175e971f8db68d9cde9ec30b5659f80c68c6c",
"zh:63c6f489683d5f1ac55e82a0df387143ed22701d5f22c109a4d5c9924dd4e437",
"zh:8115fd21965954fa4568c09331e05bb29da967fab8d077419aed09954378e216",
"zh:8efdc95fde108f777ed9c79ae25dc17aea9771903250f5c5c8a4c726b90a345f",
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
"zh:9d42a7bc34d84b70c1d1bcc215cabd63abbcbd0352b70bd84da6c3916634932f",
"zh:aacbcceb241aa475888c0869e87593182edeced3170c76a0c960dd9c905df449",
"zh:c7fe7904511052e4102870256819a1917177572cf684f0611ebf767f9c1fbaa8",
"zh:c8e07c3424663d1d0e7e32f4ade8099c19f6326d37c6da98104d90c986ff66fc",
"zh:e47cafbd38b56ef14fd8d727b4ffea847c166b1c684f585ee5fb78983b537248",
]
}

View File

@ -0,0 +1,72 @@
provider "aws" {
region = "us-east-1"
}
resource "aws_vpc" "main" {
cidr_block = "10.100.0.0/16"
}
resource "aws_internet_gateway" "gw" {
vpc_id = aws_vpc.main.id
}
resource "aws_subnet" "main-1" {
vpc_id = aws_vpc.main.id
cidr_block = "10.100.0.0/24"
availability_zone = "us-east-1a"
}
resource "aws_subnet" "main-2" {
vpc_id = aws_vpc.main.id
cidr_block = "10.100.1.0/24"
availability_zone = "us-east-1b"
}
resource "aws_security_group" "lb_sg" {
name = "allow_tls_lb"
description = "Allow TLS inbound traffic"
vpc_id = aws_vpc.main.id
ingress {
description = "TLS from VPC"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [aws_vpc.main.cidr_block]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
}
resource "aws_lb" "test" {
name = "test-lb-tf-with-listeners"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.lb_sg.id]
subnets = [aws_subnet.main-1.id,aws_subnet.main-2.id]
enable_deletion_protection = false
}
resource "aws_lb_target_group" "test" {
name = "tf-acc-lb-with-listeners-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.main.id
}
resource "aws_alb_listener" "front_end" {
load_balancer_arn = aws_lb.test.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.test.arn
}
}

View File

@ -0,0 +1,2 @@
*
!aws_lb_listener

View File

@ -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/aws" {
version = "4.9.0"
hashes = [
"h1:GtmIOZMkKmr9tMLWouHWiGXmKEL/diOTNar5XfOVLjs=",
"zh:084b83aef3335ad4f5e4b8323c6fe43c1ff55e17a7647c6a5cad6af519f72b42",
"zh:132e47ce69f14de4523b84b213cedf7173398acda14245b1ffe7747aac50f050",
"zh:2068baef7dfce3613f3b4f27314175e971f8db68d9cde9ec30b5659f80c68c6c",
"zh:63c6f489683d5f1ac55e82a0df387143ed22701d5f22c109a4d5c9924dd4e437",
"zh:8115fd21965954fa4568c09331e05bb29da967fab8d077419aed09954378e216",
"zh:8efdc95fde108f777ed9c79ae25dc17aea9771903250f5c5c8a4c726b90a345f",
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
"zh:9d42a7bc34d84b70c1d1bcc215cabd63abbcbd0352b70bd84da6c3916634932f",
"zh:aacbcceb241aa475888c0869e87593182edeced3170c76a0c960dd9c905df449",
"zh:c7fe7904511052e4102870256819a1917177572cf684f0611ebf767f9c1fbaa8",
"zh:c8e07c3424663d1d0e7e32f4ade8099c19f6326d37c6da98104d90c986ff66fc",
"zh:e47cafbd38b56ef14fd8d727b4ffea847c166b1c684f585ee5fb78983b537248",
]
}

View File

@ -0,0 +1,72 @@
provider "aws" {
region = "us-east-1"
}
resource "aws_vpc" "main" {
cidr_block = "10.100.0.0/16"
}
resource "aws_internet_gateway" "gw" {
vpc_id = aws_vpc.main.id
}
resource "aws_subnet" "main-1" {
vpc_id = aws_vpc.main.id
cidr_block = "10.100.0.0/24"
availability_zone = "us-east-1a"
}
resource "aws_subnet" "main-2" {
vpc_id = aws_vpc.main.id
cidr_block = "10.100.1.0/24"
availability_zone = "us-east-1b"
}
resource "aws_security_group" "lb_sg" {
name = "allow_tls_lb"
description = "Allow TLS inbound traffic"
vpc_id = aws_vpc.main.id
ingress {
description = "TLS from VPC"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [aws_vpc.main.cidr_block]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
}
resource "aws_lb" "test" {
name = "test-lb-tf-with-listeners"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.lb_sg.id]
subnets = [aws_subnet.main-1.id,aws_subnet.main-2.id]
enable_deletion_protection = false
}
resource "aws_lb_target_group" "test" {
name = "tf-acc-lb-with-listeners-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.main.id
}
resource "aws_lb_listener" "front_end" {
load_balancer_arn = aws_lb.test.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.test.arn
}
}

View File

@ -27,7 +27,11 @@ var supportedTypes = map[string]ResourceTypeMeta{
"aws_alb": {children: []ResourceType{
"aws_lb",
}},
"aws_lb": {},
"aws_lb": {},
"aws_lb_listener": {},
"aws_alb_listener": {children: []ResourceType{
"aws_lb_listener",
}},
"aws_ebs_encryption_by_default": {},
"aws_ecr_repository": {},
"aws_eip": {children: []ResourceType{