trusted-advisor-status 0.0.1

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 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
@@ -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: []