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.
@@ -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