abide_dev_utils 0.1.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.
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'abide_dev_utils/jira'
5
+
6
+ module Abide
7
+ module CLI
8
+ JIRA = AbideDevUtils::Jira
9
+
10
+ class JiraCommand < CmdParse::Command
11
+ CMD_NAME = 'jira'
12
+ CMD_SHORT = 'Commands related to Jira tickets'
13
+ CMD_LONG = 'Namespace for commands related to Jira tickets'
14
+ def initialize
15
+ super(CMD_NAME, takes_commands: true)
16
+ short_desc(CMD_SHORT)
17
+ long_desc(CMD_LONG)
18
+ add_command(CmdParse::HelpCommand.new, default: true)
19
+ add_command(JiraAuthCommand.new)
20
+ add_command(JiraGetIssueCommand.new)
21
+ add_command(JiraNewIssueCommand.new)
22
+ add_command(JiraFromCoverageCommand.new)
23
+ end
24
+ end
25
+
26
+ class JiraAuthCommand < CmdParse::Command
27
+ CMD_NAME = 'auth'
28
+ CMD_SHORT = 'Test authentication with Jira'
29
+ CMD_LONG = 'Allows you to test authenticating with Jira'
30
+ def initialize
31
+ super(CMD_NAME, takes_commands: false)
32
+ short_desc(CMD_SHORT)
33
+ long_desc(CMD_LONG)
34
+ end
35
+
36
+ def execute
37
+ client = JIRA.client
38
+ myself = JIRA.get_myself(client)
39
+ Abide::CLI::OUTPUT.simple("Successfully authenticated user #{myself.attrs['name']}!") unless myself.attrs['name'].empty?
40
+ end
41
+ end
42
+
43
+ class JiraGetIssueCommand < CmdParse::Command
44
+ CMD_NAME = 'get_issue'
45
+ CMD_SHORT = 'Gets a specific issue'
46
+ CMD_LONG = 'Returns JSON of a specific issue from key (<project>-<num>)'
47
+ def initialize
48
+ super(CMD_NAME, takes_commands: false)
49
+ short_desc(CMD_SHORT)
50
+ long_desc(CMD_LONG)
51
+ argument_desc(ISSUE: 'A Jira issue key (<PROJECT>-<NUM>)')
52
+ options.on('-o [FILE]', '--out-file [FILE]', 'Path to save the JSON output') { |o| @data[:file] = o }
53
+ end
54
+
55
+ def execute(issue)
56
+ client = JIRA.client(options: {})
57
+ issue = client.Issue.find(issue)
58
+ console = @data[:file].nil? ? true : false
59
+ out_json = issue.attrs.select { |_,v| !v.nil? || !v.empty? }
60
+ Abide::CLI::OUTPUT.json(out_json, console: console, file: @data[:file])
61
+ end
62
+ end
63
+
64
+ class JiraNewIssueCommand < CmdParse::Command
65
+ CMD_NAME = 'new_issue'
66
+ CMD_SHORT = 'Creates a new issue in a project'
67
+ CMD_LONG = 'Allows you to create a new issue in a project'
68
+ def initialize
69
+ super(CMD_NAME, takes_commands: false)
70
+ short_desc(CMD_SHORT)
71
+ long_desc(CMD_LONG)
72
+ argument_desc(
73
+ PROJECT: 'Jira project name (should be all caps)',
74
+ SUMMARY: 'Brief summary of the issue',
75
+ SUBTASKS: 'One or more summaries that become subtasks'
76
+ )
77
+ end
78
+
79
+ def execute(project, summary, *subtasks)
80
+ client = JIRA.client(options: {})
81
+ issue = JIRA.new_issue(client, project, summary)
82
+ Abide::CLI::OUTPUT.simple("Successfully created #{issue.attrs['key']}")
83
+ return if subtasks.nil? || subtasks.empty?
84
+
85
+ Abide::CLI::OUTPUT.simple('Creatings subtasks...')
86
+ JIRA.bulk_new_subtask(client, JIRA.issue(client, issue.attrs['key']), subtasks) unless subtasks.empty?
87
+ end
88
+ end
89
+
90
+ class JiraFromCoverageCommand < CmdParse::Command
91
+ CMD_NAME = 'from_coverage'
92
+ CMD_SHORT = 'Creates a parent issue with subtasks from a coverage report'
93
+ CMD_LONG = 'Creates a parent issue with subtasks for a benchmark and any uncovered controls'
94
+ def initialize
95
+ super(CMD_NAME, takes_commands: false)
96
+ short_desc(CMD_SHORT)
97
+ long_desc(CMD_LONG)
98
+ argument_desc(REPORT: 'A JSON coverage report from the abide puppet coverage command', PROJECT: 'A Jira project')
99
+ options.on('-d', '--dry-run', 'Print to console instead of saving objects') { |_| @data[:dry_run] = true }
100
+ end
101
+
102
+ def execute(report, project)
103
+ Abide::CLI::VALIDATE.file(report)
104
+ @data[:dry_run] = false if @data[:dry_run].nil?
105
+ client = JIRA.client(options: {})
106
+ proj = JIRA.project(client, project)
107
+ File.open(report) do |f|
108
+ JIRA.new_issues_from_coverage(client, proj, JSON.parse(f.read), dry_run: @data[:dry_run])
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abide
4
+ module CLI
5
+ class PuppetCommand < CmdParse::Command
6
+ CMD_NAME = 'puppet'
7
+ CMD_SHORT = 'Commands related to Puppet code'
8
+ CMD_LONG = 'Namespace for commands related to Puppet code'
9
+ def initialize
10
+ super(CMD_NAME, takes_commands: true)
11
+ short_desc(CMD_SHORT)
12
+ long_desc(CMD_LONG)
13
+ add_command(CmdParse::HelpCommand.new, default: true)
14
+ add_command(PuppetCoverageCommand.new)
15
+ end
16
+ end
17
+
18
+ class PuppetCoverageCommand < CmdParse::Command
19
+ CMD_NAME = 'coverage'
20
+ CMD_SHORT = 'Generates control coverage report'
21
+ CMD_LONG = 'Generates report of valid Puppet classes that match with Hiera controls'
22
+ CMD_CLASS_DIR = 'Directory that holds Puppet manifests'
23
+ CMD_HIERA_FILE = 'Hiera file generated from an XCCDF'
24
+ def initialize
25
+ super(CMD_NAME, takes_commands: false)
26
+ short_desc(CMD_SHORT)
27
+ long_desc(CMD_LONG)
28
+ argument_desc(CLASS_DIR: CMD_CLASS_DIR, HIERA_FILE: CMD_HIERA_FILE)
29
+ options.on('-o [FILE]', '--out-file [FILE]', 'Path to save the coverage report') { |f| @data[:file] = f }
30
+ options.on('-p [PROFILE]', '--profile [PROFILE]', 'Generate only for profile') { |p| @data[:profile] = p }
31
+ end
32
+
33
+ def help_arguments
34
+ <<~ARGHELP
35
+ Arguments:
36
+ CLASS_DIR #{CMD_CLASS_DIR}
37
+ HIERA_FILE #{CMD_HIERA_FILE}
38
+
39
+ ARGHELP
40
+ end
41
+
42
+ def execute(class_dir, hiera_file)
43
+ require 'abide_dev_utils/ppt'
44
+ Abide::CLI::VALIDATE.directory(class_dir)
45
+ Abide::CLI::VALIDATE.file(hiera_file)
46
+ coverage = AbideDevUtils::Ppt.coverage_report(class_dir, hiera_file, @data[:profile])
47
+ coverage.each do |k, v|
48
+ next if ['classes', 'benchmark'].include?(k)
49
+
50
+ Abide::CLI::OUTPUT.simple("#{k} coverage: #{v[:coverage]}%")
51
+ end
52
+ return if @data[:file].nil?
53
+
54
+ Abide::CLI::OUTPUT.json(coverage, file: @data[:file])
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abide
4
+ module CLI
5
+ class TestCommand < CmdParse::Command
6
+ CMD_NAME = 'test'
7
+ CMD_SHORT = 'Run test suites against a Puppet module'
8
+ CMD_LONG = 'Run various test suites against a Puppet module. Requires PDK to be installed.'
9
+ CMD_PDK = 'command -v pdk'
10
+ CMD_LIT_BASE = 'bundle exec rake'
11
+
12
+ def initialize
13
+ super(CMD_NAME, takes_commands: false)
14
+ short_desc(CMD_SHORT)
15
+ long_desc(CMD_LONG)
16
+ argument_desc(SUITE: 'Test suite to run [all, validate, unit, limus]')
17
+ options.on('-p', '--puppet-version', 'Set Puppet version for unit tests. Takes SemVer string') { |p| @data[:puppet] = p }
18
+ options.on('-e', '--pe-version', 'Set PE version for unit tests. Takes SemVer String') { |e| @data[:pe] = e }
19
+ options.on('-n', '--no-teardown', 'Do not tear down Litmus machines after tests') { |_| @data[:no_teardown] = true }
20
+ options.on('-c [puppet[67]]', '--collection [puppet[67]]', 'Puppet collection to use with litmus tests') { |c| @data[:collection] = c }
21
+ options.on('-l [LIST]', '--provision-list [LIST]', 'Set the provision list for Litmus') { |l| @data[:provision_list] = l }
22
+ options.on('-M [PATH]', '--module-dir [PATH]', 'Set a different directory as the module dir (defaults to current dir)') { |m| @data[:module_dir] = m }
23
+ # Declare and setup commands
24
+ @validate = ['validate', '--parallel']
25
+ @unit = ['test', 'unit', '--parallel']
26
+ # Add unit args if they exist
27
+ @unit << "--puppet-version #{@data[:puppet]}" unless @data[:puppet].nil? && !@data[:pe].nil?
28
+ @unit << "--pe-version #{@data[:pe]}" unless @data[:pe].nil?
29
+ # Get litmus args and supply defaults if necessary
30
+ litmus_pl = @data[:provision_list].nil? ? 'default' : @data[:provision_list]
31
+ litmus_co = @data[:collection].nil? ? 'puppet6' : @data[:collection]
32
+ # Now we craft the litmus commands
33
+ @litmus_pr = [CMD_LIT_BASE, "'litmus:provision_list[#{litmus_pl}]'"]
34
+ @litmus_ia = [CMD_LIT_BASE, "'litmus:install_agent[#{litmus_co}]'"]
35
+ @litmus_im = [CMD_LIT_BASE, "'litmus:install_module'"]
36
+ @litmus_ap = [CMD_LIT_BASE, "'litmus:acceptance:parallel'"]
37
+ @litmus_td = [CMD_LIT_BASE, "'litmus:tear_down'"]
38
+ validate_env_and_opts
39
+ end
40
+
41
+ def execute(suite)
42
+ case suite.downcase
43
+ when /^a[A-Za-z]*/
44
+ run_command(@validate)
45
+ run_command(@unit)
46
+ run_litmus
47
+ when /^v[A-Za-z]*/
48
+ run_command(@validate)
49
+ when /^u[A-Za-z]*/
50
+ run_command(@unit)
51
+ when /^l[A-Za-z]*/
52
+ run_litmus
53
+ else
54
+ Abide::CLI::OUTPUT.simple("Suite #{suite} in invalid!")
55
+ Abide::CLI::OUTPUT.simple('Valid options for TEST are [a]ll, [v]alidate, [u]nit, [l]itmus')
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def validate_env_and_opts
62
+ Abide::CLI::VALIDATE.directory(@data[:module_dir]) unless @data[:module_dir].nil?
63
+ Abide::CLI::VALIDATE.not_empty(`#{CMD_PDK}`, 'PDK is required for running test suites!')
64
+ end
65
+
66
+ def run_litmus
67
+ run_command(@litmus_pr)
68
+ run_command(@litmus_ia)
69
+ run_command(@litmus_im)
70
+ run_command(@litmus_ap)
71
+ run_command(@litmus_td) unless @data[:no_teardown]
72
+ end
73
+
74
+ def run_command(*args)
75
+ arg_str = args.join(' ')
76
+ if @data[:module_dir]
77
+ `cd #{@data[:module_dir]} && $(#{CMD_PDK}) #{arg_str}`
78
+ else
79
+ `$(#{CMD_PDK}) #{arg_str}`
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'abide_dev_utils/xccdf'
4
+
5
+ module Abide
6
+ module CLI
7
+ class XccdfCommand < CmdParse::Command
8
+ CMD_NAME = 'xccdf'
9
+ CMD_SHORT = 'Commands related to XCCDF files'
10
+ CMD_LONG = 'Namespace for commands related to XCCDF files'
11
+ def initialize
12
+ super(CMD_NAME, takes_commands: true)
13
+ short_desc(CMD_SHORT)
14
+ long_desc(CMD_LONG)
15
+ add_command(CmdParse::HelpCommand.new, default: true)
16
+ add_command(XccdfToHieraCommand.new)
17
+ end
18
+ end
19
+
20
+ class XccdfToHieraCommand < CmdParse::Command
21
+ CMD_NAME = 'to_hiera'
22
+ CMD_SHORT = 'Generates control coverage report'
23
+ CMD_LONG = 'Generates report of valid Puppet classes that match with Hiera controls'
24
+ def initialize
25
+ super(CMD_NAME, takes_commands: false)
26
+ short_desc(CMD_SHORT)
27
+ long_desc(CMD_LONG)
28
+ options.on('-b [TYPE]', '--benchmark-type [TYPE]', 'XCCDF Benchmark type') { |b| @data[:type] = b }
29
+ options.on('-o [FILE]', '--out-file [FILE]', 'Path to save file') { |f| @data[:file] = f }
30
+ options.on('-p [PREFIX]', '--parent-key-prefix [PREFIX]', 'A prefix to append to the parent key') { |p| @data[:parent_key_prefix] = p }
31
+ end
32
+
33
+ def execute(xccdf_file)
34
+ @data[:type] = 'cis' if @data[:type].nil?
35
+
36
+ to_hiera(xccdf_file)
37
+ end
38
+
39
+ private
40
+
41
+ def to_hiera(xccdf_file)
42
+ xfile = AbideDevUtils::XCCDF.to_hiera(xccdf_file, @data)
43
+ console = @data[:file].nil? ? true : false
44
+ Abide::CLI::OUTPUT.yaml(xfile, console: console, file: @data[:file])
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module AbideDevUtils
6
+ module Config
7
+ DEFAULT_PATH = "#{File.expand_path('~')}/.abide_dev.yaml"
8
+
9
+ def self.to_h(path = DEFAULT_PATH)
10
+ h = YAML.safe_load(File.open(path), [Symbol])
11
+ h.transform_keys(&:to_sym)
12
+ end
13
+
14
+ def self.config_section(section, path = DEFAULT_PATH)
15
+ h = to_h(path)
16
+ s = h[section.to_sym]
17
+ s.transform_keys(&:to_sym)
18
+ end
19
+
20
+ def self.fetch(key, default = nil, path = DEFAULT_PATH)
21
+ to_h(path).fetch(key, default)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbideDevUtils
4
+ module CliConstants
5
+ require 'abide_dev_utils/config'
6
+ require 'abide_dev_utils/errors'
7
+ require 'abide_dev_utils/output'
8
+ require 'abide_dev_utils/prompt'
9
+ require 'abide_dev_utils/validate'
10
+
11
+ CONFIG = AbideDevUtils::Config
12
+ ERRORS = AbideDevUtils::Errors
13
+ OUTPUT = AbideDevUtils::Output
14
+ PROMPT = AbideDevUtils::Prompt
15
+ VALIDATE = AbideDevUtils::Validate
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'abide_dev_utils/errors/general'
4
+ require 'abide_dev_utils/errors/jira'
5
+ require 'abide_dev_utils/errors/xccdf'
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbideDevUtils
4
+ module Errors
5
+ # Generic error class. Errors in AbideDevUtil all follow the
6
+ # same format: "<msg> <subject>". Each error has a default
7
+ # error message relating to error class name. Subjects should
8
+ # always be the thing that failed (file, class, data, etc.).
9
+ # @param subject [String] what failed
10
+ # @param msg [String] an error message to override the default
11
+ class GenericError < StandardError
12
+ @default = 'Generic error:'
13
+ class << self
14
+ attr_reader :default
15
+ end
16
+
17
+ attr_reader :subject
18
+
19
+ def initialize(subject = nil, msg: self.class.default)
20
+ @msg = msg
21
+ @subject = subject
22
+ message = subject.nil? ? @msg : "#{@msg} #{@subject}"
23
+ super(message)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'abide_dev_utils/errors/base'
4
+
5
+ module AbideDevUtils
6
+ module Errors
7
+ # Raised when something is empty and it shouldn't be
8
+ class ObjectEmptyError < GenericError
9
+ @default = 'Object is empty and should not be:'
10
+ end
11
+
12
+ # Raised when a an object is initialized with a nil param
13
+ class NewObjectParamNilError < GenericError
14
+ @default = 'Object init parameter is nil and should not be:'
15
+ end
16
+
17
+ # Raised when a file path does not exist
18
+ class FileNotFoundError < GenericError
19
+ @default = 'File not found:'
20
+ end
21
+
22
+ # Raised when a file path is not a regular file
23
+ class PathNotFileError < GenericError
24
+ @default = 'Path is not a regular file:'
25
+ end
26
+
27
+ # Raised when the path is not a directory
28
+ class PathNotDirectoryError < GenericError
29
+ @default = 'Path is not a directory:'
30
+ end
31
+
32
+ # Raised when a searched for service is not found in the parser
33
+ class ServiceNotFoundError < GenericError
34
+ @default = 'Service not found:'
35
+ end
36
+
37
+ # Raised when getting an InetdConfConfig object that does not exist
38
+ class ConfigObjectNotFoundError < GenericError
39
+ @default = 'Config object not found:'
40
+ end
41
+
42
+ # Raised when adding an InetdConfConfig object that already exists
43
+ class ConfigObjectExistsError < GenericError
44
+ @default = 'Config object already exists:'
45
+ end
46
+
47
+ # Raised when an object should respond to :to_hash or :to_h and doesn't
48
+ class NotHashableError < GenericError
49
+ @default = 'Object does not respond to #to_hash or #to_h:'
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'abide_dev_utils/errors/base'
4
+
5
+ module AbideDevUtils
6
+ module Errors
7
+ module Jira
8
+ class CreateIssueError < GenericError
9
+ @default = 'Failed to create Jira issue:'
10
+ end
11
+
12
+ class CreateSubtaskError < GenericError
13
+ @default = 'Failed to create Jira subtask for issue:'
14
+ end
15
+
16
+ class FindIssueError < GenericError
17
+ @default = 'Failed to find Jira issue:'
18
+ end
19
+ end
20
+ end
21
+ end