sport_ngin_aws_auditor 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.octopolo.yml +4 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.soyuz.yml +13 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.markdown +30 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +103 -0
- data/Rakefile +2 -0
- data/bin/sport-ngin-aws-auditor +27 -0
- data/lib/sport_ngin_aws_auditor/aws.rb +44 -0
- data/lib/sport_ngin_aws_auditor/cache_instance.rb +69 -0
- data/lib/sport_ngin_aws_auditor/commands/audit.rb +17 -0
- data/lib/sport_ngin_aws_auditor/commands/export.rb +11 -0
- data/lib/sport_ngin_aws_auditor/commands/inspect.rb +12 -0
- data/lib/sport_ngin_aws_auditor/config.rb +51 -0
- data/lib/sport_ngin_aws_auditor/convenience_wrappers.rb +59 -0
- data/lib/sport_ngin_aws_auditor/ec2_instance.rb +119 -0
- data/lib/sport_ngin_aws_auditor/google.rb +62 -0
- data/lib/sport_ngin_aws_auditor/google_sheet.rb +80 -0
- data/lib/sport_ngin_aws_auditor/instance_helper.rb +71 -0
- data/lib/sport_ngin_aws_auditor/notify_slack.rb +31 -0
- data/lib/sport_ngin_aws_auditor/output.rb +13 -0
- data/lib/sport_ngin_aws_auditor/rds_instance.rb +78 -0
- data/lib/sport_ngin_aws_auditor/scripts/audit.rb +124 -0
- data/lib/sport_ngin_aws_auditor/scripts/export.rb +146 -0
- data/lib/sport_ngin_aws_auditor/scripts/inspect.rb +44 -0
- data/lib/sport_ngin_aws_auditor/stack.rb +63 -0
- data/lib/sport_ngin_aws_auditor/version.rb +3 -0
- data/lib/sport_ngin_aws_auditor.rb +14 -0
- data/spec/spec_helper.rb +45 -0
- data/spec/sport_ngin_aws_auditor/aws_spec.rb +48 -0
- data/spec/sport_ngin_aws_auditor/cache_instance_spec.rb +125 -0
- data/spec/sport_ngin_aws_auditor/config_spec.rb +44 -0
- data/spec/sport_ngin_aws_auditor/ec2_instance_spec.rb +181 -0
- data/spec/sport_ngin_aws_auditor/notify_slack_spec.rb +33 -0
- data/spec/sport_ngin_aws_auditor/rds_instance_spec.rb +140 -0
- data/sport_ngin_aws_auditor.gemspec +33 -0
- metadata +251 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7d0c635ba58b86cec68263f3c929d54ffbede978
|
4
|
+
data.tar.gz: ff8c7fb16a94ef122280a08396eaf27edb79c32f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 49491ebb5acbf0c6df12525bf54c070e7caaf2936ba21e911e9665e5efca353f324d8b71ab80a2ea53a6c51d728155d0e6a1f8c6c6a5f2118727e17945da121c
|
7
|
+
data.tar.gz: f6311b455eb7ba7e7c0e04c4562195bab4e209715e5e4498f5ac6344930ea6b8bc64927953313582eb87766271a00d1c376f1e5da224cad4de0f3743f8cb200b
|
data/.gitignore
ADDED
data/.octopolo.yml
ADDED
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
sport_ngin_aws_auditor
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.2.5
|
data/.soyuz.yml
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
defaults:
|
2
|
+
deploy_cmd: gem push *.gem
|
3
|
+
before_deploy_cmds:
|
4
|
+
- /usr/local/bin/op tag-release
|
5
|
+
- sed -i "" -e "s/\".*/\"$(git tag| sort -n -t. -k1,1 -k2,2 -k3,3 | tail -1 | sed s/v//)\"/" lib/sport_ngin_aws_auditor/version.rb
|
6
|
+
- git add lib/sport_ngin_aws_auditor/version.rb
|
7
|
+
- git commit -m "Version Bump" && git push
|
8
|
+
- gem build sport_ngin_aws_auditor.gemspec
|
9
|
+
after_deploy_cmds:
|
10
|
+
- rm *.gem
|
11
|
+
environments:
|
12
|
+
-
|
13
|
+
rubygems: {}
|
data/.travis.yml
ADDED
data/CHANGELOG.markdown
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#### v3.0.2
|
2
|
+
#### v3.0.1
|
3
|
+
#### v3.0.0
|
4
|
+
* Rename gem directories and modules
|
5
|
+
|
6
|
+
> Emma Sax: Brian Bergstrom: https://github.com/sportngin/sport_ngin_aws_auditor/pull/6
|
7
|
+
|
8
|
+
#### v2.1.0
|
9
|
+
* Adding option to print audit results to Slack channel
|
10
|
+
|
11
|
+
> Emma Sax, Matt Krieger: Brian Bergstrom: https://github.com/sportngin/aws_auditor/pull/4
|
12
|
+
|
13
|
+
* Adding option to print audit results to Slack channel
|
14
|
+
|
15
|
+
> Emma Sax, Matt Krieger: Brian Bergstrom: https://github.com/sportngin/aws_auditor/pull/4
|
16
|
+
|
17
|
+
#### v2.0.0
|
18
|
+
* Adding enhancements for taking no-reserved-instance tag into consideration during audit
|
19
|
+
|
20
|
+
> Emma Sax: Brian Bergstrom: https://github.com/sportngin/aws_auditor/pull/2
|
21
|
+
|
22
|
+
#### v1.0.0
|
23
|
+
* Upgrading aws-sdk version from v1 to v2
|
24
|
+
|
25
|
+
> Emma Sax: Brian Bergstrom: https://github.com/sportngin/aws_auditor/pull/3
|
26
|
+
|
27
|
+
* First tests, Travis CI, MFA support, and fog file compatibility
|
28
|
+
|
29
|
+
> Brian Bergstrom: Emma Sax: https://github.com/sportngin/aws_auditor/pull/1
|
30
|
+
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Elliot Hursh
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# SportNginAwsAuditor
|
2
|
+
|
3
|
+
Audits your AWS accounts to find discrepancies between the number of running instances and purchased reserved instances.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'sport_ngin_aws_auditor'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install sport_ngin_aws_auditor
|
20
|
+
|
21
|
+
## How-to
|
22
|
+
|
23
|
+
### AWS Setup
|
24
|
+
Create an `~/.aws/credentials` file that should have the following structure:
|
25
|
+
|
26
|
+
```
|
27
|
+
[ACCOUNT 1]
|
28
|
+
aws_access_key_id = [AWS ACCESS KEY]
|
29
|
+
aws_secret_access_key = [SECRET ACCESS KEY]
|
30
|
+
|
31
|
+
[ACCOUNT 2]
|
32
|
+
aws_access_key_id = [AWS ACCESS KEY]
|
33
|
+
aws_secret_access_key = [SECRET ACCESS KEY]
|
34
|
+
|
35
|
+
[ACCOUNT 3]
|
36
|
+
aws_access_key_id = [AWS ACCESS KEY]
|
37
|
+
aws_secret_access_key = [SECRET ACCESS KEY]
|
38
|
+
```
|
39
|
+
|
40
|
+
### Google Setup (optional)
|
41
|
+
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.
|
42
|
+
|
43
|
+
```yaml
|
44
|
+
---
|
45
|
+
credentials:
|
46
|
+
client_id: 'GOOGLE_CLIENT_ID'
|
47
|
+
client_secret: 'GOOGLE_CLIENT_ID'
|
48
|
+
file:
|
49
|
+
path: 'DESIRED_PATH_TO_FILE' # optional, creates in root directory otherwise
|
50
|
+
name: 'NAME_OF_FILE'
|
51
|
+
```
|
52
|
+
|
53
|
+
## Usage
|
54
|
+
|
55
|
+
### The Audit Command
|
56
|
+
|
57
|
+
To find discrepancies between number of running instances and purchased instances, run:
|
58
|
+
|
59
|
+
$ sport_ngin_aws_auditor audit account1
|
60
|
+
|
61
|
+
Any running instances that are not matched with a reserved instance with show up as yellow (with the negative number indicating the amount), the reserved instances that are not matched with an running instance will show up in red (with the positive number indicating the amount), and any reserved instances and running instances that match will show up in green. Any instances in blue with asteriks have a special tag that can either be specified in the audit command or will be defaulted to `no-reserved-instance`.
|
62
|
+
|
63
|
+
To specify your own tag name, run:
|
64
|
+
|
65
|
+
$ sport_ngin_aws_auditor audit --tag=your_custom_tag account1
|
66
|
+
|
67
|
+
If you don't want to use any tag at all, run:
|
68
|
+
|
69
|
+
$ sport_ngin_aws_auditor audit --no_tag account1
|
70
|
+
|
71
|
+
To print a condensed version of the discrepancies to a Slack account (instead of printing to the terminal), run:
|
72
|
+
|
73
|
+
$ sport_ngin_aws_auditor audit --slack account1
|
74
|
+
|
75
|
+
For this option to use a designated channel, username, icon/emoji, and webhook, set up a global config file (called `.aws_auditor.yml`) in your home directory. The webhook urls for slack can be obtained [here](https://api.slack.com/incoming-webhooks). The config file should look something like this:
|
76
|
+
|
77
|
+
```
|
78
|
+
slack:
|
79
|
+
username: [AN AWESOME USERNAME]
|
80
|
+
icon_url: [AN AWESOME IMAGE]
|
81
|
+
channel: "#[AN SUPER COOL CHANNEL]"
|
82
|
+
webhook: [YOUR WEBHOOK URL]
|
83
|
+
```
|
84
|
+
|
85
|
+
### The Inspect Command
|
86
|
+
|
87
|
+
To list information about all running instances in your account, run:
|
88
|
+
|
89
|
+
$ sport_ngin_aws_auditor inspect account1
|
90
|
+
|
91
|
+
### The Export Command
|
92
|
+
|
93
|
+
To export audit information to a Google Spreadsheet, make sure you added a `.google.yml` and run:
|
94
|
+
|
95
|
+
$ sport_ngin_aws_auditor export -d account1
|
96
|
+
|
97
|
+
## Contributing
|
98
|
+
|
99
|
+
1. Fork it (https://github.com/sportngin/sport_ngin_aws_auditor/fork)
|
100
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
101
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
102
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
103
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'gli'
|
4
|
+
require_relative '../lib/sport_ngin_aws_auditor'
|
5
|
+
|
6
|
+
include GLI::App
|
7
|
+
|
8
|
+
program_desc 'Sport Ngin AWS Auditor'
|
9
|
+
version SportNginAwsAuditor::VERSION
|
10
|
+
|
11
|
+
wrap_help_text :verbatim
|
12
|
+
|
13
|
+
flag [:config], :desc => 'SportNginAwsAuditor config file path', :default_value => SportNginAwsAuditor::DefaultPaths.config
|
14
|
+
|
15
|
+
program_long_desc """
|
16
|
+
DOCUMENTATION
|
17
|
+
"""
|
18
|
+
|
19
|
+
commands_from File.expand_path(File.dirname(__FILE__) + '/../lib/sport_ngin_aws_auditor/commands')
|
20
|
+
|
21
|
+
pre do |global,command,options,args|
|
22
|
+
SportNginAwsAuditor::Config.load(global[:config])
|
23
|
+
SportNginAwsAuditor::Config.merge! global
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
exit run(ARGV)
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require 'yaml'
|
3
|
+
require 'hashie'
|
4
|
+
|
5
|
+
module SportNginAwsAuditor
|
6
|
+
class AwsConfig < Hash
|
7
|
+
include Hashie::Extensions::IndifferentAccess
|
8
|
+
end
|
9
|
+
|
10
|
+
class AWSSDK
|
11
|
+
def self.authenticate(environment)
|
12
|
+
shared_credentials = Aws::SharedCredentials.new(profile_name: environment)
|
13
|
+
Aws.config.update({region: 'us-east-1', credentials: shared_credentials})
|
14
|
+
|
15
|
+
iam = Aws::IAM::Client.new
|
16
|
+
|
17
|
+
# this will be an array of 0 or 1 because iam.list_mfa_devices.mfa_devices will only return 0 or 1 device per user;
|
18
|
+
# if user doesn't have MFA enabled, then this loop won't even execute
|
19
|
+
iam.list_mfa_devices.mfa_devices.each do |mfadevice|
|
20
|
+
mfa_serial_number = mfadevice.serial_number
|
21
|
+
mfa_token = Output.ask("Enter MFA token: "){ |q| q.validate = /^\d{6}$/ }
|
22
|
+
session_credentials_hash = get_session(mfa_token,
|
23
|
+
mfa_serial_number,
|
24
|
+
shared_credentials.credentials.access_key_id,
|
25
|
+
shared_credentials.credentials.secret_access_key).credentials
|
26
|
+
|
27
|
+
session_credentials = Aws::Credentials.new(session_credentials_hash.access_key_id,
|
28
|
+
session_credentials_hash.secret_access_key,
|
29
|
+
session_credentials_hash.session_token)
|
30
|
+
Aws.config.update({region: 'us-east-1', credentials: session_credentials})
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.get_session(mfa_token, mfa_serial_number, access_key_id, secret_access_key)
|
35
|
+
return @session if @session
|
36
|
+
sts = Aws::STS::Client.new(access_key_id: access_key_id,
|
37
|
+
secret_access_key: secret_access_key,
|
38
|
+
region: 'us-east-1')
|
39
|
+
@session = sts.get_session_token(duration_seconds: 3600,
|
40
|
+
serial_number: mfa_serial_number,
|
41
|
+
token_code: mfa_token)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require_relative './instance_helper'
|
2
|
+
|
3
|
+
module SportNginAwsAuditor
|
4
|
+
class CacheInstance
|
5
|
+
extend InstanceHelper
|
6
|
+
extend CacheWrapper
|
7
|
+
extend AWSWrapper
|
8
|
+
|
9
|
+
class << self
|
10
|
+
attr_accessor :instances, :reserved_instances
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :id, :name, :instance_type, :engine, :count, :tag_value
|
14
|
+
def initialize(cache_instance, account_id=nil, tag_name=nil, cache=nil)
|
15
|
+
if cache_instance.class.to_s == "Aws::ElastiCache::Types::ReservedCacheNode"
|
16
|
+
self.id = cache_instance.reserved_cache_node_id
|
17
|
+
self.name = cache_instance.reserved_cache_node_id
|
18
|
+
self.instance_type = cache_instance.cache_node_type
|
19
|
+
self.engine = cache_instance.product_description
|
20
|
+
self.count = cache_instance.cache_node_count
|
21
|
+
elsif cache_instance.class.to_s == "Aws::ElastiCache::Types::CacheCluster"
|
22
|
+
self.id = cache_instance.cache_cluster_id
|
23
|
+
self.name = cache_instance.cache_cluster_id
|
24
|
+
self.instance_type = cache_instance.cache_node_type
|
25
|
+
self.engine = cache_instance.engine
|
26
|
+
self.count = cache_instance.num_cache_nodes
|
27
|
+
|
28
|
+
if tag_name
|
29
|
+
region = cache_instance.preferred_availability_zone.split(//).first(9).join
|
30
|
+
region = "us-east-1" if region == "Multiple"
|
31
|
+
arn = "arn:aws:elasticache:#{region}:#{account_id}:cluster:#{self.id}"
|
32
|
+
|
33
|
+
# go through to see if the tag we're looking for is one of them
|
34
|
+
cache.list_tags_for_resource(resource_name: arn).tag_list.each do |tag|
|
35
|
+
if tag.key == tag_name
|
36
|
+
self.tag_value = tag.value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
"#{engine} #{instance_type}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.get_instances(tag_name=nil)
|
48
|
+
return @instances if @instances
|
49
|
+
account_id = get_account_id
|
50
|
+
@instances = cache.describe_cache_clusters.cache_clusters.map do |instance|
|
51
|
+
next unless instance.cache_cluster_status.to_s == 'available'
|
52
|
+
new(instance, account_id, tag_name, cache)
|
53
|
+
end.compact
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.get_reserved_instances
|
57
|
+
return @reserved_instances if @reserved_instances
|
58
|
+
@reserved_instances = cache.describe_reserved_cache_nodes.reserved_cache_nodes.map do |instance|
|
59
|
+
next unless instance.state.to_s == 'active'
|
60
|
+
new(instance)
|
61
|
+
end.compact
|
62
|
+
end
|
63
|
+
|
64
|
+
def no_reserved_instance_tag_value
|
65
|
+
@tag_value
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
arg :aws_account
|
2
|
+
desc 'Audits Reserved Instance Counts'
|
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"
|
9
|
+
c.flag [:t, :tag], :default_value => "no-reserved-instance", :desc => "Read a tag and group separately during audit"
|
10
|
+
c.switch [:n, :no_tag], :desc => "Ignore all tags during audit"
|
11
|
+
c.switch [:s, :slack], :desc => "Will print condensed version of audit to a Slack channel"
|
12
|
+
c.action do |global_options, options, args|
|
13
|
+
require_relative '../scripts/audit'
|
14
|
+
raise ArgumentError, 'You must specify an AWS account' unless args.first
|
15
|
+
SportNginAwsAuditor::Scripts::Audit.execute(args.first, options)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
arg :aws_account
|
2
|
+
desc 'Export an Audit to Google SpreadSheets'
|
3
|
+
command 'export' do |c|
|
4
|
+
c.switch [:c, :csv], :desc => "Exports to CSV"
|
5
|
+
c.switch [:d, :drive], :desc => "Exports to Google Drive"
|
6
|
+
c.action do |global_options, options, args|
|
7
|
+
require_relative '../scripts/export'
|
8
|
+
raise ArgumentError, 'You must specify an AWS account' unless args.first
|
9
|
+
SportNginAwsAuditor::Scripts::Export.execute(args.first, options)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
arg :aws_account
|
2
|
+
desc 'Reviews Stack Instances'
|
3
|
+
command 'inspect' do |c|
|
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
|
+
c.action do |global_options, options, args|
|
8
|
+
require_relative '../scripts/inspect'
|
9
|
+
raise ArgumentError, 'You must specify an AWS account' unless args.first
|
10
|
+
SportNginAwsAuditor::Scripts::Inspect.execute(args.first,options)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'hashie'
|
3
|
+
|
4
|
+
module SportNginAwsAuditor
|
5
|
+
|
6
|
+
class DefaultPaths
|
7
|
+
class << self
|
8
|
+
def config
|
9
|
+
File.join(self.home,'.aws_auditor.yml')
|
10
|
+
end
|
11
|
+
|
12
|
+
def home
|
13
|
+
ENV['HOME'] ? ENV['HOME'] : "."
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Config
|
19
|
+
class << self
|
20
|
+
|
21
|
+
def config
|
22
|
+
config_data.to_hash
|
23
|
+
end
|
24
|
+
|
25
|
+
def load(path)
|
26
|
+
if File.exist?(path)
|
27
|
+
load_config(path)
|
28
|
+
return config
|
29
|
+
else
|
30
|
+
return {}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def config_data
|
35
|
+
@config_data ||= Hashie::Mash.new
|
36
|
+
end
|
37
|
+
private :config_data
|
38
|
+
|
39
|
+
def method_missing(method, args=false)
|
40
|
+
config_data.send(method, args)
|
41
|
+
end
|
42
|
+
private :method_missing
|
43
|
+
|
44
|
+
def load_config(file)
|
45
|
+
YAML.load_file(file).each{ |key,value| config_data.assign_property(key, value) }
|
46
|
+
end
|
47
|
+
private :load_config
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require_relative './aws'
|
2
|
+
require_relative './google'
|
3
|
+
|
4
|
+
module SportNginAwsAuditor
|
5
|
+
attr_accessor :creds
|
6
|
+
|
7
|
+
module AWSWrapper
|
8
|
+
attr_accessor :aws, :account_id
|
9
|
+
|
10
|
+
def aws(environment)
|
11
|
+
SportNginAwsAuditor::AWSSDK.authenticate(environment)
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_account_id
|
15
|
+
@account_id ||= Aws::STS::Client.new.get_caller_identity.account
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module EC2Wrapper
|
20
|
+
attr_accessor :ec2
|
21
|
+
|
22
|
+
def ec2
|
23
|
+
@ec2 ||= Aws::EC2::Client.new
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module OpsWorksWrapper
|
28
|
+
attr_accessor :opsworks
|
29
|
+
|
30
|
+
def opsworks
|
31
|
+
@opsworks ||= Aws::OpsWorks::Client.new
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module RDSWrapper
|
36
|
+
attr_accessor :rds
|
37
|
+
|
38
|
+
def rds
|
39
|
+
@rds ||= Aws::RDS::Client.new
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module CacheWrapper
|
44
|
+
attr_accessor :cache
|
45
|
+
|
46
|
+
def cache
|
47
|
+
@cache ||= Aws::ElastiCache::Client.new
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
module GoogleWrapper
|
52
|
+
attr_accessor :google
|
53
|
+
|
54
|
+
def google
|
55
|
+
@google ||= SportNginAwsAuditor::Google.configuration
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require_relative './instance_helper'
|
2
|
+
|
3
|
+
module SportNginAwsAuditor
|
4
|
+
class EC2Instance
|
5
|
+
extend InstanceHelper
|
6
|
+
extend EC2Wrapper
|
7
|
+
|
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, :tag_value
|
13
|
+
def initialize(ec2_instance, tag_name, count=1)
|
14
|
+
if ec2_instance.class.to_s == "Aws::EC2::Types::ReservedInstances"
|
15
|
+
self.id = ec2_instance.reserved_instances_id
|
16
|
+
self.name = nil
|
17
|
+
self.platform = platform_helper(ec2_instance)
|
18
|
+
self.availability_zone = ec2_instance.availability_zone
|
19
|
+
self.instance_type = ec2_instance.instance_type
|
20
|
+
self.count = count
|
21
|
+
self.stack_name = nil
|
22
|
+
elsif ec2_instance.class.to_s == "Aws::EC2::Types::Instance"
|
23
|
+
self.id = ec2_instance.instance_id
|
24
|
+
self.name = nil
|
25
|
+
self.platform = platform_helper(ec2_instance)
|
26
|
+
self.availability_zone = ec2_instance.placement.availability_zone
|
27
|
+
self.instance_type = ec2_instance.instance_type
|
28
|
+
self.count = count
|
29
|
+
self.stack_name = nil
|
30
|
+
|
31
|
+
# go through to see if the tag we're looking for is one of them
|
32
|
+
if tag_name
|
33
|
+
ec2_instance.tags.each do |tag|
|
34
|
+
if tag.key == tag_name
|
35
|
+
self.tag_value = tag.value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
"#{platform} #{availability_zone} #{instance_type}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.get_instances(tag_name=nil)
|
47
|
+
return @instances if @instances
|
48
|
+
@instances = ec2.describe_instances.reservations.map do |reservation|
|
49
|
+
reservation.instances.map do |instance|
|
50
|
+
next unless instance.state.name == 'running'
|
51
|
+
new(instance, tag_name)
|
52
|
+
end.compact
|
53
|
+
end.flatten.compact
|
54
|
+
get_more_info
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.get_reserved_instances
|
58
|
+
return @reserved_instances if @reserved_instances
|
59
|
+
@reserved_instances = ec2.describe_reserved_instances.reserved_instances.map do |ri|
|
60
|
+
next unless ri.state == 'active'
|
61
|
+
new(ri, nil, ri.instance_count)
|
62
|
+
end.compact
|
63
|
+
end
|
64
|
+
|
65
|
+
def no_reserved_instance_tag_value
|
66
|
+
@tag_value
|
67
|
+
end
|
68
|
+
|
69
|
+
def platform_helper(ec2_instance)
|
70
|
+
if ec2_instance.class.to_s == "Aws::EC2::Types::Instance"
|
71
|
+
if ec2_instance.vpc_id
|
72
|
+
return 'VPC'
|
73
|
+
elsif ec2_instance.platform
|
74
|
+
if ec2_instance.platform.downcase.include? 'windows'
|
75
|
+
return 'Windows'
|
76
|
+
else
|
77
|
+
return 'Linux'
|
78
|
+
end
|
79
|
+
else
|
80
|
+
return 'Linux'
|
81
|
+
end
|
82
|
+
elsif ec2_instance.class.to_s == "Aws::EC2::Types::ReservedInstances"
|
83
|
+
if ec2_instance.product_description.downcase.include? 'vpc'
|
84
|
+
return 'VPC'
|
85
|
+
elsif ec2_instance.product_description.downcase.include? 'windows'
|
86
|
+
return 'Windows'
|
87
|
+
else
|
88
|
+
return 'Linux'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
private :platform_helper
|
93
|
+
|
94
|
+
def self.get_more_info
|
95
|
+
get_instances.each do |instance|
|
96
|
+
tags = ec2.describe_tags(:filters => [{:name => "resource-id", :values => [instance.id]}]).tags
|
97
|
+
tags = Hash[tags.map { |tag| [tag[:key], tag[:value]]}.compact]
|
98
|
+
instance.name = tags["Name"]
|
99
|
+
instance.stack_name = tags["opsworks:stack"]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
private_class_method :get_more_info
|
103
|
+
|
104
|
+
def self.bucketize
|
105
|
+
buckets = {}
|
106
|
+
get_instances.map do |instance|
|
107
|
+
name = instance.stack_name || instance.name
|
108
|
+
if name
|
109
|
+
buckets[name] = [] unless buckets.has_key? name
|
110
|
+
buckets[name] << instance
|
111
|
+
else
|
112
|
+
puts "Could not sort #{instance.id}, as it has no stack_name or name"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
buckets.sort_by{|k,v| k }
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|