abide_dev_utils 0.4.2 → 0.5.0

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
  SHA256:
3
- metadata.gz: 71c8c65ce60fc385fdc289f00b98328f35e0b343b397864fea49010683ab274c
4
- data.tar.gz: 1060f161729e0330efadf391bc35f9a84544522cc443364833c56beeec8fb308
3
+ metadata.gz: a27976b9f740b67261fa080eeba927bc6fc98e73ffb592fafdd91d4d612cc51f
4
+ data.tar.gz: 64778a0d476e2f96d70a14ab318b517c597aa3c5e33afd8939173385b013fe6c
5
5
  SHA512:
6
- metadata.gz: 608138ff9fecf9835094848f5d7967b8b9ba87d9da766e943e1ec053936cc605e823befa0e5c893579dd4a8ad9a3b8dff49b4129624e9a338cb5cf0775f32272
7
- data.tar.gz: 3610c0fae0872a7883a5608e703c0a025ff529b39e025315f984c40f6a63bc09206eaa9e874a11a180c2ad392c9afb14cd2eb18974129fecfee8eed4bbb2470e
6
+ metadata.gz: e31df40e6dd34a57156ff517d39fcc036f5655ce2f31bf86b1ebb66754304575b7379e0a3751ae378bc81bc1520dbe095255012dc7e10b24e4f6c3f4a85e6551
7
+ data.tar.gz: c9dd204d55a37d389c0c8a4d3d281310b87241cbbd149ca029a3fa6a38fd8414bcb350467cd348cdd330fe946e6ee75625bfe2828319ad25d9eb8ed5cc37a9a3
data/README.md CHANGED
@@ -29,6 +29,10 @@ Issues and pull requests are welcome!
29
29
 
30
30
  * Create Jira issues in bulk from coverage reports
31
31
 
32
+ ### Puppet Comply Report Generation
33
+
34
+ * Allows you ot programatically generate compliance reports from Puppet Comply
35
+
32
36
  ### Supports Configuration via Local YAML file
33
37
 
34
38
  * Fully configurable via the `~/.abide_dev.yaml` file
@@ -37,6 +37,7 @@ Gem::Specification.new do |spec|
37
37
  spec.add_dependency 'puppet', '>= 6.19'
38
38
  spec.add_dependency 'jira-ruby', '~> 2.1'
39
39
  spec.add_dependency 'ruby-progressbar', '~> 1.11'
40
+ spec.add_dependency 'selenium-webdriver', '~> 4.0.0.beta4'
40
41
 
41
42
  # Dev dependencies
42
43
  spec.add_development_dependency 'bundler'
@@ -3,6 +3,7 @@
3
3
  require 'cmdparse'
4
4
  require 'abide_dev_utils/version'
5
5
  require 'abide_dev_utils/constants'
6
+ require 'abide_dev_utils/cli/comply'
6
7
  require 'abide_dev_utils/cli/puppet'
7
8
  require 'abide_dev_utils/cli/xccdf'
8
9
  require 'abide_dev_utils/cli/test'
@@ -21,6 +22,7 @@ module Abide
21
22
  parser.main_options.banner = ROOT_CMD_BANNER
22
23
  parser.add_command(CmdParse::HelpCommand.new, default: true)
23
24
  parser.add_command(CmdParse::VersionCommand.new(add_switches: true))
25
+ parser.add_command(ComplyCommand.new)
24
26
  parser.add_command(PuppetCommand.new)
25
27
  parser.add_command(XccdfCommand.new)
26
28
  parser.add_command(TestCommand.new)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'abide_dev_utils/config'
4
+
3
5
  module Abide
4
6
  module CLI
5
7
  # @abstract
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'abide_dev_utils/comply'
4
+ require 'abide_dev_utils/cli/abstract'
5
+
6
+ module Abide
7
+ module CLI
8
+ class ComplyCommand < AbideCommand
9
+ CMD_NAME = 'comply'
10
+ CMD_SHORT = 'Commands related to Puppet Comply'
11
+ CMD_LONG = 'Namespace for commands related to Puppet Comply'
12
+ def initialize
13
+ super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: true)
14
+ add_command(ComplyReportCommand.new)
15
+ end
16
+ end
17
+
18
+ class ComplyReportCommand < AbideCommand
19
+ CMD_NAME = 'report'
20
+ CMD_SHORT = 'Generates a yaml report of Puppet Comply scan results'
21
+ CMD_LONG = <<~LONGCMD
22
+ Generates a yaml file that shows the scan results of all nodes in Puppet Comply.
23
+ This command utilizes Selenium WebDriver and the Google Chrome browser to automate
24
+ clicking through the Comply UI and building a report. In order to use this command,
25
+ you MUST have Google Chrome installed and you MUST install the chromedriver binary.
26
+ More info and instructions can be found here:
27
+ https://www.selenium.dev/documentation/en/getting_started_with_webdriver/.
28
+ LONGCMD
29
+ CMD_COMPLY_URL = 'The URL (including https://) of Puppet Comply'
30
+ CMD_COMPLY_PASSWORD = 'The password for Puppet Comply'
31
+ OPT_STATUS_DESC = <<~EODESC
32
+ A comma-separated list of check statuses to ONLY include in the report.
33
+ Valid statuses are: pass, fail, error, notapplicable, notchecked, unknown, informational
34
+ EODESC
35
+ OPT_IGNORE_NODES = <<~EOIGN
36
+ A comma-separated list of node certnames to ignore building reports for. This
37
+ options is mutually exclusive with --only and, if both are set, --only will take precedence
38
+ over this option.
39
+ EOIGN
40
+ OPT_ONLY_NODES = <<~EOONLY
41
+ A comma-separated list of node certnames to ONLY build reports for. No other
42
+ nodes will have reports built for them except the ones specified. This option
43
+ is mutually exclusive with --ignore and, if both are set, this options will
44
+ take precedence over --ignore.
45
+ EOONLY
46
+ def initialize
47
+ super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: false)
48
+ argument_desc(COMPLY_URL: CMD_COMPLY_URL, COMPLY_PASSWORD: CMD_COMPLY_PASSWORD)
49
+ options.on('-o [FILE]', '--out-file [FILE]', 'Path to save the report') { |f| @data[:file] = f }
50
+ options.on('-u [USERNAME]', '--username [USERNAME]', 'The username for Comply (defaults to comply)') do |u|
51
+ @data[:username] = u
52
+ end
53
+ options.on('-s [STATUS]', '--status [STATUS]', OPT_STATUS_DESC) do |s|
54
+ status_array = s.nil? ? nil : s.split(',').map(&:downcase)
55
+ status_array&.map! { |i| i == 'notchecked' ? 'not checked' : i }
56
+ @data[:status] = status_array
57
+ end
58
+ options.on('-O [CERTNAME]', '--only [CERTNAME]', OPT_ONLY_NODES) do |o|
59
+ only_array = o.nil? ? nil : s.split(',').map(&:downcase)
60
+ @data[:only] = only_array
61
+ end
62
+ options.on('-I [CERTNAME]', '--ignore [CERTNAME]', OPT_IGNORE_NODES) do |i|
63
+ ignore_array = i.nil? ? nil : i.split(',').map(&:downcase)
64
+ @data[:ignore] = ignore_array
65
+ end
66
+ end
67
+
68
+ def help_arguments
69
+ <<~ARGHELP
70
+ Arguments:
71
+ COMPLY_URL #{CMD_COMPLY_URL}
72
+ COMPLY_PASSWORD #{CMD_COMPLY_PASSWORD}
73
+
74
+ ARGHELP
75
+ end
76
+
77
+ def execute(comply_url = nil, comply_password = nil)
78
+ Abide::CLI::VALIDATE.filesystem_path(`command -v chromedriver`.strip)
79
+ conf = config_section('comply')
80
+ comply_url = conf.fetch(:url) if comply_url.nil?
81
+ comply_password = comply_password.nil? ? conf.fetch(:password, Abide::CLI::PROMPT.password) : comply_password
82
+ username = @data.fetch(:username, nil).nil? ? conf.fetch(:username, 'comply') : @data[:username]
83
+ status = @data.fetch(:status, nil).nil? ? conf.fecth(:status, nil) : @data[:status]
84
+ ignorelist = @data.fetch(:ignore, nil).nil? ? conf.fetch(:ignore, nil) : @data[:ignore]
85
+ onlylist = @data.fetch(:only, nil).nil? ? conf.fetch(:only, nil) : @data[:only]
86
+ report = AbideDevUtils::Comply.scan_report(comply_url,
87
+ comply_password,
88
+ username: username,
89
+ status: status,
90
+ ignorelist: ignorelist,
91
+ onlylist: onlylist)
92
+ outfile = @data.fetch(:file, nil).nil? ? conf.fetch(:report_path, 'comply_scan_report.yaml') : @data[:file]
93
+ Abide::CLI::OUTPUT.yaml(report, file: outfile)
94
+ end
95
+ end
96
+ end
97
+ end
@@ -59,8 +59,8 @@ module Abide
59
59
  def execute(issue)
60
60
  client = JIRA.client(options: {})
61
61
  issue = client.Issue.find(issue)
62
- console = @data[:file].nil? ? true : false
63
- out_json = issue.attrs.select { |_,v| !v.nil? || !v.empty? }
62
+ console = @data[:file].nil?
63
+ out_json = issue.attrs.select { |_, v| !v.nil? || !v.empty? }
64
64
  Abide::CLI::OUTPUT.json(out_json, console: console, file: @data[:file])
65
65
  end
66
66
  end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'selenium-webdriver'
5
+ require 'abide_dev_utils/output'
6
+
7
+ module AbideDevUtils
8
+ module Comply
9
+ def self.scan_report(url, password, username: 'comply', status: nil, ignorelist: nil, onlylist: nil)
10
+ begin
11
+ AbideDevUtils::Output.simple 'Starting headless Chrome...'
12
+ options = Selenium::WebDriver::Chrome::Options.new
13
+ options.args = %w[
14
+ --headless
15
+ --test-type
16
+ --disable-gpu
17
+ --no-first-run
18
+ --no-default-browser-check
19
+ --ignore-certificate-errors
20
+ --start-maximized
21
+ ]
22
+ driver = Selenium::WebDriver.for :chrome, options: options
23
+ driver.get(url)
24
+ bypass_ssl_warning_page(driver)
25
+ AbideDevUtils::Output.simple "Logging into Comply at #{url}..."
26
+ login_to_comply(driver, username: username, password: password)
27
+ AbideDevUtils::Output.simple 'Finding nodes with scan reports...'
28
+ links = find_node_report_links(driver)
29
+ AbideDevUtils::Output.simple 'Building scan reports, this may take a while...'
30
+ build_report(driver, links, status: status, ignorelist: ignorelist, onlylist: onlylist)
31
+ ensure
32
+ driver.quit
33
+ end
34
+ end
35
+
36
+ def self.ignore_no_such_element
37
+ begin
38
+ yield
39
+ rescue Selenium::WebDriver::Error::NoSuchElementError => e
40
+ AbideDevUtils::Output.simple "Ignored exception #{e}", stream: $stderr
41
+ end
42
+ end
43
+
44
+ def self.wait_on(timeout = 10)
45
+ Selenium::WebDriver::Wait.new(timeout: timeout).until do
46
+ yield
47
+ end
48
+ end
49
+
50
+ def self.bypass_ssl_warning_page(driver)
51
+ ignore_no_such_element do
52
+ driver.find_element(id: 'details-button').click
53
+ driver.find_element(id: 'proceed-link').click
54
+ end
55
+ end
56
+
57
+ def self.login_to_comply(driver, username: 'comply', password: 'compliance')
58
+ wait_on { driver.find_element(id: 'username') }
59
+ driver.find_element(id: 'username').send_keys username
60
+ driver.find_element(id: 'password').send_keys password
61
+ driver.find_element(id: 'kc-login').click
62
+ end
63
+
64
+ def self.find_node_report_links(driver)
65
+ wait_on { driver.find_element(class: 'metric-containers-failed-hosts-count') }
66
+ hosts = driver.find_element(class: 'metric-containers-failed-hosts-count')
67
+ table = hosts.find_element(class: 'rc-table')
68
+ table_body = table.find_element(tag_name: 'tbody')
69
+ wait_on { table_body.find_element(tag_name: 'a') }
70
+ table_body.find_elements(tag_name: 'a')
71
+ end
72
+
73
+ def self.build_report(driver, links, status: nil, ignorelist: nil, onlylist: nil)
74
+ all_checks = {}
75
+ original_window = driver.window_handle
76
+ links.each do |link|
77
+ if !onlylist.nil? && !onlylist.empty?
78
+ next unless onlylist.include?(link.text)
79
+ elsif !ignorelist.nil? && !ignorelist.empty?
80
+ next if ignorelist.include?(link.text)
81
+ end
82
+ begin
83
+ node_name = link.text
84
+ progress = AbideDevUtils::Output.progress title: "Builingd report for #{node_name}", total: nil
85
+ link_url = link.attribute('href')
86
+ driver.manage.new_window(:tab)
87
+ wait_on { driver.window_handles.length == 2 }
88
+ progress.increment
89
+ driver.switch_to.window driver.window_handles[1]
90
+ driver.get(link_url)
91
+ wait_on { driver.find_element(class: 'details-scan-info') }
92
+ progress.increment
93
+ wait_on { driver.find_element(class: 'details-table') }
94
+ progress.increment
95
+ report = {}
96
+ report['scan_results'] = {}
97
+ scan_info_table = driver.find_element(class: 'details-scan-info')
98
+ scan_info_table_rows = scan_info_table.find_elements(tag_name: 'tr')
99
+ progress.increment
100
+ check_table_body = driver.find_element(tag_name: 'tbody')
101
+ check_table_rows = check_table_body.find_elements(tag_name: 'tr')
102
+ progress.increment
103
+ scan_info_table_rows.each do |row|
104
+ key = row.find_element(tag_name: 'h5').text
105
+ value = row.find_element(tag_name: 'strong').text
106
+ report[key.downcase.gsub(/:/, '').gsub(/ /, '_')] = value
107
+ progress.increment
108
+ end
109
+ check_table_rows.each do |row|
110
+ chk_objs = row.find_elements(tag_name: 'td')
111
+ chk_objs.map!(&:text)
112
+ if status.nil? || status.include?(chk_objs[1].downcase)
113
+ report['scan_results'][chk_objs[0][/^[0-9.]+/, 0]] = {
114
+ 'name' => chk_objs[0].gsub(/\n/, ' '),
115
+ 'status' => chk_objs[1]
116
+ }
117
+ end
118
+ progress.increment
119
+ end
120
+ all_checks[node_name] = report
121
+ driver.close
122
+ AbideDevUtils::Output.simple "Created report for #{node_name}"
123
+ ensure
124
+ driver.switch_to.window original_window
125
+ end
126
+ end
127
+ all_checks
128
+ end
129
+ end
130
+ end
@@ -13,6 +13,13 @@ module AbideDevUtils
13
13
  h.transform_keys(&:to_sym)
14
14
  end
15
15
 
16
+ def to_h(path = DEFAULT_PATH)
17
+ return {} unless File.file?(path)
18
+
19
+ h = YAML.safe_load(File.open(path), [Symbol])
20
+ h.transform_keys(&:to_sym)
21
+ end
22
+
16
23
  def self.config_section(section, path = DEFAULT_PATH)
17
24
  h = to_h(path)
18
25
  s = h.fetch(section.to_sym, nil)
@@ -21,8 +28,20 @@ module AbideDevUtils
21
28
  s.transform_keys(&:to_sym)
22
29
  end
23
30
 
31
+ def config_section(section, path = DEFAULT_PATH)
32
+ h = to_h(path)
33
+ s = h.fetch(section.to_sym, nil)
34
+ return {} if s.nil?
35
+
36
+ s.transform_keys(&:to_sym)
37
+ end
38
+
24
39
  def self.fetch(key, default = nil, path = DEFAULT_PATH)
25
40
  to_h(path).fetch(key, default)
26
41
  end
42
+
43
+ def fetch(key, default = nil, path = DEFAULT_PATH)
44
+ to_h(path).fetch(key, default)
45
+ end
27
46
  end
28
47
  end
@@ -135,6 +135,16 @@ module AbideDevUtils
135
135
  end
136
136
  end
137
137
 
138
+ # def self.new_issues_from_comply_report(client, project, report, dry_run: false)
139
+ # dr_prefix = dry_run ? 'DRY RUN: ' : ''
140
+ # i_attrs = all_project_issues_attrs(project)
141
+ # rep_sums = summaries_from_coverage_report(report)
142
+ # rep_sums.each do |k, v|
143
+ # next if summary_exist?(k, i_attrs)
144
+
145
+ # progress = AbideDevUtils::Output.progress(title: "#{dr_prefix}Creating Tasks", total: nil)
146
+ # v.each do |s|
147
+
138
148
  def self.merge_options(options)
139
149
  config.merge(options)
140
150
  end
@@ -167,6 +177,11 @@ module AbideDevUtils
167
177
  summaries.transform_keys { |k| "#{COV_PARENT_SUMMARY_PREFIX}#{benchmark}-#{k}"}
168
178
  end
169
179
 
180
+ # def self.summaries_from_comply_report(report)
181
+ # summaries = {}
182
+ # report.each do |_, v|
183
+ # end
184
+
170
185
  class Dummy
171
186
  def attrs
172
187
  { 'fields' => {
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AbideDevUtils
4
- VERSION = "0.4.2"
4
+ VERSION = "0.5.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abide_dev_utils
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Heston Snodgrass
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-05 00:00:00.000000000 Z
11
+ date: 2021-06-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '1.11'
83
+ - !ruby/object:Gem::Dependency
84
+ name: selenium-webdriver
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 4.0.0.beta4
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 4.0.0.beta4
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: bundler
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -275,10 +289,12 @@ files:
275
289
  - lib/abide_dev_utils.rb
276
290
  - lib/abide_dev_utils/cli.rb
277
291
  - lib/abide_dev_utils/cli/abstract.rb
292
+ - lib/abide_dev_utils/cli/comply.rb
278
293
  - lib/abide_dev_utils/cli/jira.rb
279
294
  - lib/abide_dev_utils/cli/puppet.rb
280
295
  - lib/abide_dev_utils/cli/test.rb
281
296
  - lib/abide_dev_utils/cli/xccdf.rb
297
+ - lib/abide_dev_utils/comply.rb
282
298
  - lib/abide_dev_utils/config.rb
283
299
  - lib/abide_dev_utils/constants.rb
284
300
  - lib/abide_dev_utils/errors.rb