trusted-advisor-status 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/trusted-advisor-status +15 -0
- data/bin/wipe-trusted-advisor-history +9 -0
- data/lib/hash_util.rb +23 -0
- data/lib/results_dao.rb +84 -0
- data/lib/results_differencer.rb +69 -0
- data/lib/trusted_advisor_status.rb +135 -0
- metadata +80 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 629b80755ac7f3bb7ddf9f9a28495c274a0ecd14
|
4
|
+
data.tar.gz: f8335b6cc73fe74e0e45c556fdf9f3d4751d18e9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1337b5c13fa5ce08df3638123367a1473489df23cf53828327bba5fcad2519415c89018a088c315369a22a48063189906e2a537d22d9347d2aec40cdb45c7916
|
7
|
+
data.tar.gz: ae6ca68c5c3305b2b2a3e1e7c86ecae36e198ee1b35d004196ad5b4adc44671ab195ccb0e738134b0602370a8119034613c29653904776413f4b49c7b1d897de
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'trollop'
|
3
|
+
require 'trusted_advisor_status'
|
4
|
+
|
5
|
+
opts = Trollop::options do
|
6
|
+
opt :categories, '', type: :strings, required: false, default: %w(security performance)
|
7
|
+
opt :fail_on_warn, '', type: :boolean, required: false, default: false, conflicts: :fail_on_error
|
8
|
+
opt :fail_on_error, '', type: :boolean, required: false, default: false, conflicts: :fail_on_warn
|
9
|
+
opt :delta_name, 'Given a name here, the results will be the delta of results already stored against the name', type: :string, required: false
|
10
|
+
end
|
11
|
+
|
12
|
+
exit TrustedAdvisorStatus.new.check_status categories: opts[:categories],
|
13
|
+
fail_on_warn: opts[:fail_on_warn],
|
14
|
+
fail_on_error: opts[:fail_on_error],
|
15
|
+
delta_name: opts[:delta_name]
|
@@ -0,0 +1,9 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'trollop'
|
3
|
+
require 'results_dao'
|
4
|
+
|
5
|
+
opts = Trollop::options do
|
6
|
+
opt :delta_name, 'Given a name here, the results will be the delta of results already stored against the name', type: :string, required: true
|
7
|
+
end
|
8
|
+
|
9
|
+
ResultsDAO.new.nuke_results opts[:delta_name]
|
data/lib/hash_util.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module HashUtil
|
2
|
+
|
3
|
+
def self.stringify_keys(hash)
|
4
|
+
new_hash = {}
|
5
|
+
hash.each do |k,v|
|
6
|
+
if v.is_a? Hash
|
7
|
+
new_hash[k.to_s] = stringify_keys(v)
|
8
|
+
elsif v.is_a? Array
|
9
|
+
new_array = v.map do |element|
|
10
|
+
if element.is_a? Hash
|
11
|
+
stringify_keys(element)
|
12
|
+
else
|
13
|
+
element
|
14
|
+
end
|
15
|
+
end
|
16
|
+
new_hash[k.to_s] = new_array
|
17
|
+
else
|
18
|
+
new_hash[k.to_s] = v
|
19
|
+
end
|
20
|
+
end
|
21
|
+
new_hash
|
22
|
+
end
|
23
|
+
end
|
data/lib/results_dao.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
|
3
|
+
class ResultsDAO
|
4
|
+
|
5
|
+
def nuke_results(delta_name)
|
6
|
+
table = dynamo_db.table(delta_name)
|
7
|
+
|
8
|
+
table.delete
|
9
|
+
|
10
|
+
client.wait_until(:table_not_exists, table_name: delta_name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def retrieve_prior_results(delta_name:)
|
14
|
+
if table_exists? delta_name
|
15
|
+
table = dynamo_db.table(delta_name)
|
16
|
+
|
17
|
+
item_response = table.get_item key: {
|
18
|
+
'result_label' => 'previous_result'
|
19
|
+
}
|
20
|
+
unless item_response.item.nil?
|
21
|
+
item = item_response.item['result']
|
22
|
+
if item.nil?
|
23
|
+
raise "Result key must have value in prior results: #{item_response.item}"
|
24
|
+
else
|
25
|
+
item
|
26
|
+
end
|
27
|
+
end
|
28
|
+
else
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def update_prior_result(delta_name:, results:)
|
34
|
+
conditionally_create_table table_name: delta_name
|
35
|
+
|
36
|
+
table = dynamo_db.table(delta_name)
|
37
|
+
result_item = {
|
38
|
+
'result_label' => 'previous_result',
|
39
|
+
'result' => results
|
40
|
+
}
|
41
|
+
|
42
|
+
table.put_item item: result_item
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def conditionally_create_table(table_name:)
|
48
|
+
unless table_exists? table_name
|
49
|
+
dynamo_db.create_table attribute_definitions: [
|
50
|
+
{
|
51
|
+
attribute_name: 'result_label',
|
52
|
+
attribute_type: 'S'
|
53
|
+
}
|
54
|
+
],
|
55
|
+
table_name: table_name,
|
56
|
+
key_schema: [
|
57
|
+
{
|
58
|
+
attribute_name: 'result_label',
|
59
|
+
key_type: 'HASH'
|
60
|
+
}
|
61
|
+
],
|
62
|
+
provisioned_throughput: {
|
63
|
+
read_capacity_units: 1,
|
64
|
+
write_capacity_units: 1
|
65
|
+
}
|
66
|
+
|
67
|
+
client.wait_until(:table_exists, table_name: table_name)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
def table_exists?(table_name)
|
73
|
+
found_table = dynamo_db.tables.find { |table| table.name == table_name }
|
74
|
+
not found_table.nil?
|
75
|
+
end
|
76
|
+
|
77
|
+
def client
|
78
|
+
Aws::DynamoDB::Client.new
|
79
|
+
end
|
80
|
+
|
81
|
+
def dynamo_db
|
82
|
+
Aws::DynamoDB::Resource.new(client: client)
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
class ResultsDifferencer
|
4
|
+
|
5
|
+
def fixed(prior:,
|
6
|
+
current:)
|
7
|
+
|
8
|
+
prior_hash = {}
|
9
|
+
prior.each { |result| prior_hash[result['check_id']] = result }
|
10
|
+
|
11
|
+
current_hash = {}
|
12
|
+
current.each { |result| current_hash[result['check_id']] = result }
|
13
|
+
|
14
|
+
prior_ids = prior.map { |result| result['check_id'] }
|
15
|
+
current_ids = current.map { |result| result['check_id'] }
|
16
|
+
|
17
|
+
fixed_check_ids = prior_ids - current_ids
|
18
|
+
same_check_ids = prior_ids - fixed_check_ids
|
19
|
+
|
20
|
+
delta = []
|
21
|
+
|
22
|
+
same_check_ids.each do |check_id|
|
23
|
+
fixed_resources = prior_hash[check_id]['flagged_resources'] - current_hash[check_id]['flagged_resources']
|
24
|
+
if fixed_resources != []
|
25
|
+
delta_result = prior_hash[check_id].dup
|
26
|
+
delta_result['flagged_resources'] = fixed_resources
|
27
|
+
delta << delta_result
|
28
|
+
else
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
fixed_check_ids.each { |check_id| delta << prior_hash[check_id] }
|
34
|
+
|
35
|
+
delta
|
36
|
+
end
|
37
|
+
|
38
|
+
def new_violations(prior:,
|
39
|
+
current:)
|
40
|
+
|
41
|
+
prior_hash = {}
|
42
|
+
prior.each { |result| prior_hash[result['check_id']] = result }
|
43
|
+
|
44
|
+
current_hash = {}
|
45
|
+
current.each { |result| current_hash[result['check_id']] = result }
|
46
|
+
|
47
|
+
prior_ids = prior.map { |result| result['check_id'] }
|
48
|
+
current_ids = current.map { |result| result['check_id'] }
|
49
|
+
|
50
|
+
new_check_ids = current_ids - prior_ids
|
51
|
+
same_check_ids = current_ids - new_check_ids
|
52
|
+
|
53
|
+
delta = []
|
54
|
+
|
55
|
+
same_check_ids.each do |check_id|
|
56
|
+
new_resources = current_hash[check_id]['flagged_resources'] - prior_hash[check_id]['flagged_resources']
|
57
|
+
if new_resources != []
|
58
|
+
delta_result = current_hash[check_id].dup
|
59
|
+
delta_result['flagged_resources'] = new_resources
|
60
|
+
delta << delta_result
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
new_check_ids.each { |check_id| delta << current_hash[check_id] }
|
65
|
+
|
66
|
+
delta
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require 'json'
|
3
|
+
require_relative 'results_dao'
|
4
|
+
require_relative 'results_differencer'
|
5
|
+
require_relative 'hash_util'
|
6
|
+
|
7
|
+
class TrustedAdvisorStatus
|
8
|
+
|
9
|
+
|
10
|
+
def check_status(categories: %w(security performance),
|
11
|
+
fail_on_warn: false,
|
12
|
+
fail_on_error: false,
|
13
|
+
delta_name: nil)
|
14
|
+
|
15
|
+
results = discover_results(categories: categories,
|
16
|
+
delta_name: delta_name)
|
17
|
+
|
18
|
+
render_results(results)
|
19
|
+
|
20
|
+
if results.is_a? Array
|
21
|
+
new_violations = results
|
22
|
+
elsif results.is_a? Hash
|
23
|
+
new_violations = results['new_violation']
|
24
|
+
end
|
25
|
+
|
26
|
+
if fail_on_error
|
27
|
+
error_found = new_violations.find { |result| result['status'] == 'error' }
|
28
|
+
error_found.nil? ? 0 : 1
|
29
|
+
elsif fail_on_warn
|
30
|
+
warning_fond = new_violations.find { |result| result['status'] == 'error' or result['status'] == 'warning' }
|
31
|
+
warning_fond.nil? ? 0 : 1
|
32
|
+
else
|
33
|
+
0
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def discover_results(categories:, delta_name:)
|
38
|
+
results_dao = ResultsDAO.new
|
39
|
+
|
40
|
+
full_results = not_ok_check_results(categories: categories)
|
41
|
+
if delta_name.nil?
|
42
|
+
full_results
|
43
|
+
else
|
44
|
+
prior_results = results_dao.retrieve_prior_results delta_name: delta_name
|
45
|
+
if prior_results.nil?
|
46
|
+
delta_results = full_results
|
47
|
+
else
|
48
|
+
diff = ResultsDifferencer.new
|
49
|
+
new_violations = diff.new_violations(prior: prior_results,
|
50
|
+
current: full_results)
|
51
|
+
fixes = diff.fixed(prior: prior_results,
|
52
|
+
current: full_results)
|
53
|
+
|
54
|
+
delta_results = {
|
55
|
+
'new_violation' => new_violations,
|
56
|
+
'fixes' => fixes
|
57
|
+
}
|
58
|
+
end
|
59
|
+
results_dao.update_prior_result(delta_name: delta_name, results: full_results)
|
60
|
+
|
61
|
+
delta_results
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# resp.result.check_id #=> String
|
68
|
+
# resp.result.timestamp #=> String
|
69
|
+
# resp.result.status #=> String
|
70
|
+
|
71
|
+
# resp.result.resources_summary.resources_processed #=> Integer
|
72
|
+
# resp.result.resources_summary.resources_flagged #=> Integer
|
73
|
+
# resp.result.resources_summary.resources_ignored #=> Integer
|
74
|
+
# resp.result.resources_summary.resources_suppressed #=> Integer
|
75
|
+
|
76
|
+
# resp.result.flagged_resources #=> Array
|
77
|
+
# resp.result.flagged_resources[0].status #=> String
|
78
|
+
# resp.result.flagged_resources[0].region #=> String
|
79
|
+
# resp.result.flagged_resources[0].resource_id #=> String
|
80
|
+
# resp.result.flagged_resources[0].is_suppressed #=> true/false
|
81
|
+
# resp.result.flagged_resources[0].metadata #=> Array
|
82
|
+
# resp.result.flagged_resources[0].metadata[0] #=> String
|
83
|
+
def not_ok_check_results(categories:)
|
84
|
+
|
85
|
+
# the region is on purpose - support intfc is global, but can't find endpoint outside of us-east-1
|
86
|
+
support = Aws::Support::Client.new region: 'us-east-1'
|
87
|
+
|
88
|
+
describe_trusted_advisor_checks_response = support.describe_trusted_advisor_checks language: 'en'
|
89
|
+
|
90
|
+
if categories.nil?
|
91
|
+
checks = describe_trusted_advisor_checks_response.checks
|
92
|
+
else
|
93
|
+
checks = describe_trusted_advisor_checks_response.checks.select { |check| categories.include? check.category }
|
94
|
+
end
|
95
|
+
|
96
|
+
checks.reduce([]) do |aggregate, check|
|
97
|
+
describe_trusted_advisor_check_result_response = support.describe_trusted_advisor_check_result check_id: check.id,
|
98
|
+
language: 'en'
|
99
|
+
|
100
|
+
if describe_trusted_advisor_check_result_response.result.status != 'ok'
|
101
|
+
aggregate << convert_check_result_into_hash(check.name,
|
102
|
+
describe_trusted_advisor_check_result_response.result)
|
103
|
+
end
|
104
|
+
|
105
|
+
aggregate
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def convert_check_result_into_hash(check_name, check_result)
|
110
|
+
hash_result = check_result.to_h
|
111
|
+
|
112
|
+
hash_result.delete :timestamp
|
113
|
+
hash_result.delete :resources_summary
|
114
|
+
hash_result.delete :category_specific_summary
|
115
|
+
|
116
|
+
hash_result[:description] = check_name
|
117
|
+
|
118
|
+
unless hash_result[:flagged_resources].nil?
|
119
|
+
hash_result[:flagged_resources] = hash_result[:flagged_resources].reject do |flagged_resource|
|
120
|
+
is_suppressed = flagged_resource[:is_suppressed]
|
121
|
+
|
122
|
+
flagged_resource.delete :resource_id
|
123
|
+
flagged_resource.delete :is_suppressed
|
124
|
+
|
125
|
+
is_suppressed
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
HashUtil::stringify_keys(hash_result)
|
130
|
+
end
|
131
|
+
|
132
|
+
def render_results(results)
|
133
|
+
puts JSON.pretty_generate(results)
|
134
|
+
end
|
135
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: trusted-advisor-status
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- someguy
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-03-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: aws-sdk
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.2.17
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.2.17
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: trollop
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.1.2
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.1.2
|
41
|
+
description: Some scripting around the AWS Trusted Advisor interface for convenience
|
42
|
+
in calling from pipeline
|
43
|
+
email:
|
44
|
+
executables:
|
45
|
+
- trusted-advisor-status
|
46
|
+
- wipe-trusted-advisor-history
|
47
|
+
extensions: []
|
48
|
+
extra_rdoc_files: []
|
49
|
+
files:
|
50
|
+
- bin/trusted-advisor-status
|
51
|
+
- bin/wipe-trusted-advisor-history
|
52
|
+
- lib/hash_util.rb
|
53
|
+
- lib/results_dao.rb
|
54
|
+
- lib/results_differencer.rb
|
55
|
+
- lib/trusted_advisor_status.rb
|
56
|
+
homepage: https://github.com/stelligent/trusted-advisor-status
|
57
|
+
licenses: []
|
58
|
+
metadata: {}
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options: []
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 2.1.0
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 2.6.0
|
77
|
+
signing_key:
|
78
|
+
specification_version: 4
|
79
|
+
summary: ''
|
80
|
+
test_files: []
|