abide_dev_utils 0.1.0 → 0.2.3

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: cb6febf04efd889f8fae16d58b7951a1e209c57aef125a03017ac3fdc5229a88
4
- data.tar.gz: f133a75839ef09396eba54bb6f563567c9f5cf7057aab1a03a5ec09598600cd8
3
+ metadata.gz: 5e19e043b233c6842331d4c4883a74d8557b163739e041d910b60b953acddc85
4
+ data.tar.gz: cf425a2b3a0c83ecd5e41cbee5d476fc7444e8984f4f03df12f91b36ad8aeee8
5
5
  SHA512:
6
- metadata.gz: 798de0252108b50dbe3486f1edceb916e985039087ccf23f2ff0c2a0f1f1f75e0478a45c4a78b634968e197a03628869f968cf55236ab8b7e4bd7e3f1f9f17e1
7
- data.tar.gz: e41abc0cbcd7bdb8ac3b700beed14479baf9287ad10c1bc8405d22384afb75df7e8a0dee0f9914f99e2b81576dff2b5d8f10b06a3dfe66595858fa6967e71eb1
6
+ metadata.gz: 52e685b3d446b7c6256fd0e45d0542dccc77490b75ccc6408e2ba66e8edbc49d76267446940b997a0e3ab6a7449e1c5a03323c98cc66dd34ca58fa87178d4602
7
+ data.tar.gz: 558295f45b37110781fcd22bc4cac8a450c520f706933ddc0fe908c4f2ef863b63029603ab3e1df09142a17c6238f3f5514f20e4fefcc1ed6e2a5531abc5a479
data/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ ## [v0.1.0](https://github.com/hsnodgrass/abide_dev_utils/tree/v0.1.0) (2021-01-22)
4
+
5
+ [Full Changelog](https://github.com/hsnodgrass/abide_dev_utils/compare/f24cea86afb34d0b4904576400db8b8039f1eecc...v0.1.0)
6
+
7
+
8
+
9
+ \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
data/Gemfile CHANGED
@@ -4,5 +4,3 @@ source "https://rubygems.org"
4
4
 
5
5
  # Specify your gem's dependencies in abide_dev_utils.gemspec
6
6
  gemspec
7
-
8
- gem "rake", "~> 13.0"
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,168 @@ 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
+
132
+ `abide puppet new` exposes a few variables for you to use in your templates by default:
133
+
134
+ * `@obj_name` - The value of the `NAME` parameter passed into the command
135
+ * `@vars` - A hash of any key=value pairs you passed into the command using `--vars`
136
+
137
+ Examples:
138
+
139
+ 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`.
140
+
141
+ * control_class.erb
142
+
143
+ ```text
144
+ # @api private
145
+ class <%= @obj_name %> (
146
+ Boolean $enforce = true,
147
+ Hash $config = {},
148
+ ) {
149
+ if $enforce {
150
+ warning('Class not implemented yet')
151
+ }
152
+ }
153
+
154
+ ```
155
+
156
+ * util_class.erb
157
+
158
+ ```text
159
+ class <%= @obj_name %> (
160
+ <% if @vars.key?('testvar1') -%>
161
+ $testparam1 = '<%= @vars['testvar1'] %>',
162
+ <% else -%>
163
+ $testparam1 = undef,
164
+ <% end -%>
165
+ ) {
166
+ file { $testparam1:
167
+ ensure => file,
168
+ mode => '<%= @vars.fetch('testvar2', '0644') %>',
169
+ }
170
+ }
171
+
172
+ ```
173
+
174
+ ```text
175
+ $ cd ~/test_module
176
+ $ ls object_templates
177
+ control_class.erb util_class.erb
178
+ $ ls manifests
179
+ init.pp
180
+ $ abide puppet new control_class 'test_module::controls::test_new_control'
181
+ Created file /Users/the.dude/test_module/manifests/controls/test_new_control.pp
182
+ $ abide puppet new util_class 'test_module::utils::test_new_util' -v 'testvar1=dude,testvar2=sweet'
183
+ Created file /Users/the.dude/test_module/manifests/utils/test_new_util.pp
184
+ $ cat manifests/controls/test_new_control.pp
185
+ # @api private
186
+ class test_module::controls::test_new_control (
187
+ Boolean $enforce = true,
188
+ Hash $config = {},
189
+ ) {
190
+ if $enforce {
191
+ warning('Class not implemented yet')
192
+ }
193
+ }
194
+
195
+ $ cat manifests/utils/test_new_util.pp
196
+ class test_module::utils::test_new_util (
197
+ $testparam1 = 'dude',
198
+ ) {
199
+ file { 'dude':
200
+ ensure => file,
201
+ mode => 'sweet',
202
+ }
203
+ }
204
+
205
+ ```
206
+
207
+ ### XCCDF Command Reference
208
+
209
+ #### to_hiera
210
+
211
+ 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:
212
+
213
+ * All letters are coverted to lower case
214
+ * The first and last characters of the control name is dropped if they are not letters (a-z)
215
+ * If a control name has a prefix of `l1_`, `l2_`, or `ng_`, that prefix is dropped
216
+ * Differentiation between profile level occurs outside of control names
217
+ * The characters `/` and `\` are deleted. This means that paths are smashed together
218
+ * Whitespace and the characters `(`, `)`, `.`, and `-` are substituted with underscores (`_`)
219
+
220
+ * Required positional parameters:
221
+ * `XCCDF_FILE` - Path to an XCCDF XML file
222
+ * Options:
223
+ * `--benchmark-type`, `-b` - The type of benchmark. Defaults to `cis`
224
+ * `--out-file`, `-o` - A path to a file where you would like to save the generated Hiera
225
+ * `--parent-key-prefix`, `-p` - Allows you to append a prefix to all top-level Hiera keys
32
226
 
33
227
  ## Development
34
228
 
@@ -32,21 +32,24 @@ Gem::Specification.new do |spec|
32
32
  spec.require_paths = ['lib']
33
33
 
34
34
  # Prod dependencies
35
- spec.add_dependency 'bundler', '~> 2.0'
36
35
  spec.add_dependency 'nokogiri', '~> 1.11'
37
36
  spec.add_dependency 'cmdparse', '~> 3.0'
38
- spec.add_dependency 'puppet', '<= 6.19'
37
+ spec.add_dependency 'puppet', '>= 6.19'
39
38
  spec.add_dependency 'jira-ruby', '~> 2.1'
40
39
  spec.add_dependency 'ruby-progressbar', '~> 1.11'
41
40
 
42
41
  # Dev dependencies
42
+ spec.add_development_dependency 'bundler'
43
+ spec.add_development_dependency 'rake'
44
+ spec.add_development_dependency 'github_changelog_generator'
45
+ spec.add_development_dependency 'gem-release'
43
46
  spec.add_development_dependency 'rspec', '~> 3.10'
44
47
  spec.add_development_dependency 'rubocop', '~> 1.8'
45
48
  spec.add_development_dependency 'rubocop-rspec', '~> 2.1'
46
49
  spec.add_development_dependency 'rubocop-ast', '~> 1.4'
47
50
  spec.add_development_dependency 'rubocop-performance', '~> 1.9'
48
51
  spec.add_development_dependency 'rubocop-i18n', '~> 3.0'
49
- spec.add_development_dependency 'fast_gettext', '~> 1.1'
52
+ spec.add_development_dependency 'fast_gettext', '~> 1.8'
50
53
 
51
54
  # For more information and examples about making a new gem, checkout our
52
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,54 @@ 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
+ end
91
+
92
+ def execute(type, name)
93
+ require 'abide_dev_utils/ppt/new_obj'
94
+ builder = AbideDevUtils::Ppt::NewObjectBuilder.new(
95
+ type,
96
+ name,
97
+ opts: @data,
98
+ vars: @data.fetch(:vars, '').split(',').map { |i| i.split('=') }.to_h # makes the str a hash
99
+ )
100
+ result = builder.build
101
+ Abide::CLI::OUTPUT.simple(result)
102
+ end
103
+ end
57
104
  end
58
105
  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,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'erb'
4
+ require 'pathname'
5
+ require 'abide_dev_utils/prompt'
6
+ require 'abide_dev_utils/errors/ppt'
7
+
8
+ module AbideDevUtils
9
+ module Ppt
10
+ class NewObjectBuilder
11
+ DEFAULT_EXT = '.pp'
12
+
13
+ def initialize(obj_type, obj_name, opts: {}, vars: {})
14
+ @obj_type = obj_type
15
+ @obj_name = namespace_format(obj_name)
16
+ @opts = opts
17
+ @vars = vars
18
+ class_vars
19
+ validate_class_vars
20
+ end
21
+
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
27
+
28
+ 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}"
39
+ end
40
+
41
+ # If a method gets called on the Hiera object which is not defined,
42
+ # this sends that method call to hash, then doc, then super.
43
+ def method_missing(method, *args, &block)
44
+ return true if ['exist?', 'exists?'].include?(method.to_s)
45
+
46
+ return @hash.send(method, *args, &block) if @hash.respond_to?(method)
47
+
48
+ return @doc.send(method, *args, &block) if @doc.respond_to?(method)
49
+
50
+ super(method, *args, &block)
51
+ end
52
+
53
+ # Checks the respond_to? of hash, doc, or super
54
+ def respond_to_missing?(method_name, include_private = false)
55
+ return true if ['exist?', 'exists?'].include?(method_name.to_s)
56
+
57
+ @hash || @doc || super
58
+ end
59
+
60
+ private
61
+
62
+ def class_vars
63
+ @tmpl_name = @opts.fetch(:tmpl_name, "#{@obj_type}.erb")
64
+ @root_dir = Pathname.new(@opts.fetch(:root_dir, Dir.pwd))
65
+ @tmpl_dir = if @opts.fetch(:absolute_template_dir, false)
66
+ @opts.fetch(:tmpl_dir)
67
+ else
68
+ "#{@opts.fetch(:root_dir, Dir.pwd)}/#{@opts.fetch(:tmpl_dir, 'object_templates')}"
69
+ 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
+ @obj_path = new_obj_path
73
+ end
74
+
75
+ def validate_class_vars
76
+ raise AbideDevUtils::Errors::PathNotDirectoryError, @root_dir unless Dir.exist? @root_dir
77
+ 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
+ end
80
+
81
+ def basename(obj_name)
82
+ obj_name.split('::')[-1]
83
+ end
84
+
85
+ def new_obj_path
86
+ if obj_type == 'class'
87
+ obj_path_from_name
88
+ else
89
+ custom_obj_path
90
+ end
91
+ end
92
+
93
+ def namespace_format(name)
94
+ name.split(':').reject(&:empty?).join('::')
95
+ end
96
+
97
+ def obj_path_from_name
98
+ parts = @obj_name.split('::')[1..-2]
99
+ parts.insert(0, 'manifests')
100
+ parts.insert(-1, "#{basename(@obj_name)}#{DEFAULT_EXT}")
101
+ path = @root_dir + Pathname.new(parts.join('/'))
102
+ path.to_s
103
+ end
104
+
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
126
+ end
127
+ end
128
+ end
129
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AbideDevUtils
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.3"
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-z]$/, '')
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,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abide_dev_utils
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.3
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-22 00:00:00.000000000 Z
11
+ date: 2021-03-29 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '2.0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '2.0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: nokogiri
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -56,14 +42,14 @@ dependencies:
56
42
  name: puppet
57
43
  requirement: !ruby/object:Gem::Requirement
58
44
  requirements:
59
- - - "<="
45
+ - - ">="
60
46
  - !ruby/object:Gem::Version
61
47
  version: '6.19'
62
48
  type: :runtime
63
49
  prerelease: false
64
50
  version_requirements: !ruby/object:Gem::Requirement
65
51
  requirements:
66
- - - "<="
52
+ - - ">="
67
53
  - !ruby/object:Gem::Version
68
54
  version: '6.19'
69
55
  - !ruby/object:Gem::Dependency
@@ -94,6 +80,62 @@ dependencies:
94
80
  - - "~>"
95
81
  - !ruby/object:Gem::Version
96
82
  version: '1.11'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: github_changelog_generator
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'
125
+ - !ruby/object:Gem::Dependency
126
+ name: gem-release
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'
97
139
  - !ruby/object:Gem::Dependency
98
140
  name: rspec
99
141
  requirement: !ruby/object:Gem::Requirement
@@ -184,14 +226,14 @@ dependencies:
184
226
  requirements:
185
227
  - - "~>"
186
228
  - !ruby/object:Gem::Version
187
- version: '1.1'
229
+ version: '1.8'
188
230
  type: :development
189
231
  prerelease: false
190
232
  version_requirements: !ruby/object:Gem::Requirement
191
233
  requirements:
192
234
  - - "~>"
193
235
  - !ruby/object:Gem::Version
194
- version: '1.1'
236
+ version: '1.8'
195
237
  description: Provides a CLI with helpful utilities for developing Abide
196
238
  email:
197
239
  - hsnodgrass3@gmail.com
@@ -204,6 +246,7 @@ files:
204
246
  - ".rspec"
205
247
  - ".rubocop.yml"
206
248
  - ".rubocop_todo.yml"
249
+ - CHANGELOG.md
207
250
  - Gemfile
208
251
  - LICENSE.txt
209
252
  - README.md
@@ -215,6 +258,7 @@ files:
215
258
  - exe/abide
216
259
  - lib/abide_dev_utils.rb
217
260
  - lib/abide_dev_utils/cli.rb
261
+ - lib/abide_dev_utils/cli/abstract.rb
218
262
  - lib/abide_dev_utils/cli/jira.rb
219
263
  - lib/abide_dev_utils/cli/puppet.rb
220
264
  - lib/abide_dev_utils/cli/test.rb
@@ -225,11 +269,14 @@ files:
225
269
  - lib/abide_dev_utils/errors/base.rb
226
270
  - lib/abide_dev_utils/errors/general.rb
227
271
  - lib/abide_dev_utils/errors/jira.rb
272
+ - lib/abide_dev_utils/errors/ppt.rb
228
273
  - lib/abide_dev_utils/errors/xccdf.rb
229
274
  - lib/abide_dev_utils/files.rb
230
275
  - lib/abide_dev_utils/jira.rb
231
276
  - lib/abide_dev_utils/output.rb
232
277
  - lib/abide_dev_utils/ppt.rb
278
+ - lib/abide_dev_utils/ppt/coverage.rb
279
+ - lib/abide_dev_utils/ppt/new_obj.rb
233
280
  - lib/abide_dev_utils/prompt.rb
234
281
  - lib/abide_dev_utils/utils/general.rb
235
282
  - lib/abide_dev_utils/validate.rb