abide_dev_utils 0.2.3 → 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: 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