abide_dev_utils 0.10.1 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|