abide_dev_utils 0.10.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +7 -1
- data/Gemfile.lock +22 -3
- data/Rakefile +28 -0
- data/abide_dev_utils.gemspec +1 -0
- data/lib/abide_dev_utils/cem/benchmark.rb +291 -0
- data/lib/abide_dev_utils/cem/coverage_report.rb +348 -0
- data/lib/abide_dev_utils/cem/generate/reference.rb +116 -0
- data/lib/abide_dev_utils/cem/generate.rb +10 -0
- data/lib/abide_dev_utils/cem/mapping/mapper.rb +155 -0
- data/lib/abide_dev_utils/cem.rb +2 -0
- data/lib/abide_dev_utils/cli/cem.rb +80 -0
- data/lib/abide_dev_utils/errors/cem.rb +22 -0
- data/lib/abide_dev_utils/errors/general.rb +8 -2
- data/lib/abide_dev_utils/errors/ppt.rb +4 -0
- data/lib/abide_dev_utils/errors.rb +6 -0
- data/lib/abide_dev_utils/markdown.rb +104 -0
- data/lib/abide_dev_utils/ppt/facter_utils.rb +140 -0
- data/lib/abide_dev_utils/ppt/hiera.rb +297 -0
- data/lib/abide_dev_utils/ppt/puppet_module.rb +74 -0
- data/lib/abide_dev_utils/ppt.rb +3 -5
- data/lib/abide_dev_utils/validate.rb +14 -0
- data/lib/abide_dev_utils/version.rb +1 -1
- metadata +26 -3
- data/lib/abide_dev_utils/ppt/coverage.rb +0 -86
@@ -15,10 +15,90 @@ module Abide
|
|
15
15
|
CMD_LONG = 'Namespace for commands related to Puppet CEM'
|
16
16
|
def initialize
|
17
17
|
super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: true)
|
18
|
+
add_command(CemGenerate.new)
|
18
19
|
add_command(CemUpdateConfig.new)
|
19
20
|
end
|
20
21
|
end
|
21
22
|
|
23
|
+
class CemGenerate < AbideCommand
|
24
|
+
CMD_NAME = 'generate'
|
25
|
+
CMD_SHORT = 'Holds subcommands for generating objects / files'
|
26
|
+
CMD_LONG = 'Holds subcommands for generating objects / files'
|
27
|
+
def initialize
|
28
|
+
super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: true)
|
29
|
+
add_command(CemGenerateCoverageReport.new)
|
30
|
+
add_command(CemGenerateReference.new)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class CemGenerateCoverageReport < AbideCommand
|
35
|
+
CMD_NAME = 'coverage-report'
|
36
|
+
CMD_SHORT = 'Generates control coverage report'
|
37
|
+
CMD_LONG = <<-EOLC.chomp
|
38
|
+
Generates report of resources that are associated with controls in mapping data. This command must
|
39
|
+
be run from a module directory.
|
40
|
+
EOLC
|
41
|
+
def initialize
|
42
|
+
super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: false)
|
43
|
+
options.on('-o [FILE]', '--out-file [FILE]', 'Path to save the coverage report') { |f| @data[:file] = f }
|
44
|
+
options.on('-f [FORMAT]', '--format [FORMAT]', 'The format to output the report in (hash, json, yaml)') do |f|
|
45
|
+
@data[:format] = f
|
46
|
+
end
|
47
|
+
options.on('-I', '--ignore-benchmark-errors', 'Ignores errors while generating benchmark reports') do
|
48
|
+
@data[:ignore_all] = true
|
49
|
+
end
|
50
|
+
options.on('-v', '--verbose', 'Will output the report to the console') { @data[:verbose] = true }
|
51
|
+
options.on('-q', '--quiet', 'Will not output anything to the console') { @data[:quiet] = true }
|
52
|
+
end
|
53
|
+
|
54
|
+
def execute
|
55
|
+
file_name = @data.fetch(:file, 'coverage_report')
|
56
|
+
out_format = @data.fetch(:format, 'yaml')
|
57
|
+
quiet = @data.fetch(:quiet, false)
|
58
|
+
console = @data.fetch(:verbose, false) && !quiet
|
59
|
+
ignore_all = @data.fetch(:ignore_all, false)
|
60
|
+
AbideDevUtils::Output.simple('Generating coverage report...') unless quiet
|
61
|
+
coverage = AbideDevUtils::CEM::CoverageReport.basic_coverage(format_func: :to_h, ignore_benchmark_errors: ignore_all)
|
62
|
+
AbideDevUtils::Output.simple("Saving coverage report to #{file_name}...")
|
63
|
+
case out_format
|
64
|
+
when /yaml/i
|
65
|
+
AbideDevUtils::Output.yaml(coverage, console: console, file: file_name)
|
66
|
+
when /json/i
|
67
|
+
AbideDevUtils::Output.json(coverage, console: console, file: file_name)
|
68
|
+
else
|
69
|
+
File.open(file_name, 'w') do |f|
|
70
|
+
AbideDevUtils::Output.simple(coverage.to_s, stream: f)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class CemGenerateReference < AbideCommand
|
77
|
+
CMD_NAME = 'reference'
|
78
|
+
CMD_SHORT = 'Generates a reference doc for the module'
|
79
|
+
CMD_LONG = 'Generates a reference doc for the module'
|
80
|
+
def initialize
|
81
|
+
super(CMD_NAME, CMD_SHORT, CMD_LONG, takes_commands: false)
|
82
|
+
options.on('-o [FILE]', '--out-file [FILE]', 'Path to save the updated config file') do |o|
|
83
|
+
@data[:out_file] = o
|
84
|
+
end
|
85
|
+
options.on('-f [FORMAT]', '--format [FORMAT]', 'Format to save reference as') do |f|
|
86
|
+
@data[:format] = f
|
87
|
+
end
|
88
|
+
options.on('-v', '--verbose', 'Verbose output') do
|
89
|
+
@data[:verbose] = true
|
90
|
+
end
|
91
|
+
options.on('-q', '--quiet', 'Quiet output') do
|
92
|
+
@data[:quiet] = true
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def execute
|
97
|
+
AbideDevUtils::Validate.puppet_module_directory
|
98
|
+
AbideDevUtils::CEM::Generate::Reference.generate(@data)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
22
102
|
class CemUpdateConfig < AbideCommand
|
23
103
|
CMD_NAME = 'update-config'
|
24
104
|
CMD_SHORT = 'Updates the Puppet CEM config'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'abide_dev_utils/errors/base'
|
4
|
+
|
5
|
+
module AbideDevUtils
|
6
|
+
module Errors
|
7
|
+
# Raised by Benchmark when mapping data cannot be loaded
|
8
|
+
class MappingFilesNotFoundError < GenericError
|
9
|
+
@default = 'Mapping files not found using facts:'
|
10
|
+
end
|
11
|
+
|
12
|
+
# Raised by Benchmark when mapping files are not found for the specified framework
|
13
|
+
class MappingDataFrameworkMismatchError < GenericError
|
14
|
+
@default = 'Mapping data could not be found for the specified framework:'
|
15
|
+
end
|
16
|
+
|
17
|
+
# Raised by Benchmark when resource data cannot be loaded
|
18
|
+
class ResourceDataNotFoundError < GenericError
|
19
|
+
@default = 'Resource data not found using facts:'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -9,7 +9,12 @@ module AbideDevUtils
|
|
9
9
|
@default = 'Object is empty and should not be:'
|
10
10
|
end
|
11
11
|
|
12
|
-
# Raised when a
|
12
|
+
# Raised when something is not a string, or is an empty string
|
13
|
+
class NotPopulatedStringError < GenericError
|
14
|
+
@default = 'Object is either not a String or is empty:'
|
15
|
+
end
|
16
|
+
|
17
|
+
# Raised when an object is initialized with a nil param
|
13
18
|
class NewObjectParamNilError < GenericError
|
14
19
|
@default = 'Object init parameter is nil and should not be:'
|
15
20
|
end
|
@@ -54,8 +59,9 @@ module AbideDevUtils
|
|
54
59
|
@default = 'Object does not respond to #to_hash or #to_h:'
|
55
60
|
end
|
56
61
|
|
62
|
+
# Raised when conflicting CLI options are specified for a command
|
57
63
|
class CliOptionsConflict < GenericError
|
58
|
-
@default =
|
64
|
+
@default = 'Console options conflict:'
|
59
65
|
end
|
60
66
|
end
|
61
67
|
end
|
@@ -5,6 +5,10 @@ require 'abide_dev_utils/errors/base'
|
|
5
5
|
module AbideDevUtils
|
6
6
|
module Errors
|
7
7
|
module Ppt
|
8
|
+
class NotModuleDirError < GenericError
|
9
|
+
@default = 'Path is not a Puppet module directory:'
|
10
|
+
end
|
11
|
+
|
8
12
|
class ObjClassPathError < GenericError
|
9
13
|
@default = 'Invalid path for class:'
|
10
14
|
end
|
@@ -1,9 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'abide_dev_utils/errors/base'
|
4
|
+
require 'abide_dev_utils/errors/cem'
|
4
5
|
require 'abide_dev_utils/errors/comply'
|
5
6
|
require 'abide_dev_utils/errors/gcloud'
|
6
7
|
require 'abide_dev_utils/errors/general'
|
7
8
|
require 'abide_dev_utils/errors/jira'
|
8
9
|
require 'abide_dev_utils/errors/xccdf'
|
9
10
|
require 'abide_dev_utils/errors/ppt'
|
11
|
+
|
12
|
+
module AbideDevUtils
|
13
|
+
# Namespace for Error objects
|
14
|
+
module Errors; end
|
15
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AbideDevUtils
|
4
|
+
# Formats text for output in markdown
|
5
|
+
class Markdown
|
6
|
+
def initialize(file, with_toc: true)
|
7
|
+
@file = file
|
8
|
+
@with_toc = with_toc
|
9
|
+
@toc = ["## Table of Contents\n"]
|
10
|
+
@body = []
|
11
|
+
@title = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_markdown
|
15
|
+
toc = @toc.join("\n")
|
16
|
+
body = @body.join("\n")
|
17
|
+
"#{@title}\n#{toc}\n\n#{body}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_file
|
21
|
+
File.write(@file, to_markdown)
|
22
|
+
end
|
23
|
+
|
24
|
+
def method_missing(name, *args, &block)
|
25
|
+
if name.to_s.start_with?('add_')
|
26
|
+
add(name.to_s.sub('add_', '').to_sym, *args, &block)
|
27
|
+
else
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def respond_to_missing?(name, include_private = false)
|
33
|
+
name.to_s.start_with?('add_') || super
|
34
|
+
end
|
35
|
+
|
36
|
+
def title(text)
|
37
|
+
"# #{text}\n"
|
38
|
+
end
|
39
|
+
|
40
|
+
def h1(text)
|
41
|
+
"## #{text}\n"
|
42
|
+
end
|
43
|
+
|
44
|
+
def h2(text)
|
45
|
+
"### #{text}\n"
|
46
|
+
end
|
47
|
+
|
48
|
+
def h3(text)
|
49
|
+
"#### #{text}\n"
|
50
|
+
end
|
51
|
+
|
52
|
+
def ul(text, indent: 0)
|
53
|
+
indented_text = []
|
54
|
+
indent.times { indented_text << ' ' } if indent.positive?
|
55
|
+
|
56
|
+
indented_text << "* #{text}"
|
57
|
+
indented_text.join
|
58
|
+
end
|
59
|
+
|
60
|
+
def bold(text)
|
61
|
+
"**#{text}**"
|
62
|
+
end
|
63
|
+
|
64
|
+
def italic(text)
|
65
|
+
"*#{text}*"
|
66
|
+
end
|
67
|
+
|
68
|
+
def link(text, url, anchor: false)
|
69
|
+
url = anchor(url) if anchor
|
70
|
+
"[#{text}](#{url.downcase})"
|
71
|
+
end
|
72
|
+
|
73
|
+
def code(text)
|
74
|
+
"\`#{text}\`"
|
75
|
+
end
|
76
|
+
|
77
|
+
def code_block(text, language: nil)
|
78
|
+
language.nil? ? "```\n#{text}\n```" : "```#{language}\n#{text}\n```"
|
79
|
+
end
|
80
|
+
|
81
|
+
def anchor(text)
|
82
|
+
"##{text.downcase.gsub(%r{\s|_}, '-').tr('.,\'"()', '')}"
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def add(type, text, *args, **kwargs)
|
88
|
+
@toc << ul(link(text, text, anchor: true), indent: 0) if @with_toc && type == :h1
|
89
|
+
|
90
|
+
case type.to_sym
|
91
|
+
when :title
|
92
|
+
@title = title(text)
|
93
|
+
when :ul
|
94
|
+
@body << ul(text, indent: kwargs.fetch(:indent, 0))
|
95
|
+
when :link
|
96
|
+
@body << link(text, args.first, anchor: kwargs.fetch(:anchor, false))
|
97
|
+
when :code_block
|
98
|
+
@body << code_block(text, language: kwargs.fetch(:language, nil))
|
99
|
+
else
|
100
|
+
@body << send(type, text)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'facterdb'
|
5
|
+
|
6
|
+
module AbideDevUtils
|
7
|
+
module Ppt
|
8
|
+
# Methods relating to Facter
|
9
|
+
module FacterUtils
|
10
|
+
class << self
|
11
|
+
attr_writer :current_version
|
12
|
+
|
13
|
+
def current_version
|
14
|
+
return latest_version unless defined?(@current_version)
|
15
|
+
|
16
|
+
@current_version
|
17
|
+
end
|
18
|
+
|
19
|
+
def use_version(version)
|
20
|
+
self.current_version = version
|
21
|
+
current_version
|
22
|
+
end
|
23
|
+
|
24
|
+
def with_version(version, reset: true)
|
25
|
+
return unless block_given?
|
26
|
+
|
27
|
+
old_ver = current_version.dup
|
28
|
+
use_version(version)
|
29
|
+
output = yield
|
30
|
+
use_version(old_ver) if reset
|
31
|
+
output
|
32
|
+
end
|
33
|
+
|
34
|
+
def fact_files
|
35
|
+
@fact_files ||= FacterDB.facterdb_fact_files.each_with_object({}) do |f, h|
|
36
|
+
facter_version = file_facter_version(f)
|
37
|
+
h[facter_version] = [] unless h.key?(facter_version)
|
38
|
+
h[facter_version] << f
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def fact_sets(facter_version: current_version)
|
43
|
+
@fact_sets ||= fact_files[facter_version].each_with_object({}) do |fp, h|
|
44
|
+
h[facter_version] = [] unless h.key?(facter_version)
|
45
|
+
h[facter_version] << JSON.parse(File.read(fp))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def file_facter_version(path)
|
50
|
+
File.basename(File.dirname(path))
|
51
|
+
end
|
52
|
+
|
53
|
+
def all_versions
|
54
|
+
@all_versions ||= fact_files.keys.sort
|
55
|
+
end
|
56
|
+
|
57
|
+
def latest_version
|
58
|
+
@latest_version ||= all_versions[-1]
|
59
|
+
end
|
60
|
+
|
61
|
+
def previous_major_version(facter_version = current_version)
|
62
|
+
@previous_major_version_map ||= {}
|
63
|
+
|
64
|
+
majver = facter_version.split('.')[0]
|
65
|
+
return @previous_major_version_map[majver] if @previous_major_version_map.key?(majver)
|
66
|
+
|
67
|
+
prev_majver = (majver.to_i - 1).to_s
|
68
|
+
prev_ver = all_versions.select { |v| v.start_with?(prev_majver) }.max
|
69
|
+
return nil if prev_ver.to_i < 1
|
70
|
+
|
71
|
+
@previous_major_version_map[majver] = prev_ver
|
72
|
+
@previous_major_version_map[majver]
|
73
|
+
end
|
74
|
+
|
75
|
+
def recurse_versions(version = current_version, &block)
|
76
|
+
use_version(version)
|
77
|
+
output = yield
|
78
|
+
return output unless output.nil? || output.empty?
|
79
|
+
|
80
|
+
prev_ver = previous_major_version(version).dup
|
81
|
+
return nil if prev_ver.nil?
|
82
|
+
|
83
|
+
recurse_versions(prev_ver, &block)
|
84
|
+
rescue SystemStackError
|
85
|
+
locals = {
|
86
|
+
prev_ver_map: @previous_major_version_map,
|
87
|
+
current_version: current_version,
|
88
|
+
}
|
89
|
+
raise "Failed to find output while recursing versions. Locals: #{locals}"
|
90
|
+
end
|
91
|
+
|
92
|
+
def recursive_facts_for_os(os_name, os_release_major = nil, os_hardware: 'x86_64')
|
93
|
+
saved_ver = current_version.dup
|
94
|
+
output = recurse_versions do
|
95
|
+
facts_for_os(os_name, os_release_major, os_hardware: os_hardware)
|
96
|
+
end
|
97
|
+
use_version(saved_ver)
|
98
|
+
output
|
99
|
+
end
|
100
|
+
|
101
|
+
def facts_for_os(os_name, os_release_major = nil, os_hardware: 'x86_64', facter_version: current_version)
|
102
|
+
cache_key = "#{os_name.downcase}_#{os_release_major}_#{os_hardware}"
|
103
|
+
return @facts_for_os[cache_key] if @facts_for_os&.key?(cache_key)
|
104
|
+
|
105
|
+
fact_file = fact_files[facter_version].find do |f|
|
106
|
+
name_parts = File.basename(f, '.facts').split('-')
|
107
|
+
name = name_parts[0]
|
108
|
+
relmaj = name_parts.length >= 3 ? name_parts[1] : nil
|
109
|
+
hardware = name_parts[-1]
|
110
|
+
name == os_name.downcase && relmaj == os_release_major && hardware == os_hardware
|
111
|
+
end
|
112
|
+
return if fact_file.nil? || fact_file.empty?
|
113
|
+
|
114
|
+
@facts_for_os = {} unless defined?(@facts_for_os)
|
115
|
+
@facts_for_os[cache_key] = JSON.parse(File.read(fact_file))
|
116
|
+
@facts_for_os[cache_key]
|
117
|
+
end
|
118
|
+
|
119
|
+
def resolve_dot_path(dot_path, facter_version: latest_version)
|
120
|
+
path_array = dot_path.delete_prefix('facts.').split('.')
|
121
|
+
resolved = fact_sets[facter_version].map do |fs|
|
122
|
+
fs.dig(*path_array)
|
123
|
+
end
|
124
|
+
resolved.compact.uniq
|
125
|
+
end
|
126
|
+
|
127
|
+
def resolve_related_dot_paths(*dot_paths, facter_version: current_version)
|
128
|
+
resolved = []
|
129
|
+
fact_sets[facter_version].map do |fs|
|
130
|
+
resolved << dot_paths.map do |p|
|
131
|
+
path_array = p.delete_prefix('facts.').split('.')
|
132
|
+
fs.dig(*path_array)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
resolved
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|