@@ -66,6 +66,7 @@ var awsResourceTypes = []awsResourceType{
66
66
dhcpOptions {},
67
67
volumes {},
68
68
addresses {},
69
+ iamRoles {},
69
70
}
70
71
71
72
type awsResource interface {
@@ -75,6 +76,10 @@ type awsResource interface {
75
76
// intended to be globally unique across regions and accounts, so
76
77
// that works.
77
78
ARN () string
79
+
80
+ // ResourceKey() returns a per-resource key, because ARNs might conflict if two objects
81
+ // with the same name are created at different times (e.g. IAM roles)
82
+ ResourceKey () string
78
83
}
79
84
80
85
// awsResourceSet keeps track of the first time we saw a particular
@@ -122,24 +127,24 @@ func (s *awsResourceSet) Save(sess *session.Session, p *s3path) error {
122
127
// on whether it should be deleted. If Mark(r) returns true, the TTL
123
128
// has expired for r and it should be deleted.
124
129
func (s * awsResourceSet ) Mark (r awsResource ) bool {
125
- arn := r .ARN ()
130
+ key := r .ResourceKey ()
126
131
now := time .Now ()
127
132
128
- s .marked [arn ] = true
129
- if t , ok := s .firstSeen [arn ]; ok {
133
+ s .marked [key ] = true
134
+ if t , ok := s .firstSeen [key ]; ok {
130
135
since := now .Sub (t )
131
136
if since > s .ttl {
132
- s .swept = append (s .swept , arn )
137
+ s .swept = append (s .swept , key )
133
138
return true
134
139
}
135
- glog .V (1 ).Infof ("%s: seen for %v" , r . ARN () , since )
140
+ glog .V (1 ).Infof ("%s: seen for %v" , key , since )
136
141
return false
137
142
}
138
- s .firstSeen [arn ] = now
139
- glog .V (1 ).Infof ("%s: first seen" , r . ARN () )
143
+ s .firstSeen [key ] = now
144
+ glog .V (1 ).Infof ("%s: first seen" , key )
140
145
if s .ttl == 0 {
141
146
// If the TTL is 0, it should be deleted now.
142
- s .swept = append (s .swept , arn )
147
+ s .swept = append (s .swept , key )
143
148
return true
144
149
}
145
150
return false
@@ -150,14 +155,14 @@ func (s *awsResourceSet) Mark(r awsResource) bool {
150
155
// resources have been marked.
151
156
func (s * awsResourceSet ) MarkComplete () int {
152
157
var gone []string
153
- for arn := range s .firstSeen {
154
- if ! s .marked [arn ] {
155
- gone = append (gone , arn )
158
+ for key := range s .firstSeen {
159
+ if ! s .marked [key ] {
160
+ gone = append (gone , key )
156
161
}
157
162
}
158
- for _ , arn := range gone {
159
- glog .V (1 ).Infof ("%s: deleted since last run" , arn )
160
- delete (s .firstSeen , arn )
163
+ for _ , key := range gone {
164
+ glog .V (1 ).Infof ("%s: deleted since last run" , key )
165
+ delete (s .firstSeen , key )
161
166
}
162
167
if len (s .swept ) > 0 {
163
168
glog .Errorf ("%d resources swept: %v" , len (s .swept ), s .swept )
@@ -221,6 +226,10 @@ func (i instance) ARN() string {
221
226
return fmt .Sprintf ("arn:aws:ec2:%s:%s:instance/%s" , i .Region , i .Account , i .InstanceID )
222
227
}
223
228
229
+ func (i instance ) ResourceKey () string {
230
+ return i .ARN ()
231
+ }
232
+
224
233
// AutoScalingGroups: https://docs.aws.amazon.com/sdk-for-go/api/service/autoscaling/#AutoScaling.DescribeAutoScalingGroups
225
234
226
235
type autoScalingGroups struct {}
@@ -276,6 +285,10 @@ func (asg autoScalingGroup) ARN() string {
276
285
return asg .ID
277
286
}
278
287
288
+ func (asg autoScalingGroup ) ResourceKey () string {
289
+ return asg .ARN ()
290
+ }
291
+
279
292
// LaunchConfigurations: http://docs.aws.amazon.com/sdk-for-go/api/service/autoscaling/#AutoScaling.DescribeLaunchConfigurations
280
293
281
294
type launchConfigurations struct {}
@@ -317,6 +330,10 @@ func (lc launchConfiguration) ARN() string {
317
330
return lc .ID
318
331
}
319
332
333
+ func (lc launchConfiguration ) ResourceKey () string {
334
+ return lc .ARN ()
335
+ }
336
+
320
337
// Subnets: https://docs.aws.amazon.com/sdk-for-go/api/service/ec2/#EC2.DescribeSubnets
321
338
322
339
type subnets struct {}
@@ -359,6 +376,10 @@ func (sub subnet) ARN() string {
359
376
return fmt .Sprintf ("arn:aws:ec2:%s:%s:subnet/%s" , sub .Region , sub .Account , sub .ID )
360
377
}
361
378
379
+ func (sub subnet ) ResourceKey () string {
380
+ return sub .ARN ()
381
+ }
382
+
362
383
// SecurityGroups: https://docs.aws.amazon.com/sdk-for-go/api/service/ec2/#EC2.DescribeSecurityGroups
363
384
364
385
type securityGroups struct {}
@@ -441,6 +462,10 @@ func (sg securityGroup) ARN() string {
441
462
return fmt .Sprintf ("arn:aws:ec2:%s:%s:security-group/%s" , sg .Region , sg .Account , sg .ID )
442
463
}
443
464
465
+ func (sg securityGroup ) ResourceKey () string {
466
+ return sg .ARN ()
467
+ }
468
+
444
469
// InternetGateways: https://docs.aws.amazon.com/sdk-for-go/api/service/ec2/#EC2.DescribeInternetGateways
445
470
446
471
type internetGateways struct {}
@@ -508,6 +533,10 @@ func (ig internetGateway) ARN() string {
508
533
return fmt .Sprintf ("arn:aws:ec2:%s:%s:internet-gateway/%s" , ig .Region , ig .Account , ig .ID )
509
534
}
510
535
536
+ func (ig internetGateway ) ResourceKey () string {
537
+ return ig .ARN ()
538
+ }
539
+
511
540
// RouteTables: https://docs.aws.amazon.com/sdk-for-go/api/service/ec2/#EC2.DescribeRouteTables
512
541
513
542
type routeTables struct {}
@@ -563,6 +592,10 @@ func (rt routeTable) ARN() string {
563
592
return fmt .Sprintf ("arn:aws:ec2:%s:%s:route-table/%s" , rt .Region , rt .Account , rt .ID )
564
593
}
565
594
595
+ func (rt routeTable ) ResourceKey () string {
596
+ return rt .ARN ()
597
+ }
598
+
566
599
// VPCs: https://docs.aws.amazon.com/sdk-for-go/api/service/ec2/#EC2.DescribeVpcs
567
600
568
601
type vpcs struct {}
@@ -614,6 +647,10 @@ func (vp vpc) ARN() string {
614
647
return fmt .Sprintf ("arn:aws:ec2:%s:%s:vpc/%s" , vp .Region , vp .Account , vp .ID )
615
648
}
616
649
650
+ func (vp vpc ) ResourceKey () string {
651
+ return vp .ARN ()
652
+ }
653
+
617
654
// DhcpOptions: https://docs.aws.amazon.com/sdk-for-go/api/service/ec2/#EC2.DescribeDhcpOptions
618
655
619
656
type dhcpOptions struct {}
@@ -718,6 +755,10 @@ func (dhcp dhcpOption) ARN() string {
718
755
return fmt .Sprintf ("arn:aws:ec2:%s:%s:dhcp-option/%s" , dhcp .Region , dhcp .Account , dhcp .ID )
719
756
}
720
757
758
+ func (dhcp dhcpOption ) ResourceKey () string {
759
+ return dhcp .ARN ()
760
+ }
761
+
721
762
// Volumes: https://docs.aws.amazon.com/sdk-for-go/api/service/ec2/#EC2.DescribeVolumes
722
763
723
764
type volumes struct {}
@@ -757,6 +798,10 @@ func (vol volume) ARN() string {
757
798
return fmt .Sprintf ("arn:aws:ec2:%s:%s:volume/%s" , vol .Region , vol .Account , vol .ID )
758
799
}
759
800
801
+ func (vol volume ) ResourceKey () string {
802
+ return vol .ARN ()
803
+ }
804
+
760
805
// Elastic IPs: https://docs.aws.amazon.com/sdk-for-go/api/service/ec2/#EC2.DescribeAddresses
761
806
762
807
type addresses struct {}
@@ -801,6 +846,57 @@ func (addr address) ARN() string {
801
846
return fmt .Sprintf ("arn:aws:ec2:%s:%s:address/%s" , addr .Region , addr .Account , addr .ID )
802
847
}
803
848
849
+ func (addr address ) ResourceKey () string {
850
+ return addr .ARN ()
851
+ }
852
+
853
+ // IAM Roles
854
+
855
+ type iamRoles struct {}
856
+
857
+ func (iamRoles ) MarkAndSweep (sess * session.Session , acct string , region string , set * awsResourceSet ) error {
858
+ svc := iam .New (sess , & aws.Config {Region : aws .String (region )})
859
+
860
+ var toDelete []* iamRole // Paged call, defer deletion until we have the whole list.
861
+ if err := svc .ListRolesPages (& iam.ListRolesInput {}, func (page * iam.ListRolesOutput , _ bool ) bool {
862
+ for _ , r := range page .Roles {
863
+ l := & iamRole {arn : aws .StringValue (r .Arn ), roleID : aws .StringValue (r .RoleId ), roleName : aws .StringValue (r .RoleName )}
864
+ if set .Mark (l ) {
865
+ glog .Warningf ("%s: deleting %T: %v" , l .ARN (), r , r )
866
+ toDelete = append (toDelete , l )
867
+ }
868
+ }
869
+ return true
870
+ }); err != nil {
871
+ return err
872
+ }
873
+
874
+ for _ , r := range toDelete {
875
+ _ , err := svc .DeleteRole (
876
+ & iam.DeleteRoleInput {
877
+ RoleName : aws .String (r .roleName ),
878
+ })
879
+ if err != nil {
880
+ glog .Warningf ("%v: delete failed: %v" , r .ARN (), err )
881
+ }
882
+ }
883
+ return nil
884
+ }
885
+
886
+ type iamRole struct {
887
+ arn string
888
+ roleID string
889
+ roleName string
890
+ }
891
+
892
+ func (lc iamRole ) ARN () string {
893
+ return lc .arn
894
+ }
895
+
896
+ func (lc iamRole ) ResourceKey () string {
897
+ return lc .roleID + "::" + lc .ARN ()
898
+ }
899
+
804
900
// ARNs (used for uniquifying within our previous mark file)
805
901
806
902
type arn struct {
0 commit comments