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