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 +4 -4
- data/README.md +207 -6
- data/abide_dev_utils.gemspec +1 -1
- data/lib/abide_dev_utils.rb +4 -3
- data/lib/abide_dev_utils/cli.rb +1 -0
- data/lib/abide_dev_utils/cli/abstract.rb +16 -0
- data/lib/abide_dev_utils/cli/jira.rb +3 -1
- data/lib/abide_dev_utils/cli/puppet.rb +66 -10
- data/lib/abide_dev_utils/errors.rb +2 -0
- data/lib/abide_dev_utils/errors/ppt.rb +29 -0
- data/lib/abide_dev_utils/jira.rb +1 -1
- data/lib/abide_dev_utils/ppt.rb +2 -71
- data/lib/abide_dev_utils/ppt/coverage.rb +87 -0
- data/lib/abide_dev_utils/ppt/new_obj.rb +168 -0
- data/lib/abide_dev_utils/resources/generic_spec.erb +13 -0
- data/lib/abide_dev_utils/version.rb +1 -1
- data/lib/abide_dev_utils/xccdf/cis/hiera.rb +18 -10
- metadata +9 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c4ee71223292cc98028db5407eaef69cb2a0a150de9552b4d51d3b1ee17197bf
|
4
|
+
data.tar.gz: 3f56a0ad99e2ae3ccc8478210f7bb1961c4500267bedc1d42612c1947ee7c3fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
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
|
|
data/abide_dev_utils.gemspec
CHANGED
@@ -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.
|
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
|
data/lib/abide_dev_utils.rb
CHANGED
@@ -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/
|
6
|
-
require 'abide_dev_utils/
|
5
|
+
require 'abide_dev_utils/ppt'
|
6
|
+
require 'abide_dev_utils/jira'
|
7
|
+
require 'abide_dev_utils/config'
|
7
8
|
|
8
|
-
#
|
9
|
+
# Root namespace all modules / classes
|
9
10
|
module AbideDevUtils; end
|
data/lib/abide_dev_utils/cli.rb
CHANGED
@@ -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
|
-
|
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 <
|
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 <
|
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.
|
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
|
@@ -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
|
data/lib/abide_dev_utils/jira.rb
CHANGED
@@ -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|
|
data/lib/abide_dev_utils/ppt.rb
CHANGED
@@ -1,52 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
require '
|
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
|
@@ -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, :
|
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.
|
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
|
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
|
-
|
130
|
-
|
131
|
-
|
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.
|
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
|
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.
|
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.
|
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
|