aws_auditor 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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