abide_dev_utils 0.1.0

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