Skip to content

Commit e9dbfa0

Browse files
author
Allan Liu
committed
Initial commit
0 parents  commit e9dbfa0

File tree

7 files changed

+2960
-0
lines changed

7 files changed

+2960
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Ignore compiled binary
2+
cloudformation_s3bucket_cleanup

README.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# cloudformation_s3bucket_cleanup
2+
[![Build Status](https://travis-ci.org/PermissionData/cloudformation_s3bucket_cleanup.svg?branch=master)](https://travis-ci.org/PermissionData/cloudformation_s3bucket_cleanup)
3+
cloudformation_s3bucket_cleanup is tool used for cleaning up retired S3 buckets used by AWS to provision CloudFormation stacks
4+
5+
6+
## Usage
7+
**compile for your platform using go build**
8+
```bash
9+
// Example for 64bit Ubuntu
10+
$ cd ~/cloudformation_s3_bucket
11+
$ GOOS=linux GOARCH=amd64 go build .
12+
```
13+
**set up aws credentials in ~/.aws/credentials**
14+
```bash
15+
$ cat ~/.aws/credentials
16+
[default]
17+
aws_access_key_id = <aws_access_key_id>
18+
aws_secret_access_key = <aws_secret_access_key>
19+
```
20+
**run tool for backing up**
21+
```bash
22+
$ ./cloudformation_s3bucket_cleanup --aws-region us-east-1 --bucketfilter exhibitors3bucket
23+
```
24+
The default value for aws-region is us-east-1; the default value for bucketfilter is exhibitors3bucket. If you want these exact parameters you don't need to use the CLI args.
25+
26+
The bucketfilter is the substring that gets appended on every s3bucket created by AWS when provisioning a CloudFormation stack.
27+
28+
The tool only deletes buckets for stacks that are not in the CREATE_COMPLETE state

cf_s3bucket_cleanup.go

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"strings"
6+
"time"
7+
8+
"github.com/aws/aws-sdk-go/aws"
9+
"github.com/aws/aws-sdk-go/aws/session"
10+
"github.com/aws/aws-sdk-go/service/cloudformation"
11+
"github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface"
12+
"github.com/aws/aws-sdk-go/service/s3"
13+
"github.com/aws/aws-sdk-go/service/s3/s3iface"
14+
)
15+
16+
var (
17+
awsRegion = flag.String(
18+
"aws-region",
19+
"us-east-1",
20+
"AWS region",
21+
)
22+
bucketFilter = flag.String(
23+
"bucket-filter",
24+
"exhibitors3bucket",
25+
"Search critieria for buckets that fall under CF",
26+
)
27+
)
28+
29+
type cfS3BucketCleanup struct {
30+
cfSVC cloudformationiface.CloudFormationAPI
31+
s3SVC s3iface.S3API
32+
stacks []*cloudformation.StackSummary
33+
bucketFilter string
34+
}
35+
36+
func panicErr(err error) {
37+
if err != nil {
38+
panic(err)
39+
}
40+
}
41+
42+
func getSessionConfigs() (*session.Session, *aws.Config) {
43+
return session.New(), &aws.Config{Region: awsRegion}
44+
}
45+
46+
func (c *cfS3BucketCleanup) getAllCfStackNames() {
47+
params := &cloudformation.ListStacksInput{
48+
StackStatusFilter: []*string{
49+
aws.String(cloudformation.StackStatusCreateComplete),
50+
},
51+
}
52+
resp, err := c.cfSVC.ListStacks(params)
53+
panicErr(err)
54+
c.stacks = resp.StackSummaries
55+
}
56+
57+
func checkStackBucketbyName(bucketName string, stackName string) bool {
58+
return strings.Contains(bucketName, stackName)
59+
}
60+
61+
func isCloudformationBucket(bucketName string, bucketFilter string) bool {
62+
return strings.Contains(bucketName, bucketFilter)
63+
}
64+
65+
func checkStackBucketbyDate(bucketDate time.Time, stackDate time.Time) bool {
66+
diff := stackDate.Sub(bucketDate).Seconds()
67+
return diff < 60 && diff > -60
68+
}
69+
70+
func (c *cfS3BucketCleanup) isBucketDeletable(bucket *s3.Bucket) bool {
71+
for _, stack := range c.stacks {
72+
if checkStackBucketbyName(*bucket.Name, *stack.StackName) &&
73+
checkStackBucketbyDate(
74+
*bucket.CreationDate,
75+
*stack.CreationTime,
76+
) {
77+
return false
78+
}
79+
}
80+
return true
81+
}
82+
83+
func (c *cfS3BucketCleanup) getBucketContents(bucket *s3.Bucket) []*s3.Object {
84+
resp, err := c.s3SVC.ListObjects(
85+
&s3.ListObjectsInput{
86+
Bucket: bucket.Name,
87+
},
88+
)
89+
panicErr(err)
90+
return resp.Contents
91+
}
92+
93+
func getObjectIDStruct(objects []*s3.Object) []*s3.ObjectIdentifier {
94+
var result []*s3.ObjectIdentifier
95+
for _, object := range objects {
96+
result = append(
97+
result,
98+
&s3.ObjectIdentifier{
99+
Key: object.Key,
100+
},
101+
)
102+
}
103+
return result
104+
}
105+
106+
func isBucketEmpty(objects []*s3.Object) bool {
107+
return len(objects) <= 0
108+
}
109+
110+
func (c *cfS3BucketCleanup) emptyBucket(
111+
bucket *s3.Bucket,
112+
objects []*s3.Object,
113+
) []*s3.Error {
114+
resp, err := c.s3SVC.DeleteObjects(
115+
&s3.DeleteObjectsInput{
116+
Bucket: bucket.Name,
117+
Delete: &s3.Delete{
118+
Objects: getObjectIDStruct(objects),
119+
},
120+
},
121+
)
122+
panicErr(err)
123+
if resp.Errors == nil {
124+
return []*s3.Error{}
125+
}
126+
return resp.Errors
127+
}
128+
129+
func (c *cfS3BucketCleanup) removeUnusedCFBuckets() []*s3.Error {
130+
var (
131+
errors = []*s3.Error{}
132+
objects []*s3.Object
133+
)
134+
resp, err := c.s3SVC.ListBuckets(&s3.ListBucketsInput{})
135+
panicErr(err)
136+
for _, bucket := range resp.Buckets {
137+
if isCloudformationBucket(*bucket.Name, c.bucketFilter) &&
138+
c.isBucketDeletable(bucket) {
139+
//fmt.Println("This bucket is to be deleted ", *bucket.Name)
140+
objects = c.getBucketContents(bucket)
141+
if !isBucketEmpty(objects) {
142+
errs := c.emptyBucket(bucket, objects)
143+
if len(errs) > 0 {
144+
errors = append(errors, errs...)
145+
continue
146+
}
147+
}
148+
_, err := c.s3SVC.DeleteBucket(
149+
&s3.DeleteBucketInput{
150+
Bucket: bucket.Name,
151+
},
152+
)
153+
panicErr(err)
154+
155+
} //else {
156+
// fmt.Println("This bucket is NOT to be deleted", *bucket.Name)
157+
//}
158+
}
159+
return errors
160+
}

0 commit comments

Comments
 (0)