From e9dc238c94b85dbfcd7b739c980c0febfb0503b0 Mon Sep 17 00:00:00 2001 From: Elie Date: Tue, 12 Oct 2021 17:59:48 +0200 Subject: [PATCH] Add resource type hierarchy for smart ignore --- pkg/filter/driftignore.go | 19 ++ pkg/filter/driftignore_test.go | 99 ++++++++ .../testdata/drift_ignore_type/.driftignore | 3 + .../drift_ignore_type/.driftignore_child_1 | 3 + .../drift_ignore_type/.driftignore_child_2 | 3 + pkg/resource/resource_types.go | 223 ++++++++++++------ 6 files changed, 272 insertions(+), 78 deletions(-) create mode 100644 pkg/filter/testdata/drift_ignore_type/.driftignore create mode 100644 pkg/filter/testdata/drift_ignore_type/.driftignore_child_1 create mode 100644 pkg/filter/testdata/drift_ignore_type/.driftignore_child_2 diff --git a/pkg/filter/driftignore.go b/pkg/filter/driftignore.go index 3f44c78e..cd5f51de 100644 --- a/pkg/filter/driftignore.go +++ b/pkg/filter/driftignore.go @@ -67,7 +67,26 @@ func (r *DriftIgnore) readIgnoreFile() error { return nil } +func (r *DriftIgnore) isAnyOfChildrenTypesNotIgnored(ty resource.ResourceType) bool { + childrenTypes := resource.GetMeta(ty).GetChildrenTypes() + for _, childrenType := range childrenTypes { + if !r.match(fmt.Sprintf("%s.*", childrenType)) { + return true + } + if r.isAnyOfChildrenTypesNotIgnored(childrenType) { + return true + } + } + return false +} + func (r *DriftIgnore) IsTypeIgnored(ty resource.ResourceType) bool { + // Iterate over children types, and do not ignore parent resource + // if at least one of children type is not ignored. + if r.isAnyOfChildrenTypesNotIgnored(ty) { + return false + } + return r.match(fmt.Sprintf("%s.*", ty)) } diff --git a/pkg/filter/driftignore_test.go b/pkg/filter/driftignore_test.go index 2ea99dcd..244c8f0c 100644 --- a/pkg/filter/driftignore_test.go +++ b/pkg/filter/driftignore_test.go @@ -435,3 +435,102 @@ func TestDriftIgnore_IsFieldIgnored(t *testing.T) { }) } } + +func TestDriftIgnore_IsTypeIgnored(t *testing.T) { + tests := []struct { + name string + resources []*resource.Resource + want []bool + path string + }{ + { + name: "drift_ignore_type_exclude_with_child_1_nesting", + resources: []*resource.Resource{ + { + Type: "aws_route", + }, + { + Type: "aws_route_table", + }, + { + Type: "non_ignored_type", + }, + { + Type: "ignored_type", + }, + }, + want: []bool{ + false, + false, + false, + true, + }, + path: "testdata/drift_ignore_type/.driftignore_child_1", + }, + { + name: "drift_ignore_type_exclude_with_child_2_nesting", + resources: []*resource.Resource{ + { + Type: "non_ignored_type", + }, + { + Type: "aws_iam_user", + }, + { + Type: "aws_iam_user_policy", + }, + { + Type: "aws_iam_user_policy_attachment", + }, + { + Type: "ignored_type", + }, + }, + want: []bool{ + false, + false, + false, + false, + true, + }, + path: "testdata/drift_ignore_type/.driftignore_child_2", + }, + { + name: "drift_ignore_type_exclude", + resources: []*resource.Resource{ + { + Type: "type", + }, + { + Type: "type_1", + }, + { + Type: "type_2", + }, + { + Type: "type_3", + }, + }, + want: []bool{ + true, + false, + true, + true, + }, + path: "testdata/drift_ignore_type/.driftignore", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cwd, _ := os.Getwd() + defer func() { _ = os.Chdir(cwd) }() + + r := NewDriftIgnore(tt.path) + got := make([]bool, 0, len(tt.want)) + for _, res := range tt.resources { + got = append(got, r.IsTypeIgnored(resource.ResourceType(res.ResourceType()))) + } + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/filter/testdata/drift_ignore_type/.driftignore b/pkg/filter/testdata/drift_ignore_type/.driftignore new file mode 100644 index 00000000..e783651d --- /dev/null +++ b/pkg/filter/testdata/drift_ignore_type/.driftignore @@ -0,0 +1,3 @@ +type +type_2 +type_3.* diff --git a/pkg/filter/testdata/drift_ignore_type/.driftignore_child_1 b/pkg/filter/testdata/drift_ignore_type/.driftignore_child_1 new file mode 100644 index 00000000..1d80bd98 --- /dev/null +++ b/pkg/filter/testdata/drift_ignore_type/.driftignore_child_1 @@ -0,0 +1,3 @@ +* +!aws_route +!non_ignored_type.* diff --git a/pkg/filter/testdata/drift_ignore_type/.driftignore_child_2 b/pkg/filter/testdata/drift_ignore_type/.driftignore_child_2 new file mode 100644 index 00000000..8ce314e5 --- /dev/null +++ b/pkg/filter/testdata/drift_ignore_type/.driftignore_child_2 @@ -0,0 +1,3 @@ +* +!aws_iam_user_policy_attachment +!non_ignored_type.* diff --git a/pkg/resource/resource_types.go b/pkg/resource/resource_types.go index e76d8003..2aebe620 100644 --- a/pkg/resource/resource_types.go +++ b/pkg/resource/resource_types.go @@ -2,77 +2,124 @@ package resource type ResourceType string -var supportedTypes = map[string]struct{}{ - "aws_ami": {}, - "aws_cloudfront_distribution": {}, - "aws_db_instance": {}, - "aws_db_subnet_group": {}, - "aws_default_network_acl": {}, - "aws_default_route_table": {}, - "aws_default_security_group": {}, - "aws_default_subnet": {}, - "aws_default_vpc": {}, - "aws_dynamodb_table": {}, - "aws_ebs_snapshot": {}, - "aws_ebs_volume": {}, - "aws_ecr_repository": {}, - "aws_eip": {}, - "aws_eip_association": {}, - "aws_iam_access_key": {}, - "aws_iam_policy": {}, - "aws_iam_policy_attachment": {}, - "aws_iam_role": {}, - "aws_iam_role_policy": {}, - "aws_iam_role_policy_attachment": {}, - "aws_iam_user": {}, - "aws_iam_user_policy": {}, - "aws_iam_user_policy_attachment": {}, - "aws_instance": {}, - "aws_internet_gateway": {}, - "aws_key_pair": {}, - "aws_kms_alias": {}, - "aws_kms_key": {}, - "aws_lambda_event_source_mapping": {}, - "aws_lambda_function": {}, - "aws_nat_gateway": {}, - "aws_network_acl": {}, - "aws_network_acl_rule": {}, - "aws_route": {}, - "aws_route53_health_check": {}, - "aws_route53_record": {}, - "aws_route53_zone": {}, - "aws_route_table": {}, - "aws_route_table_association": {}, - "aws_s3_bucket": {}, +var supportedTypes = map[string]ResourceTypeMeta{ + "aws_ami": {}, + "aws_cloudfront_distribution": {}, + "aws_db_instance": {}, + "aws_db_subnet_group": {}, + "aws_default_network_acl": {children: []ResourceType{ + "aws_network_acl_rule", + }}, + "aws_default_route_table": {children: []ResourceType{ + "aws_route", + }}, + "aws_default_security_group": {children: []ResourceType{ + "aws_security_group_rule", + }}, + "aws_default_subnet": {}, + "aws_default_vpc": {children: []ResourceType{ + // VPC are used by aws_internet_gateway to determine if internet gateway is the default one in middleware + "aws_internet_gateway", + }}, + "aws_dynamodb_table": {}, + "aws_ebs_snapshot": {}, + "aws_ebs_volume": {}, + "aws_ecr_repository": {}, + "aws_eip": {children: []ResourceType{ + "aws_eip_association", + }}, + "aws_eip_association": {}, + "aws_iam_access_key": {}, + "aws_iam_policy": {}, + "aws_iam_policy_attachment": {}, + "aws_iam_role": {children: []ResourceType{ + "aws_iam_role_policy", + "aws_iam_policy_attachment", + }}, + "aws_iam_role_policy": {children: []ResourceType{ + "aws_iam_role_policy_attachment", + }}, + "aws_iam_role_policy_attachment": {children: []ResourceType{ + "aws_iam_policy_attachment", + }}, + "aws_iam_user": {children: []ResourceType{ + "aws_iam_user_policy", + }}, + "aws_iam_user_policy": {children: []ResourceType{ + "aws_iam_user_policy_attachment", + }}, + "aws_iam_user_policy_attachment": {children: []ResourceType{ + "aws_iam_policy_attachment", + }}, + "aws_instance": {children: []ResourceType{ + "aws_ebs_volume", + }}, + "aws_internet_gateway": {children: []ResourceType{ + // This is used to determine internet gateway default rule + "aws_route", + }}, + "aws_key_pair": {}, + "aws_kms_alias": {}, + "aws_kms_key": {}, + "aws_lambda_event_source_mapping": {}, + "aws_lambda_function": {}, + "aws_nat_gateway": {}, + "aws_network_acl": {children: []ResourceType{ + "aws_network_acl_rule", + }}, + "aws_network_acl_rule": {}, + "aws_route": {}, + "aws_route53_health_check": {}, + "aws_route53_record": {}, + "aws_route53_zone": {}, + "aws_route_table": {children: []ResourceType{ + "aws_route", + }}, + "aws_route_table_association": {}, + "aws_s3_bucket": {children: []ResourceType{ + "aws_s3_bucket_policy", + }}, "aws_s3_bucket_analytics_configuration": {}, "aws_s3_bucket_inventory": {}, "aws_s3_bucket_metric": {}, "aws_s3_bucket_notification": {}, "aws_s3_bucket_policy": {}, - "aws_security_group": {}, - "aws_security_group_rule": {}, - "aws_sns_topic": {}, - "aws_sns_topic_policy": {}, - "aws_sns_topic_subscription": {}, - "aws_sqs_queue": {}, - "aws_sqs_queue_policy": {}, - "aws_subnet": {}, - "aws_vpc": {}, - "aws_rds_cluster": {}, - "aws_cloudformation_stack": {}, - "aws_api_gateway_rest_api": {}, - "aws_api_gateway_account": {}, - "aws_api_gateway_api_key": {}, - "aws_api_gateway_authorizer": {}, - "aws_api_gateway_deployment": {}, - "aws_api_gateway_stage": {}, - "aws_api_gateway_resource": {}, - "aws_api_gateway_domain_name": {}, - "aws_api_gateway_vpc_link": {}, - "aws_appautoscaling_target": {}, - "aws_rds_cluster_instance": {}, - "aws_appautoscaling_policy": {}, - "aws_appautoscaling_scheduled_action": {}, + "aws_security_group": {children: []ResourceType{ + "aws_security_group_rule", + }}, + "aws_security_group_rule": {}, + "aws_sns_topic": {children: []ResourceType{ + "aws_sns_topic_policy", + }}, + "aws_sns_topic_policy": {}, + "aws_sns_topic_subscription": {}, + "aws_sqs_queue": {children: []ResourceType{ + "aws_sqs_queue_policy", + }}, + "aws_sqs_queue_policy": {}, + "aws_subnet": {}, + "aws_vpc": {}, + "aws_rds_cluster": {}, + "aws_cloudformation_stack": {}, + "aws_api_gateway_rest_api": {children: []ResourceType{ + "aws_api_gateway_resource", + }}, + "aws_api_gateway_account": {}, + "aws_api_gateway_api_key": {}, + "aws_api_gateway_authorizer": {}, + "aws_api_gateway_deployment": {children: []ResourceType{ + "aws_api_gateway_stage", + }}, + "aws_api_gateway_stage": {}, + "aws_api_gateway_resource": {}, + "aws_api_gateway_domain_name": {}, + "aws_api_gateway_vpc_link": {}, + "aws_appautoscaling_target": {}, + "aws_rds_cluster_instance": {children: []ResourceType{ + "aws_db_instance", + }}, + "aws_appautoscaling_policy": {}, + "aws_appautoscaling_scheduled_action": {}, "github_branch_protection": {}, "github_membership": {}, @@ -80,19 +127,27 @@ var supportedTypes = map[string]struct{}{ "github_team": {}, "github_team_membership": {}, - "google_storage_bucket": {}, - "google_compute_firewall": {}, - "google_compute_router": {}, - "google_compute_instance": {}, - "google_compute_network": {}, - "google_storage_bucket_iam_binding": {}, - "google_storage_bucket_iam_member": {}, - "google_storage_bucket_iam_policy": {}, + "google_storage_bucket": {}, + "google_compute_firewall": {}, + "google_compute_router": {}, + "google_compute_instance": {}, + "google_compute_network": {}, + "google_storage_bucket_iam_binding": {children: []ResourceType{ + "google_storage_bucket_iam_member", + }}, + "google_storage_bucket_iam_member": {}, + "google_storage_bucket_iam_policy": {children: []ResourceType{ + "google_storage_bucket_iam_member", + }}, - "azurerm_storage_account": {}, - "azurerm_storage_container": {}, - "azurerm_virtual_network": {}, - "azurerm_route_table": {}, + "azurerm_storage_account": {}, + "azurerm_storage_container": {}, + "azurerm_virtual_network": {children: []ResourceType{ + "azurerm_subnet", + }}, + "azurerm_route_table": {children: []ResourceType{ + "azurerm_route", + }}, "azurerm_route": {}, "azurerm_resource_group": {}, "azurerm_subnet": {}, @@ -110,3 +165,15 @@ func IsResourceTypeSupported(ty string) bool { func (ty ResourceType) String() string { return string(ty) } + +func GetMeta(ty ResourceType) ResourceTypeMeta { + return supportedTypes[ty.String()] +} + +type ResourceTypeMeta struct { + children []ResourceType +} + +func (ty ResourceTypeMeta) GetChildrenTypes() []ResourceType { + return ty.children +}