abide_dev_utils 0.4.2 → 0.5.0

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 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