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.
@@ -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 an object is initialized with a nil param
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 = "Console options conflict."
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