Skip to content

Commit

Permalink
Add AwsECSClusterDuplicateNameDetector
Browse files Browse the repository at this point in the history
  • Loading branch information
wata727 committed Jul 22, 2017
1 parent 9c7d4d2 commit b57eb9e
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 0 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,6 @@ mock: prepare
mockgen -source vendor/github.com/aws/aws-sdk-go/service/elbv2/elbv2iface/interface.go -destination mock/elbv2mock.go
mockgen -source vendor/github.com/aws/aws-sdk-go/service/iam/iamiface/interface.go -destination mock/iammock.go
mockgen -source vendor/github.com/aws/aws-sdk-go/service/rds/rdsiface/interface.go -destination mock/rdsmock.go
mockgen -source vendor/github.com/aws/aws-sdk-go/service/ecs/ecsiface/interface.go -destination mock/ecsmock.go

.PHONY: default prepare test build install release clean mock
23 changes: 23 additions & 0 deletions config/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/aws/aws-sdk-go/service/ecs/ecsiface"
"github.com/aws/aws-sdk-go/service/elasticache"
"github.com/aws/aws-sdk-go/service/elasticache/elasticacheiface"
"github.com/aws/aws-sdk-go/service/elb"
Expand All @@ -27,6 +29,7 @@ type AwsClient struct {
Elasticache elasticacheiface.ElastiCacheAPI
Elb elbiface.ELBAPI
Elbv2 elbv2iface.ELBV2API
Ecs ecsiface.ECSAPI
Cache *ResponseCache
}

Expand All @@ -42,6 +45,7 @@ func (c *Config) NewAwsClient() *AwsClient {
client.Elasticache = elasticache.New(s)
client.Elb = elb.New(s)
client.Elbv2 = elbv2.New(s)
client.Ecs = ecs.New(s)

return client
}
Expand Down Expand Up @@ -102,6 +106,7 @@ type ResponseCache struct {
DescribeCacheClustersOutput *elasticache.DescribeCacheClustersOutput
DescribeLoadBalancersOutput *elbv2.DescribeLoadBalancersOutput
DescribeClassicLoadBalancersOutput *elb.DescribeLoadBalancersOutput
DescribeClusterOutput *ecs.DescribeClustersOutput
}

func (c *AwsClient) DescribeImages() (*ec2.DescribeImagesOutput, error) {
Expand Down Expand Up @@ -356,3 +361,21 @@ func (c *AwsClient) DescribeClassicLoadBalancers() (*elb.DescribeLoadBalancersOu
}
return c.Cache.DescribeClassicLoadBalancersOutput, nil
}

func (c *AwsClient) DescribeClusters() (*ecs.DescribeClustersOutput, error) {
if c.Cache.DescribeClusterOutput == nil {
arns, err := c.Ecs.ListClusters(&ecs.ListClustersInput{})
if err != nil {
return nil, err
}

resp, err := c.Ecs.DescribeClusters(&ecs.DescribeClustersInput{
Clusters: arns.ClusterArns,
})
if err != nil {
return nil, err
}
c.Cache.DescribeClusterOutput = resp
}
return c.Cache.DescribeClusterOutput, nil
}
63 changes: 63 additions & 0 deletions detector/aws_ecs_cluster_duplicate_name.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package detector

import (
"fmt"

"github.com/wata727/tflint/issue"
"github.com/wata727/tflint/schema"
)

type AwsECSClusterDuplicateNameDetector struct {
*Detector
clusters map[string]bool
}

func (d *Detector) CreateAwsECSClusterDuplicateNameDetector() *AwsECSClusterDuplicateNameDetector {
nd := &AwsECSClusterDuplicateNameDetector{
Detector: d,
clusters: map[string]bool{},
}
nd.Name = "aws_ecs_cluster_duplicate_name"
nd.IssueType = issue.ERROR
nd.TargetType = "resource"
nd.Target = "aws_ecs_cluster"
nd.DeepCheck = true
return nd
}

func (d *AwsECSClusterDuplicateNameDetector) PreProcess() {
resp, err := d.AwsClient.DescribeClusters()
if err != nil {
d.Logger.Error(err)
d.Error = true
return
}

for _, cluster := range resp.Clusters {
d.clusters[*cluster.ClusterName] = true
}
}

func (d *AwsECSClusterDuplicateNameDetector) Detect(resource *schema.Resource, issues *[]*issue.Issue) {
nameToken, ok := resource.GetToken("name")
if !ok {
return
}
name, err := d.evalToString(nameToken.Text)
if err != nil {
d.Logger.Error(err)
return
}

identityCheckFunc := func(attributes map[string]string) bool { return attributes["name"] == name }
if d.clusters[name] && !d.State.Exists(d.Target, resource.Id, identityCheckFunc) {
issue := &issue.Issue{
Detector: d.Name,
Type: d.IssueType,
Message: fmt.Sprintf("\"%s\" is duplicate name. It must be unique.", name),
Line: nameToken.Pos.Line,
File: nameToken.Pos.Filename,
}
*issues = append(*issues, issue)
}
}
138 changes: 138 additions & 0 deletions detector/aws_ecs_cluster_duplicate_name_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package detector

import (
"reflect"
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/golang/mock/gomock"
"github.com/k0kubun/pp"
"github.com/wata727/tflint/config"
"github.com/wata727/tflint/issue"
"github.com/wata727/tflint/mock"
)

func TestDetectAwsECSDuplicateName(t *testing.T) {
cases := []struct {
Name string
Src string
State string
Response []*ecs.Cluster
Issues []*issue.Issue
}{
{
Name: "Cluster name is duplicate",
Src: `
resource "aws_ecs_cluster" "foo" {
name = "white-hart"
}`,
Response: []*ecs.Cluster{
{
ClusterName: aws.String("white-hart"),
},
{
ClusterName: aws.String("black-hart"),
},
},
Issues: []*issue.Issue{
{
Detector: "aws_ecs_cluster_duplicate_name",
Type: "ERROR",
Message: "\"white-hart\" is duplicate name. It must be unique.",
Line: 3,
File: "test.tf",
},
},
},
{
Name: "Cluster name is unique",
Src: `
resource "aws_ecs_cluster" "foo" {
name = "white-hart"
}`,
Response: []*ecs.Cluster{
{
ClusterName: aws.String("brown-hart"),
},
{
ClusterName: aws.String("black-hart"),
},
},
Issues: []*issue.Issue{},
},
{
Name: "Cluster is duplicate, but exists in state",
Src: `
resource "aws_ecs_cluster" "foo" {
name = "white-hart"
}`,
State: `
{
"modules": [
{
"resources": {
"aws_ecs_cluster.foo": {
"type": "aws_ecs_cluster",
"depends_on": [],
"primary": {
"id": "arn:aws:ecs:us-east-1:hogehoge:cluster/white-hart",
"attributes": {
"id": "arn:aws:ecs:us-east-1:hogehoge:cluster/white-hart",
"name": "white-hart"
},
"meta": {},
"tainted": false
},
"deposed": [],
"provider": ""
}
}
}
]
}
`,
Response: []*ecs.Cluster{
{
ClusterName: aws.String("white-hart"),
},
{
ClusterName: aws.String("black-hart"),
},
},
Issues: []*issue.Issue{},
},
}

for _, tc := range cases {
c := config.Init()
c.DeepCheck = true

awsClient := c.NewAwsClient()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ecsmock := mock.NewMockECSAPI(ctrl)
ecsmock.EXPECT().ListClusters(&ecs.ListClustersInput{}).Return(&ecs.ListClustersOutput{}, nil)
ecsmock.EXPECT().DescribeClusters(&ecs.DescribeClustersInput{}).Return(&ecs.DescribeClustersOutput{
Clusters: tc.Response,
}, nil)
awsClient.Ecs = ecsmock

var issues = []*issue.Issue{}
err := TestDetectByCreatorName(
"CreateAwsECSClusterDuplicateNameDetector",
tc.Src,
tc.State,
c,
awsClient,
&issues,
)
if err != nil {
t.Fatalf("\nERROR: %s", err)
}

if !reflect.DeepEqual(issues, tc.Issues) {
t.Fatalf("\nBad: %s\nExpected: %s\n\ntestcase: %s", pp.Sprint(issues), pp.Sprint(tc.Issues), tc.Name)
}
}
}
1 change: 1 addition & 0 deletions detector/detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ var detectorFactories = []string{
"CreateAwsRouteInvalidInstanceDetector",
"CreateAwsRouteInvalidNetworkInterfaceDetector",
"CreateAwsCloudWatchMetricAlarmInvalidUnitDetector",
"CreateAwsECSClusterDuplicateNameDetector",
"CreateTerraformModulePinnedSourceDetector",
}

Expand Down
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,5 @@ Report these issues if you have specified resource ID, name, etc that already ex
- aws_db_instance_duplicate_identifier
- **AWS ElastiCache Cluster**
- aws_elasticache_cluster_duplicate_id
- **AWS ECS Cluster**
- aws_ecs_cluster_duplicate_name

0 comments on commit b57eb9e

Please sign in to comment.