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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ab46106d378fae4b5bdeb24caad77cf619c9b5d8
4
- data.tar.gz: 5b793c1b55f6720f199c3fd3ff9a520caee361e9
3
+ metadata.gz: d0f543e05c774a62f7dcc400a2cbdb00f3fec473
4
+ data.tar.gz: 79ae672c65fa1e14cae28394f2dcc042c5dc3211
5
5
  SHA512:
6
- metadata.gz: a1d1dfaf2d515d74a6ec34a03cf5953720f73985277eeacb37e413a0fc9ba10cf5b72f7eb0350ad9c94f58ff6e7dc62404e09ed8aff863b37a1b81d3d9b431bd
7
- data.tar.gz: dcd6f19f8607f7e4c731b1ac89dbc0744fa3ac6a2482f9bd0c875c537def9d5848d4fec0eb3fc544775368db036982465f91d39bef4a07f2b37b281c02bffc32
6
+ metadata.gz: c07e87a2609f71d2d5c7a6d35b825c7a657133190a847c2a676dc587c4dfb5ac9240643a0a0b03e75b3488993830263fec942da11257717a95cbff3cff9e1ff8
7
+ data.tar.gz: cc2b3d0bf48be20e31d01d6d3a636e86318124f92a8daaf7cbed8100e9fd594ea26420227fe5ea03e0acbb47a15f7847abf1e1b84c00f748505245fef424747a
data/.gitignore CHANGED
@@ -13,6 +13,7 @@
13
13
  *.a
14
14
  mkmf.log
15
15
 
16
+ .DS_Store
16
17
  .aws.yml
17
18
  .google.yml
18
19
  *.gem
@@ -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 the root directory with the following structure.
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 the root directory with the following structure.
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
- login:
42
- email: 'GOOGLE_EMAIL_ADDRESS'
43
- password: 'GOOGLE_EMAIL_PASSWORD'
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 running instances for all stacks in your account, run:
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/[my-github-username]/aws_auditor/fork )
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`)
@@ -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.3'
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"
@@ -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
- @config = @config[@environment] if @environment
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 = cache.describe_cache_clusters[:cache_clusters]
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 if instances
30
+ end.compact
27
31
  end
28
32
 
29
33
  def self.get_reserved_instances
30
- instances = cache.describe_reserved_cache_nodes[:reserved_cache_nodes]
31
- instances.map do |instance|
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 if instances
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
- 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"
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
- 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"
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
- attr_accessor :id, :platform, :availability_zone, :instance_type, :count
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.get_instances
46
- instances = ec2.instances
47
- instances.map do |instance|
48
- next unless instance.status.to_s == 'running'
49
- new(instance)
50
- end if instances
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.get_reserved_instances
54
- reserved_instances = ec2.reserved_instances
55
- reserved_instances.map do |ri|
56
- next unless ri.state == 'active'
57
- new(ri, ri.instance_count)
58
- end if reserved_instances
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
@@ -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
- FILE_NAMES = %w[.google.yml]
10
+ FILE_NAMES = %w[.google.yml]
8
11
 
9
12
  def self.configuration
10
- credentials = load_config[:login]
11
- GoogleDrive.login(credentials[:email],credentials[:password])
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
- class GoogleSheet
5
- extend GoogleWrapper
4
+ class GoogleSheet
5
+ extend GoogleWrapper
6
6
 
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
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
- def write_header(header)
14
- worksheet.list.keys = header.unshift('name')
15
- worksheet.save
16
- end
13
+ def write_header(header)
14
+ worksheet.list.keys = header.unshift('name')
15
+ worksheet.save
16
+ end
17
17
 
18
- def write_row(value_hash)
19
- worksheet.list.push(value_hash)
20
- worksheet.save
21
- end
18
+ def write_row(value_hash)
19
+ worksheet.list.push(value_hash)
20
+ worksheet.save
21
+ end
22
22
 
23
- def self.first_or_create(title)
24
- spreadsheet = google.root_collection.files("title" => title, "title-exact" => true)[0]
25
- spreadsheet ? spreadsheet : google.create_spreadsheet(title)
26
- end
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
- #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)[0]
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)[0]
40
- end
41
- else
42
- first_or_create(title)
43
- end
44
- end
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
- #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
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
- #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[0]),path[1..-1])
57
- end
58
- end
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
- #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[0])
67
- go_to_subcollection(base,subs[1..-1])
68
- end
69
- end
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
- def self.delete_all_rows(worksheet)
72
- worksheet.list.each do |row|
73
- row.clear
74
- end
75
- worksheet.save
76
- worksheet
77
- end
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
- end
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?} #{instance_type}"
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 = rds.describe_db_instances[:db_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
- instances = rds.describe_reserved_db_instances[:reserved_db_instances]
44
- instances.map do |instance|
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
- def self.execute(environment, options=nil)
9
- aws(environment)
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.audit_rds(options)
25
- puts "=============== RDS ==============="
26
- if options[:instances]
27
- RDSInstance.instance_count_hash(RDSInstance.get_instances).each do |key,value|
28
- say "<%= color('#{key}: #{value}', :white) %>"
29
- end
30
- elsif options[:reserved]
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.audit_cache(options)
59
- puts "============== CACHE =============="
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
- CacheInstance.instance_count_hash(CacheInstance.get_instances).each do |key,value|
62
- say "<%= color('#{key}: #{value}', :white) %>"
63
- end
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
- CacheInstance.instance_count_hash(CacheInstance.get_reserved_instances).each do |key,value|
66
- say "<%= color('#{key}: #{value}', :white) %>"
67
- end
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
- CacheInstance.compare.each do |key,value|
70
- colorize(key,value)
71
- end
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
- module Scripts
5
- class Export
6
- extend GoogleWrapper
7
- extend AWSWrapper
8
-
9
- def self.execute(environment, options = nil)
10
- aws(environment)
11
- file = GoogleSheet.new(Google.file[:name], Google.file[:path], environment)
12
- file.write_header(get_all_keys)
13
- write_opsworks_stacks(file)
14
- write_rds(file)
15
- write_cache(file)
16
- write_totals(file)
17
- `open #{file.sheet.human_url}`
18
- end
19
-
20
- def self.write_opsworks_stacks(file)
21
- file.write_row({name: "EC2"})
22
- opsworks_stacks.each do |stack|
23
- next if stack.instances.empty?
24
- value_hash = EC2Instance.instance_count_hash(stack.instances)
25
- value_hash[:name] = stack.name
26
- file.write_row(value_hash)
27
- end
28
- end
29
-
30
- def self.write_rds(file)
31
- file.write_row({name: "RDS"})
32
- rds_instances.each do |db|
33
- value_hash = Hash({:name => db.name, :"#{db.to_s}" => "#{db.count}"})
34
- file.write_row(value_hash)
35
- end
36
- end
37
-
38
- def self.write_cache(file)
39
- file.write_row({name: "CACHE"})
40
- cache_instances.each do |cache|
41
- value_hash = Hash({:name => cache.name, :"#{cache.to_s}" => "#{cache.count}"})
42
- file.write_row(value_hash)
43
- end
44
- end
45
-
46
- def self.write_totals(file)
47
- file.write_row({name: "TOTALS"})
48
- instance_counts = get_all_instance_counts.merge({name: "Running Instances"})
49
- file.write_row(instance_counts)
50
- reserved_counts = get_all_reserved_counts.merge({name: "Reserved Instances"})
51
- file.write_row(reserved_counts)
52
- differences = get_difference_counts.merge({name: "Differences"})
53
- file.write_row(differences)
54
- end
55
-
56
- def self.get_all_keys
57
- ec2 = EC2Instance.instance_hash.values.map{ |x| x.to_s }.uniq.sort! { |a,b| a.downcase <=> b.downcase }
58
- rds = RDSInstance.instance_hash.values.map{ |x| x.to_s }.uniq.sort! { |a,b| a.downcase <=> b.downcase }
59
- cache = CacheInstance.instance_hash.values.map{ |x| x.to_s }.uniq.sort! { |a,b| a.downcase <=> b.downcase }
60
- ec2.concat(rds).concat(cache).compact
61
- end
62
-
63
- def self.get_all_instance_counts
64
- ec2 = EC2Instance.instance_count_hash(EC2Instance.get_instances)
65
- rds = RDSInstance.instance_count_hash(RDSInstance.get_instances)
66
- cache = CacheInstance.instance_count_hash(CacheInstance.get_instances)
67
- ec2.merge(rds).merge(cache)
68
- end
69
-
70
- def self.get_all_reserved_counts
71
- ec2 = EC2Instance.instance_count_hash(EC2Instance.get_reserved_instances)
72
- rds = RDSInstance.instance_count_hash(RDSInstance.get_reserved_instances)
73
- cache = CacheInstance.instance_count_hash(CacheInstance.get_reserved_instances)
74
- ec2.merge(rds).merge(cache)
75
- end
76
-
77
- def self.get_difference_counts
78
- ec2 = EC2Instance.compare
79
- rds = RDSInstance.compare
80
- cache = CacheInstance.compare
81
- ec2.merge(rds).merge(cache)
82
- end
83
-
84
- def self.opsworks_stacks
85
- @opsworks_stacks ||= Stack.all
86
- end
87
-
88
- def self.rds_instances
89
- @rds_instances ||= RDSInstance.get_instances
90
- end
91
-
92
- def self.cache_instances
93
- @cache_instances ||= CacheInstance.get_instances
94
- end
95
-
96
- end
97
- end
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
- if options[:ec2]
10
- inspect_stacks
11
- elsif options[:rds]
12
- inspect_dbs
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.inspect_stacks
21
- Stack.all.each do |stack|
22
- stack.pretty_print
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.inspect_dbs
27
- RDSInstance.get_instances.each do |db|
28
- puts "========================"
29
- puts "#{db.name}"
30
- puts "========================"
31
- puts db.to_s
32
- puts "\n"
33
- end
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.inspect_caches
37
- CacheInstance.get_instances.each do |cache|
38
- puts "========================"
39
- puts "#{cache.name}"
40
- puts "========================"
41
- puts cache.to_s
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
@@ -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 = self.class.opsworks.describe_instances({stack_id: id})[: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 = opsworks.describe_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 } if stacks
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
 
@@ -1,3 +1,3 @@
1
1
  module AwsAuditor
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.3"
3
3
  end
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.2
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: 2014-10-21 00:00:00.000000000 Z
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: '0.3'
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: '0.3'
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.2.2
166
+ rubygems_version: 2.4.5
166
167
  signing_key:
167
168
  specification_version: 4
168
169
  summary: AWS configuration as code