abide_dev_utils 0.16.1 → 0.17.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9ba88038da5800b45085201557c28a7d4096027230083e0c8d5a811a7fd1de5a
4
- data.tar.gz: 78da6fd7887fdb50a7de4505b4dd95b6fa2e24400dc6b5419dcc44aaa1f15d24
3
+ metadata.gz: 0216511efe504da6597b81a7b494975377b8afe013697e37f9985467a6a4fa14
4
+ data.tar.gz: '08265323331b31cb17e875b951ce89dfcb83bbb9649c0df97513cff26355f713'
5
5
  SHA512:
6
- metadata.gz: 3e1577583c8fcc597f75e02d3ef5edee126984f8daaea8fc98741c718c0254044767189acf24e0e21ba2b60607f4042c1f0c52be934dbc129d270008042f8bf5
7
- data.tar.gz: eb8b1038e223efeec1f7a2b3d0d0ee0f04cba49f385b58c8de5e1dfe8c2d935dac0d760faf65d8ab45f0b539f4729ea9195926d0cce1d4fe294c19f6e426e879
6
+ metadata.gz: 9cbd439058affe3311587d81d4dfe75191abe9f9fb1a1f8c964c2a8187f02cf9057943e4bacd5aaeb1676c936cccdd5e196b9d4a0822ed0c725d06fe0c46f0d3
7
+ data.tar.gz: f01c66b8079d9d257de1158f4a1f9f529b2e9e31c8eb04a19fa21fee63f34b2fa018b8e3bba8eefce2bf90d20f23249e27a83126f54057232aef6bd7658cbe0a
data/Gemfile.lock CHANGED
@@ -1,14 +1,14 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- abide_dev_utils (0.16.1)
4
+ abide_dev_utils (0.17.0)
5
5
  cmdparse (~> 3.0)
6
6
  facterdb (>= 1.21)
7
7
  google-cloud-storage (~> 1.34)
8
8
  hashdiff (~> 1.0)
9
9
  jira-ruby (~> 2.2)
10
10
  nokogiri (~> 1.13)
11
- puppet (>= 6.23)
11
+ puppet (>= 7.0.0)
12
12
  puppet-strings (>= 2.7)
13
13
  ruby-progressbar (~> 1.11)
14
14
  selenium-webdriver (~> 4.0.0.beta4)
@@ -69,7 +69,7 @@ GEM
69
69
  faraday-http-cache (2.3.0)
70
70
  faraday (>= 0.8)
71
71
  faraday-net_http (2.0.3)
72
- fast_gettext (1.8.0)
72
+ fast_gettext (2.3.0)
73
73
  fiber-local (1.0.0)
74
74
  gem-release (2.2.2)
75
75
  github_changelog_generator (1.16.4)
@@ -137,6 +137,8 @@ GEM
137
137
  multi_json (1.15.0)
138
138
  multipart-post (2.3.0)
139
139
  nio4r (2.5.8)
140
+ nokogiri (1.15.2-arm64-darwin)
141
+ racc (~> 1.4)
140
142
  nokogiri (1.15.2-x86_64-darwin)
141
143
  racc (~> 1.4)
142
144
  nokogiri (1.15.2-x86_64-linux)
@@ -265,6 +267,7 @@ GEM
265
267
  yard (0.9.34)
266
268
 
267
269
  PLATFORMS
270
+ arm64-darwin-22
268
271
  x86_64-darwin-19
269
272
  x86_64-darwin-20
270
273
  x86_64-linux
@@ -273,7 +276,7 @@ DEPENDENCIES
273
276
  abide_dev_utils!
274
277
  bundler
275
278
  console
276
- fast_gettext (~> 1.8)
279
+ fast_gettext (>= 2.0)
277
280
  gem-release
278
281
  github_changelog_generator
279
282
  pry
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
34
34
  # Prod dependencies
35
35
  spec.add_dependency 'nokogiri', '~> 1.13'
36
36
  spec.add_dependency 'cmdparse', '~> 3.0'
37
- spec.add_dependency 'puppet', '>= 6.23'
37
+ spec.add_dependency 'puppet', '>= 7.0.0'
38
38
  spec.add_dependency 'puppet-strings', '>= 2.7'
39
39
  spec.add_dependency 'jira-ruby', '~> 2.2'
40
40
  spec.add_dependency 'ruby-progressbar', '~> 1.11'
@@ -56,7 +56,7 @@ Gem::Specification.new do |spec|
56
56
  spec.add_development_dependency 'rubocop-ast', '~> 1.4'
57
57
  spec.add_development_dependency 'rubocop-performance', '~> 1.9'
58
58
  spec.add_development_dependency 'rubocop-i18n', '~> 3.0'
59
- spec.add_development_dependency 'fast_gettext', '~> 1.8'
59
+ spec.add_development_dependency 'fast_gettext', '>= 2.0'
60
60
 
61
61
  # For more information and examples about making a new gem, checkout our
62
62
  # guide at: https://bundler.io/guides/creating_gem.html
@@ -382,7 +382,12 @@ module AbideDevUtils
382
382
  # @valid_level is populated in verify_profile_and_level_selections from the fact that we've given
383
383
  # the generator a list of levels we want to use. If we didn't give it a list of levels, then we
384
384
  # want to use all of the levels that the control supports from @control.
385
- @md.add_ul('Supported Levels:')
385
+ if @framework == 'stig'
386
+ @md.add_ul('Supported MAC Levels:')
387
+ else
388
+ @md.add_ul('Supported Levels:')
389
+ end
390
+
386
391
  if @valid_level.empty?
387
392
  @control.levels.each do |l|
388
393
  @md.add_ul(@md.code(l), indent: 1)
@@ -400,7 +405,12 @@ module AbideDevUtils
400
405
  # @valid_profile is populated in verify_profile_and_level_selections from the fact that we've given
401
406
  # the generator a list of profiles we want to use. If we didn't give it a list of profiles, then we
402
407
  # want to use all of the profiles that the control supports from @control.
403
- @md.add_ul('Supported Profiles:')
408
+ if @framework == 'stig'
409
+ @md.add_ul('Supported Confidentiality:')
410
+ else
411
+ @md.add_ul('Supported Profiles:')
412
+ end
413
+
404
414
  if @valid_profile.empty?
405
415
  @control.profiles.each do |l|
406
416
  @md.add_ul(@md.code(l), indent: 1)
@@ -413,7 +423,7 @@ module AbideDevUtils
413
423
  end
414
424
 
415
425
  def control_alternate_ids_builder
416
- return if @framework == 'stig'
426
+ # return if @framework == 'stig'
417
427
 
418
428
  @md.add_ul('Alternate Config IDs:')
419
429
  @control.alternate_ids.each do |l|
@@ -6,9 +6,6 @@ require 'abide_dev_utils/jira'
6
6
 
7
7
  module Abide
8
8
  module CLI
9
- CONFIG = AbideDevUtils::Config
10
- JIRA = AbideDevUtils::Jira
11
-
12
9
  class JiraCommand < CmdParse::Command
13
10
  CMD_NAME = 'jira'
14
11
  CMD_SHORT = 'Commands related to Jira tickets'
@@ -38,11 +35,9 @@ module Abide
38
35
  end
39
36
 
40
37
  def execute
41
- client = JIRA.client
42
- myself = JIRA.myself(client)
43
- return if myself.attrs['displayName'].empty?
38
+ return if AbideDevUtils::Jira.client.myself.attrs['displayName'].empty?
44
39
 
45
- Abide::CLI::OUTPUT.simple("Successfully authenticated user #{myself.attrs['name']}!")
40
+ Abide::CLI::OUTPUT.simple("Successfully authenticated user #{AbideDevUtils::Jira.client.myself.attrs['displayName']}!")
46
41
  end
47
42
  end
48
43
 
@@ -59,8 +54,7 @@ module Abide
59
54
  end
60
55
 
61
56
  def execute(issue)
62
- client = JIRA.client(options: {})
63
- issue = client.Issue.find(issue)
57
+ issue = AbideDevUtils::Jira.client.find(:issue, issue)
64
58
  console = @data[:file].nil?
65
59
  out_json = issue.attrs.select { |_, v| !v.nil? || !v.empty? }
66
60
  Abide::CLI::OUTPUT.json(out_json, console: console, file: @data[:file])
@@ -83,13 +77,15 @@ module Abide
83
77
  end
84
78
 
85
79
  def execute(project, summary, *subtasks)
86
- client = JIRA.client(options: {})
87
- issue = JIRA.new_issue(client, project, summary)
80
+ issue = AbideDevUtils::Jira.client.create(:issue, project: project, summary: summary)
88
81
  Abide::CLI::OUTPUT.simple("Successfully created #{issue.attrs['key']}")
89
82
  return if subtasks.nil? || subtasks.empty?
90
83
 
91
84
  Abide::CLI::OUTPUT.simple('Creatings subtasks...')
92
- JIRA.bulk_new_subtask(client, JIRA.issue(client, issue.attrs['key']), subtasks) unless subtasks.empty?
85
+ subtasks.each do |sum|
86
+ subtask = AbideDevUtils::Jira.client.create(:subtask, parent: issue, summary: sum)
87
+ Abide::CLI::OUTPUT.simple("Successfully created #{subtask.attrs['key']}")
88
+ end
93
89
  end
94
90
  end
95
91
 
@@ -107,11 +103,8 @@ module Abide
107
103
 
108
104
  def execute(report, project)
109
105
  Abide::CLI::VALIDATE.file(report)
110
- @data[:dry_run] = false if @data[:dry_run].nil?
111
- client = JIRA.client(options: {})
112
- proj = JIRA.project(client, project)
113
106
  File.open(report) do |f|
114
- JIRA.new_issues_from_coverage(client, proj, JSON.parse(f.read), dry_run: @data[:dry_run])
107
+ AbideDevUtils::Jira.new_issues_from_coverage(project, JSON.parse(f.read), dry_run: @data[:dry_run])
115
108
  end
116
109
  end
117
110
  end
@@ -127,14 +120,30 @@ module Abide
127
120
  argument_desc(PATH: 'An XCCDF file', PROJECT: 'A Jira project')
128
121
  options.on('-d', '--dry-run', 'Print to console instead of saving objects') { |_| @data[:dry_run] = true }
129
122
  options.on('-e [EPIC]', '--epic [EPIC]', 'If given, tasks will be created and assigned to this epic. Takes form <PROJECT>-<NUM>') { |e| @data[:epic] = e }
123
+ options.on('-l [LEVEL]', '--level [LEVEL]', 'Only create tasks for rules belonging to the matching level. Takes a string that is treated as RegExp') do |x|
124
+ @data[:level] = x
125
+ end
126
+ options.on('-p [PROFILE]', '--profile [PROFILE]', 'Only create tasks for rules belonging to the matching profile. Takes a string that is treated as RegExp') do |x|
127
+ @data[:profile] = x
128
+ end
130
129
  end
131
130
 
132
131
  def execute(path, project)
133
132
  Abide::CLI::VALIDATE.file(path)
134
- @data[:dry_run] = false if @data[:dry_run].nil?
135
- client = JIRA.client(options: {})
136
- proj = JIRA.project(client, project)
137
- JIRA.new_issues_from_xccdf(client, proj, path, epic: @data[:epic], dry_run: @data[:dry_run])
133
+ # Each control gets assigned labels based on the levels and profiles it supports.
134
+ # Those labels all take the form "level_<level>_<profile>". This allows us to
135
+ # filter the controls we want to create tasks for by level and profile.
136
+ @data[:label_include] = nil
137
+ @data[:label_include] = "level_#{@data[:level]}_" if @data[:level]
138
+ @data[:label_include] = "#{@data[:label_include]}#{@data[:profile]}" if @data[:profile]
139
+ Abide::CLI::Output.simple "Label include: #{@data[:label_include]}"
140
+ AbideDevUtils::Jira.new_issues_from_xccdf(
141
+ project,
142
+ path,
143
+ epic: @data[:epic],
144
+ dry_run: @data[:dry_run],
145
+ label_include: @data[:label_include],
146
+ )
138
147
  end
139
148
  end
140
149
 
@@ -167,17 +176,15 @@ module Abide
167
176
  def execute(path1, path2, project)
168
177
  Abide::CLI::VALIDATE.file(path1)
169
178
  Abide::CLI::VALIDATE.file(path2)
170
- @data[:dry_run] = false if @data[:dry_run].nil?
171
- client = JIRA.client(options: {})
172
- proj = JIRA.project(client, project)
173
- JIRA.new_issues_from_xccdf_diff(client,
174
- proj,
175
- path1,
176
- path2,
177
- epic: @data[:epic],
178
- dry_run: @data[:dry_run],
179
- auto_approve: @data[:auto_approve],
180
- diff_opts: @data[:diff_opts])
179
+ AbideDevUtils::Jira.new_issues_from_xccdf_diff(
180
+ project,
181
+ path1,
182
+ path2,
183
+ epic: @data[:epic],
184
+ dry_run: @data[:dry_run],
185
+ auto_approve: @data[:auto_approve],
186
+ diff_opts: @data[:diff_opts],
187
+ )
181
188
  end
182
189
  end
183
190
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'client_builder'
4
+ require_relative 'dry_run'
5
+ require_relative 'finder'
6
+ require_relative 'helper'
7
+ require_relative 'issue_builder'
8
+ require_relative '../config'
9
+ require_relative '../errors/jira'
10
+
11
+ module AbideDevUtils
12
+ module Jira
13
+ class Client
14
+ extend DryRun
15
+
16
+ dry_run :create, :find, :myself
17
+
18
+ attr_accessor :default_project
19
+ attr_reader :config
20
+
21
+ def initialize(dry_run: false, **options)
22
+ @dry_run = dry_run
23
+ @options = options
24
+ @config = AbideDevUtils::Config.config_section('jira')
25
+ @default_project = @config[:default_project]
26
+ @client = nil
27
+ @finder = nil
28
+ @issue_builder = nil
29
+ @helper = nil
30
+ end
31
+
32
+ def myself
33
+ @myself ||= finder.myself
34
+ end
35
+
36
+ def find(type, id)
37
+ raise ArgumentError, "Invalid type #{type}" unless finder.respond_to?(type.to_sym)
38
+
39
+ finder.send(type.to_sym, id)
40
+ end
41
+
42
+ def create(type, **fields)
43
+ issue_builder.create(type, **fields)
44
+ end
45
+
46
+ def helper
47
+ @helper ||= Helper.new(self, dry_run: @dry_run)
48
+ end
49
+
50
+ private
51
+
52
+ def client
53
+ @client ||= ClientBuilder.new(@config, **@options).build
54
+ end
55
+
56
+ def finder
57
+ @finder ||= Finder.new(client)
58
+ end
59
+
60
+ def issue_builder
61
+ @issue_builder ||= IssueBuilder.new(client, finder)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jira-ruby'
4
+ require_relative '../prompt'
5
+
6
+ module AbideDevUtils
7
+ module Jira
8
+ class ClientBuilder
9
+ def initialize(config, **options)
10
+ @options = options
11
+ @config = config
12
+ end
13
+
14
+ def username
15
+ find_option_value(:username)
16
+ end
17
+
18
+ def username=(username)
19
+ @options[:username] = username
20
+ end
21
+
22
+ def password
23
+ if find_option_value(:password)
24
+ '********'
25
+ else
26
+ nil
27
+ end
28
+ end
29
+
30
+ def password=(password)
31
+ @options[:password] = password
32
+ end
33
+
34
+ def site
35
+ find_option_value(:site)
36
+ end
37
+
38
+ def site=(site)
39
+ @options[:site] = site
40
+ end
41
+
42
+ def context_path
43
+ find_option_value(:context_path, default: '')
44
+ end
45
+
46
+ def context_path=(context_path)
47
+ @options[:context_path] = context_path
48
+ end
49
+
50
+ def auth_type
51
+ find_option_value(:auth_type, default: :basic)
52
+ end
53
+
54
+ def auth_type=(auth_type)
55
+ @options[:auth_type] = auth_type
56
+ end
57
+
58
+ def http_debug
59
+ find_option_value(:http_debug, default: false)
60
+ end
61
+
62
+ def http_debug=(http_debug)
63
+ @options[:http_debug] = http_debug
64
+ end
65
+
66
+ def build
67
+ JIRA::Client.new({
68
+ username: find_option_value(:username, prompt: true),
69
+ password: find_option_value(:password, prompt: true),
70
+ site: find_option_value(:site, prompt: 'Jira site URL'),
71
+ context_path: find_option_value(:context_path, default: ''),
72
+ auth_type: find_option_value(:auth_type, default: :basic),
73
+ http_debug: find_option_value(:http_debug, default: false),
74
+ })
75
+ end
76
+
77
+ private
78
+
79
+ def find_option_value(key, default: nil, prompt: nil)
80
+ if prompt
81
+ find_option_value_or_prompt(key, prompt)
82
+ else
83
+ find_option_value_or_default(key, default)
84
+ end
85
+ end
86
+
87
+ def find_option_val(key)
88
+ @options[key] || @config[key] || ENV["JIRA_#{key.to_s.upcase}"]
89
+ end
90
+
91
+ def find_option_value_or_prompt(key, prompt = 'Enter value')
92
+ case key
93
+ when /password/i
94
+ find_option_val(key) || AbideDevUtils::Prompt.password
95
+ when /username/i
96
+ find_option_val(key) || AbideDevUtils::Prompt.username
97
+ else
98
+ find_option_val(key) || AbideDevUtils::Prompt.single_line(prompt)
99
+ end
100
+ end
101
+
102
+ def find_option_value_or_default(key, default)
103
+ find_option_val(key) || default
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../output'
4
+
5
+ module AbideDevUtils
6
+ module Jira
7
+ module DryRun
8
+ def dry_run(*method_names)
9
+ method_names.each do |method_name|
10
+ proxy = Module.new do
11
+ define_method(method_name) do |*args, **kwargs|
12
+ if !!@dry_run
13
+ case method_name
14
+ when %r{^create}
15
+ AbideDevUtils::Output.simple("DRY RUN: #{self.class.name}##{method_name}(#{args[0]}, #{args[1].map { |k, v| "#{k}: #{v.inspect}" }.join(', ')})")
16
+ sleep 0.1
17
+ return DummyIssue.new if args[0].match?(%r{^issue$})
18
+ return DummySubtask.new if args[0].match?(%r{^subtask$})
19
+ when %r{^find}
20
+ AbideDevUtils::Output.simple("DRY RUN: #{self.class.name}##{method_name}(#{args[0]}, #{args[1].inspect})")
21
+ return DummyIssue.new if args[0].match?(%r{^issue$})
22
+ return DummySubtask.new if args[0].match?(%r{^subtask$})
23
+ return DummyProject.new if args[0].match?(%r{^project$})
24
+
25
+ "Dummy #{args[0].capitalize}"
26
+ else
27
+ AbideDevUtils::Output.simple("DRY RUN: #{self.class.name}##{method_name}(#{args.map(&:inspect).join(', ')})")
28
+ end
29
+ else
30
+ super(*args, **kwargs)
31
+ end
32
+ end
33
+ end
34
+ self.prepend(proxy)
35
+ end
36
+ end
37
+
38
+ def dry_run_simple(*method_names)
39
+ method_names.each do |method_name|
40
+ proxy = Module.new do
41
+ define_method(method_name) do |*args, **kwargs|
42
+ return if !!@dry_run
43
+
44
+ super(*args, **kwargs)
45
+ end
46
+ end
47
+ self.prepend(proxy)
48
+ end
49
+ end
50
+
51
+ def dry_run_return_true(*method_names)
52
+ method_names.each do |method_name|
53
+ proxy = Module.new do
54
+ define_method(method_name) do |*args, **kwargs|
55
+ return true if !!@dry_run
56
+
57
+ super(*args, **kwargs)
58
+ end
59
+ end
60
+ self.prepend(proxy)
61
+ end
62
+ end
63
+
64
+ def dry_run_return_false(*method_names)
65
+ method_names.each do |method_name|
66
+ proxy = Module.new do
67
+ define_method(method_name) do |*args, **kwargs|
68
+ return false if !!@dry_run
69
+
70
+ super(*args, **kwargs)
71
+ end
72
+ end
73
+ self.prepend(proxy)
74
+ end
75
+ end
76
+
77
+ class Dummy
78
+ attr_reader :dummy
79
+
80
+ def initialize
81
+ @dummy = true
82
+ end
83
+ end
84
+
85
+ class DummyIssue < Dummy
86
+ attr_reader :summary, :key
87
+
88
+ def initialize
89
+ super
90
+ @summary = 'Dummy Issue'
91
+ @key = 'DUM-111'
92
+ end
93
+
94
+ def attrs
95
+ {
96
+ 'fields' => {
97
+ 'project' => 'dummy',
98
+ 'priority' => 'dummy',
99
+ },
100
+ }
101
+ end
102
+ end
103
+
104
+ class DummySubtask < DummyIssue
105
+ def initialize
106
+ super
107
+ @summary = 'Dummy Subtask'
108
+ @key = 'DUM-222'
109
+ end
110
+
111
+ def attrs
112
+ {
113
+ 'fields' => {
114
+ 'project' => 'dummy',
115
+ 'priority' => 'dummy',
116
+ 'parent' => DummyIssue.new,
117
+ },
118
+ }
119
+ end
120
+ end
121
+
122
+ class DummyProject < Dummy
123
+ attr_reader :key, :issues
124
+
125
+ def initialize
126
+ super
127
+ @key = 'DUM'
128
+ @issues = [DummyIssue.new, DummySubtask.new]
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../errors/jira'
4
+
5
+ module AbideDevUtils
6
+ module Jira
7
+ class Finder
8
+ def initialize(client)
9
+ @client = client
10
+ end
11
+
12
+ def myself
13
+ client.User.myself
14
+ end
15
+
16
+ # @param id [String] The project key or ID
17
+ def project(id)
18
+ return id if id.is_a?(client.Project.target_class)
19
+
20
+ client.Project.find(id)
21
+ end
22
+
23
+ # @param id [String] The issue key or summary
24
+ def issue(id)
25
+ return id if id.is_a?(client.Issue.target_class)
26
+
27
+ client.Issue.find(id)
28
+ rescue URI::InvalidURIError
29
+ iss = client.Issue.all.find { |i| i.summary == id }
30
+ raise AbideDevUtils::Errors::Jira::FindIssueError, id if iss.nil?
31
+
32
+ iss
33
+ end
34
+
35
+ # @param id [String] The issuetype ID or name
36
+ def issuetype(id)
37
+ return id if id.is_a?(client.Issuetype.target_class)
38
+
39
+ if id.match?(%r{^\d+$})
40
+ client.Issuetype.find(id)
41
+ else
42
+ client.Issuetype.all.find { |i| i.name == id }
43
+ end
44
+ end
45
+
46
+ # @param id [String] The priority ID or name
47
+ def priority(id)
48
+ return id if id.is_a?(client.Priority.target_class)
49
+
50
+ if id.match?(%r{^\d+$})
51
+ client.Priority.find(id)
52
+ else
53
+ client.Priority.all.find { |i| i.name == id }
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ attr_reader :client
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'dry_run'
4
+
5
+ module AbideDevUtils
6
+ module Jira
7
+ class Helper
8
+ extend DryRun
9
+
10
+ dry_run_simple :add_issue_label
11
+ dry_run_return_false :summary_exist?
12
+
13
+ def initialize(client, dry_run: false)
14
+ @client = client
15
+ @dry_run = dry_run
16
+ end
17
+
18
+ # @param project [JIRA::Resource::Project, String]
19
+ def all_project_issues_attrs(project)
20
+ project = @client.find(:project, project)
21
+ project.issues.collect(&:attrs)
22
+ end
23
+
24
+ # @param issue [JIRA::Resource::Issue, String]
25
+ # @param label [String]
26
+ def add_issue_label(issue, label)
27
+ issue = @client.find(:issue, issue)
28
+ return if issue.labels.include?(label)
29
+
30
+ issue.labels << label
31
+ issue.save
32
+ end
33
+
34
+ # @param summary [String]
35
+ # @param issue_attrs [Array<Hash>]
36
+ def summary_exist?(summary, issue_attrs)
37
+ issue_attrs.any? { |attrs| attrs['fields'].key?('summary') && attrs['fields']['summary'] == summary }
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../errors/jira'
4
+
5
+ module AbideDevUtils
6
+ module Jira
7
+ class IssueBuilder
8
+ CUSTOM_FIELDS = {
9
+ 'epic_link' => 'customfield_10014',
10
+ 'epic_name' => 'customfield_10011',
11
+ }.freeze
12
+
13
+ FIELD_DEFAULTS = {
14
+ 'issuetype' => 'Task',
15
+ 'priority' => 'Medium',
16
+ 'labels' => ['abide_dev_utils'],
17
+ }.freeze
18
+
19
+ REQUIRED_FIELDS = %w[project summary].freeze
20
+
21
+ def initialize(client, finder)
22
+ @client = client
23
+ @finder = finder
24
+ end
25
+
26
+ def can_create?(type)
27
+ respond_to?("create_#{type}".to_sym, true)
28
+ end
29
+
30
+ def create(type, **fields)
31
+ type = type.to_s.downcase.to_sym
32
+ raise ArgumentError, "Invalid type \"#{type}\"; no method \"create_#{type}\"" unless can_create?(type)
33
+
34
+ fields = process_fields(fields)
35
+ send("create_#{type}".to_sym, **fields)
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :client
41
+
42
+ def create_issue(**fields)
43
+ iss = client.Issue.build
44
+ iss.save({ 'fields' => fields })
45
+ iss
46
+ rescue StandardError => e
47
+ raise AbideDevUtils::Errors::Jira::CreateIssueError, e
48
+ end
49
+
50
+ def create_subtask(**fields)
51
+ fields['parent'] = find_if_not_type(:issue, client.Issue.target_class, fields['parent'])
52
+ issue_fields = fields['parent'].attrs['fields']
53
+ fields['project'] = issue_fields['project']
54
+ fields['issuetype'] = find_if_not_type(:issuetype, client.Issuetype.target_class, 'Sub-task')
55
+ fields['priority'] = issue_fields['priority']
56
+ iss = client.Issue.build
57
+ iss.save({ 'fields' => fields })
58
+ iss
59
+ rescue StandardError => e
60
+ raise AbideDevUtils::Errors::Jira::CreateSubtaskError, e
61
+ end
62
+
63
+ def process_fields(fields)
64
+ fields = fields.dup
65
+ normalize_field_keys!(fields)
66
+ validate_required_fields!(fields)
67
+ normalize_field_values(fields)
68
+ end
69
+
70
+ def validate_required_fields!(fields)
71
+ missing = REQUIRED_FIELDS.reject { |f| fields.key?(f) }
72
+ raise "Missing required field(s) \"#{missing}\"; present fields: \"#{fields.keys}\"" unless missing.empty?
73
+ end
74
+
75
+ def normalize_field_keys!(fields)
76
+ fields.transform_keys! { |k| k.to_s.downcase }
77
+ fields.transform_keys! { |k| CUSTOM_FIELDS[k] || k }
78
+ end
79
+
80
+ def normalize_field_values(fields)
81
+ fields = FIELD_DEFAULTS.merge(fields).map do |k, v|
82
+ v = case k
83
+ when 'labels'
84
+ v.is_a?(Array) ? v : [v]
85
+ when 'issuetype'
86
+ find_if_not_type(:issuetype, client.Issuetype.target_class, v)
87
+ when 'parent'
88
+ find_if_not_type(:issue, client.Issue.target_class, v)
89
+ when 'priority'
90
+ find_if_not_type(:priority, client.Priority.target_class, v)
91
+ when 'epic_link', CUSTOM_FIELDS['epic_link']
92
+ find_if_not_type(:issue, client.Issue.target_class, v)&.key || v
93
+ when 'project'
94
+ find_if_not_type(:project, client.Project.target_class, v)
95
+ else
96
+ v
97
+ end
98
+ [k, v]
99
+ end
100
+ fields.to_h
101
+ end
102
+
103
+ def find_if_not_type(typesym, typeklass, obj)
104
+ return obj if obj.is_a?(typeklass)
105
+
106
+ @finder.send(typesym, obj)
107
+ end
108
+ end
109
+ end
110
+ end
@@ -5,6 +5,7 @@ require 'abide_dev_utils/output'
5
5
  require 'abide_dev_utils/prompt'
6
6
  require 'abide_dev_utils/config'
7
7
  require 'abide_dev_utils/errors/jira'
8
+ require_relative 'jira/client'
8
9
 
9
10
  module AbideDevUtils
10
11
  module Jira
@@ -14,192 +15,58 @@ module AbideDevUtils
14
15
  UPD_EPIC_SUMMARY_PREFIX = '::BENCHMARK UPDATE::'
15
16
  PROGRESS_BAR_FORMAT = '%a %e %P% Created: %c of %C'
16
17
 
17
- def self.project(client, project)
18
- client.Project.find(project)
19
- end
20
-
21
- def self.issue(client, issue)
22
- client.Issue.find(issue)
23
- rescue URI::InvalidURIError
24
- iss = client.Issue.all.find { |i| i.summary == issue }
25
- raise ERRORS::FindIssueError, issue unless iss
26
-
27
- iss
28
- end
29
-
30
- def self.myself(client)
31
- client.User.myself
32
- end
33
-
34
- def self.issuetype(client, id)
35
- if id.match?(%r{^\d+$})
36
- client.Issuetype.find(id)
37
- else
38
- client.Issuetype.all.find { |i| i.name == id }
39
- end
40
- end
41
-
42
- def self.priority(client, id)
43
- if id.match?(%r{^\d+$})
44
- client.Priority.find(id)
45
- else
46
- client.Priority.all.find { |i| i.name == id }
47
- end
48
- end
49
-
50
- def self.all_project_issues_attrs(project)
51
- raw_issues = project.issues
52
- raw_issues.collect(&:attrs)
53
- end
54
-
55
- def self.add_issue_label(iss, label, dry_run: false)
56
- return if dry_run || iss.labels.include?(label)
57
-
58
- iss.labels << profile_summary
59
- iss.save
60
- end
61
-
62
- def self.new_issue(client, project, summary, description: nil, labels: ['abide_dev_utils'], epic: nil, dry_run: false)
63
- if dry_run
64
- sleep(0.2)
65
- return Dummy.new(summary)
66
- end
67
- fields = {}
68
- fields['summary'] = summary
69
- fields['project'] = project(client, project)
70
- fields['issuetype'] = issuetype(client, 'Task')
71
- fields['priority'] = priority(client, '3')
72
- fields['description'] = description if description
73
- fields['labels'] = labels
74
- epic = issue(client, epic) if epic && !epic.is_a?(JIRA::Resource::Issue)
75
- fields['customfield_10006'] = epic.key if epic # Epic_Link
76
- iss = client.Issue.build
77
- raise ERRORS::CreateIssueError, iss.attrs unless iss.save({ 'fields' => fields })
78
-
79
- iss
80
- end
81
-
82
- def self.new_epic(client, project, summary, dry_run: false)
83
- AbideDevUtils::Output.simple("#{dr_prefix(dry_run)}Creating epic '#{summary}'")
84
- if dry_run
85
- sleep(0.2)
86
- return Dummy.new(summary)
87
- end
88
- fields = {
89
- 'summary' => summary,
90
- 'project' => project(client, project),
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
98
- end
99
-
100
- # This should probably be threaded in the future
101
- def self.bulk_new_issue(client, project, summaries, dry_run: false)
102
- summaries.each { |s| new_issue(client, project, s, dry_run: dry_run) }
103
- end
104
-
105
- def self.new_subtask(client, issue, summary, dry_run: false)
106
- if dry_run
107
- sleep(0.2)
108
- return Dummy.new
109
- end
110
- issue_fields = issue.attrs['fields']
111
- fields = {}
112
- fields['parent'] = issue
113
- fields['summary'] = summary
114
- fields['project'] = issue_fields['project']
115
- fields['issuetype'] = issuetype(client, '5')
116
- fields['priority'] = issue_fields['priority']
117
- subtask = client.Issue.build
118
- raise ERRORS::CreateSubtaskError, subtask.attrs unless subtask.save({ 'fields' => fields })
119
-
120
- subtask
121
- end
122
-
123
- def self.bulk_new_subtask(client, issue, summaries, dry_run: false)
124
- summaries.each do |s|
125
- new_subtask(client, issue, s, dry_run: dry_run)
126
- end
127
- end
128
-
129
- def self.client(options: {})
130
- opts = merge_options(options)
131
- return client_from_prompts if opts.empty?
132
-
133
- opts[:username] = AbideDevUtils::Prompt.username if opts[:username].nil?
134
- opts[:password] = AbideDevUtils::Prompt.password if opts[:password].nil?
135
- opts[:site] = AbideDevUtils::Prompt.single_line('Jira URL') if opts[:site].nil?
136
- opts[:context_path] = '' if opts[:context_path].nil?
137
- opts[:auth_type] = :basic if opts[:auth_type].nil?
138
- JIRA::Client.new(opts)
139
- end
140
-
141
- def self.client_from_prompts(http_debug: false)
142
- options = {}
143
- options[:username] = AbideDevUtils::Prompt.username
144
- options[:password] = AbideDevUtils::Prompt.password
145
- options[:site] = AbideDevUtils::Prompt.single_line('Jira URL')
146
- options[:context_path] = ''
147
- options[:auth_type] = :basic
148
- options[:http_debug] = http_debug
149
- JIRA::Client.new(options)
150
- end
18
+ def self.client(memo: true, dry_run: false, **options)
19
+ return AbideDevUtils::Jira::Client.new(dry_run: dry_run, **options) unless memo
151
20
 
152
- def self.project_from_prompts(http_debug: false)
153
- client = client_from_prompts(http_debug)
154
- project = AbideDevUtils::Prompt.single_line('Project').upcase
155
- client.Project.find(project)
21
+ @client ||= AbideDevUtils::Jira::Client.new(dry_run: dry_run, **options)
156
22
  end
157
23
 
158
24
  def self.new_issues_from_coverage(client, project, report, dry_run: false)
159
25
  dr_prefix = dry_run ? 'DRY RUN: ' : ''
160
- i_attrs = all_project_issues_attrs(project)
26
+ client(dry_run: dry_run) # Initializes the client if needed
27
+ i_attrs = client.helper.all_project_issues_attrs(project)
161
28
  rep_sums = summaries_from_coverage_report(report)
162
29
  rep_sums.each do |k, v|
163
- next if summary_exist?(k, i_attrs)
30
+ next if client.helper.summary_exist?(k, i_attrs)
164
31
 
165
- parent = new_issue(client, project.attrs['key'], k.to_s, dry_run: dry_run)
166
- AbideDevUtils::Output.simple("#{dr_prefix}Created parent issue #{k}")
167
- parent_issue = issue(client, parent.attrs['key']) unless parent.respond_to?(:dummy)
32
+ AbideDevUtils::Output.simple("#{dr_prefix}Creating parent issue #{k}...")
33
+ parent = client.create(:issue, project: project, summary: k.to_s)
168
34
  AbideDevUtils::Output.simple("#{dr_prefix}Creating subtasks, this can take a while...")
169
35
  progress = AbideDevUtils::Output.progress(title: "#{dr_prefix}Creating Subtasks", total: nil)
170
36
  v.each do |s|
171
- next if summary_exist?(s, i_attrs)
37
+ next if client.helper.summary_exist?(s, i_attrs)
172
38
 
173
39
  progress.title = "#{dr_prefix}#{s}"
174
- new_subtask(client, parent_issue, s, dry_run: dry_run)
40
+ client.create(:subtask, parent: parent, summary: s)
175
41
  progress.increment
176
42
  end
177
43
  end
178
44
  end
179
45
 
180
- def self.new_issues_from_xccdf(client, project, xccdf_path, epic: nil, dry_run: false)
181
- i_attrs = all_project_issues_attrs(project)
46
+ def self.new_issues_from_xccdf(project, xccdf_path, epic: nil, dry_run: false, label_include: nil)
47
+ client(dry_run: dry_run) # Initializes the client if needed
48
+ i_attrs = client.helper.all_project_issues_attrs(project)
182
49
  xccdf = AbideDevUtils::XCCDF::Benchmark.new(xccdf_path)
183
50
  # We need to get the actual epic Issue object, or create it if it doesn't exist
184
51
  epic = if epic.nil?
185
52
  new_epic_summary = "#{COV_PARENT_SUMMARY_PREFIX}#{xccdf.title}"
186
- if summary_exist?(new_epic_summary, i_attrs)
187
- issue(client, new_epic_summary)
53
+ if client.helper.summary_exist?(new_epic_summary, i_attrs)
54
+ client.find(:issue, new_epic_summary)
188
55
  else
189
56
  unless AbideDevUtils::Prompt.yes_no("#{dr_prefix(dry_run)}Create new epic '#{new_epic_summary}'?")
190
57
  AbideDevUtils::Output.simple("#{dr_prefix(dry_run)}Aborting")
191
58
  exit(0)
192
59
  end
193
- new_epic(client, project.key, new_epic_summary, dry_run: dry_run)
60
+ client.create(:issue, project: project, summary: new_epic_summary, issuetype: 'Epic', epic_name: new_epic_summary)
194
61
  end
195
62
  else
196
- issue(client, epic)
63
+ client.find(:issue, epic)
197
64
  end
198
65
  # Now we need to find out which issues we need to create for the benchmark
199
66
  # The profiles that the control belongs to will be added as an issue label
200
67
  to_create = {}
201
68
  summaries_from_xccdf(xccdf).each do |profile_summary, control_summaries|
202
- control_summaries.reject { |s| summary_exist?(s, i_attrs) }.each do |control_summary|
69
+ control_summaries.reject { |s| client.helper.summary_exist?(s, i_attrs) }.each do |control_summary|
203
70
  if to_create.key?(control_summary)
204
71
  to_create[control_summary] << profile_summary.split.join('_').downcase
205
72
  else
@@ -208,6 +75,13 @@ module AbideDevUtils
208
75
  end
209
76
  end
210
77
 
78
+ # If we have a label_include, we need to filter out any controls that don't have that label
79
+ unless label_include.nil?
80
+ to_create = to_create.select do |_control_summary, labels|
81
+ labels.any? { |l| l.match?(label_include) }
82
+ end
83
+ end
84
+
211
85
  unless AbideDevUtils::Prompt.yes_no("#{dr_prefix(dry_run)}Create #{to_create.keys.count} new Jira issues?")
212
86
  AbideDevUtils::Output.simple("#{dr_prefix(dry_run)}Aborting")
213
87
  exit(0)
@@ -219,31 +93,32 @@ module AbideDevUtils
219
93
  to_create.each do |control_summary, labels|
220
94
  abrev = control_summary.length > 40 ? control_summary[0..60] : control_summary
221
95
  progress.log("#{dr_prefix(dry_run)}Creating #{abrev}...")
222
- new_issue(client, project.key, control_summary, labels: labels, epic: epic, dry_run: dry_run)
96
+ client.create(:issue, project: project, summary: control_summary, labels: labels, epic_link: epic)
223
97
  progress.increment
224
98
  end
225
99
  progress.finish
226
100
  AbideDevUtils::Output.simple("#{dr_prefix(dry_run)}Done creating tasks in Epic '#{epic.summary}'")
227
101
  end
228
102
 
229
- def self.new_issues_from_xccdf_diff(client, project, xccdf1_path, xccdf2_path, epic: nil, dry_run: false, auto_approve: false, diff_opts: {})
103
+ def self.new_issues_from_xccdf_diff(project, xccdf1_path, xccdf2_path, epic: nil, dry_run: false, auto_approve: false, diff_opts: {})
230
104
  require 'abide_dev_utils/xccdf/diff'
231
105
  diff = AbideDevUtils::XCCDF::Diff::BenchmarkDiff.new(xccdf1_path, xccdf2_path, diff_opts)
232
- i_attrs = all_project_issues_attrs(project)
106
+ client(dry_run: dry_run) # Initializes the client if needed
107
+ i_attrs = client.helper.all_project_issues_attrs(project)
233
108
  # We need to get the actual epic Issue object, or create it if it doesn't exist
234
109
  epic = if epic.nil?
235
- new_epic_summary = "#{UPD_EPIC_SUMMARY_PREFIX}#{diff.this.title}: v#{diff.this.version} -> #{diff.other.version}"
236
- if summary_exist?(new_epic_summary, i_attrs)
237
- issue(client, new_epic_summary)
110
+ new_epic_summary = "#{COV_PARENT_SUMMARY_PREFIX}#{xccdf.title}"
111
+ if client.helper.summary_exist?(new_epic_summary, i_attrs)
112
+ client.find(:issue, new_epic_summary)
238
113
  else
239
- unless AbideDevUtils::Prompt.yes_no("#{dr_prefix(dry_run)}Create new epic '#{new_epic_summary}'?", auto_approve: auto_approve)
114
+ unless AbideDevUtils::Prompt.yes_no("#{dr_prefix(dry_run)}Create new epic '#{new_epic_summary}'?")
240
115
  AbideDevUtils::Output.simple("#{dr_prefix(dry_run)}Aborting")
241
116
  exit(0)
242
117
  end
243
- new_epic(client, project.key, new_epic_summary, dry_run: dry_run)
118
+ client.create(:issue, project: project, summary: new_epic_summary, issuetype: 'Epic', epic_name: new_epic_summary)
244
119
  end
245
120
  else
246
- issue(client, epic)
121
+ client.find(:issue, epic)
247
122
  end
248
123
  to_create = {}
249
124
  diff.diff[:rules].each do |key, val|
@@ -285,7 +160,7 @@ module AbideDevUtils
285
160
  format: PROGRESS_BAR_FORMAT)
286
161
  approved_create.each do |summary, description|
287
162
  progress.log("#{dr_prefix(dry_run)}Creating #{summary}...")
288
- new_issue(client, project.key, summary, description: description, labels: [], epic: epic, dry_run: dry_run)
163
+ client.create(:issue, project: project, summary: summary, description: description, labels: [], epic_link: epic)
289
164
  progress.increment
290
165
  end
291
166
  progress.finish
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AbideDevUtils
4
- VERSION = "0.16.1"
4
+ VERSION = "0.17.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abide_dev_utils
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.1
4
+ version: 0.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - abide-team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-09-07 00:00:00.000000000 Z
11
+ date: 2023-11-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '6.23'
47
+ version: 7.0.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '6.23'
54
+ version: 7.0.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: puppet-strings
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -322,16 +322,16 @@ dependencies:
322
322
  name: fast_gettext
323
323
  requirement: !ruby/object:Gem::Requirement
324
324
  requirements:
325
- - - "~>"
325
+ - - ">="
326
326
  - !ruby/object:Gem::Version
327
- version: '1.8'
327
+ version: '2.0'
328
328
  type: :development
329
329
  prerelease: false
330
330
  version_requirements: !ruby/object:Gem::Requirement
331
331
  requirements:
332
- - - "~>"
332
+ - - ">="
333
333
  - !ruby/object:Gem::Version
334
- version: '1.8'
334
+ version: '2.0'
335
335
  description: Provides a CLI with helpful utilities for developing compliance Puppet
336
336
  code
337
337
  email:
@@ -404,6 +404,12 @@ files:
404
404
  - lib/abide_dev_utils/files.rb
405
405
  - lib/abide_dev_utils/gcloud.rb
406
406
  - lib/abide_dev_utils/jira.rb
407
+ - lib/abide_dev_utils/jira/client.rb
408
+ - lib/abide_dev_utils/jira/client_builder.rb
409
+ - lib/abide_dev_utils/jira/dry_run.rb
410
+ - lib/abide_dev_utils/jira/finder.rb
411
+ - lib/abide_dev_utils/jira/helper.rb
412
+ - lib/abide_dev_utils/jira/issue_builder.rb
407
413
  - lib/abide_dev_utils/markdown.rb
408
414
  - lib/abide_dev_utils/mixins.rb
409
415
  - lib/abide_dev_utils/output.rb
@@ -463,7 +469,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
463
469
  - !ruby/object:Gem::Version
464
470
  version: '0'
465
471
  requirements: []
466
- rubygems_version: 3.4.19
472
+ rubygems_version: 3.4.18
467
473
  signing_key:
468
474
  specification_version: 4
469
475
  summary: Helper utilities for developing compliance Puppet code