abide_dev_utils 0.12.2 → 0.14.0

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