abide_dev_utils 0.12.2 → 0.14.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.
@@ -11,6 +11,7 @@ module AbideDevUtils
11
11
  ERRORS = AbideDevUtils::Errors::Jira
12
12
  COV_PARENT_SUMMARY_PREFIX = '::BENCHMARK:: '
13
13
  COV_CHILD_SUMMARY_PREFIX = '::CONTROL:: '
14
+ PROGRESS_BAR_FORMAT = '%a %e %P% Created: %c of %C'
14
15
 
15
16
  def self.project(client, project)
16
17
  client.Project.find(project)
@@ -18,6 +19,11 @@ module AbideDevUtils
18
19
 
19
20
  def self.issue(client, issue)
20
21
  client.Issue.find(issue)
22
+ rescue URI::InvalidURIError
23
+ iss = client.Issue.all.find { |i| i.summary == issue }
24
+ raise ERRORS::FindIssueError, issue unless iss
25
+
26
+ iss
21
27
  end
22
28
 
23
29
  def self.myself(client)
@@ -25,11 +31,19 @@ module AbideDevUtils
25
31
  end
26
32
 
27
33
  def self.issuetype(client, id)
28
- client.Issuetype.find(id)
34
+ if id.match?(%r{^\d+$})
35
+ client.Issuetype.find(id)
36
+ else
37
+ client.Issuetype.all.find { |i| i.name == id }
38
+ end
29
39
  end
30
40
 
31
41
  def self.priority(client, id)
32
- client.Priority.find(id)
42
+ if id.match?(%r{^\d+$})
43
+ client.Priority.find(id)
44
+ else
45
+ client.Priority.all.find { |i| i.name == id }
46
+ end
33
47
  end
34
48
 
35
49
  def self.all_project_issues_attrs(project)
@@ -37,21 +51,50 @@ module AbideDevUtils
37
51
  raw_issues.collect(&:attrs)
38
52
  end
39
53
 
40
- def self.new_issue(client, project, summary, dry_run: false)
54
+ def self.add_issue_label(iss, label, dry_run: false)
55
+ return if dry_run || iss.labels.include?(label)
56
+
57
+ iss.labels << profile_summary
58
+ iss.save
59
+ end
60
+
61
+ def self.new_issue(client, project, summary, labels: ['abide_dev_utils'], epic: nil, dry_run: false)
41
62
  if dry_run
42
63
  sleep(0.2)
43
- return Dummy.new
64
+ return Dummy.new(summary)
44
65
  end
45
66
  fields = {}
46
67
  fields['summary'] = summary
47
68
  fields['project'] = project(client, project)
48
69
  fields['reporter'] = myself(client)
49
- fields['issuetype'] = issuetype(client, '3')
70
+ fields['issuetype'] = issuetype(client, 'Task')
50
71
  fields['priority'] = priority(client, '6')
51
- issue = client.Issue.build
52
- raise ERRORS::CreateIssueError, issue.attrs unless issue.save({ 'fields' => fields })
72
+ fields['labels'] = labels
73
+ epic = issue(client, epic) if epic && !epic.is_a?(JIRA::Resource::Issue)
74
+ fields['customfield_10006'] = epic.key if epic # Epic_Link
75
+ iss = client.Issue.build
76
+ raise ERRORS::CreateIssueError, iss.attrs unless iss.save({ 'fields' => fields })
53
77
 
54
- issue
78
+ iss
79
+ end
80
+
81
+ def self.new_epic(client, project, summary, dry_run: false)
82
+ AbideDevUtils::Output.simple("#{dr_prefix(dry_run)}Creating epic '#{summary}'")
83
+ if dry_run
84
+ sleep(0.2)
85
+ return Dummy.new(summary)
86
+ end
87
+ fields = {
88
+ 'summary' => summary,
89
+ 'project' => project(client, project),
90
+ 'reporter' => myself(client),
91
+ 'issuetype' => issuetype(client, 'Epic'),
92
+ 'customfield_10007' => summary, # Epic Name
93
+ }
94
+ iss = client.Issue.build
95
+ raise ERRORS::CreateEpicError, iss.attrs unless iss.save({ 'fields' => fields })
96
+
97
+ iss
55
98
  end
56
99
 
57
100
  # This should probably be threaded in the future
@@ -135,46 +178,55 @@ module AbideDevUtils
135
178
  end
136
179
  end
137
180
 
138
- def self.new_issues_from_xccdf(client, project, xccdf_path, dry_run: false)
139
- dr_prefix = dry_run ? 'DRY RUN: ' : ''
181
+ def self.new_issues_from_xccdf(client, project, xccdf_path, epic: nil, dry_run: false)
140
182
  i_attrs = all_project_issues_attrs(project)
141
-
142
183
  xccdf = AbideDevUtils::XCCDF::Benchmark.new(xccdf_path)
143
-
144
- summaries = summaries_from_xccdf(xccdf)
145
- summaries.each do |profile_summary, control_summaries|
146
- if summary_exist?(profile_summary, i_attrs)
147
- AbideDevUtils::Output.simple("#{dr_prefix}Skipping #{profile_summary} as it already exists")
148
- next
184
+ # We need to get the actual epic Issue object, or create it if it doesn't exist
185
+ epic = if epic.nil?
186
+ new_epic_summary = "#{COV_PARENT_SUMMARY_PREFIX}#{xccdf.title}"
187
+ if summary_exist?(new_epic_summary, i_attrs)
188
+ issue(client, new_epic_summary)
189
+ else
190
+ unless AbideDevUtils::Prompt.yes_no("#{dr_prefix(dry_run)}Create new epic '#{new_epic_summary}'?")
191
+ AbideDevUtils::Output.simple("#{dr_prefix(dry_run)}Aborting")
192
+ exit(0)
193
+ end
194
+ new_epic(client, project.key, new_epic_summary, dry_run: dry_run)
195
+ end
196
+ else
197
+ issue(client, epic)
198
+ end
199
+ # Now we need to find out which issues we need to create for the benchmark
200
+ # The profiles that the control belongs to will be added as an issue label
201
+ to_create = {}
202
+ summaries_from_xccdf(xccdf).each do |profile_summary, control_summaries|
203
+ control_summaries.reject { |s| summary_exist?(s, i_attrs) }.each do |control_summary|
204
+ if to_create.key?(control_summary)
205
+ to_create[control_summary] << profile_summary.split.join('_').downcase
206
+ else
207
+ to_create[control_summary] = [profile_summary.split.join('_').downcase]
208
+ end
149
209
  end
210
+ end
150
211
 
151
- parent = new_issue(client, project.attrs['key'], profile_summary, dry_run: dry_run)
152
- AbideDevUtils::Output.simple("#{dr_prefix}Created parent issue #{profile_summary}")
153
- parent_issue = issue(client, parent.attrs['key']) unless parent.respond_to?(:dummy)
154
- AbideDevUtils::Output.simple("#{dr_prefix}Creating subtasks, this can take a while...")
155
- progress = AbideDevUtils::Output.progress(title: "#{dr_prefix}Creating Subtasks", total: nil)
156
- control_summaries.each do |control_summary|
157
- next if summary_exist?(control_summary, i_attrs)
212
+ unless AbideDevUtils::Prompt.yes_no("#{dr_prefix(dry_run)}Create #{to_create.keys.count} new Jira issues?")
213
+ AbideDevUtils::Output.simple("#{dr_prefix(dry_run)}Aborting")
214
+ exit(0)
215
+ end
158
216
 
159
- progress.title = "#{dr_prefix}#{control_summary}"
160
- new_subtask(client, parent_issue, control_summary, dry_run: dry_run)
161
- progress.increment
162
- end
163
- final_text = "#{dr_prefix}Created #{control_summaries.count} subtasks for #{profile_summary}"
164
- puts "\r\033[K#{final_text}\n"
217
+ progress = AbideDevUtils::Output.progress(title: "#{dr_prefix(dry_run)}Creating issues",
218
+ total: to_create.keys.count,
219
+ format: PROGRESS_BAR_FORMAT)
220
+ to_create.each do |control_summary, labels|
221
+ abrev = control_summary.length > 40 ? control_summary[0..60] : control_summary
222
+ progress.log("#{dr_prefix(dry_run)}Creating #{abrev}...")
223
+ new_issue(client, project.key, control_summary, labels: labels, epic: epic, dry_run: dry_run)
224
+ progress.increment
165
225
  end
226
+ progress.finish
227
+ AbideDevUtils::Output.simple("#{dr_prefix(dry_run)}Done creating tasks in Epic '#{epic.summary}'")
166
228
  end
167
229
 
168
- # def self.new_issues_from_comply_report(client, project, report, dry_run: false)
169
- # dr_prefix = dry_run ? 'DRY RUN: ' : ''
170
- # i_attrs = all_project_issues_attrs(project)
171
- # rep_sums = summaries_from_coverage_report(report)
172
- # rep_sums.each do |k, v|
173
- # next if summary_exist?(k, i_attrs)
174
-
175
- # progress = AbideDevUtils::Output.progress(title: "#{dr_prefix}Creating Tasks", total: nil)
176
- # v.each do |s|
177
-
178
230
  def self.merge_options(options)
179
231
  config.merge(options)
180
232
  end
@@ -209,25 +261,30 @@ module AbideDevUtils
209
261
 
210
262
  def self.summaries_from_xccdf(xccdf)
211
263
  summaries = {}
212
- facter_os = xccdf.facter_benchmark.join('-')
213
264
  xccdf.profiles.each do |profile|
214
- summaries["#{COV_PARENT_SUMMARY_PREFIX}#{facter_os} - #{profile.level} #{profile.title}"] = profile.controls.collect do |control|
215
- summary = "#{COV_CHILD_SUMMARY_PREFIX}#{control.vulnid} - #{control.title}"
216
- if summary.length > 255
217
- summary = summary[0..251] + '...'
218
- end
265
+ sum_key = "#{profile.level}_#{profile.title}".split.join('_').downcase
266
+ summaries[sum_key] = profile.controls.collect do |control|
267
+ control_id = control.respond_to?(:vulnid) ? control.vulnid : control.number
268
+ summary = "#{control_id} - #{control.title}"
269
+ summary = "#{summary[0..251]}..." if summary.length > 255
219
270
  summary
220
271
  end
221
272
  end
222
273
  summaries
223
274
  end
224
275
 
225
- # def self.summaries_from_comply_report(report)
226
- # summaries = {}
227
- # report.each do |_, v|
228
- # end
276
+ def self.dr_prefix(dry_run)
277
+ dry_run ? 'DRY RUN: ' : ''
278
+ end
229
279
 
230
280
  class Dummy
281
+ attr_reader :summary, :key
282
+
283
+ def initialize(summary = 'dummy summary')
284
+ @summary = summary
285
+ @key = 'DUM-111'
286
+ end
287
+
231
288
  def attrs
232
289
  { 'fields' => {
233
290
  'project' => 'dummy',
@@ -49,6 +49,10 @@ module AbideDevUtils
49
49
  "#### #{text}\n"
50
50
  end
51
51
 
52
+ def paragraph(text)
53
+ "#{text}\n"
54
+ end
55
+
52
56
  def ul(text, indent: 0)
53
57
  indented_text = []
54
58
  indent.times { indented_text << ' ' } if indent.positive?
@@ -10,30 +10,44 @@ require 'abide_dev_utils/files'
10
10
  module AbideDevUtils
11
11
  module Output
12
12
  FWRITER = AbideDevUtils::Files::Writer.new
13
- def self.simple(msg, stream: $stdout)
14
- stream.puts msg
13
+ def self.simple(msg, stream: $stdout, **_)
14
+ case msg
15
+ when Hash
16
+ stream.puts JSON.pretty_generate(msg)
17
+ else
18
+ stream.puts msg
19
+ end
15
20
  end
16
21
 
17
- def self.json(in_obj, console: false, file: nil, pretty: true)
22
+ def self.text(msg, console: false, file: nil, **_)
23
+ simple(msg) if console
24
+ FWRITER.write_text(msg, file: file) unless file.nil?
25
+ end
26
+
27
+ def self.json(in_obj, console: false, file: nil, pretty: true, **_)
18
28
  AbideDevUtils::Validate.hashable(in_obj)
19
29
  json_out = pretty ? JSON.pretty_generate(in_obj) : JSON.generate(in_obj)
20
30
  simple(json_out) if console
21
31
  FWRITER.write_json(json_out, file: file) unless file.nil?
22
32
  end
23
33
 
24
- def self.yaml(in_obj, console: false, file: nil)
34
+ def self.yaml(in_obj, console: false, file: nil, stringify: false, **_)
25
35
  yaml_out = if in_obj.is_a? String
26
36
  in_obj
27
37
  else
28
38
  AbideDevUtils::Validate.hashable(in_obj)
29
- # Use object's #to_yaml method if it exists, convert to hash if not
30
- in_obj.respond_to?(:to_yaml) ? in_obj.to_yaml : in_obj.to_h.to_yaml
39
+ if stringify
40
+ JSON.parse(JSON.generate(in_obj)).to_yaml
41
+ else
42
+ # Use object's #to_yaml method if it exists, convert to hash if not
43
+ in_obj.respond_to?(:to_yaml) ? in_obj.to_yaml : in_obj.to_h.to_yaml
44
+ end
31
45
  end
32
46
  simple(yaml_out) if console
33
47
  FWRITER.write_yaml(yaml_out, file: file) unless file.nil?
34
48
  end
35
49
 
36
- def self.yml(in_obj, console: false, file: nil)
50
+ def self.yml(in_obj, console: false, file: nil, **_)
37
51
  AbideDevUtils::Validate.hashable(in_obj)
38
52
  # Use object's #to_yaml method if it exists, convert to hash if not
39
53
  yml_out = in_obj.respond_to?(:to_yaml) ? in_obj.to_yaml : in_obj.to_h.to_yaml
@@ -41,8 +55,8 @@ module AbideDevUtils
41
55
  FWRITER.write_yml(yml_out, file: file) unless file.nil?
42
56
  end
43
57
 
44
- def self.progress(title: 'Progress', start: 0, total: 100)
45
- ProgressBar.create(title: title, starting_at: start, total: total)
58
+ def self.progress(title: 'Progress', start: 0, total: 100, format: nil, **_)
59
+ ProgressBar.create(title: title, starting_at: start, total: total, format: format)
46
60
  end
47
61
  end
48
62
  end
@@ -10,17 +10,30 @@ module AbideDevUtils
10
10
  attr_reader :manifest_file
11
11
 
12
12
  def initialize(manifest_file)
13
+ @compiler = Puppet::Pal::Compiler.new(nil)
13
14
  @manifest_file = File.expand_path(manifest_file)
14
15
  raise ArgumentError, "File #{@manifest_file} is not a file" unless File.file?(@manifest_file)
15
16
  end
16
17
 
17
18
  def ast
18
- @ast ||= Puppet::Pal::Compiler.new(nil).parse_file(manifest_file)
19
+ @ast ||= non_validating_parse_file(manifest_file)
19
20
  end
20
21
 
21
22
  def declaration
22
23
  @declaration ||= Declaration.new(ast)
23
24
  end
25
+
26
+ private
27
+
28
+ # This method gets around the normal validation performed by the regular
29
+ # Puppet::Pal::Compiler#parse_file method. This is necessary because, with
30
+ # validation enabled, the parser will raise errors during parsing if the
31
+ # file contains any calls to Facter. This is due to facter being disallowed
32
+ # in Puppet when evaluating the code in a scripting context instead of catalog
33
+ # compilation, which is what we are doing here.
34
+ def non_validating_parse_file(file)
35
+ @compiler.send(:internal_evaluator).parser.parse_file(file)&.model
36
+ end
24
37
  end
25
38
 
26
39
  class Declaration