abide_dev_utils 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +1 -0
  3. data/.gitignore +0 -0
  4. data/.rspec +0 -0
  5. data/.rubocop.yml +0 -0
  6. data/.rubocop_todo.yml +0 -0
  7. data/CHANGELOG.md +0 -0
  8. data/Dockerfile +23 -0
  9. data/Gemfile +0 -0
  10. data/LICENSE.txt +0 -0
  11. data/README.md +216 -6
  12. data/Rakefile +0 -0
  13. data/abide_dev_utils.gemspec +1 -0
  14. data/lib/abide_dev_utils.rb +0 -0
  15. data/lib/abide_dev_utils/cli.rb +1 -0
  16. data/lib/abide_dev_utils/cli/abstract.rb +1 -1
  17. data/lib/abide_dev_utils/cli/jira.rb +0 -0
  18. data/lib/abide_dev_utils/cli/puppet.rb +14 -5
  19. data/lib/abide_dev_utils/cli/test.rb +1 -1
  20. data/lib/abide_dev_utils/cli/xccdf.rb +1 -0
  21. data/lib/abide_dev_utils/config.rb +0 -0
  22. data/lib/abide_dev_utils/constants.rb +0 -0
  23. data/lib/abide_dev_utils/errors.rb +0 -0
  24. data/lib/abide_dev_utils/errors/base.rb +0 -0
  25. data/lib/abide_dev_utils/errors/general.rb +0 -0
  26. data/lib/abide_dev_utils/errors/jira.rb +0 -0
  27. data/lib/abide_dev_utils/errors/ppt.rb +0 -0
  28. data/lib/abide_dev_utils/errors/xccdf.rb +4 -0
  29. data/lib/abide_dev_utils/files.rb +0 -0
  30. data/lib/abide_dev_utils/jira.rb +0 -0
  31. data/lib/abide_dev_utils/output.rb +0 -0
  32. data/lib/abide_dev_utils/ppt.rb +3 -3
  33. data/lib/abide_dev_utils/ppt/coverage.rb +10 -4
  34. data/lib/abide_dev_utils/ppt/new_obj.rb +115 -61
  35. data/lib/abide_dev_utils/prompt.rb +0 -0
  36. data/lib/abide_dev_utils/resources/generic_spec.erb +13 -0
  37. data/lib/abide_dev_utils/utils/general.rb +0 -0
  38. data/lib/abide_dev_utils/validate.rb +0 -0
  39. data/lib/abide_dev_utils/version.rb +1 -1
  40. data/lib/abide_dev_utils/xccdf.rb +1 -1
  41. data/lib/abide_dev_utils/xccdf/cis.rb +0 -0
  42. data/lib/abide_dev_utils/xccdf/cis/hiera.rb +39 -16
  43. metadata +23 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 75678bc1c177a3187619cfec9d2c25e566b08d91439c3b46c578c4202f4d9a7c
4
- data.tar.gz: f9749e6c8ad3d5b0439a8fe0fd03ba3ea91e5c0052057eea43b3862c08b45fbd
3
+ metadata.gz: 9e53d77f476f80d886ab35e07bbfcf897f117d24be9fdf6cc3c31fc50b3d40e7
4
+ data.tar.gz: 1fc189884a7c351da992f6ad6e915cc0321c290c67b5e10ae3e35fb0bbc26d79
5
5
  SHA512:
6
- metadata.gz: 97ba4b3b0898a40ed6df3facaf3aa03beb2d1375d61e1915c6566e107e6edffa083ab7af93827c2679c14a2503504cafd40d9275b12708f582c3723953b5c9c7
7
- data.tar.gz: 2680eb354112e27223d590a645318ea14c4988dd0e331bf5a5f3b347402ead6398c8c07c1b7ded55704d76011522ff790459cf40452ceba8ca15dc2a19a76a7a
6
+ metadata.gz: 2dedd8ca47872aa859852f51172233ffefca9420004804d750969c0b7558caa10caba812570538c5e4989c021c2a6409620502f7459dfcd5027f9c7afafbb6fe
7
+ data.tar.gz: fe3b5f70661297672e171fce5f452c06a9042f2e101918caaf65be0527c5e401e22169bdfde0d1649b1d8ea6ce85c04262c7147c4fd8f0514d81e0bfdb2e860e
data/.dockerignore ADDED
@@ -0,0 +1 @@
1
+ Gemfile.lock
data/.gitignore CHANGED
File without changes
data/.rspec CHANGED
File without changes
data/.rubocop.yml CHANGED
File without changes
data/.rubocop_todo.yml CHANGED
File without changes
data/CHANGELOG.md CHANGED
File without changes
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/Gemfile CHANGED
File without changes
data/LICENSE.txt CHANGED
File without changes
data/README.md CHANGED
@@ -2,15 +2,50 @@
2
2
 
3
3
  Helper library and CLI app for Abide development.
4
4
 
5
+ Issues and pull requests are welcome!
6
+
7
+ ## Features
8
+
9
+ ### CLI
10
+
11
+ * Type `abide -h` to see the CLI help menu
12
+ * All commands have help menus
13
+ * Tested on MacOS, should also work on Linux
14
+
15
+ ### PDK-like manifest generation from local ERB templates
16
+
17
+ * Create a directory in your module called `object_templates` to hold ERB files
18
+ * Generate manifests from those ERB files
19
+
20
+ ### XCCDF to Hiera conversion
21
+
22
+ * Convert the contents of an XCCDF XML file to Hiera
23
+
24
+ ### Coverage Reporting
25
+
26
+ * Generate a coverage report (i.e. how many CIS controls are covered by classes in your module)
27
+
28
+ ### Jira Integration
29
+
30
+ * Create Jira issues in bulk from coverage reports
31
+
32
+ ### Supports Configuration via Local YAML file
33
+
34
+ * Fully configurable via the `~/.abide_dev.yaml` file
35
+
5
36
  ## Installation
6
37
 
7
38
  ### CLI app
8
39
 
9
- ```sh
10
- $ gem install abide_dev_utils
11
- ...
12
- $ abide -h
13
- ```
40
+ Install from RubyGems:
41
+
42
+ `gem install abide_dev_utils`
43
+
44
+ Then to access the help menu:
45
+
46
+ `abide -h`
47
+
48
+ ### As a dependency
14
49
 
15
50
  Add this line to your application's Gemfile:
16
51
 
@@ -26,9 +61,184 @@ Or install it yourself as:
26
61
 
27
62
  `$ gem install abide_dev_utils`
28
63
 
64
+ ### Build it yourself
65
+
66
+ Clone this repo:
67
+
68
+ `git clone <this repo>`
69
+
70
+ Build the gem:
71
+
72
+ `bundle exec rake build`
73
+
74
+ The gem will be located at `pkg/<gem file>`
75
+
76
+ Install the gem:
77
+
78
+ `gem install pkg/<gem file>`
79
+
29
80
  ## Usage
30
81
 
31
- Coming soon, in the mean time run `abide -h` and `abide [command] -h` for help.
82
+ * `abide -h` - CLI top-level help menu
83
+ * `abide <command> -h` - Command-specific help menu
84
+
85
+ ### Overview of Commands
86
+
87
+ * `abide jira` - Command namespace for Jira commands
88
+ * `abide jira auth` - Authenticate with Jira. Only useful as a stand-alone command to test authentication
89
+ * `abide jira from_coverage` - Creates a parent issue with subtasks from a Puppet coverage report
90
+ * `abide jira get_issue` - Gets a specific Jira issue
91
+ * `abide jira new_issue` - Creates a new Jira issue
92
+ * `abide puppet` - Command namespace for Puppet commands
93
+ * `abide puppet coverage` - Generate a "coverage" report. This examines how many manifests you have in your Puppet module versus how many CIS controls exist in the local Hiera data and gives you a breakdown of what percentage of the selected CIS benchmark / profile you have successfully covered.
94
+ * `abide puppet new` - Generate a new manifest from a local ERB template. Functions similar to `pdk new` except you can create arbitrary manifests.
95
+ * `abide test` - **BUGGED** Runs a module's test suite. Currently has issues with local gem environments.
96
+ * `abide xccdf` - Command namespace for XCCDF commands
97
+ * `abide xccdf to_hiera` - Converts a benchmark XCCDF file to a Hiera yaml file
98
+
99
+ ### Jira Command Reference
100
+
101
+ #### from_coverage
102
+
103
+ * Required positional parameters:
104
+ * `REPORT` - A path to a JSON file generated by the `abide puppet coverage` command
105
+ * `PROJECT` - A Jira project code. This is typically an all-caps abbreviation of a Jira project
106
+ * Options:
107
+ * `--dry-run`, `-d` - Prints the results of this command to the console instead of actually creating Jira issues
108
+
109
+ ### Puppet Command Reference
110
+
111
+ #### coverage
112
+
113
+ * Required positional parameters:
114
+ * `CLASS_DIR` - The path to the directory in your module that holds manifests named after the benchmark controls
115
+ * `HIERA_FILE` - The path to the Hiera file generated by the `abide xccdf to_hiera` command
116
+ * Options:
117
+ * `--out-file`, `-o` - The path to save the coverage report JSON file
118
+ * `--profile`, `-p` - A benchmark profile to generate the report for. By default, generates the report for all profiles
119
+
120
+ #### new
121
+
122
+ * Required positional parameters:
123
+ * `TYPE` - The type of object you would like to generate. This value is the name of an ERB template without `.erb` located in your template directory
124
+ * `NAME` - The fully namespaced name of the new object. Subdirectories will be automatically created within `manifests` based on the namespacing of this parameter if the directories do not exist.
125
+ * Options:
126
+ * `--template-dir`, `-t` - Name of the template directory relative to your module's root directory. Defaults to `object_templates`
127
+ * `--root-dir`, `-r` - Path to the root directory of your module. Defaults to the current working directory
128
+ * `--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
+ * `--template-name`, `-n` - Allows you to specify a template name if it is different than the `TYPE` parameter
130
+ * `--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.
131
+ * `--spec-template`, `-S` - Path to an ERB template to use for rspec test generation instead of the default
132
+ * `--force`, `-f` - Skips any prompts and executes the command
133
+
134
+ `abide puppet new` exposes a few variables for you to use in your templates by default:
135
+
136
+ * `@obj_name` - The value of the `NAME` parameter passed into the command
137
+ * `@vars` - A hash of any key=value pairs you passed into the command using `--vars`
138
+
139
+ Examples:
140
+
141
+ Lets assume we have a module at `~/test_module` and we have a directory in that module called `object_templates`. In the template directory, we have two ERB template files: `control_class.erb` and `util_class.erb`.
142
+
143
+ * control_class.erb
144
+
145
+ ```text
146
+ # @api private
147
+ class <%= @obj_name %> (
148
+ Boolean $enforce = true,
149
+ Hash $config = {},
150
+ ) {
151
+ if $enforce {
152
+ warning('Class not implemented yet')
153
+ }
154
+ }
155
+
156
+ ```
157
+
158
+ * util_class.erb
159
+
160
+ ```text
161
+ class <%= @obj_name %> (
162
+ <% if @vars.key?('testvar1') -%>
163
+ $testparam1 = '<%= @vars['testvar1'] %>',
164
+ <% else -%>
165
+ $testparam1 = undef,
166
+ <% end -%>
167
+ ) {
168
+ file { $testparam1:
169
+ ensure => file,
170
+ mode => '<%= @vars.fetch('testvar2', '0644') %>',
171
+ }
172
+ }
173
+
174
+ ```
175
+
176
+ ```text
177
+ $ cd ~/test_module
178
+ $ ls object_templates
179
+ control_class.erb util_class.erb
180
+ $ ls manifests
181
+ init.pp
182
+ $ abide puppet new control_class 'test_module::controls::test_new_control'
183
+ Created file /Users/the.dude/test_module/manifests/controls/test_new_control.pp
184
+ Created file /Users/the.dude/test_module/spec/classes/controls/test_new_control_spec.rb
185
+ $ abide puppet new util_class 'test_module::utils::test_new_util' -v 'testvar1=dude,testvar2=sweet'
186
+ Created file /Users/the.dude/test_module/manifests/utils/test_new_util.pp
187
+ Created file /Users/the.dude/test_module/spec/classes/utils/test_new_util_spec.rb
188
+ $ cat manifests/controls/test_new_control.pp
189
+ # @api private
190
+ class test_module::controls::test_new_control (
191
+ Boolean $enforce = true,
192
+ Hash $config = {},
193
+ ) {
194
+ if $enforce {
195
+ warning('Class not implemented yet')
196
+ }
197
+ }
198
+
199
+ $ cat manifests/utils/test_new_util.pp
200
+ class test_module::utils::test_new_util (
201
+ $testparam1 = 'dude',
202
+ ) {
203
+ file { 'dude':
204
+ ensure => file,
205
+ mode => 'sweet',
206
+ }
207
+ }
208
+
209
+ ```
210
+
211
+ **NOTE**: You can use two special prefixes on your template files to denote where the rspec test should be generated for that object.
212
+ 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`.
213
+
214
+ ### XCCDF Command Reference
215
+
216
+ #### to_hiera
217
+
218
+ NOTE: When converting XCCDF files to Hiera, control names are sanitized. This means that some characters will be changed to ensure the resulting control names in the Hiera file are valid for both YAML and Puppet class names. Here's what gets changed:
219
+
220
+ * All letters are coverted to lower case
221
+ * The first and last characters of the control name is dropped if they are not letters (a-z)
222
+ * If a control name has a prefix of `l1_`, `l2_`, or `ng_`, that prefix is dropped
223
+ * Differentiation between profile level occurs outside of control names
224
+ * The characters `/` and `\` are deleted. This means that paths are smashed together
225
+ * Whitespace and the characters `(`, `)`, `.`, and `-` are substituted with underscores (`_`)
226
+
227
+ * Required positional parameters:
228
+ * `XCCDF_FILE` - Path to an XCCDF XML file
229
+ * Options:
230
+ * `--benchmark-type`, `-b` - The type of benchmark. Defaults to `cis`
231
+ * `--out-file`, `-o` - A path to a file where you would like to save the generated Hiera
232
+ * `--parent-key-prefix`, `-p` - Allows you to append a prefix to all top-level Hiera keys
233
+
234
+ ## Docker
235
+
236
+ 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:
237
+
238
+ * Build the Dockerfile: `docker build . -t abide_dev_utils --build-arg version=<semver>`
239
+ * Run the commands using the container: `docker run -it abide_dev_utils --help`
240
+ * 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`
241
+ * When using the volume, all paths should be absolute based on the root directory `/extvol`
32
242
 
33
243
  ## Development
34
244
 
data/Rakefile CHANGED
File without changes
@@ -41,6 +41,7 @@ Gem::Specification.new do |spec|
41
41
  # Dev dependencies
42
42
  spec.add_development_dependency 'bundler'
43
43
  spec.add_development_dependency 'rake'
44
+ spec.add_development_dependency 'console'
44
45
  spec.add_development_dependency 'github_changelog_generator'
45
46
  spec.add_development_dependency 'gem-release'
46
47
  spec.add_development_dependency 'rspec', '~> 3.10'
File without changes
@@ -20,6 +20,7 @@ module Abide
20
20
  parser.main_options.version = AbideDevUtils::VERSION
21
21
  parser.main_options.banner = ROOT_CMD_BANNER
22
22
  parser.add_command(CmdParse::HelpCommand.new, default: true)
23
+ parser.add_command(CmdParse::VersionCommand.new(add_switches: true))
23
24
  parser.add_command(PuppetCommand.new)
24
25
  parser.add_command(XccdfCommand.new)
25
26
  parser.add_command(TestCommand.new)
@@ -3,7 +3,7 @@
3
3
  module Abide
4
4
  module CLI
5
5
  # @abstract
6
- class Command < CmdParse::Command
6
+ class AbideCommand < CmdParse::Command
7
7
  include AbideDevUtils::Config
8
8
  def initialize(cmd_name, cmd_short, cmd_long, **opts)
9
9
  super(cmd_name, **opts)
File without changes
@@ -4,7 +4,7 @@ require 'abide_dev_utils/cli/abstract'
4
4
 
5
5
  module Abide
6
6
  module CLI
7
- class PuppetCommand < Command
7
+ class PuppetCommand < AbideCommand
8
8
  CMD_NAME = 'puppet'
9
9
  CMD_SHORT = 'Commands related to Puppet code'
10
10
  CMD_LONG = 'Namespace for commands related to Puppet code'
@@ -15,7 +15,7 @@ module Abide
15
15
  end
16
16
  end
17
17
 
18
- class PuppetCoverageCommand < Command
18
+ class PuppetCoverageCommand < AbideCommand
19
19
  CMD_NAME = 'coverage'
20
20
  CMD_SHORT = 'Generates control coverage report'
21
21
  CMD_LONG = 'Generates report of valid Puppet classes that match with Hiera controls'
@@ -53,7 +53,7 @@ module Abide
53
53
  end
54
54
  end
55
55
 
56
- class PuppetNewCommand < Command
56
+ class PuppetNewCommand < AbideCommand
57
57
  CMD_NAME = 'new'
58
58
  CMD_SHORT = 'Generates a new Puppet object from templates'
59
59
  CMD_LONG = 'Generates a new Puppet object (class, test, etc.) from templates stored in the module repo'
@@ -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)
@@ -28,6 +28,7 @@ module Abide
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
30
  options.on('-p [PREFIX]', '--parent-key-prefix [PREFIX]', 'A prefix to append to the parent key') { |p| @data[:parent_key_prefix] = p }
31
+ options.on('-N', '--number-fmt', 'Format Hiera control names based off of control number instead of name.') { |s| @data[:num] = true }
31
32
  end
32
33
 
33
34
  def execute(xccdf_file)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -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
File without changes
File without changes
File without changes
@@ -9,7 +9,7 @@ module AbideDevUtils
9
9
  # the full namespace for all classes in that directory.
10
10
  # @param puppet_class_dir [String] path to a dir containing Puppet manifests
11
11
  # @return [String] The namespace for all classes in manifests in the dir
12
- def find_class_namespace(puppet_class_dir)
12
+ def self.find_class_namespace(puppet_class_dir)
13
13
  path = Pathname.new(puppet_class_dir)
14
14
  mod_root = nil
15
15
  ns_parts = []
@@ -34,7 +34,7 @@ module AbideDevUtils
34
34
  # metadata.json, if it exists, or by using the parent directory name.
35
35
  # @param pathname [Pathname] A Pathname object of the module's manifests dir
36
36
  # @return [String] The module's namespace root
37
- def find_mod_root(pathname)
37
+ def self.find_mod_root(pathname)
38
38
  metadata_file = nil
39
39
  pathname.entries.each do |e|
40
40
  metadata_file = "#{pathname}/metadata.json" if File.basename(e) == 'metadata.json'
@@ -52,7 +52,7 @@ module AbideDevUtils
52
52
 
53
53
  # @return [Array] An array of frozen arrays where each sub-array's
54
54
  # index 0 is class_name and index 1 is the full path to the file.
55
- def find_all_classes_and_paths(puppet_class_dir)
55
+ def self.find_all_classes_and_paths(puppet_class_dir)
56
56
  all_cap = []
57
57
  Dir.each_child(puppet_class_dir) do |c|
58
58
  path = "#{puppet_class_dir}/#{c}"
@@ -13,12 +13,14 @@ module AbideDevUtils
13
13
  def self.generate(puppet_class_dir, hiera_path, profile = nil)
14
14
  coverage = {}
15
15
  coverage['classes'] = {}
16
- all_cap = find_all_classes_and_paths(puppet_class_dir)
16
+ all_cap = AbideDevUtils::Ppt.find_all_classes_and_paths(puppet_class_dir)
17
17
  invalid_classes = find_invalid_classes(all_cap)
18
- valid_classes = all_cap.dup.transpose[0] - invalid_classes
18
+ valid_classes = find_valid_classes(all_cap, invalid_classes)
19
19
  coverage['classes']['invalid'] = invalid_classes
20
20
  coverage['classes']['valid'] = valid_classes
21
21
  hiera = YAML.safe_load(File.open(hiera_path))
22
+ profile&.gsub!(/^profile_/, '') unless profile.nil?
23
+
22
24
  matcher = profile.nil? ? /^profile_/ : /^profile_#{profile}/
23
25
  hiera.each do |k, v|
24
26
  key_base = k.split('::')[-1]
@@ -50,9 +52,13 @@ module AbideDevUtils
50
52
  out_hash
51
53
  end
52
54
 
53
- def self.find_valid_classes(all_cap)
55
+ def self.find_valid_classes(all_cap, invalid_classes)
54
56
  all_classes = all_cap.dup.transpose[0]
55
- all_classes - find_invalid_classes(all_cap)
57
+ return [] if all_classes.nil?
58
+
59
+ return all_classes - invalid_classes unless invalid_classes.nil?
60
+
61
+ all_classes
56
62
  end
57
63
 
58
64
  def self.find_invalid_classes(all_cap)
@@ -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,53 +10,29 @@ 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
15
- @obj_name = obj_name.split(':').reject(&:empty?).join('::') # removes
16
- @obj_basename = obj_name.split('::')[-1]
17
- @root_dir = Pathname.new(opts.fetch(:root_dir, Dir.pwd))
18
- @tmpl_dir = if opts.fetch(:absolute_template_dir, false)
19
- opts.fetch(:tmpl_dir)
20
- else
21
- "#{@root_dir}/#{opts.fetch(:tmpl_dir, 'object_templates')}"
22
- end
23
- @tmpl_name = opts.fetch(:tmpl_name, "#{@obj_type}.erb")
24
- @tmpl_path = Pathname.new("#{@tmpl_dir}/#{@tmpl_name}")
25
- @type_path_map = opts.fetch(:type_path_map, {})
20
+ @obj_name = namespace_format(obj_name)
21
+ @opts = opts
26
22
  @vars = vars
23
+ class_vars
24
+ validate_class_vars
25
+ @tmpl_data = template_data(@opts.fetch(:tmpl_name, @obj_type))
27
26
  end
28
27
 
29
- def obj_path
30
- case @obj_type
31
- when 'class'
32
- obj_path_from_name
33
- else
34
- custom_obj_path
35
- end
36
- end
37
-
38
- def template?
39
- @tmpl_path.file?
40
- end
41
-
42
- def render
43
- raise AbideDevUtils::Errors::Ppt::TemplateNotFoundError, @tmpl_path.to_s unless template?
44
-
45
- ERB.new(File.read(@tmpl_path.to_s), 0, '<>-').result(binding)
46
- end
28
+ attr_reader :obj_type, :obj_name, :root_dir, :tmpl_dir, :obj_path, :vars, :tmpl_data
47
29
 
48
30
  def build
49
- continue = File.exist?(obj_path) ? AbideDevUtils::Prompt.yes_no('File exists, would you like to overwrite?') : true
50
- return "Not overwriting file #{obj_path}" unless continue
51
-
52
- dir, = Pathname.new(obj_path).split
53
- Pathname.new(dir).mkpath unless Dir.exist?(dir)
54
- content = render
55
- File.open(obj_path, 'w') { |f| f.write(render) } unless content.empty?
56
- raise AbideDevUtils::Errors::Ppt::FailedToCreateFileError, obj_path unless File.file?(obj_path)
57
-
58
- "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
59
36
  end
60
37
 
61
38
  # If a method gets called on the Hiera object which is not defined,
@@ -79,35 +56,112 @@ module AbideDevUtils
79
56
 
80
57
  private
81
58
 
82
- def obj_path_from_name
83
- parts = @obj_name.split('::')[1..-2]
84
- parts.insert(0, 'manifests')
85
- parts.insert(-1, "#{@obj_basename}#{DEFAULT_EXT}")
86
- path = @root_dir + Pathname.new(parts.join('/'))
87
- path.to_s
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
88
68
  end
89
69
 
90
- def custom_obj_path
91
- map_val = @type_path_map.fetch(@obj_type.to_sym, nil)
92
- return obj_path_from_name if map_val.nil?
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)
93
76
 
94
- if map_val.respond_to?(:key?)
95
- custom_obj_path_from_hash(map_val)
96
- else
97
- abs_path = Pathname.new(map_val).absolute? ? map_val : "#{Dir.pwd}/#{map_val}"
98
- "#{abs_path}/#{@obj_basename}#{DEFAULT_EXT}"
99
- end
77
+ AbideDevUtils::Output.simple("Created file #{path}")
100
78
  end
101
79
 
102
- def custom_obj_path_from_hash(map_val)
103
- raise AbideDevUtils::Errors::Ppt::CustomObjPathKeyError, map_val unless map_val.key?(:path)
80
+ def build_obj; end
104
81
 
105
- abs_path = Pathname.new(map_val[:path]).absolute? ? map_val[:path] : "#{Dir.pwd}/#{map_val[:path]}"
106
- if map_val.key?(:extension)
107
- "#{abs_path}/#{@obj_basename}#{map_val[:extension]}"
108
- else
109
- "#{abs_path}/#{@obj_basename}#{DEFAULT_EXT}"
82
+ def class_vars
83
+ @root_dir = Pathname.new(@opts.fetch(:root_dir, Dir.pwd))
84
+ @tmpl_dir = if @opts.fetch(:absolute_template_dir, false)
85
+ @opts.fetch(:tmpl_dir)
86
+ else
87
+ "#{@root_dir}/#{@opts.fetch(:tmpl_dir, 'object_templates')}"
88
+ end
89
+ @obj_path = new_obj_path
90
+ @spec_tmpl = @opts.fetch(:spec_template, File.expand_path(File.join(__dir__, '../resources/generic_spec.erb')))
91
+ end
92
+
93
+ def validate_class_vars
94
+ raise AbideDevUtils::Errors::PathNotDirectoryError, @root_dir unless Dir.exist? @root_dir
95
+ raise AbideDevUtils::Errors::PathNotDirectoryError, @tmpl_dir unless Dir.exist? @tmpl_dir
96
+ end
97
+
98
+ def basename(obj_name)
99
+ obj_name.split('::')[-1]
100
+ end
101
+
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
110
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)
144
+ end
145
+
146
+ def namespace_format(name)
147
+ name.split(':').reject(&:empty?).join('::')
148
+ end
149
+
150
+ def new_obj_path
151
+ parts = @obj_name.split('::')[1..-2]
152
+ parts.insert(0, 'manifests')
153
+ parts.insert(-1, "#{basename(@obj_name)}#{DEFAULT_EXT}")
154
+ path = @root_dir + Pathname.new(parts.join('/'))
155
+ path.to_s
156
+ end
157
+
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
111
165
  end
112
166
  end
113
167
  end
File without changes
@@ -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
File without changes
File without changes
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AbideDevUtils
4
- VERSION = "0.2.0"
4
+ VERSION = "0.4.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], strategy: opts[:strategy])
19
19
  else
20
20
  AbideDevUtils::Output.simple("XCCDF type #{type} is unsupported!")
21
21
  end
File without changes
@@ -13,6 +13,7 @@ module AbideDevUtils
13
13
  # @!attribute [r] yaml_title
14
14
  class Hiera
15
15
  CONTROL_PREFIX = /^[\d.]+_/.freeze
16
+ UNDERSCORED = /(\s|\(|\)|-|\.)/.freeze
16
17
  XPATHS = {
17
18
  benchmark: {
18
19
  all: 'xccdf:Benchmark',
@@ -25,6 +26,7 @@ module AbideDevUtils
25
26
  relative_select: './xccdf:select'
26
27
  }
27
28
  }.freeze
29
+ NEXT_GEN_WINDOWS = /(next_generation_windows_security)/.freeze
28
30
 
29
31
  attr_reader :title, :version
30
32
 
@@ -33,13 +35,13 @@ module AbideDevUtils
33
35
  # @param parent_key_prefix [String] a string to be prepended to the
34
36
  # top-level key in the Hiera structure. Useful for namespacing
35
37
  # the top-level key.
36
- def initialize(xccdf_file, parent_key_prefix: nil)
38
+ def initialize(xccdf_file, parent_key_prefix: nil, num: false)
37
39
  @doc = parse(xccdf_file)
38
40
  @title = xpath(XPATHS[:benchmark][:title]).children.to_s
39
41
  @version = xpath(XPATHS[:benchmark][:version]).children.to_s
40
42
  @profiles = xpath(XPATHS[:profiles][:all])
41
43
  @parent_key = make_parent_key(@doc, parent_key_prefix)
42
- @hash = make_hash(@doc, @parent_key)
44
+ @hash = make_hash(@doc, @parent_key, num)
43
45
  end
44
46
 
45
47
  def yaml_title
@@ -88,15 +90,18 @@ module AbideDevUtils
88
90
 
89
91
  private
90
92
 
91
- attr_accessor :doc, :parent_key, :hash, :profiles
93
+ attr_accessor :doc, :hash, :parent_key, :profiles
92
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
93
98
  def parse(xccdf_file)
94
99
  raise AbideDevUtils::Errors::FileNotFoundError, xccdf_file unless File.file?(xccdf_file)
95
100
 
96
101
  Nokogiri.XML(File.open(xccdf_file))
97
102
  end
98
103
 
99
- def make_hash(doc, parent_key)
104
+ def make_hash(doc, parent_key, num)
100
105
  hash = { parent_key.to_sym => { title: @title, version: @version } }
101
106
  profiles = doc.xpath('xccdf:Benchmark/xccdf:Profile')
102
107
  profiles.each do |p|
@@ -104,33 +109,51 @@ module AbideDevUtils
104
109
  hash[parent_key.to_sym][title.to_sym] = []
105
110
  selects = p.xpath('./xccdf:select')
106
111
  selects.each do |s|
107
- 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)
108
113
  end
109
114
  end
110
115
  hash
111
116
  end
112
117
 
113
118
  def normalize_str(str)
114
- str.delete('-').gsub(/\s/, '_').downcase
119
+ nstr = str.downcase
120
+ nstr.gsub!(/[^a-z0-9]$/, '')
121
+ nstr.gsub!(/^[^a-z]/, '')
122
+ nstr.gsub!(/^(l1_|l2_|ng_)/, '')
123
+ nstr.delete!('(/|\\)')
124
+ nstr.gsub!(UNDERSCORED, '_')
125
+ nstr
115
126
  end
116
127
 
117
128
  def normalize_profile_name(prof)
118
- normalize_str("profile_#{prof}")
129
+ prof_name = normalize_str("profile_#{prof}")
130
+ prof_name.gsub!(NEXT_GEN_WINDOWS, 'ngws')
131
+ prof_name
119
132
  end
120
133
 
121
- def normalize_ctrl_name(ctrl)
122
- new_ctrl = ctrl.split('_rule_')[-1].gsub(CONTROL_PREFIX, '')
123
- normalize_str(new_ctrl.gsub(/\./, '_'))
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, '')
142
+ normalize_str(new_ctrl)
143
+ end
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}"
124
149
  end
125
150
 
126
151
  def make_parent_key(doc, prefix)
127
152
  doc_title = normalize_str(doc.xpath(XPATHS[:benchmark][:title]).children.to_s)
128
- if prefix.nil?
129
- doc_title
130
- else
131
- sepped_prefix = prefix.end_with?('::') ? prefix : "#{prefix}::"
132
- "#{sepped_prefix.chomp}#{doc_title}"
133
- end
153
+ return doc_title if prefix.nil?
154
+
155
+ sepped_prefix = prefix.end_with?('::') ? prefix : "#{prefix}::"
156
+ "#{sepped_prefix.chomp}#{doc_title}"
134
157
  end
135
158
  end
136
159
  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.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Heston Snodgrass
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-16 00:00:00.000000000 Z
11
+ date: 2021-04-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: console
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: github_changelog_generator
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -242,11 +256,13 @@ executables:
242
256
  extensions: []
243
257
  extra_rdoc_files: []
244
258
  files:
259
+ - ".dockerignore"
245
260
  - ".gitignore"
246
261
  - ".rspec"
247
262
  - ".rubocop.yml"
248
263
  - ".rubocop_todo.yml"
249
264
  - CHANGELOG.md
265
+ - Dockerfile
250
266
  - Gemfile
251
267
  - LICENSE.txt
252
268
  - README.md
@@ -278,6 +294,7 @@ files:
278
294
  - lib/abide_dev_utils/ppt/coverage.rb
279
295
  - lib/abide_dev_utils/ppt/new_obj.rb
280
296
  - lib/abide_dev_utils/prompt.rb
297
+ - lib/abide_dev_utils/resources/generic_spec.erb
281
298
  - lib/abide_dev_utils/utils/general.rb
282
299
  - lib/abide_dev_utils/validate.rb
283
300
  - lib/abide_dev_utils/version.rb
@@ -291,7 +308,7 @@ metadata:
291
308
  homepage_uri: https://github.com/hsnodgrass/abide_dev_utils
292
309
  source_code_uri: https://github.com/hsnodgrass/abide_dev_utils
293
310
  changelog_uri: https://github.com/hsnodgrass/abide_dev_utils
294
- post_install_message:
311
+ post_install_message:
295
312
  rdoc_options: []
296
313
  require_paths:
297
314
  - lib
@@ -306,8 +323,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
306
323
  - !ruby/object:Gem::Version
307
324
  version: '0'
308
325
  requirements: []
309
- rubygems_version: 3.0.9
310
- signing_key:
326
+ rubygems_version: 3.1.2
327
+ signing_key:
311
328
  specification_version: 4
312
329
  summary: Helper utilities for developing Abide
313
330
  test_files: []