abide_dev_utils 0.1.1 → 0.3.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: 53dde19f67b39702c5430d9e9e9c0003c5e27f3440bcd9d60a575911ae9959dd
4
- data.tar.gz: 768c7f2b0ca6d3bfccef663668bfcb01a5c4828593884116ff5580df1ee1f4da
3
+ metadata.gz: c4ee71223292cc98028db5407eaef69cb2a0a150de9552b4d51d3b1ee17197bf
4
+ data.tar.gz: 3f56a0ad99e2ae3ccc8478210f7bb1961c4500267bedc1d42612c1947ee7c3fb
5
5
  SHA512:
6
- metadata.gz: 44e2b63c7488ac4bda0ae50ac8f672ba481fe20cb6a1087395921b115b773e96aaf08b08849c9a3dbb08cd03cde712b4d0977978434d1f7619d9f4bda403d758
7
- data.tar.gz: 35a1c0755970943435a928414da5f25c9d082372d787c6310d63557875a0ae6c5e9a272928b1073c5aa63d5a0081638ef8e3b9703a49a8900334699881ea41f0
6
+ metadata.gz: 821d519b152ffc0a246939050b2b6117b5f3986fdf094d6333cfef8617b06c1572517a93b96f76039368c860715a4ef7ef39c78c039581369c4e61b484bce0c0
7
+ data.tar.gz: 6010ad3359da0de05b352e48210f3e4ddbd95b30e942598c6e6cfa77c175a1041ab87491d2a2afa15490a12fc197c549ab5d58dbb4bfc816e569afe5ac66dd9e
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,175 @@ 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
32
233
 
33
234
  ## Development
34
235
 
@@ -49,7 +49,7 @@ Gem::Specification.new do |spec|
49
49
  spec.add_development_dependency 'rubocop-ast', '~> 1.4'
50
50
  spec.add_development_dependency 'rubocop-performance', '~> 1.9'
51
51
  spec.add_development_dependency 'rubocop-i18n', '~> 3.0'
52
- spec.add_development_dependency 'fast_gettext', '~> 1.1'
52
+ spec.add_development_dependency 'fast_gettext', '~> 1.8'
53
53
 
54
54
  # For more information and examples about making a new gem, checkout our
55
55
  # guide at: https://bundler.io/guides/creating_gem.html
@@ -2,8 +2,9 @@
2
2
 
3
3
  require 'abide_dev_utils/version'
4
4
  require 'abide_dev_utils/xccdf'
5
- require 'abide_dev_utils/utils'
6
- require 'abide_dev_utils/errors'
5
+ require 'abide_dev_utils/ppt'
6
+ require 'abide_dev_utils/jira'
7
+ require 'abide_dev_utils/config'
7
8
 
8
- # Just creates the namespace
9
+ # Root namespace all modules / classes
9
10
  module AbideDevUtils; end
@@ -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)
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abide
4
+ module CLI
5
+ # @abstract
6
+ class AbideCommand < CmdParse::Command
7
+ include AbideDevUtils::Config
8
+ def initialize(cmd_name, cmd_short, cmd_long, **opts)
9
+ super(cmd_name, **opts)
10
+ short_desc(cmd_short)
11
+ long_desc(cmd_long)
12
+ add_command(CmdParse::HelpCommand.new, default: true) if opts[:takes_commands]
13
+ end
14
+ end
15
+ end
16
+ end
@@ -36,7 +36,9 @@ module Abide
36
36
  def execute
37
37
  client = JIRA.client
38
38
  myself = JIRA.get_myself(client)
39
- Abide::CLI::OUTPUT.simple("Successfully authenticated user #{myself.attrs['name']}!") unless myself.attrs['name'].empty?
39
+ return if myself.attrs['name'].empty?
40
+
41
+ Abide::CLI::OUTPUT.simple("Successfully authenticated user #{myself.attrs['name']}!")
40
42
  end
41
43
  end
42
44
 
@@ -1,30 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'abide_dev_utils/cli/abstract'
4
+
3
5
  module Abide
4
6
  module CLI
5
- class PuppetCommand < CmdParse::Command
7
+ class PuppetCommand < AbideCommand
6
8
  CMD_NAME = 'puppet'
7
9
  CMD_SHORT = 'Commands related to Puppet code'
8
10
  CMD_LONG = 'Namespace for commands related to Puppet code'
9
11
  def initialize
10
- super(CMD_NAME, takes_commands: true)
11
- short_desc(CMD_SHORT)
12
- long_desc(CMD_LONG)
13
- add_command(CmdParse::HelpCommand.new, default: true)
12
+ super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: true)
14
13
  add_command(PuppetCoverageCommand.new)
14
+ add_command(PuppetNewCommand.new)
15
15
  end
16
16
  end
17
17
 
18
- class PuppetCoverageCommand < CmdParse::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'
22
22
  CMD_CLASS_DIR = 'Directory that holds Puppet manifests'
23
23
  CMD_HIERA_FILE = 'Hiera file generated from an XCCDF'
24
24
  def initialize
25
- super(CMD_NAME, takes_commands: false)
26
- short_desc(CMD_SHORT)
27
- long_desc(CMD_LONG)
25
+ super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: false)
28
26
  argument_desc(CLASS_DIR: CMD_CLASS_DIR, HIERA_FILE: CMD_HIERA_FILE)
29
27
  options.on('-o [FILE]', '--out-file [FILE]', 'Path to save the coverage report') { |f| @data[:file] = f }
30
28
  options.on('-p [PROFILE]', '--profile [PROFILE]', 'Generate only for profile') { |p| @data[:profile] = p }
@@ -43,7 +41,7 @@ module Abide
43
41
  require 'abide_dev_utils/ppt'
44
42
  Abide::CLI::VALIDATE.directory(class_dir)
45
43
  Abide::CLI::VALIDATE.file(hiera_file)
46
- coverage = AbideDevUtils::Ppt.coverage_report(class_dir, hiera_file, @data[:profile])
44
+ coverage = AbideDevUtils::Ppt::CoverageReport.generate(class_dir, hiera_file, @data[:profile])
47
45
  coverage.each do |k, v|
48
46
  next if ['classes', 'benchmark'].include?(k)
49
47
 
@@ -54,5 +52,63 @@ module Abide
54
52
  Abide::CLI::OUTPUT.json(coverage, file: @data[:file])
55
53
  end
56
54
  end
55
+
56
+ class PuppetNewCommand < AbideCommand
57
+ CMD_NAME = 'new'
58
+ CMD_SHORT = 'Generates a new Puppet object from templates'
59
+ CMD_LONG = 'Generates a new Puppet object (class, test, etc.) from templates stored in the module repo'
60
+ CMD_TYPE_ARG = 'The type of object to generate. This value must be the name of a template (without .erb) file in <template dir>'
61
+ CMD_NAME_ARG = 'The fully namespaced name of the new object'
62
+ def initialize
63
+ super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: false)
64
+ argument_desc(TYPE: CMD_TYPE_ARG, NAME: CMD_NAME_ARG)
65
+ options.on(
66
+ '-t [DIR]',
67
+ '--template-dir [DIR]',
68
+ 'Path to the directory holding your ERB templates for custom objects. Defaults to "object_templates" in the root dir.'
69
+ ) { |t| @data[:tmpl_dir] = t }
70
+ options.on(
71
+ '-r [DIR]',
72
+ '--root-dir [DIR]',
73
+ 'Path to the root directory of the module. Defaults to the current working directory.'
74
+ ) { |r| @data[:root_dir] = r }
75
+ options.on(
76
+ '-A',
77
+ '--absolute-template-dir',
78
+ 'Use this flage if the template dir is an absolute path'
79
+ ) { |a| @data[:absolute_template_dir] = a }
80
+ options.on(
81
+ '-n [NAME]',
82
+ '--template-name [NAME]',
83
+ 'Allows you to specify a name for the template if it is different from the basename (last segment) of the object name.'
84
+ )
85
+ options.on(
86
+ '-V [VARNAME=VALUE]',
87
+ '--vars [VARNAME=VALUE]',
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
+ ) { |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 }
100
+ end
101
+
102
+ def execute(type, name)
103
+ require 'abide_dev_utils/ppt/new_obj'
104
+ builder = AbideDevUtils::Ppt::NewObjectBuilder.new(
105
+ type,
106
+ name,
107
+ opts: @data,
108
+ vars: @data.fetch(:vars, '').split(',').map { |i| i.split('=') }.to_h # makes the str a hash
109
+ )
110
+ builder.build
111
+ end
112
+ end
57
113
  end
58
114
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'abide_dev_utils/errors/base'
3
4
  require 'abide_dev_utils/errors/general'
4
5
  require 'abide_dev_utils/errors/jira'
5
6
  require 'abide_dev_utils/errors/xccdf'
7
+ require 'abide_dev_utils/errors/ppt'
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'abide_dev_utils/errors/base'
4
+
5
+ module AbideDevUtils
6
+ module Errors
7
+ module Ppt
8
+ class ObjClassPathError < GenericError
9
+ @default = 'Invalid path for class:'
10
+ end
11
+
12
+ class CustomObjPathKeyError < GenericError
13
+ @default = 'Custom Object value hash does not have :path key: '
14
+ end
15
+
16
+ class CustomObjNotFoundError < GenericError
17
+ @default = 'Could not find custom object in map:'
18
+ end
19
+
20
+ class TemplateNotFoundError < GenericError
21
+ @default = 'Template does not exist at:'
22
+ end
23
+
24
+ class FailedToCreateFileError < GenericError
25
+ @default = 'Failed to create file:'
26
+ end
27
+ end
28
+ end
29
+ end
@@ -148,7 +148,7 @@ module AbideDevUtils
148
148
  false
149
149
  end
150
150
 
151
- def self.summaries_from_coverage_report(report)
151
+ def self.summaries_from_coverage_report(report) # rubocop:disable Metrics/CyclomaticComplexity
152
152
  summaries = {}
153
153
  benchmark = nil
154
154
  report.each do |k, v|
@@ -1,52 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
- require 'pathname'
5
- require 'yaml'
6
- require 'puppet_pal'
3
+ require 'abide_dev_utils/ppt/coverage'
4
+ require 'abide_dev_utils/ppt/new_obj'
7
5
 
8
6
  module AbideDevUtils
9
7
  module Ppt
10
- def self.coverage_report(puppet_class_dir, hiera_path, profile = nil)
11
- coverage = {}
12
- coverage['classes'] = {}
13
- all_cap = find_all_classes_and_paths(puppet_class_dir)
14
- invalid_classes = find_invalid_classes(all_cap)
15
- valid_classes = all_cap.dup.transpose[0] - invalid_classes
16
- coverage['classes']['invalid'] = invalid_classes
17
- coverage['classes']['valid'] = valid_classes
18
- hiera = YAML.safe_load(File.open(hiera_path))
19
- matcher = profile.nil? ? /^profile_/ : /^profile_#{profile}/
20
- hiera.each do |k, v|
21
- key_base = k.split('::')[-1]
22
- coverage['benchmark'] = v if key_base == 'title'
23
- next unless key_base.match?(matcher)
24
-
25
- coverage[key_base] = generate_uncovered_data(v, valid_classes)
26
- end
27
- coverage
28
- end
29
-
30
- def self.generate_uncovered_data(ctrl_list, valid_classes)
31
- out_hash = {}
32
- out_hash[:num_total] = ctrl_list.length
33
- out_hash[:uncovered] = []
34
- out_hash[:covered] = []
35
- ctrl_list.each do |c|
36
- if valid_classes.include?(c)
37
- out_hash[:covered] << c
38
- else
39
- out_hash[:uncovered] << c
40
- end
41
- end
42
- out_hash[:num_covered] = out_hash[:covered].length
43
- out_hash[:num_uncovered] = out_hash[:uncovered].length
44
- out_hash[:coverage] = Float(
45
- (Float(out_hash[:num_covered]) / Float(out_hash[:num_total])) * 100.0
46
- ).floor(3)
47
- out_hash
48
- end
49
-
50
8
  # Given a directory holding Puppet manifests, returns
51
9
  # the full namespace for all classes in that directory.
52
10
  # @param puppet_class_dir [String] path to a dir containing Puppet manifests
@@ -104,32 +62,5 @@ module AbideDevUtils
104
62
  end
105
63
  all_cap
106
64
  end
107
-
108
- def self.find_valid_classes(all_cap)
109
- all_classes = all_cap.dup.transpose[0]
110
- all_classes - find_invalid_classes(all_cap)
111
- end
112
-
113
- def self.find_invalid_classes(all_cap)
114
- invalid_classes = []
115
- all_cap.each do |cap|
116
- invalid_classes << cap[0] unless class_valid?(cap[1])
117
- end
118
- invalid_classes
119
- end
120
-
121
- def self.class_valid?(manifest_path)
122
- compiler = Puppet::Pal::Compiler.new(nil)
123
- ast = compiler.parse_file(manifest_path)
124
- ast.body.body.statements.each do |s|
125
- next unless s.respond_to?(:arguments)
126
- next unless s.arguments.respond_to?(:each)
127
-
128
- s.arguments.each do |i|
129
- return false if i.value == 'Not implemented'
130
- end
131
- end
132
- true
133
- end
134
65
  end
135
66
  end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'pathname'
5
+ require 'yaml'
6
+ require 'puppet_pal'
7
+ require 'abide_dev_utils/ppt'
8
+
9
+ module AbideDevUtils
10
+ module Ppt
11
+ class CoverageReport
12
+ include AbideDevUtils::Ppt
13
+ def self.generate(puppet_class_dir, hiera_path, profile = nil)
14
+ coverage = {}
15
+ coverage['classes'] = {}
16
+ all_cap = AbideDevUtils::Ppt.find_all_classes_and_paths(puppet_class_dir)
17
+ invalid_classes = find_invalid_classes(all_cap)
18
+ valid_classes = find_valid_classes(all_cap, invalid_classes)
19
+ coverage['classes']['invalid'] = invalid_classes
20
+ coverage['classes']['valid'] = valid_classes
21
+ hiera = YAML.safe_load(File.open(hiera_path))
22
+ profile&.gsub!(/^profile_/, '') unless profile.nil?
23
+
24
+ matcher = profile.nil? ? /^profile_/ : /^profile_#{profile}/
25
+ hiera.each do |k, v|
26
+ key_base = k.split('::')[-1]
27
+ coverage['benchmark'] = v if key_base == 'title'
28
+ next unless key_base.match?(matcher)
29
+
30
+ coverage[key_base] = generate_uncovered_data(v, valid_classes)
31
+ end
32
+ coverage
33
+ end
34
+
35
+ def self.generate_uncovered_data(ctrl_list, valid_classes)
36
+ out_hash = {}
37
+ out_hash[:num_total] = ctrl_list.length
38
+ out_hash[:uncovered] = []
39
+ out_hash[:covered] = []
40
+ ctrl_list.each do |c|
41
+ if valid_classes.include?(c)
42
+ out_hash[:covered] << c
43
+ else
44
+ out_hash[:uncovered] << c
45
+ end
46
+ end
47
+ out_hash[:num_covered] = out_hash[:covered].length
48
+ out_hash[:num_uncovered] = out_hash[:uncovered].length
49
+ out_hash[:coverage] = Float(
50
+ (Float(out_hash[:num_covered]) / Float(out_hash[:num_total])) * 100.0
51
+ ).floor(3)
52
+ out_hash
53
+ end
54
+
55
+ def self.find_valid_classes(all_cap, invalid_classes)
56
+ all_classes = all_cap.dup.transpose[0]
57
+ return [] if all_classes.nil?
58
+
59
+ return all_classes - invalid_classes unless invalid_classes.nil?
60
+
61
+ all_classes
62
+ end
63
+
64
+ def self.find_invalid_classes(all_cap)
65
+ invalid_classes = []
66
+ all_cap.each do |cap|
67
+ invalid_classes << cap[0] unless class_valid?(cap[1])
68
+ end
69
+ invalid_classes
70
+ end
71
+
72
+ def self.class_valid?(manifest_path)
73
+ compiler = Puppet::Pal::Compiler.new(nil)
74
+ ast = compiler.parse_file(manifest_path)
75
+ ast.body.body.statements.each do |s|
76
+ next unless s.respond_to?(:arguments)
77
+ next unless s.arguments.respond_to?(:each)
78
+
79
+ s.arguments.each do |i|
80
+ return false if i.value == 'Not implemented'
81
+ end
82
+ end
83
+ true
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'erb'
4
+ require 'pathname'
5
+ require 'abide_dev_utils/output'
6
+ require 'abide_dev_utils/prompt'
7
+ require 'abide_dev_utils/errors/ppt'
8
+
9
+ module AbideDevUtils
10
+ module Ppt
11
+ class NewObjectBuilder
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
17
+
18
+ def initialize(obj_type, obj_name, opts: {}, vars: {})
19
+ @obj_type = obj_type
20
+ @obj_name = namespace_format(obj_name)
21
+ @opts = opts
22
+ @vars = vars
23
+ class_vars
24
+ validate_class_vars
25
+ end
26
+
27
+ attr_reader :obj_type, :obj_name, :root_dir, :tmpl_dir, :obj_path, :vars
28
+
29
+ def build
30
+ force = @opts.fetch(:force, false)
31
+ obj_cont = force ? true : continue?(obj_path)
32
+ spec_cont = force ? true : continue?(@tmpl_data[:spec_path])
33
+ write_file(obj_path, @tmpl_data[:path]) if obj_cont
34
+ write_file(@tmpl_data[:spec_path], @spec_tmpl) if spec_cont
35
+ end
36
+
37
+ # If a method gets called on the Hiera object which is not defined,
38
+ # this sends that method call to hash, then doc, then super.
39
+ def method_missing(method, *args, &block)
40
+ return true if ['exist?', 'exists?'].include?(method.to_s)
41
+
42
+ return @hash.send(method, *args, &block) if @hash.respond_to?(method)
43
+
44
+ return @doc.send(method, *args, &block) if @doc.respond_to?(method)
45
+
46
+ super(method, *args, &block)
47
+ end
48
+
49
+ # Checks the respond_to? of hash, doc, or super
50
+ def respond_to_missing?(method_name, include_private = false)
51
+ return true if ['exist?', 'exists?'].include?(method_name.to_s)
52
+
53
+ @hash || @doc || super
54
+ end
55
+
56
+ private
57
+
58
+ def continue?(path)
59
+ continue = if File.exist?(path)
60
+ AbideDevUtils::Prompt.yes_no('File exists, would you like to overwrite?')
61
+ else
62
+ true
63
+ end
64
+ AbideDevUtils::Output.simple("Not overwriting file #{path}") unless continue
65
+
66
+ continue
67
+ end
68
+
69
+ def write_file(path, tmpl_path)
70
+ dir, = Pathname.new(path).split
71
+ Pathname.new(dir).mkpath unless Dir.exist?(dir)
72
+ content = render(tmpl_path)
73
+ File.open(path, 'w') { |f| f.write(content) } unless content.empty?
74
+ raise AbideDevUtils::Errors::Ppt::FailedToCreateFileError, path unless File.file?(path)
75
+
76
+ AbideDevUtils::Output.simple("Created file #{path}")
77
+ end
78
+
79
+ def build_obj; end
80
+
81
+ def class_vars
82
+ @root_dir = Pathname.new(@opts.fetch(:root_dir, Dir.pwd))
83
+ @tmpl_dir = if @opts.fetch(:absolute_template_dir, false)
84
+ @opts.fetch(:tmpl_dir)
85
+ else
86
+ "#{@root_dir}/#{@opts.fetch(:tmpl_dir, 'object_templates')}"
87
+ end
88
+ @tmpl_data = template_data(@opts.fetch(:tmpl_name, @obj_type))
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
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] = "#{data[:obj_name].slice(/([^\s]+)(?:#{Regexp.quote(data[:ext])})/, 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
165
+ end
166
+ end
167
+ end
168
+ 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.1.1"
4
+ VERSION = "0.3.0"
5
5
  end
@@ -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
 
@@ -88,7 +90,7 @@ 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
 
93
95
  def parse(xccdf_file)
94
96
  raise AbideDevUtils::Errors::FileNotFoundError, xccdf_file unless File.file?(xccdf_file)
@@ -111,26 +113,32 @@ module AbideDevUtils
111
113
  end
112
114
 
113
115
  def normalize_str(str)
114
- str.delete('-').gsub(/\s/, '_').downcase
116
+ nstr = str.downcase
117
+ nstr.gsub!(/[^a-z0-9]$/, '')
118
+ nstr.gsub!(/^[^a-z]/, '')
119
+ nstr.gsub!(/^(l1_|l2_|ng_)/, '')
120
+ nstr.delete!('(/|\\)')
121
+ nstr.gsub!(UNDERSCORED, '_')
122
+ nstr
115
123
  end
116
124
 
117
125
  def normalize_profile_name(prof)
118
- normalize_str("profile_#{prof}")
126
+ prof_name = normalize_str("profile_#{prof}")
127
+ prof_name.gsub!(NEXT_GEN_WINDOWS, 'ngws')
128
+ prof_name
119
129
  end
120
130
 
121
131
  def normalize_ctrl_name(ctrl)
122
132
  new_ctrl = ctrl.split('_rule_')[-1].gsub(CONTROL_PREFIX, '')
123
- normalize_str(new_ctrl.gsub(/\./, '_'))
133
+ normalize_str(new_ctrl)
124
134
  end
125
135
 
126
136
  def make_parent_key(doc, prefix)
127
137
  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
138
+ return doc_title if prefix.nil?
139
+
140
+ sepped_prefix = prefix.end_with?('::') ? prefix : "#{prefix}::"
141
+ "#{sepped_prefix.chomp}#{doc_title}"
134
142
  end
135
143
  end
136
144
  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.1.1
4
+ version: 0.3.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-01-26 00:00:00.000000000 Z
11
+ date: 2021-04-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -226,14 +226,14 @@ dependencies:
226
226
  requirements:
227
227
  - - "~>"
228
228
  - !ruby/object:Gem::Version
229
- version: '1.1'
229
+ version: '1.8'
230
230
  type: :development
231
231
  prerelease: false
232
232
  version_requirements: !ruby/object:Gem::Requirement
233
233
  requirements:
234
234
  - - "~>"
235
235
  - !ruby/object:Gem::Version
236
- version: '1.1'
236
+ version: '1.8'
237
237
  description: Provides a CLI with helpful utilities for developing Abide
238
238
  email:
239
239
  - hsnodgrass3@gmail.com
@@ -258,6 +258,7 @@ files:
258
258
  - exe/abide
259
259
  - lib/abide_dev_utils.rb
260
260
  - lib/abide_dev_utils/cli.rb
261
+ - lib/abide_dev_utils/cli/abstract.rb
261
262
  - lib/abide_dev_utils/cli/jira.rb
262
263
  - lib/abide_dev_utils/cli/puppet.rb
263
264
  - lib/abide_dev_utils/cli/test.rb
@@ -268,12 +269,16 @@ files:
268
269
  - lib/abide_dev_utils/errors/base.rb
269
270
  - lib/abide_dev_utils/errors/general.rb
270
271
  - lib/abide_dev_utils/errors/jira.rb
272
+ - lib/abide_dev_utils/errors/ppt.rb
271
273
  - lib/abide_dev_utils/errors/xccdf.rb
272
274
  - lib/abide_dev_utils/files.rb
273
275
  - lib/abide_dev_utils/jira.rb
274
276
  - lib/abide_dev_utils/output.rb
275
277
  - lib/abide_dev_utils/ppt.rb
278
+ - lib/abide_dev_utils/ppt/coverage.rb
279
+ - lib/abide_dev_utils/ppt/new_obj.rb
276
280
  - lib/abide_dev_utils/prompt.rb
281
+ - lib/abide_dev_utils/resources/generic_spec.erb
277
282
  - lib/abide_dev_utils/utils/general.rb
278
283
  - lib/abide_dev_utils/validate.rb
279
284
  - lib/abide_dev_utils/version.rb