aws_auditor 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.ruby-version +1 -0
- data/README.md +10 -8
- data/aws_auditor.gemspec +1 -1
- data/lib/aws_auditor/aws.rb +6 -3
- data/lib/aws_auditor/cache_instance.rb +10 -6
- data/lib/aws_auditor/commands/audit.rb +5 -5
- data/lib/aws_auditor/commands/export.rb +2 -0
- data/lib/aws_auditor/commands/inspect.rb +3 -3
- data/lib/aws_auditor/ec2_instance.rb +45 -13
- data/lib/aws_auditor/google.rb +25 -3
- data/lib/aws_auditor/google_sheet.rb +66 -66
- data/lib/aws_auditor/instance_helper.rb +6 -1
- data/lib/aws_auditor/rds_instance.rb +20 -19
- data/lib/aws_auditor/scripts/audit.rb +31 -57
- data/lib/aws_auditor/scripts/export.rb +143 -95
- data/lib/aws_auditor/scripts/inspect.rb +25 -29
- data/lib/aws_auditor/stack.rb +18 -5
- data/lib/aws_auditor/version.rb +1 -1
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d0f543e05c774a62f7dcc400a2cbdb00f3fec473
|
4
|
+
data.tar.gz: 79ae672c65fa1e14cae28394f2dcc042c5dc3211
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c07e87a2609f71d2d5c7a6d35b825c7a657133190a847c2a676dc587c4dfb5ac9240643a0a0b03e75b3488993830263fec942da11257717a95cbff3cff9e1ff8
|
7
|
+
data.tar.gz: cc2b3d0bf48be20e31d01d6d3a636e86318124f92a8daaf7cbed8100e9fd594ea26420227fe5ea03e0acbb47a15f7847abf1e1b84c00f748505245fef424747a
|
data/.gitignore
CHANGED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.0.0-p481
|
data/README.md
CHANGED
@@ -21,7 +21,7 @@ Or install it yourself as:
|
|
21
21
|
## How-to
|
22
22
|
|
23
23
|
### AWS Setup
|
24
|
-
Create a `.aws.yml` file in
|
24
|
+
Create a `.aws.yml` file in your home directory with the following structure.
|
25
25
|
|
26
26
|
```yaml
|
27
27
|
---
|
@@ -34,33 +34,35 @@ account2:
|
|
34
34
|
```
|
35
35
|
|
36
36
|
### Google Setup (optional)
|
37
|
-
You can export audit information to a Google Spreadsheet, but you must first create a `.google.yml` in
|
37
|
+
You can export audit information to a Google Spreadsheet, but you must first follow “Create a client ID and client secret” on [this page](https://developers.google.com/drive/web/auth/web-server) to get a client ID and client secret for OAuth. Then create a `.google.yml` in your home directory with the following structure.
|
38
38
|
|
39
39
|
```yaml
|
40
40
|
---
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
credentials:
|
42
|
+
client_id: 'GOOGLE_CLIENT_ID'
|
43
|
+
client_secret: 'GOOGLE_CLIENT_ID'
|
44
44
|
file:
|
45
45
|
path: 'DESIRED_PATH_TO_FILE' #optional, creates in root directory otherwise
|
46
46
|
name: 'NAME_OF_FILE'
|
47
47
|
```
|
48
48
|
|
49
|
+
## Usage
|
50
|
+
|
49
51
|
To find discrepancies between number of running instances and purchased instances, run:
|
50
52
|
|
51
53
|
$ aws_auditor audit account1
|
52
54
|
|
53
|
-
To list
|
55
|
+
To list information about all running instances in your account, run:
|
54
56
|
|
55
57
|
$ aws_auditor inspect account1
|
56
58
|
|
57
59
|
To export audit information to a Google Spreadsheet, make sure you added a `.google.yml` and run:
|
58
60
|
|
59
|
-
$ aws_auditor export account1
|
61
|
+
$ aws_auditor export -d account1
|
60
62
|
|
61
63
|
## Contributing
|
62
64
|
|
63
|
-
1. Fork it ( https://github.com/
|
65
|
+
1. Fork it ( https://github.com/elliothursh/aws_auditor/fork )
|
64
66
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
65
67
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
66
68
|
4. Push to the branch (`git push origin my-new-feature`)
|
data/aws_auditor.gemspec
CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.add_dependency 'hashie', '~> 3.3'
|
23
23
|
spec.add_dependency 'gli', '~> 2.10'
|
24
24
|
spec.add_dependency 'highline', '~> 1.6'
|
25
|
-
spec.add_dependency 'google_drive', '~> 0.
|
25
|
+
spec.add_dependency 'google_drive', '~> 1.0.0.pre2'
|
26
26
|
|
27
27
|
spec.add_development_dependency "bundler", "~> 1.7"
|
28
28
|
spec.add_development_dependency "rake", "~> 10.0"
|
data/lib/aws_auditor/aws.rb
CHANGED
@@ -23,7 +23,11 @@ module AwsAuditor
|
|
23
23
|
def self.load_config
|
24
24
|
return @config if @config
|
25
25
|
@config = AwsConfig[YAML.load_file(config_path)]
|
26
|
-
|
26
|
+
if @config.has_key? @environment
|
27
|
+
@config = @config[@environment]
|
28
|
+
else
|
29
|
+
puts "Could not find AWS credentials for #{@environment} environment"; exit
|
30
|
+
end
|
27
31
|
@config[:region] ||= 'us-east-1'
|
28
32
|
@config
|
29
33
|
end
|
@@ -37,8 +41,7 @@ module AwsAuditor
|
|
37
41
|
if old_dir != Dir.pwd
|
38
42
|
config_path
|
39
43
|
else
|
40
|
-
puts "Could not find #{FILE_NAMES.join(' or ')}"
|
41
|
-
exit
|
44
|
+
puts "Could not find #{FILE_NAMES.join(' or ')}"; exit
|
42
45
|
end
|
43
46
|
end
|
44
47
|
end
|
@@ -5,6 +5,10 @@ module AwsAuditor
|
|
5
5
|
extend InstanceHelper
|
6
6
|
extend CacheWrapper
|
7
7
|
|
8
|
+
class <<self
|
9
|
+
attr_accessor :instances, :reserved_instances
|
10
|
+
end
|
11
|
+
|
8
12
|
attr_accessor :id, :name, :instance_type, :engine, :count
|
9
13
|
def initialize(cache_instance)
|
10
14
|
@id = cache_instance[:cache_cluster_id] || cache_instance[:reserved_cache_node_id]
|
@@ -19,19 +23,19 @@ module AwsAuditor
|
|
19
23
|
end
|
20
24
|
|
21
25
|
def self.get_instances
|
22
|
-
instances
|
23
|
-
instances.map do |instance|
|
26
|
+
return @instances if @instances
|
27
|
+
@instances = cache.describe_cache_clusters[:cache_clusters].map do |instance|
|
24
28
|
next unless instance[:cache_cluster_status].to_s == 'available'
|
25
29
|
new(instance)
|
26
|
-
end
|
30
|
+
end.compact
|
27
31
|
end
|
28
32
|
|
29
33
|
def self.get_reserved_instances
|
30
|
-
|
31
|
-
|
34
|
+
return @reserved_instances if @reserved_instances
|
35
|
+
@reserved_instances = cache.describe_reserved_cache_nodes[:reserved_cache_nodes].map do |instance|
|
32
36
|
next unless instance[:state].to_s == 'active'
|
33
37
|
new(instance)
|
34
|
-
end
|
38
|
+
end.compact
|
35
39
|
end
|
36
40
|
|
37
41
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
arg :aws_account
|
2
2
|
desc 'Audits Reserved Instance Counts'
|
3
3
|
command 'audit' do |c|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
c.switch [:e, :ec2], :desc => "Only audit EC2 instances"
|
5
|
+
c.switch [:d, :rds], :desc => "Only audit RDS instances"
|
6
|
+
c.switch [:c, :cache], :desc => "Only audit ElastiCache instances"
|
7
|
+
c.switch [:r, :reserved], :desc => "Shows reserved instance counts"
|
8
|
+
c.switch [:i, :instances], :desc => "Shows current instance counts"
|
9
9
|
c.action do |global_options, options, args|
|
10
10
|
require_relative '../scripts/audit'
|
11
11
|
raise ArgumentError, 'You must specify an AWS account' unless args.first
|
@@ -1,6 +1,8 @@
|
|
1
1
|
arg :aws_account
|
2
2
|
desc 'Export an Audit to Google SpreadSheets'
|
3
3
|
command 'export' do |c|
|
4
|
+
c.switch [:c, :csv], :desc => "Exports to CSV"
|
5
|
+
c.switch [:d, :drive], :desc => "Exports to Google Drive"
|
4
6
|
c.action do |global_options, options, args|
|
5
7
|
require_relative '../scripts/export'
|
6
8
|
raise ArgumentError, 'You must specify an AWS account' unless args.first
|
@@ -1,9 +1,9 @@
|
|
1
1
|
arg :aws_account
|
2
2
|
desc 'Reviews Stack Instances'
|
3
3
|
command 'inspect' do |c|
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
c.switch [:e, :ec2], :desc => "Only inspect EC2 instances"
|
5
|
+
c.switch [:d, :rds], :desc => "Only inspect RDS instances"
|
6
|
+
c.switch [:c, :cache], :desc => "Only inspect ElastiCache instances"
|
7
7
|
c.action do |global_options, options, args|
|
8
8
|
require_relative '../scripts/inspect'
|
9
9
|
raise ArgumentError, 'You must specify an AWS account' unless args.first
|
@@ -5,19 +5,42 @@ module AwsAuditor
|
|
5
5
|
extend InstanceHelper
|
6
6
|
extend EC2Wrapper
|
7
7
|
|
8
|
-
|
8
|
+
class <<self
|
9
|
+
attr_accessor :instances, :reserved_instances
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_accessor :id, :name, :platform, :availability_zone, :instance_type, :count, :stack_name
|
9
13
|
def initialize(ec2_instance, count=1)
|
10
14
|
@id = ec2_instance.id
|
15
|
+
@name = nil
|
11
16
|
@platform = platform_helper(ec2_instance)
|
12
17
|
@availability_zone = ec2_instance.availability_zone
|
13
18
|
@instance_type = ec2_instance.instance_type
|
14
19
|
@count = count
|
20
|
+
@stack_name = nil
|
15
21
|
end
|
16
22
|
|
17
23
|
def to_s
|
18
24
|
"#{@platform} #{@availability_zone} #{@instance_type}"
|
19
25
|
end
|
20
26
|
|
27
|
+
def self.get_instances
|
28
|
+
return @instances if @instances
|
29
|
+
@instances = ec2.instances.map do |instance|
|
30
|
+
next unless instance.status.to_s == 'running'
|
31
|
+
new(instance)
|
32
|
+
end.compact
|
33
|
+
get_more_info
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.get_reserved_instances
|
37
|
+
return @reserved_instances if @reserved_instances
|
38
|
+
@reserved_instances = ec2.reserved_instances.map do |ri|
|
39
|
+
next unless ri.state == 'active'
|
40
|
+
new(ri, ri.instance_count)
|
41
|
+
end.compact
|
42
|
+
end
|
43
|
+
|
21
44
|
def platform_helper(ec2_instance)
|
22
45
|
if ec2_instance.class.to_s == 'AWS::EC2::Instance'
|
23
46
|
if ec2_instance.vpc?
|
@@ -41,21 +64,30 @@ module AwsAuditor
|
|
41
64
|
end
|
42
65
|
end
|
43
66
|
end
|
67
|
+
private :platform_helper
|
44
68
|
|
45
|
-
def self.
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
69
|
+
def self.get_more_info
|
70
|
+
get_instances.each do |instance|
|
71
|
+
tags = ec2.client.describe_tags(:filters => [{:name => "resource-id", :values => [instance.id]}])[:tag_set]
|
72
|
+
tags = Hash[tags.map { |tag| [tag[:key], tag[:value]]}.compact]
|
73
|
+
instance.name = tags["Name"]
|
74
|
+
instance.stack_name = tags["opsworks:stack"]
|
75
|
+
end
|
51
76
|
end
|
77
|
+
private_class_method :get_more_info
|
52
78
|
|
53
|
-
def self.
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
79
|
+
def self.bucketize
|
80
|
+
buckets = {}
|
81
|
+
get_instances.map do |instance|
|
82
|
+
name = instance.stack_name || instance.name
|
83
|
+
if name
|
84
|
+
buckets[name] = [] unless buckets.has_key? name
|
85
|
+
buckets[name] << instance
|
86
|
+
else
|
87
|
+
puts "Could not sort #{instance.id}, as it has no stack_name or name"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
buckets.sort_by{|k,v| k }
|
59
91
|
end
|
60
92
|
|
61
93
|
end
|
data/lib/aws_auditor/google.rb
CHANGED
@@ -1,14 +1,36 @@
|
|
1
|
+
require "google/api_client"
|
2
|
+
require "google_drive"
|
3
|
+
|
1
4
|
module AwsAuditor
|
2
5
|
class GoogleConfig < Hash
|
3
6
|
include Hashie::Extensions::IndifferentAccess
|
4
7
|
end
|
5
8
|
|
6
9
|
class Google
|
7
|
-
|
10
|
+
FILE_NAMES = %w[.google.yml]
|
8
11
|
|
9
12
|
def self.configuration
|
10
|
-
|
11
|
-
|
13
|
+
GoogleDrive.login_with_oauth(get_authorization)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.get_authorization
|
17
|
+
creds = load_config[:credentials]
|
18
|
+
client = ::Google::APIClient.new
|
19
|
+
auth = client.authorization
|
20
|
+
auth.client_id = creds[:client_id]
|
21
|
+
auth.client_secret = creds[:client_secret]
|
22
|
+
auth.scope =
|
23
|
+
"https://www.googleapis.com/auth/drive " +
|
24
|
+
"https://docs.google.com/feeds/ " +
|
25
|
+
"https://docs.googleusercontent.com/ " +
|
26
|
+
"https://spreadsheets.google.com/feeds/"
|
27
|
+
auth.redirect_uri = "urn:ietf:wg:oauth:2.0:oob"
|
28
|
+
print("1. If it doesn't automatically open, open this page:\n%s\n\n" % auth.authorization_uri)
|
29
|
+
`open "#{auth.authorization_uri}"`
|
30
|
+
print("2. Enter the authorization code shown in the page: ")
|
31
|
+
auth.code = $stdin.gets.chomp
|
32
|
+
auth.fetch_access_token!
|
33
|
+
access_token = auth.access_token
|
12
34
|
end
|
13
35
|
|
14
36
|
def self.file
|
@@ -1,80 +1,80 @@
|
|
1
1
|
require 'google_drive'
|
2
2
|
|
3
3
|
module AwsAuditor
|
4
|
-
|
5
|
-
|
4
|
+
class GoogleSheet
|
5
|
+
extend GoogleWrapper
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
attr_accessor :sheet, :worksheet, :path
|
8
|
+
def initialize(title, path, environment)
|
9
|
+
@sheet = self.class.create_sheet(title, path)
|
10
|
+
@worksheet = self.class.worksheet(sheet, environment)
|
11
|
+
end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
def write_header(header)
|
14
|
+
worksheet.list.keys = header.unshift('name')
|
15
|
+
worksheet.save
|
16
|
+
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
def write_row(value_hash)
|
19
|
+
worksheet.list.push(value_hash)
|
20
|
+
worksheet.save
|
21
|
+
end
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
def self.first_or_create(title)
|
24
|
+
spreadsheet = google.root_collection.files("title" => title, "title-exact" => true).first
|
25
|
+
spreadsheet ? spreadsheet : google.create_spreadsheet(title)
|
26
|
+
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
28
|
+
#returns a spreadsheet object
|
29
|
+
def self.create_sheet(title, path)
|
30
|
+
folder = go_to_collection(path) if path
|
31
|
+
if folder
|
32
|
+
spreadsheet = folder.files("title" => title, "title-exact" => true).first
|
33
|
+
if spreadsheet
|
34
|
+
return spreadsheet
|
35
|
+
else
|
36
|
+
file = first_or_create(title)
|
37
|
+
folder.add(file)
|
38
|
+
google.root_collection.remove(file)
|
39
|
+
return folder.files("title" => title, "title-exact" => true).first
|
40
|
+
end
|
41
|
+
else
|
42
|
+
first_or_create(title)
|
43
|
+
end
|
44
|
+
end
|
45
45
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
46
|
+
#returns a worksheet object
|
47
|
+
def self.worksheet(spreadsheet, title)
|
48
|
+
worksheet = spreadsheet.worksheet_by_title(title)
|
49
|
+
worksheet ? delete_all_rows(worksheet) : spreadsheet.add_worksheet(title)
|
50
|
+
end
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
52
|
+
#returns a collection object
|
53
|
+
def self.go_to_collection(directory)
|
54
|
+
if directory
|
55
|
+
path = directory.split('/')
|
56
|
+
go_to_subcollection(google.collection_by_title(path.first),path[1..-1])
|
57
|
+
end
|
58
|
+
end
|
59
59
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
60
|
+
#returns a collection object
|
61
|
+
def self.go_to_subcollection(base, subs)
|
62
|
+
puts "Folder doesn't exist in specified path" and exit if base.nil?
|
63
|
+
if subs.empty?
|
64
|
+
return base
|
65
|
+
else
|
66
|
+
base = base.subcollection_by_title(subs.first)
|
67
|
+
go_to_subcollection(base,subs[1..-1])
|
68
|
+
end
|
69
|
+
end
|
70
70
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
71
|
+
def self.delete_all_rows(worksheet)
|
72
|
+
worksheet.list.each do |row|
|
73
|
+
row.clear
|
74
|
+
end
|
75
|
+
worksheet.save
|
76
|
+
worksheet
|
77
|
+
end
|
78
78
|
|
79
|
-
|
79
|
+
end
|
80
80
|
end
|
@@ -1,9 +1,14 @@
|
|
1
1
|
module AwsAuditor
|
2
2
|
module InstanceHelper
|
3
|
+
|
3
4
|
def instance_hash
|
4
5
|
Hash[get_instances.map { |instance| instance.nil? ? next : [instance.id, instance]}.compact]
|
5
6
|
end
|
6
7
|
|
8
|
+
def reserved_instance_hash
|
9
|
+
Hash[get_reserved_instances.map { |instance| instance.nil? ? next : [instance.id, instance]}.compact]
|
10
|
+
end
|
11
|
+
|
7
12
|
def instance_count_hash(instances)
|
8
13
|
instance_hash = Hash.new()
|
9
14
|
instances.each do |instance|
|
@@ -25,4 +30,4 @@ module AwsAuditor
|
|
25
30
|
differences
|
26
31
|
end
|
27
32
|
end
|
28
|
-
end
|
33
|
+
end
|
@@ -5,47 +5,48 @@ module AwsAuditor
|
|
5
5
|
extend InstanceHelper
|
6
6
|
extend RDSWrapper
|
7
7
|
|
8
|
+
class <<self
|
9
|
+
attr_accessor :instances, :reserved_instances
|
10
|
+
end
|
11
|
+
|
8
12
|
attr_accessor :id, :name, :multi_az, :instance_type, :engine, :count
|
9
13
|
def initialize(rds_instance)
|
10
14
|
@id = rds_instance[:db_instance_identifier] || rds_instance[:reserved_db_instances_offering_id]
|
11
15
|
@name = rds_instance[:db_instance_identifier] || rds_instance[:db_name]
|
12
|
-
@multi_az = rds_instance[:multi_az]
|
16
|
+
@multi_az = rds_instance[:multi_az] ? "Multi-AZ" : "Single-AZ"
|
13
17
|
@instance_type = rds_instance[:db_instance_class]
|
14
18
|
@engine = rds_instance[:engine] || rds_instance[:product_description]
|
15
19
|
@count = rds_instance[:db_instance_count] || 1
|
16
20
|
end
|
17
21
|
|
18
22
|
def to_s
|
19
|
-
"#{engine_helper} #{multi_az
|
20
|
-
end
|
21
|
-
|
22
|
-
def multi_az?
|
23
|
-
multi_az ? "Multi-AZ" : "Single-AZ"
|
24
|
-
end
|
25
|
-
|
26
|
-
def engine_helper
|
27
|
-
if engine.downcase.include? "post"
|
28
|
-
return "PostgreSQL"
|
29
|
-
elsif engine.downcase.include? "mysql"
|
30
|
-
return "MySQL"
|
31
|
-
end
|
23
|
+
"#{engine_helper} #{multi_az} #{instance_type}"
|
32
24
|
end
|
33
25
|
|
34
26
|
def self.get_instances
|
35
|
-
instances
|
36
|
-
instances.map do |instance|
|
27
|
+
return @instances if @instances
|
28
|
+
@instances = rds.describe_db_instances[:db_instances].map do |instance|
|
37
29
|
next unless instance[:db_instance_status].to_s == 'available'
|
38
30
|
new(instance)
|
39
|
-
end
|
31
|
+
end.compact
|
40
32
|
end
|
41
33
|
|
42
34
|
def self.get_reserved_instances
|
43
|
-
|
44
|
-
|
35
|
+
return @reserved_instances if @reserved_instances
|
36
|
+
@reserved_instances = rds.describe_reserved_db_instances[:reserved_db_instances].map do |instance|
|
45
37
|
next unless instance[:state].to_s == 'active'
|
46
38
|
new(instance)
|
39
|
+
end.compact
|
40
|
+
end
|
41
|
+
|
42
|
+
def engine_helper
|
43
|
+
if engine.downcase.include? "post"
|
44
|
+
return "PostgreSQL"
|
45
|
+
elsif engine.downcase.include? "mysql"
|
46
|
+
return "MySQL"
|
47
47
|
end
|
48
48
|
end
|
49
|
+
private :engine_helper
|
49
50
|
|
50
51
|
end
|
51
52
|
end
|
@@ -5,70 +5,34 @@ module AwsAuditor
|
|
5
5
|
class Audit
|
6
6
|
extend AWSWrapper
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
if options[:ec2]
|
11
|
-
audit_ec2(options)
|
12
|
-
elsif options[:rds]
|
13
|
-
audit_rds(options)
|
14
|
-
elsif options[:cache]
|
15
|
-
audit_cache(options)
|
16
|
-
else
|
17
|
-
audit_ec2(options)
|
18
|
-
audit_rds(options)
|
19
|
-
audit_cache(options)
|
20
|
-
end
|
21
|
-
|
8
|
+
class <<self
|
9
|
+
attr_accessor :options
|
22
10
|
end
|
23
11
|
|
24
|
-
def self.
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
RDSInstance.instance_count_hash(RDSInstance.get_reserved_instances).each do |key,value|
|
32
|
-
say "<%= color('#{key}: #{value}', :white) %>"
|
33
|
-
end
|
34
|
-
else
|
35
|
-
RDSInstance.compare.each do |key, value|
|
36
|
-
colorize(key,value)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.audit_ec2(options)
|
42
|
-
puts "=============== EC2 ==============="
|
43
|
-
if options[:instances]
|
44
|
-
EC2Instance.instance_count_hash(EC2Instance.get_instances).each do |key,value|
|
45
|
-
say "<%= color('#{key}: #{value}', :white) %>"
|
46
|
-
end
|
47
|
-
elsif options[:reserved]
|
48
|
-
EC2Instance.instance_count_hash(EC2Instance.get_reserved_instances).each do |key,value|
|
49
|
-
say "<%= color('#{key}: #{value}', :white) %>"
|
50
|
-
end
|
51
|
-
else
|
52
|
-
EC2Instance.compare.each do |key,value|
|
53
|
-
colorize(key,value)
|
54
|
-
end
|
55
|
-
end
|
12
|
+
def self.execute(environment, options=nil)
|
13
|
+
aws(environment)
|
14
|
+
@options = options
|
15
|
+
no_selection = options.values.uniq == [false]
|
16
|
+
output("EC2Instance") if options[:ec2] || no_selection
|
17
|
+
output("RDSInstance") if options[:rds] || no_selection
|
18
|
+
output("CacheInstance") if options[:cache] || no_selection
|
56
19
|
end
|
57
20
|
|
58
|
-
def self.
|
59
|
-
|
21
|
+
def self.output(class_type)
|
22
|
+
klass = AwsAuditor.const_get(class_type)
|
23
|
+
print "Gathering info, please wait..."; print "\r"
|
60
24
|
if options[:instances]
|
61
|
-
|
62
|
-
|
63
|
-
|
25
|
+
instances = klass.instance_count_hash(klass.get_instances)
|
26
|
+
puts header(class_type)
|
27
|
+
instances.each{ |key,value| say "<%= color('#{key}: #{value}', :white) %>" }
|
64
28
|
elsif options[:reserved]
|
65
|
-
|
66
|
-
|
67
|
-
|
29
|
+
reserved = klass.instance_count_hash(klass.get_reserved_instances)
|
30
|
+
puts header(class_type)
|
31
|
+
reserved.each{ |key,value| say "<%= color('#{key}: #{value}', :white) %>" }
|
68
32
|
else
|
69
|
-
|
70
|
-
|
71
|
-
|
33
|
+
compared = klass.compare
|
34
|
+
puts header(class_type)
|
35
|
+
compared.each{ |key,value| colorize(key,value) }
|
72
36
|
end
|
73
37
|
end
|
74
38
|
|
@@ -82,6 +46,16 @@ module AwsAuditor
|
|
82
46
|
end
|
83
47
|
end
|
84
48
|
|
49
|
+
def self.header(type, length = 50)
|
50
|
+
type.upcase!.slice! "INSTANCE"
|
51
|
+
half_length = (length - type.length)/2.0 - 1
|
52
|
+
[
|
53
|
+
"*" * length,
|
54
|
+
"*" * half_length.floor + " #{type} " + "*" * half_length.ceil,
|
55
|
+
"*" * length
|
56
|
+
].join("\n")
|
57
|
+
end
|
58
|
+
|
85
59
|
end
|
86
60
|
end
|
87
61
|
end
|
@@ -1,98 +1,146 @@
|
|
1
|
+
require 'csv'
|
1
2
|
require_relative "../google"
|
2
3
|
|
3
4
|
module AwsAuditor
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
end
|
5
|
+
module Scripts
|
6
|
+
class Export
|
7
|
+
extend GoogleWrapper
|
8
|
+
extend AWSWrapper
|
9
|
+
|
10
|
+
class <<self
|
11
|
+
attr_accessor :ec2_instances, :rds_instances, :cache_instances, :options, :file, :keys_hash, :environment
|
12
|
+
end
|
13
|
+
|
14
|
+
CLASS_TYPES = %w[EC2Instance RDSInstance CacheInstance]
|
15
|
+
|
16
|
+
def self.execute(environment, options = nil)
|
17
|
+
@environment = environment
|
18
|
+
(puts "Must specify either --drive or --csv"; exit) unless options[:csv] || options[:drive]
|
19
|
+
aws(environment)
|
20
|
+
print "Gathering info, please wait..."
|
21
|
+
all_keys = get_all_keys
|
22
|
+
all_info = prepare
|
23
|
+
print "\r" + " " * 50 + "\r"
|
24
|
+
|
25
|
+
create_csv(all_keys,all_info) if options[:csv]
|
26
|
+
upload_to_drive(all_keys,all_info) if options[:drive]
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.create_csv(keys, info)
|
30
|
+
CSV.open("#{environment}.csv", "wb") do |csv|
|
31
|
+
csv << ["name",keys].flatten
|
32
|
+
info.each do |hash|
|
33
|
+
csv << all_keys_hash.merge(hash).values
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
`open "#{environment}.csv"`
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.upload_to_drive(keys, info)
|
41
|
+
@file = GoogleSheet.new(Google.file[:name], Google.file[:path], environment)
|
42
|
+
print "Exporting to Google Drive, please wait..."
|
43
|
+
file.write_header(keys)
|
44
|
+
info.each do |value_hash|
|
45
|
+
response = file.worksheet.list.push(value_hash)
|
46
|
+
puts response unless response.is_a? GoogleDrive::ListRow
|
47
|
+
end
|
48
|
+
file.worksheet.save
|
49
|
+
print "\r" + " " * 50 + "\r"
|
50
|
+
puts "Exporting Complete."
|
51
|
+
`open #{file.sheet.human_url}`
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.prepare
|
55
|
+
[get_all_arrays,get_all_counts].flatten
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.get_all_keys
|
59
|
+
return @keys if @keys
|
60
|
+
@keys = [
|
61
|
+
[ec2_reserved_instances.values,ec2_instances.values].flatten.map{ |x| x.to_s }.uniq.sort! { |a,b| a.downcase <=> b.downcase },
|
62
|
+
[rds_reserved_instances.values,rds_instances.values].flatten.map{ |x| x.to_s }.uniq.sort! { |a,b| a.downcase <=> b.downcase },
|
63
|
+
[cache_reserved_instances.values,cache_instances.values].flatten.map{ |x| x.to_s }.uniq.sort! { |a,b| a.downcase <=> b.downcase }
|
64
|
+
].flatten
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.all_keys_hash(name = nil, value = nil)
|
68
|
+
return @keys_hash if @keys_hash && @keys_hash[:name] == name
|
69
|
+
@keys_hash = {:name => name}
|
70
|
+
get_all_keys.each{ |key| @keys_hash[key] = value }
|
71
|
+
@keys_hash
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.get_all_arrays
|
75
|
+
return @all_array if @all_array
|
76
|
+
@all_array = [ec2_array,rds_array,cache_array].flatten
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.ec2_array
|
80
|
+
instance_array = [{name: "OPSWORKS"}]
|
81
|
+
EC2Instance.bucketize.map do |stack_name, stack_instances|
|
82
|
+
instance_array << {:name => stack_name}.merge(EC2Instance.instance_count_hash(stack_instances))
|
83
|
+
end
|
84
|
+
instance_array
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.rds_array
|
88
|
+
instance_array = [{name: "RDS"}]
|
89
|
+
rds_instances.each do |db_name, db|
|
90
|
+
instance_array << Hash({:name => db_name, "#{db.to_s}" => "#{db.count}"})
|
91
|
+
end
|
92
|
+
instance_array
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.cache_array
|
96
|
+
instance_array = [{name: "CACHE"}]
|
97
|
+
cache_instances.each do |cache_name, cache|
|
98
|
+
instance_array << Hash({:name => cache_name, "#{cache.to_s}" => "#{cache.count}"})
|
99
|
+
end
|
100
|
+
instance_array
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.get_all_counts
|
104
|
+
total_array = [{:name => "TOTALS"}]
|
105
|
+
total_array << all_keys_hash("Running Instances").merge(counts(:instance => true))
|
106
|
+
total_array << all_keys_hash("Reserved Instances", 0).merge(counts(:reserved => true))
|
107
|
+
total_array << all_keys_hash("Differences").merge(counts(:compare => true))
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.counts(options = {:instance => false, :reserved => false, :compare => false })
|
111
|
+
CLASS_TYPES.map do |class_type|
|
112
|
+
klass = AwsAuditor.const_get(class_type)
|
113
|
+
instances = klass.instance_count_hash(klass.get_instances) if options[:instance]
|
114
|
+
instances = klass.instance_count_hash(klass.get_reserved_instances) if options[:reserved]
|
115
|
+
instances = klass.compare if options[:compare]
|
116
|
+
instances
|
117
|
+
end.inject(:merge)
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.ec2_instances
|
121
|
+
@ec2_instances ||= EC2Instance.instance_hash
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.ec2_reserved_instances
|
125
|
+
@ec2_reserved_instances ||= EC2Instance.reserved_instance_hash
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.rds_instances
|
129
|
+
@rds_instances ||= RDSInstance.instance_hash
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.rds_reserved_instances
|
133
|
+
@rds_reserved_instances ||= RDSInstance.reserved_instance_hash
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.cache_instances
|
137
|
+
@cache_instances ||= CacheInstance.instance_hash
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.cache_reserved_instances
|
141
|
+
@cache_reserved_instances ||= CacheInstance.reserved_instance_hash
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -6,43 +6,39 @@ module AwsAuditor
|
|
6
6
|
|
7
7
|
def self.execute(environment, options=nil)
|
8
8
|
aws(environment)
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
elsif options[:cache]
|
14
|
-
inspect_caches
|
15
|
-
else
|
16
|
-
puts "You must use a switch. See `aws-auditor inspect --help` for more info."
|
17
|
-
end
|
9
|
+
no_selection = options.values.uniq == [false]
|
10
|
+
output("EC2Instance") if options[:ec2] || no_selection
|
11
|
+
output("RDSInstance") if options[:rds] || no_selection
|
12
|
+
output("CacheInstance") if options[:cache] || no_selection
|
18
13
|
end
|
19
14
|
|
20
|
-
def self.
|
21
|
-
|
22
|
-
|
15
|
+
def self.output(class_type)
|
16
|
+
klass = AwsAuditor.const_get(class_type)
|
17
|
+
print "Gathering info, please wait..."; print "\r"
|
18
|
+
instances = class_type == "EC2Instance" ? klass.bucketize : klass.instance_hash
|
19
|
+
say "<%= color('#{header(class_type)}', :white) %>"
|
20
|
+
instances.each do |key, value|
|
21
|
+
pretty_print(key, klass.instance_count_hash(Array(value)))
|
23
22
|
end
|
24
23
|
end
|
25
24
|
|
26
|
-
def self.
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
25
|
+
def self.header(type, length = 50)
|
26
|
+
type.upcase!.slice! "INSTANCE"
|
27
|
+
half_length = (length - type.length)/2.0 - 1
|
28
|
+
[
|
29
|
+
"*" * length,
|
30
|
+
"*" * half_length.floor + " #{type} " + "*" * half_length.ceil,
|
31
|
+
"*" * length
|
32
|
+
].join("\n")
|
34
33
|
end
|
35
34
|
|
36
|
-
def self.
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
puts "\n"
|
43
|
-
end
|
35
|
+
def self.pretty_print(title, body)
|
36
|
+
puts "======================================="
|
37
|
+
puts "#{title}"
|
38
|
+
puts "======================================="
|
39
|
+
body.each{ |key, value| say "<%= color('#{key}: #{value}', :white) %>" }
|
40
|
+
puts "\n"
|
44
41
|
end
|
45
|
-
|
46
42
|
end
|
47
43
|
end
|
48
44
|
end
|
data/lib/aws_auditor/stack.rb
CHANGED
@@ -5,6 +5,10 @@ module AwsAuditor
|
|
5
5
|
extend OpsWorksWrapper
|
6
6
|
extend EC2Wrapper
|
7
7
|
|
8
|
+
class <<self
|
9
|
+
attr_accessor :instances, :stacks
|
10
|
+
end
|
11
|
+
|
8
12
|
attr_accessor :id, :name, :instances
|
9
13
|
def initialize(aws_stack)
|
10
14
|
@id = aws_stack[:stack_id]
|
@@ -13,9 +17,10 @@ module AwsAuditor
|
|
13
17
|
end
|
14
18
|
|
15
19
|
def get_instances
|
16
|
-
instances
|
17
|
-
instances.map do |instance|
|
20
|
+
return @instances if @instances
|
21
|
+
@instances = self.class.opsworks.describe_instances({stack_id: id})[:instances].map do |instance|
|
18
22
|
next unless instance[:status].to_s == 'online'
|
23
|
+
self.class.all_instances[instance[:ec2_instance_id]].stack_id = id
|
19
24
|
self.class.all_instances[instance[:ec2_instance_id]]
|
20
25
|
end
|
21
26
|
end
|
@@ -35,16 +40,24 @@ module AwsAuditor
|
|
35
40
|
end
|
36
41
|
|
37
42
|
def self.all
|
38
|
-
stacks
|
39
|
-
stacks.data[:stacks].map do |stack|
|
43
|
+
return @stacks if @stacks
|
44
|
+
@stacks = opsworks.describe_stacks.data[:stacks].map do |stack|
|
40
45
|
new(stack)
|
41
|
-
end.sort! { |a,b| a.name.downcase <=> b.name.downcase }
|
46
|
+
end.sort! { |a,b| a.name.downcase <=> b.name.downcase }
|
42
47
|
end
|
43
48
|
|
44
49
|
def self.all_instances
|
45
50
|
@all_instances ||= EC2Instance.instance_hash
|
46
51
|
end
|
47
52
|
|
53
|
+
def self.instances_without_stack
|
54
|
+
all #simply getting all stacks to make sure instance stack_ids is set
|
55
|
+
all_instances.map do |id, instance|
|
56
|
+
next if instance.stack_id
|
57
|
+
instance
|
58
|
+
end.compact
|
59
|
+
end
|
60
|
+
|
48
61
|
end
|
49
62
|
end
|
50
63
|
|
data/lib/aws_auditor/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aws_auditor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Elliot Hursh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-05-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk
|
@@ -72,14 +72,14 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
75
|
+
version: 1.0.0.pre2
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
82
|
+
version: 1.0.0.pre2
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: bundler
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -118,6 +118,7 @@ extensions: []
|
|
118
118
|
extra_rdoc_files: []
|
119
119
|
files:
|
120
120
|
- ".gitignore"
|
121
|
+
- ".ruby-version"
|
121
122
|
- Gemfile
|
122
123
|
- LICENSE.txt
|
123
124
|
- README.md
|
@@ -162,7 +163,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
162
163
|
version: '0'
|
163
164
|
requirements: []
|
164
165
|
rubyforge_project:
|
165
|
-
rubygems_version: 2.
|
166
|
+
rubygems_version: 2.4.5
|
166
167
|
signing_key:
|
167
168
|
specification_version: 4
|
168
169
|
summary: AWS configuration as code
|