abide_dev_utils 0.2.3 → 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: 5e19e043b233c6842331d4c4883a74d8557b163739e041d910b60b953acddc85
4
- data.tar.gz: cf425a2b3a0c83ecd5e41cbee5d476fc7444e8984f4f03df12f91b36ad8aeee8
3
+ metadata.gz: a27976b9f740b67261fa080eeba927bc6fc98e73ffb592fafdd91d4d612cc51f
4
+ data.tar.gz: 64778a0d476e2f96d70a14ab318b517c597aa3c5e33afd8939173385b013fe6c
5
5
  SHA512:
6
- metadata.gz: 52e685b3d446b7c6256fd0e45d0542dccc77490b75ccc6408e2ba66e8edbc49d76267446940b997a0e3ab6a7449e1c5a03323c98cc66dd34ca58fa87178d4602
7
- data.tar.gz: 558295f45b37110781fcd22bc4cac8a450c520f706933ddc0fe908c4f2ef863b63029603ab3e1df09142a17c6238f3f5514f20e4fefcc1ed6e2a5531abc5a479
6
+ metadata.gz: e31df40e6dd34a57156ff517d39fcc036f5655ce2f31bf86b1ebb66754304575b7379e0a3751ae378bc81bc1520dbe095255012dc7e10b24e4f6c3f4a85e6551
7
+ data.tar.gz: c9dd204d55a37d389c0c8a4d3d281310b87241cbbd149ca029a3fa6a38fd8414bcb350467cd348cdd330fe946e6ee75625bfe2828319ad25d9eb8ed5cc37a9a3
data/.dockerignore ADDED
@@ -0,0 +1 @@
1
+ Gemfile.lock
data/Dockerfile ADDED
@@ -0,0 +1,23 @@
1
+ FROM ruby:2.7.3-alpine
2
+
3
+ ARG version
4
+
5
+ RUN mkdir /extvol && \
6
+ apk update && \
7
+ apk add git build-base
8
+
9
+ VOLUME /extvol
10
+
11
+ WORKDIR /usr/src/app
12
+
13
+ RUN mkdir -p ./lib/abide_dev_utils/
14
+ COPY Gemfile abide_dev_utils.gemspec ./
15
+ COPY lib/abide_dev_utils/version.rb lib/abide_dev_utils
16
+ RUN bundle install
17
+
18
+ COPY . .
19
+
20
+ RUN bundle exec rake build && \
21
+ gem install pkg/abide_dev_utils-${version}.gem
22
+
23
+ ENTRYPOINT [ "abide" ]
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
@@ -128,6 +132,8 @@ Install the gem:
128
132
  * `--absolute-template-dir`, `-A` - Allows you to specify an absolute path with `--template-dir`. This is useful if your template directory is not relative to your module's root directory
129
133
  * `--template-name`, `-n` - Allows you to specify a template name if it is different than the `TYPE` parameter
130
134
  * `--vars`, `-V` - Comma-separated key=value pairs to pass in to the template renderer. This allows you to pass arbitrary values that can be used in your templates.
135
+ * `--spec-template`, `-S` - Path to an ERB template to use for rspec test generation instead of the default
136
+ * `--force`, `-f` - Skips any prompts and executes the command
131
137
 
132
138
  `abide puppet new` exposes a few variables for you to use in your templates by default:
133
139
 
@@ -179,8 +185,10 @@ $ ls manifests
179
185
  init.pp
180
186
  $ abide puppet new control_class 'test_module::controls::test_new_control'
181
187
  Created file /Users/the.dude/test_module/manifests/controls/test_new_control.pp
188
+ Created file /Users/the.dude/test_module/spec/classes/controls/test_new_control_spec.rb
182
189
  $ abide puppet new util_class 'test_module::utils::test_new_util' -v 'testvar1=dude,testvar2=sweet'
183
190
  Created file /Users/the.dude/test_module/manifests/utils/test_new_util.pp
191
+ Created file /Users/the.dude/test_module/spec/classes/utils/test_new_util_spec.rb
184
192
  $ cat manifests/controls/test_new_control.pp
185
193
  # @api private
186
194
  class test_module::controls::test_new_control (
@@ -204,6 +212,9 @@ class test_module::utils::test_new_util (
204
212
 
205
213
  ```
206
214
 
215
+ **NOTE**: You can use two special prefixes on your template files to denote where the rspec test should be generated for that object.
216
+ If the prefix `c-` is used, the test will be generated in the `spec/classes` directory. If the prefix `d-` is used, the test will be generated in the `spec/defines` directory. For example, to create a template for a defined type, name the template something like this: `d-my_defined_type.pp.erb`.
217
+
207
218
  ### XCCDF Command Reference
208
219
 
209
220
  #### to_hiera
@@ -224,6 +235,15 @@ NOTE: When converting XCCDF files to Hiera, control names are sanitized. This me
224
235
  * `--out-file`, `-o` - A path to a file where you would like to save the generated Hiera
225
236
  * `--parent-key-prefix`, `-p` - Allows you to append a prefix to all top-level Hiera keys
226
237
 
238
+ ## Docker
239
+
240
+ A Dockerfile has been provided in this repo for convenience since Ruby environments can be painful to deal with. To abide_dev_utils with Docker:
241
+
242
+ * Build the Dockerfile: `docker build . -t abide_dev_utils --build-arg version=<semver>`
243
+ * Run the commands using the container: `docker run -it abide_dev_utils --help`
244
+ * The container declares a volume for external resources such as files. To use the volume, add the following flag to your `docker run` commands: `-v /path/to/my/files:/extvol`
245
+ * When using the volume, all paths should be absolute based on the root directory `/extvol`
246
+
227
247
  ## Development
228
248
 
229
249
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -37,10 +37,12 @@ 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'
43
44
  spec.add_development_dependency 'rake'
45
+ spec.add_development_dependency 'console'
44
46
  spec.add_development_dependency 'github_changelog_generator'
45
47
  spec.add_development_dependency 'gem-release'
46
48
  spec.add_development_dependency 'rspec', '~> 3.10'
@@ -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
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
+ require 'abide_dev_utils/config'
4
5
  require 'abide_dev_utils/jira'
5
6
 
6
7
  module Abide
7
8
  module CLI
9
+ CONFIG = AbideDevUtils::Config
8
10
  JIRA = AbideDevUtils::Jira
9
11
 
10
12
  class JiraCommand < CmdParse::Command
@@ -57,8 +59,8 @@ module Abide
57
59
  def execute(issue)
58
60
  client = JIRA.client(options: {})
59
61
  issue = client.Issue.find(issue)
60
- console = @data[:file].nil? ? true : false
61
- 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? }
62
64
  Abide::CLI::OUTPUT.json(out_json, console: console, file: @data[:file])
63
65
  end
64
66
  end
@@ -87,6 +87,16 @@ module Abide
87
87
  '--vars [VARNAME=VALUE]',
88
88
  'Allows you to specify comma-separated variable names and values that will be converted into a hash that is available for you to use in your templates'
89
89
  ) { |v| @data[:vars] = v }
90
+ options.on(
91
+ '-S [PATH]',
92
+ '--spec-template [PATH]',
93
+ 'Path to an ERB template to use for rspec test generation instead of the default'
94
+ )
95
+ options.on(
96
+ '-f',
97
+ '--force',
98
+ 'Skips any prompts and executes the command'
99
+ ) { |_| @data[:force] = true }
90
100
  end
91
101
 
92
102
  def execute(type, name)
@@ -97,8 +107,7 @@ module Abide
97
107
  opts: @data,
98
108
  vars: @data.fetch(:vars, '').split(',').map { |i| i.split('=') }.to_h # makes the str a hash
99
109
  )
100
- result = builder.build
101
- Abide::CLI::OUTPUT.simple(result)
110
+ builder.build
102
111
  end
103
112
  end
104
113
  end
@@ -35,10 +35,10 @@ module Abide
35
35
  @litmus_im = [CMD_LIT_BASE, "'litmus:install_module'"]
36
36
  @litmus_ap = [CMD_LIT_BASE, "'litmus:acceptance:parallel'"]
37
37
  @litmus_td = [CMD_LIT_BASE, "'litmus:tear_down'"]
38
- validate_env_and_opts
39
38
  end
40
39
 
41
40
  def execute(suite)
41
+ validate_env_and_opts
42
42
  case suite.downcase
43
43
  when /^a[A-Za-z]*/
44
44
  run_command(@validate)
@@ -27,7 +27,12 @@ module Abide
27
27
  long_desc(CMD_LONG)
28
28
  options.on('-b [TYPE]', '--benchmark-type [TYPE]', 'XCCDF Benchmark type') { |b| @data[:type] = b }
29
29
  options.on('-o [FILE]', '--out-file [FILE]', 'Path to save file') { |f| @data[:file] = f }
30
- options.on('-p [PREFIX]', '--parent-key-prefix [PREFIX]', 'A prefix to append to the parent key') { |p| @data[:parent_key_prefix] = p }
30
+ options.on('-p [PREFIX]', '--parent-key-prefix [PREFIX]', 'A prefix to append to the parent key') do |p|
31
+ @data[:parent_key_prefix] = p
32
+ end
33
+ options.on('-N', '--number-fmt', 'Format Hiera control names based off of control number instead of name.') do
34
+ @data[:num] = true
35
+ end
31
36
  end
32
37
 
33
38
  def execute(xccdf_file)
@@ -40,8 +45,7 @@ module Abide
40
45
 
41
46
  def to_hiera(xccdf_file)
42
47
  xfile = AbideDevUtils::XCCDF.to_hiera(xccdf_file, @data)
43
- console = @data[:file].nil? ? true : false
44
- Abide::CLI::OUTPUT.yaml(xfile, console: console, file: @data[:file])
48
+ Abide::CLI::OUTPUT.yaml(xfile, console: @data[:file].nil?, file: @data[:file])
45
49
  end
46
50
  end
47
51
  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
@@ -7,18 +7,41 @@ module AbideDevUtils
7
7
  DEFAULT_PATH = "#{File.expand_path('~')}/.abide_dev.yaml"
8
8
 
9
9
  def self.to_h(path = DEFAULT_PATH)
10
+ return {} unless File.file?(path)
11
+
12
+ h = YAML.safe_load(File.open(path), [Symbol])
13
+ h.transform_keys(&:to_sym)
14
+ end
15
+
16
+ def to_h(path = DEFAULT_PATH)
17
+ return {} unless File.file?(path)
18
+
10
19
  h = YAML.safe_load(File.open(path), [Symbol])
11
20
  h.transform_keys(&:to_sym)
12
21
  end
13
22
 
14
23
  def self.config_section(section, path = DEFAULT_PATH)
15
24
  h = to_h(path)
16
- s = h[section.to_sym]
25
+ s = h.fetch(section.to_sym, nil)
26
+ return {} if s.nil?
27
+
28
+ s.transform_keys(&:to_sym)
29
+ end
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
+
17
36
  s.transform_keys(&:to_sym)
18
37
  end
19
38
 
20
39
  def self.fetch(key, default = nil, path = DEFAULT_PATH)
21
40
  to_h(path).fetch(key, default)
22
41
  end
42
+
43
+ def fetch(key, default = nil, path = DEFAULT_PATH)
44
+ to_h(path).fetch(key, default)
45
+ end
23
46
  end
24
47
  end
@@ -8,5 +8,9 @@ module AbideDevUtils
8
8
  class XPathSearchError < GenericError
9
9
  @default = 'XPath seach failed to find anything at:'
10
10
  end
11
+
12
+ class StrategyInvalidError < GenericError
13
+ @default = 'Invalid strategy selected. Should be either \'name\' or \'num\''
14
+ end
11
15
  end
12
16
  end
@@ -86,6 +86,8 @@ module AbideDevUtils
86
86
 
87
87
  def self.client(options: {})
88
88
  opts = merge_options(options)
89
+ return client_from_prompts if opts.empty?
90
+
89
91
  opts[:username] = AbideDevUtils::Prompt.username if opts[:username].nil?
90
92
  opts[:password] = AbideDevUtils::Prompt.password if opts[:password].nil?
91
93
  opts[:site] = AbideDevUtils::Prompt.single_line('Jira URL') if opts[:site].nil?
@@ -133,6 +135,16 @@ module AbideDevUtils
133
135
  end
134
136
  end
135
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
+
136
148
  def self.merge_options(options)
137
149
  config.merge(options)
138
150
  end
@@ -165,6 +177,11 @@ module AbideDevUtils
165
177
  summaries.transform_keys { |k| "#{COV_PARENT_SUMMARY_PREFIX}#{benchmark}-#{k}"}
166
178
  end
167
179
 
180
+ # def self.summaries_from_comply_report(report)
181
+ # summaries = {}
182
+ # report.each do |_, v|
183
+ # end
184
+
168
185
  class Dummy
169
186
  def attrs
170
187
  { 'fields' => {
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'erb'
4
4
  require 'pathname'
5
+ require 'abide_dev_utils/output'
5
6
  require 'abide_dev_utils/prompt'
6
7
  require 'abide_dev_utils/errors/ppt'
7
8
 
@@ -9,6 +10,10 @@ module AbideDevUtils
9
10
  module Ppt
10
11
  class NewObjectBuilder
11
12
  DEFAULT_EXT = '.pp'
13
+ VALID_EXT = /(\.pp|\.rb)\.erb$/.freeze
14
+ TMPL_PATTERN = /^[a-zA-Z][^\s]*\.erb$/.freeze
15
+ OBJ_PREFIX = /^(c-|d-)/.freeze
16
+ PREFIX_TEST_PATH = { 'c-' => 'classes', 'd-' => 'defines' }.freeze
12
17
 
13
18
  def initialize(obj_type, obj_name, opts: {}, vars: {})
14
19
  @obj_type = obj_type
@@ -17,25 +22,17 @@ module AbideDevUtils
17
22
  @vars = vars
18
23
  class_vars
19
24
  validate_class_vars
25
+ @tmpl_data = template_data(@opts.fetch(:tmpl_name, @obj_type))
20
26
  end
21
27
 
22
- attr_reader :obj_type, :obj_name, :tmpl_name, :root_dir, :tmpl_dir, :tmpl_path, :type_path_map, :obj_path, :vars
23
-
24
- def render
25
- ERB.new(File.read(@tmpl_path.to_s), 0, '<>-').result(binding)
26
- end
28
+ attr_reader :obj_type, :obj_name, :root_dir, :tmpl_dir, :obj_path, :vars, :tmpl_data
27
29
 
28
30
  def build
29
- continue = File.exist?(obj_path) ? AbideDevUtils::Prompt.yes_no('File exists, would you like to overwrite?') : true
30
- return "Not overwriting file #{obj_path}" unless continue
31
-
32
- dir, = Pathname.new(obj_path).split
33
- Pathname.new(dir).mkpath unless Dir.exist?(dir)
34
- content = render
35
- File.open(obj_path, 'w') { |f| f.write(render) } unless content.empty?
36
- raise AbideDevUtils::Errors::Ppt::FailedToCreateFileError, obj_path unless File.file?(obj_path)
37
-
38
- "Created file #{obj_path}"
31
+ force = @opts.fetch(:force, false)
32
+ obj_cont = force ? true : continue?(obj_path)
33
+ spec_cont = force ? true : continue?(@tmpl_data[:spec_path])
34
+ write_file(obj_path, @tmpl_data[:path]) if obj_cont
35
+ write_file(@tmpl_data[:spec_path], @spec_tmpl) if spec_cont
39
36
  end
40
37
 
41
38
  # If a method gets called on the Hiera object which is not defined,
@@ -59,42 +56,98 @@ module AbideDevUtils
59
56
 
60
57
  private
61
58
 
59
+ def continue?(path)
60
+ continue = if File.exist?(path)
61
+ AbideDevUtils::Prompt.yes_no('File exists, would you like to overwrite?')
62
+ else
63
+ true
64
+ end
65
+ AbideDevUtils::Output.simple("Not overwriting file #{path}") unless continue
66
+
67
+ continue
68
+ end
69
+
70
+ def write_file(path, tmpl_path)
71
+ dir, = Pathname.new(path).split
72
+ Pathname.new(dir).mkpath unless Dir.exist?(dir)
73
+ content = render(tmpl_path)
74
+ File.open(path, 'w') { |f| f.write(content) } unless content.empty?
75
+ raise AbideDevUtils::Errors::Ppt::FailedToCreateFileError, path unless File.file?(path)
76
+
77
+ AbideDevUtils::Output.simple("Created file #{path}")
78
+ end
79
+
80
+ def build_obj; end
81
+
62
82
  def class_vars
63
- @tmpl_name = @opts.fetch(:tmpl_name, "#{@obj_type}.erb")
64
83
  @root_dir = Pathname.new(@opts.fetch(:root_dir, Dir.pwd))
65
84
  @tmpl_dir = if @opts.fetch(:absolute_template_dir, false)
66
85
  @opts.fetch(:tmpl_dir)
67
86
  else
68
- "#{@opts.fetch(:root_dir, Dir.pwd)}/#{@opts.fetch(:tmpl_dir, 'object_templates')}"
87
+ "#{@root_dir}/#{@opts.fetch(:tmpl_dir, 'object_templates')}"
69
88
  end
70
- @tmpl_path = Pathname.new("#{@tmpl_dir}/#{@opts.fetch(:tmpl_name, "#{@obj_type}.erb")}")
71
- @type_path_map = @opts.fetch(:type_path_map, {})
72
89
  @obj_path = new_obj_path
90
+ @spec_tmpl = @opts.fetch(:spec_template, File.expand_path(File.join(__dir__, '../resources/generic_spec.erb')))
73
91
  end
74
92
 
75
93
  def validate_class_vars
76
94
  raise AbideDevUtils::Errors::PathNotDirectoryError, @root_dir unless Dir.exist? @root_dir
77
95
  raise AbideDevUtils::Errors::PathNotDirectoryError, @tmpl_dir unless Dir.exist? @tmpl_dir
78
- raise AbideDevUtils::Errors::Ppt::TemplateNotFoundError, @tmpl_path.to_s unless @tmpl_path.file?
79
96
  end
80
97
 
81
98
  def basename(obj_name)
82
99
  obj_name.split('::')[-1]
83
100
  end
84
101
 
85
- def new_obj_path
86
- if obj_type == 'class'
87
- obj_path_from_name
88
- else
89
- custom_obj_path
102
+ def prefix
103
+ pfx = basename.match(OBJ_PREFIX)
104
+ return pfx[1] unless pfx.empty?
105
+ end
106
+
107
+ def templates
108
+ return [] if Dir.entries(tmpl_dir).empty?
109
+
110
+ file_names = Dir.entries(tmpl_dir).select { |f| f.match?(TMPL_PATTERN) }
111
+ file_names.map { |i| File.join(tmpl_dir, i) }
112
+ end
113
+
114
+ def template_data(query)
115
+ raise AbideDevUtils::Errors::Ppt::TemplateNotFoundError, @tmpl_dir if Dir.entries(@tmpl_dir).empty?
116
+
117
+ data = {}
118
+ pattern = /#{Regexp.quote(query)}/
119
+ templates.each do |i|
120
+ pn = Pathname.new(i)
121
+ next unless pn.basename.to_s.match?(pattern)
122
+
123
+ data[:path] = pn.to_s
124
+ data[:fname] = pn.basename.to_s
90
125
  end
126
+ raise AbideDevUtils::Errors::Ppt::TemplateNotFoundError, @tmpl_dir unless data.key?(:fname)
127
+
128
+ data[:ext] = data[:fname].match?(VALID_EXT) ? data[:fname].match(VALID_EXT)[1] : '.pp'
129
+ data[:pfx] = data[:fname].match?(OBJ_PREFIX) ? data[:fname].match(OBJ_PREFIX)[1] : 'c-'
130
+ data[:spec_base] = PREFIX_TEST_PATH[data[:pfx]]
131
+ data[:obj_name] = normalize_obj_name(data.dup)
132
+ data[:spec_name] = "#{@obj_name.split('::')[-1]}_spec.rb"
133
+ data[:spec_path] = spec_path(data[:spec_base], data[:spec_name])
134
+ data
135
+ end
136
+
137
+ def normalize_obj_name(data)
138
+ new_name = data[:fname].slice(/^(?:#{Regexp.quote(data[:pfx])})?(?<name>[^\s.]+)(?:#{Regexp.quote(data[:ext])})?\.erb$/, 'name')
139
+ "#{new_name}#{data[:ext]}"
140
+ end
141
+
142
+ def render(path)
143
+ ERB.new(File.read(path), 0, '<>-').result(binding)
91
144
  end
92
145
 
93
146
  def namespace_format(name)
94
147
  name.split(':').reject(&:empty?).join('::')
95
148
  end
96
149
 
97
- def obj_path_from_name
150
+ def new_obj_path
98
151
  parts = @obj_name.split('::')[1..-2]
99
152
  parts.insert(0, 'manifests')
100
153
  parts.insert(-1, "#{basename(@obj_name)}#{DEFAULT_EXT}")
@@ -102,27 +155,13 @@ module AbideDevUtils
102
155
  path.to_s
103
156
  end
104
157
 
105
- def custom_obj_path
106
- map_val = type_path_map.fetch(@obj_type.to_sym, nil)
107
- return obj_path_from_name if map_val.nil?
108
-
109
- if map_val.respond_to?(:key?)
110
- custom_obj_path_from_hash(map_val, @obj_name)
111
- else
112
- abs_path = Pathname.new(map_val).absolute? ? map_val : "#{Dir.pwd}/#{map_val}"
113
- "#{abs_path}/#{basename(@obj_name)}#{DEFAULT_EXT}"
114
- end
115
- end
116
-
117
- def custom_obj_path_from_hash(map_val, obj_name)
118
- raise AbideDevUtils::Errors::Ppt::CustomObjPathKeyError, map_val unless map_val.key?(:path)
119
-
120
- abs_path = Pathname.new(map_val[:path]).absolute? ? map_val[:path] : "#{Dir.pwd}/#{map_val[:path]}"
121
- if map_val.key?(:extension)
122
- "#{abs_path}/#{basename(obj_name)}#{map_val[:extension]}"
123
- else
124
- "#{abs_path}/#{basename(obj_name)}#{DEFAULT_EXT}"
125
- end
158
+ def spec_path(base_dir, spec_name)
159
+ parts = @obj_name.split('::')[1..-2]
160
+ parts.insert(0, 'spec')
161
+ parts.insert(1, base_dir)
162
+ parts.insert(-1, spec_name)
163
+ path = @root_dir + Pathname.new(parts.join('/'))
164
+ path.to_s
126
165
  end
127
166
  end
128
167
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe '<%= @obj_name %>' do
6
+ on_supported_os.each do |os, os_facts|
7
+ context "on #{os}" do
8
+ let(:facts) { os_facts }
9
+
10
+ it { is_expected.to compile }
11
+ end
12
+ end
13
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AbideDevUtils
4
- VERSION = "0.2.3"
4
+ VERSION = "0.5.0"
5
5
  end
@@ -15,7 +15,7 @@ module AbideDevUtils
15
15
  type = opts.fetch(:type, 'cis')
16
16
  case type.downcase
17
17
  when 'cis'
18
- AbideDevUtils::XCCDF::CIS::Hiera.new(xccdf_file, parent_key_prefix: opts[:parent_key_prefix])
18
+ AbideDevUtils::XCCDF::CIS::Hiera.new(xccdf_file, parent_key_prefix: opts[:parent_key_prefix], num: opts[:num])
19
19
  else
20
20
  AbideDevUtils::Output.simple("XCCDF type #{type} is unsupported!")
21
21
  end
@@ -35,13 +35,13 @@ module AbideDevUtils
35
35
  # @param parent_key_prefix [String] a string to be prepended to the
36
36
  # top-level key in the Hiera structure. Useful for namespacing
37
37
  # the top-level key.
38
- def initialize(xccdf_file, parent_key_prefix: nil)
38
+ def initialize(xccdf_file, parent_key_prefix: nil, num: false)
39
39
  @doc = parse(xccdf_file)
40
40
  @title = xpath(XPATHS[:benchmark][:title]).children.to_s
41
41
  @version = xpath(XPATHS[:benchmark][:version]).children.to_s
42
42
  @profiles = xpath(XPATHS[:profiles][:all])
43
43
  @parent_key = make_parent_key(@doc, parent_key_prefix)
44
- @hash = make_hash(@doc, @parent_key)
44
+ @hash = make_hash(@doc, @parent_key, num)
45
45
  end
46
46
 
47
47
  def yaml_title
@@ -92,13 +92,16 @@ module AbideDevUtils
92
92
 
93
93
  attr_accessor :doc, :hash, :parent_key, :profiles
94
94
 
95
+ # Accepts a path to an xccdf xml file and returns a parsed Nokogiri object of the file
96
+ # @param xccdf_file [String] path to an xccdf xml file
97
+ # @return [Nokogiri::Node] A Nokogiri node object of the XML document
95
98
  def parse(xccdf_file)
96
99
  raise AbideDevUtils::Errors::FileNotFoundError, xccdf_file unless File.file?(xccdf_file)
97
100
 
98
101
  Nokogiri.XML(File.open(xccdf_file))
99
102
  end
100
103
 
101
- def make_hash(doc, parent_key)
104
+ def make_hash(doc, parent_key, num)
102
105
  hash = { parent_key.to_sym => { title: @title, version: @version } }
103
106
  profiles = doc.xpath('xccdf:Benchmark/xccdf:Profile')
104
107
  profiles.each do |p|
@@ -106,7 +109,7 @@ module AbideDevUtils
106
109
  hash[parent_key.to_sym][title.to_sym] = []
107
110
  selects = p.xpath('./xccdf:select')
108
111
  selects.each do |s|
109
- hash[parent_key.to_sym][title.to_sym] << normalize_ctrl_name(s['idref'].to_s)
112
+ hash[parent_key.to_sym][title.to_sym] << normalize_ctrl_name(s['idref'].to_s, num)
110
113
  end
111
114
  end
112
115
  hash
@@ -114,7 +117,7 @@ module AbideDevUtils
114
117
 
115
118
  def normalize_str(str)
116
119
  nstr = str.downcase
117
- nstr.gsub!(/[^a-z]$/, '')
120
+ nstr.gsub!(/[^a-z0-9]$/, '')
118
121
  nstr.gsub!(/^[^a-z]/, '')
119
122
  nstr.gsub!(/^(l1_|l2_|ng_)/, '')
120
123
  nstr.delete!('(/|\\)')
@@ -128,11 +131,23 @@ module AbideDevUtils
128
131
  prof_name
129
132
  end
130
133
 
131
- def normalize_ctrl_name(ctrl)
132
- new_ctrl = ctrl.split('_rule_')[-1].gsub(CONTROL_PREFIX, '')
134
+ def normalize_ctrl_name(ctrl, num)
135
+ return num_normalize_ctrl(ctrl) if num
136
+
137
+ name_normalize_ctrl(ctrl)
138
+ end
139
+
140
+ def name_normalize_ctrl(ctrl)
141
+ new_ctrl = ctrl.split('benchmarks_rule_')[-1].gsub(CONTROL_PREFIX, '')
133
142
  normalize_str(new_ctrl)
134
143
  end
135
144
 
145
+ def num_normalize_ctrl(ctrl)
146
+ part = ctrl.split('benchmarks_rule_')[-1]
147
+ numpart = CONTROL_PREFIX.match(part).to_s.chop.gsub(UNDERSCORED, '_')
148
+ "c#{numpart}"
149
+ end
150
+
136
151
  def make_parent_key(doc, prefix)
137
152
  doc_title = normalize_str(doc.xpath(XPATHS[:benchmark][:title]).children.to_s)
138
153
  return doc_title if prefix.nil?
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.2.3
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-03-29 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
@@ -108,6 +122,20 @@ dependencies:
108
122
  - - ">="
109
123
  - !ruby/object:Gem::Version
110
124
  version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: console
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
111
139
  - !ruby/object:Gem::Dependency
112
140
  name: github_changelog_generator
113
141
  requirement: !ruby/object:Gem::Requirement
@@ -242,11 +270,13 @@ executables:
242
270
  extensions: []
243
271
  extra_rdoc_files: []
244
272
  files:
273
+ - ".dockerignore"
245
274
  - ".gitignore"
246
275
  - ".rspec"
247
276
  - ".rubocop.yml"
248
277
  - ".rubocop_todo.yml"
249
278
  - CHANGELOG.md
279
+ - Dockerfile
250
280
  - Gemfile
251
281
  - LICENSE.txt
252
282
  - README.md
@@ -259,10 +289,12 @@ files:
259
289
  - lib/abide_dev_utils.rb
260
290
  - lib/abide_dev_utils/cli.rb
261
291
  - lib/abide_dev_utils/cli/abstract.rb
292
+ - lib/abide_dev_utils/cli/comply.rb
262
293
  - lib/abide_dev_utils/cli/jira.rb
263
294
  - lib/abide_dev_utils/cli/puppet.rb
264
295
  - lib/abide_dev_utils/cli/test.rb
265
296
  - lib/abide_dev_utils/cli/xccdf.rb
297
+ - lib/abide_dev_utils/comply.rb
266
298
  - lib/abide_dev_utils/config.rb
267
299
  - lib/abide_dev_utils/constants.rb
268
300
  - lib/abide_dev_utils/errors.rb
@@ -278,6 +310,7 @@ files:
278
310
  - lib/abide_dev_utils/ppt/coverage.rb
279
311
  - lib/abide_dev_utils/ppt/new_obj.rb
280
312
  - lib/abide_dev_utils/prompt.rb
313
+ - lib/abide_dev_utils/resources/generic_spec.erb
281
314
  - lib/abide_dev_utils/utils/general.rb
282
315
  - lib/abide_dev_utils/validate.rb
283
316
  - lib/abide_dev_utils/version.rb