diff --git a/pkg/cmd/scan/output/console.go b/pkg/cmd/scan/output/console.go index 07b9987d..42fa96a9 100644 --- a/pkg/cmd/scan/output/console.go +++ b/pkg/cmd/scan/output/console.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "reflect" + "sort" "strings" "github.com/aws/aws-sdk-go/aws/awsutil" @@ -15,7 +16,6 @@ import ( "github.com/yudai/gojsondiff/formatter" "github.com/cloudskiff/driftctl/pkg/analyser" - "github.com/cloudskiff/driftctl/pkg/output" "github.com/cloudskiff/driftctl/pkg/remote" "github.com/cloudskiff/driftctl/pkg/resource" ) @@ -41,10 +41,8 @@ func (c *Console) Write(analysis *analyser.Analysis) error { fmt.Printf(" %s:\n", ty) for _, res := range resources { humanString := fmt.Sprintf(" - %s", res.TerraformId()) - if humanizerRes, ok := res.(output.AttributesGetter); ok { - if humanAttrs := output.HumanizeAttribute(humanizerRes); humanAttrs != "" { - humanString += fmt.Sprintf("\n %s", humanAttrs) - } + if humanAttrs := formatResourceAttributes(res); humanAttrs != "" { + humanString += fmt.Sprintf("\n %s", humanAttrs) } fmt.Println(humanString) } @@ -58,10 +56,8 @@ func (c *Console) Write(analysis *analyser.Analysis) error { fmt.Printf(" %s:\n", ty) for _, res := range resource { humanString := fmt.Sprintf(" - %s", res.TerraformId()) - if humanizerRes, ok := res.(output.AttributesGetter); ok { - if humanAttrs := output.HumanizeAttribute(humanizerRes); humanAttrs != "" { - humanString += fmt.Sprintf("\n %s", humanAttrs) - } + if humanAttrs := formatResourceAttributes(res); humanAttrs != "" { + humanString += fmt.Sprintf("\n %s", humanAttrs) } fmt.Println(humanString) } @@ -73,11 +69,9 @@ func (c *Console) Write(analysis *analyser.Analysis) error { for _, difference := range analysis.Differences() { humanString := fmt.Sprintf(" - %s (%s):", difference.Res.TerraformId(), difference.Res.TerraformType()) whiteSpace := " " - if humanizerRes, ok := difference.Res.(output.AttributesGetter); ok { - if humanAttrs := output.HumanizeAttribute(humanizerRes); humanAttrs != "" { - humanString += fmt.Sprintf("\n %s", humanAttrs) - whiteSpace = " " - } + if humanAttrs := formatResourceAttributes(difference.Res); humanAttrs != "" { + humanString += fmt.Sprintf("\n %s", humanAttrs) + whiteSpace = " " } fmt.Println(humanString) for _, change := range difference.Changelog { @@ -211,3 +205,28 @@ func jsonDiff(a, b interface{}, prefix string) string { return diffStr } + +func formatResourceAttributes(res resource.Resource) string { + if res.Schema() == nil || res.Schema().HumanReadableAttributesFunc == nil { + return "" + } + attributes := res.Schema().HumanReadableAttributesFunc(res.(*resource.AbstractResource)) + if len(attributes) <= 0 { + return "" + } + // sort attributes + keys := make([]string, 0, len(attributes)) + for k := range attributes { + keys = append(keys, k) + } + sort.Strings(keys) + // retrieve stringer + attrString := "" + for _, k := range keys { + if attrString != "" { + attrString += ", " + } + attrString += fmt.Sprintf("%s: %s", k, attributes[k]) + } + return attrString +} diff --git a/pkg/cmd/scan/output/output_test.go b/pkg/cmd/scan/output/output_test.go index 03bf57d5..74f2e35b 100644 --- a/pkg/cmd/scan/output/output_test.go +++ b/pkg/cmd/scan/output/output_test.go @@ -160,32 +160,45 @@ func fakeAnalysisWithoutAttrs() *analyser.Analysis { func fakeAnalysisWithStringerResources() *analyser.Analysis { a := analyser.Analysis{} + fakeResourceStringerSchema := &resource.Schema{HumanReadableAttributesFunc: func(res *resource.AbstractResource) map[string]string { + return map[string]string{ + "Name": (*res.Attrs)["name"].(string), + } + }} a.AddDeleted( - &testresource.FakeResourceStringer{ - Id: "dfjkgnbsgj", + &resource.AbstractResource{ + Id: "dfjkgnbsgj", + Type: "FakeResourceStringer", + Sch: fakeResourceStringerSchema, Attrs: &resource.Attributes{ "name": "deleted resource", }, }, ) a.AddManaged( - &testresource.FakeResourceStringer{ - Id: "usqyfsdbgjsdgjkdfg", + &resource.AbstractResource{ + Id: "usqyfsdbgjsdgjkdfg", + Type: "FakeResourceStringer", + Sch: fakeResourceStringerSchema, Attrs: &resource.Attributes{ "name": "managed resource", }, }, ) a.AddUnmanaged( - &testresource.FakeResourceStringer{ - Id: "duysgkfdjfdgfhd", + &resource.AbstractResource{ + Id: "duysgkfdjfdgfhd", + Type: "FakeResourceStringer", + Sch: fakeResourceStringerSchema, Attrs: &resource.Attributes{ "name": "unmanaged resource", }, }, ) - a.AddDifference(analyser.Difference{Res: &testresource.FakeResourceStringer{ - Id: "gdsfhgkbn", + a.AddDifference(analyser.Difference{Res: &resource.AbstractResource{ + Id: "gdsfhgkbn", + Type: "FakeResourceStringer", + Sch: fakeResourceStringerSchema, Attrs: &resource.Attributes{ "name": "resource with diff", }, diff --git a/pkg/output/humanizer.go b/pkg/output/humanizer.go deleted file mode 100644 index 24e5ec1b..00000000 --- a/pkg/output/humanizer.go +++ /dev/null @@ -1,32 +0,0 @@ -package output - -import ( - "fmt" - "sort" -) - -type AttributesGetter interface { - HumanReadableAttributes() map[string]string -} - -func HumanizeAttribute(res AttributesGetter) string { - attributes := res.HumanReadableAttributes() - if len(attributes) <= 0 { - return "" - } - // sort attributes - keys := make([]string, 0, len(attributes)) - for k := range attributes { - keys = append(keys, k) - } - sort.Strings(keys) - // retrieve stringer - attrString := "" - for _, k := range keys { - if attrString != "" { - attrString += ", " - } - attrString += fmt.Sprintf("%s: %s", k, attributes[k]) - } - return attrString -} diff --git a/pkg/output/humanizer_test.go b/pkg/output/humanizer_test.go deleted file mode 100644 index 7a445ec1..00000000 --- a/pkg/output/humanizer_test.go +++ /dev/null @@ -1,504 +0,0 @@ -package output - -import ( - "testing" - - "github.com/cloudskiff/driftctl/pkg/resource" - "github.com/cloudskiff/driftctl/pkg/resource/aws" - "github.com/cloudskiff/driftctl/pkg/resource/github" - testresource "github.com/cloudskiff/driftctl/test/resource" -) - -func TestHumanizeAttribute_AWS(t *testing.T) { - tests := []struct { - name string - res AttributesGetter - want string - }{ - { - name: "test empty iam_access_key", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsIamAccessKeyResourceType, - Attrs: &resource.Attributes{}, - }, - want: "", - }, - { - name: "test valid iam_access_key", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsIamAccessKeyResourceType, - Attrs: &resource.Attributes{ - "user": "foo", - }, - }, - want: "User: foo", - }, - { - name: "test empty aws_instance", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsInstanceResourceType, - Attrs: &resource.Attributes{}, - }, - want: "", - }, - { - name: "test valid aws_instance", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsInstanceResourceType, - Attrs: &resource.Attributes{ - "tags": map[string]interface{}{ - "name": "foo", - }, - }, - }, - want: "Name: foo", - }, - { - name: "test empty aws_lambda_event_source_mapping", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsLambdaEventSourceMappingResourceType, - Attrs: &resource.Attributes{}, - }, - want: "", - }, - { - name: "test with source aws_lambda_event_source_mapping", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsLambdaEventSourceMappingResourceType, - Attrs: &resource.Attributes{ - "event_source_arn": "source-arn", - }, - }, - want: "", - }, - { - name: "test with source and dest aws_lambda_event_source_mapping", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsLambdaEventSourceMappingResourceType, - Attrs: &resource.Attributes{ - "event_source_arn": "source-arn", - "function_name": "function-name", - }, - }, - want: "Dest: function-name, Source: source-arn", - }, - { - name: "test empty aws_route", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsRouteResourceType, - Attrs: &resource.Attributes{}, - }, - want: "", - }, - { - name: "test with no destination aws_route", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsRouteResourceType, - Attrs: &resource.Attributes{ - "route_table_id": "table-id", - }, - }, - want: "Table: table-id", - }, - { - name: "test with ipv4 destination aws_route", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsRouteResourceType, - Attrs: &resource.Attributes{ - "destination_cidr_block": "0.0.0.0/0", - "route_table_id": "table-id", - }, - }, - want: "Destination: 0.0.0.0/0, Table: table-id", - }, - { - name: "test with ipv6 destination aws_route", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsRouteResourceType, - Attrs: &resource.Attributes{ - "destination_ipv6_cidr_block": "::/0", - "route_table_id": "table-id", - }, - }, - want: "Destination: ::/0, Table: table-id", - }, - { - name: "test empty aws_route53_health_check", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsRoute53HealthCheckResourceType, - Attrs: &resource.Attributes{}, - }, - want: "", - }, - { - name: "test with name tag, fqdn and resource path aws_route53_health_check", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsRoute53HealthCheckResourceType, - Attrs: &resource.Attributes{ - "tags": map[string]interface{}{ - "name": "foo", - }, - "fqdn": "fq.dn", - "resource_path": "/toto", - }, - }, - want: "Fqdn: fq.dn, Name: foo, Path: /toto", - }, - { - name: "test with ip and port aws_route53_health_check", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsRoute53HealthCheckResourceType, - Attrs: &resource.Attributes{ - "tags": map[string]interface{}{ - "name": "foo", - }, - "ip_address": "10.0.0.10", - "port": float64(443), - }, - }, - want: "IpAddress: 10.0.0.10, Name: foo, Port: 443", - }, - { - name: "test with ip, port and resource path aws_route53_health_check", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsRoute53HealthCheckResourceType, - Attrs: &resource.Attributes{ - "tags": map[string]interface{}{ - "name": "foo", - }, - "ip_address": "10.0.0.10", - "port": float64(443), - "resource_path": "/toto", - }, - }, - want: "IpAddress: 10.0.0.10, Name: foo, Path: /toto, Port: 443", - }, - { - name: "test empty aws_route53_record", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsRoute53RecordResourceType, - Attrs: &resource.Attributes{}, - }, - want: "", - }, - { - name: "test with fqdn, type and zoneId aws_route53_record", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsRoute53RecordResourceType, - Attrs: &resource.Attributes{ - "fqdn": "_github-challenge-cloudskiff.cloudskiff.com", - "type": "TXT", - "zone_id": "ZOS30SFDAFTU9", - }, - }, - want: "Fqdn: _github-challenge-cloudskiff.cloudskiff.com, Type: TXT, ZoneId: ZOS30SFDAFTU9", - }, - { - name: "test empty aws_route53_zone", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsRoute53ZoneResourceType, - Attrs: &resource.Attributes{}, - }, - want: "", - }, - { - name: "test with name aws_route53_zone", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsRoute53ZoneResourceType, - Attrs: &resource.Attributes{ - "name": "example.com", - }, - }, - want: "Name: example.com", - }, - { - name: "test empty aws_route_table_association", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsRouteTableAssociationResourceType, - Attrs: &resource.Attributes{}, - }, - want: "", - }, - { - name: "test with gateway aws_route_table_association", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsRouteTableAssociationResourceType, - Attrs: &resource.Attributes{ - "route_table_id": "table-id", - "gateway_id": "gtw-id", - }, - }, - want: "Gateway: gtw-id, Table: table-id", - }, - { - name: "test with subnet aws_route_table_association", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsRouteTableAssociationResourceType, - Attrs: &resource.Attributes{ - "route_table_id": "table-id", - "subnet_id": "subnet-id", - }, - }, - want: "Subnet: subnet-id, Table: table-id", - }, - { - name: "test empty aws_security_group_rule", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsSecurityGroupRuleResourceType, - Attrs: &resource.Attributes{}, - }, - want: "", - }, - { - name: "test ingress_ssh_ipv4 aws_security_group_rule", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsSecurityGroupRuleResourceType, - Attrs: &resource.Attributes{ - "security_group_id": "sg-12345", - "type": "ingress", - "protocol": "tcp", - "from_port": float64(22), - "to_port": float64(22), - "cidr_blocks": []interface{}{"0.0.0.0/0", "1.2.3.4/32"}, - }, - }, - want: "Ports: 22, Protocol: tcp, SecurityGroup: sg-12345, Source: 0.0.0.0/0, 1.2.3.4/32, Type: ingress", - }, - { - name: "test egress_ssh_ipv4 aws_security_group_rule", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsSecurityGroupRuleResourceType, - Attrs: &resource.Attributes{ - "security_group_id": "sg-12345", - "type": "egress", - "protocol": "tcp", - "from_port": float64(22), - "to_port": float64(22), - "cidr_blocks": []interface{}{"0.0.0.0/0", "1.2.3.4/32"}, - }, - }, - want: "Destination: 0.0.0.0/0, 1.2.3.4/32, Ports: 22, Protocol: tcp, SecurityGroup: sg-12345, Type: egress", - }, - { - name: "test ingress_all aws_security_group_rule", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsSecurityGroupRuleResourceType, - Attrs: &resource.Attributes{ - "security_group_id": "sg-12345", - "type": "ingress", - "protocol": "-1", - }, - }, - want: "Protocol: All, SecurityGroup: sg-12345, Type: ingress", - }, - { - name: "test ingress_all_range_0 aws_security_group_rule", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsSecurityGroupRuleResourceType, - Attrs: &resource.Attributes{ - "security_group_id": "sg-12345", - "type": "ingress", - "protocol": "-1", - "from_port": float64(0), - "to_port": float64(0), - }, - }, - want: "Ports: All, Protocol: All, SecurityGroup: sg-12345, Type: ingress", - }, - { - name: "test ingress_all_ipv6 aws_security_group_rule", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsSecurityGroupRuleResourceType, - Attrs: &resource.Attributes{ - "security_group_id": "sg-12345", - "type": "ingress", - "protocol": "-1", - "ipv6_cidr_blocks": []interface{}{"::/0"}, - }, - }, - want: "Protocol: All, SecurityGroup: sg-12345, Source: ::/0, Type: ingress", - }, - { - name: "test ingress_all_prefix aws_security_group_rule", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsSecurityGroupRuleResourceType, - Attrs: &resource.Attributes{ - "security_group_id": "sg-12345", - "type": "ingress", - "protocol": "-1", - "prefix_list_ids": []interface{}{"pl-12345"}, - }, - }, - want: "Protocol: All, SecurityGroup: sg-12345, Source: pl-12345, Type: ingress", - }, - { - name: "test egress_all_source aws_security_group_rule", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsSecurityGroupRuleResourceType, - Attrs: &resource.Attributes{ - "security_group_id": "sg-12345", - "type": "egress", - "protocol": "all", - "source_security_group_id": "sg-67890", - }, - }, - want: "Destination: sg-67890, Protocol: all, SecurityGroup: sg-12345, Type: egress", - }, - { - name: "test empty aws_sns_topic", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsSnsTopicResourceType, - Attrs: &resource.Attributes{}, - }, - want: "", - }, - { - name: "test with name and display name aws_sns_topic", - res: &resource.AbstractResource{ - Id: "foo", - Type: aws.AwsSnsTopicResourceType, - Attrs: &resource.Attributes{ - "name": "foo", - "display_name": "bar", - }, - }, - want: "DisplayName: bar, Name: foo", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - repo := testresource.InitFakeSchemaRepository("aws", "3.19.0") - aws.InitResourcesMetadata(repo) - - abstractResource, ok := tt.res.(*resource.AbstractResource) - if ok { - schema, _ := repo.GetSchema(abstractResource.TerraformType()) - abstractResource.Sch = schema - } - - if got := HumanizeAttribute(tt.res); got != tt.want { - t.Errorf("HumanizeAttribute() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestHumanizeAttribute_Github(t *testing.T) { - tests := []struct { - name string - res AttributesGetter - want string - }{ - { - name: "test empty github_branch_protection", - res: &resource.AbstractResource{ - Id: "foo", - Type: github.GithubBranchProtectionResourceType, - Attrs: &resource.Attributes{}, - }, - want: "Id: foo", - }, - { - name: "test with pattern github_branch_protection", - res: &resource.AbstractResource{ - Id: "foo", - Type: github.GithubBranchProtectionResourceType, - Attrs: &resource.Attributes{ - "pattern": "my-branch", - }, - }, - want: "Branch: my-branch, Id: foo", - }, - { - name: "test with pattern and invalid base64 repo_id github_branch_protection", - res: &resource.AbstractResource{ - Id: "foo", - Type: github.GithubBranchProtectionResourceType, - Attrs: &resource.Attributes{ - "pattern": "my-branch", - "repository_id": "invalid", - }, - }, - want: "Branch: my-branch, Id: foo", - }, - { - name: "test with pattern and valid base64 repo_id github_branch_protection", - res: &resource.AbstractResource{ - Id: "foo", - Type: github.GithubBranchProtectionResourceType, - Attrs: &resource.Attributes{ - "pattern": "my-branch", - "repository_id": "MDEwOlJlcG9zaXRvcnkxMjM0NTY=", - }, - }, - want: "Branch: my-branch, RepoId: 010:Repository123456", - }, - { - name: "test empty github_team", - res: &resource.AbstractResource{ - Id: "foo", - Type: github.GithubTeamResourceType, - Attrs: &resource.Attributes{}, - }, - want: "Id: foo", - }, - { - name: "test with name github_team", - res: &resource.AbstractResource{ - Id: "foo", - Type: github.GithubTeamResourceType, - Attrs: &resource.Attributes{ - "name": "my-org-name", - }, - }, - want: "Id: foo, Name: my-org-name", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - repo := testresource.InitFakeSchemaRepository("github", "4.4.0") - github.InitResourcesMetadata(repo) - - abstractResource, ok := tt.res.(*resource.AbstractResource) - if ok { - schema, _ := repo.GetSchema(abstractResource.TerraformType()) - abstractResource.Sch = schema - } - - if got := HumanizeAttribute(tt.res); got != tt.want { - t.Errorf("HumanizeAttribute() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/resource/resource.go b/pkg/resource/resource.go index 7a2360b7..578cacca 100644 --- a/pkg/resource/resource.go +++ b/pkg/resource/resource.go @@ -40,15 +40,6 @@ func (a *AbstractResource) Attributes() *Attributes { return a.Attrs } -func (a *AbstractResource) HumanReadableAttributes() map[string]string { - var attrs map[string]string - schema := a.Schema() - if schema.HumanReadableAttributesFunc != nil { - attrs = schema.HumanReadableAttributesFunc(a) - } - return attrs -} - type ResourceFactory interface { CreateAbstractResource(ty, id string, data map[string]interface{}) *AbstractResource } diff --git a/test/resource/resource.go b/test/resource/resource.go index e9b698d0..04abaaab 100644 --- a/test/resource/resource.go +++ b/test/resource/resource.go @@ -40,14 +40,6 @@ func (d *FakeResourceStringer) Schema() *resource.Schema { return nil } -func (d *FakeResourceStringer) HumanReadableAttributes() map[string]string { - attrs := make(map[string]string) - if name := d.Attributes().GetString("name"); name != nil && *name != "" { - attrs["Name"] = *name - } - return attrs -} - func (d *FakeResourceStringer) TerraformId() string { return d.Id }