firespring_dev_commands 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +83 -0
  4. data/lib/firespring_dev_commands/audit/report/item.rb +33 -0
  5. data/lib/firespring_dev_commands/audit/report/levels.rb +36 -0
  6. data/lib/firespring_dev_commands/audit/report.rb +49 -0
  7. data/lib/firespring_dev_commands/aws/account/info.rb +15 -0
  8. data/lib/firespring_dev_commands/aws/account.rb +164 -0
  9. data/lib/firespring_dev_commands/aws/cloudformation/parameters.rb +26 -0
  10. data/lib/firespring_dev_commands/aws/cloudformation.rb +188 -0
  11. data/lib/firespring_dev_commands/aws/codepipeline.rb +96 -0
  12. data/lib/firespring_dev_commands/aws/credentials.rb +136 -0
  13. data/lib/firespring_dev_commands/aws/login.rb +131 -0
  14. data/lib/firespring_dev_commands/aws/parameter.rb +32 -0
  15. data/lib/firespring_dev_commands/aws/profile.rb +55 -0
  16. data/lib/firespring_dev_commands/aws/s3.rb +42 -0
  17. data/lib/firespring_dev_commands/aws.rb +10 -0
  18. data/lib/firespring_dev_commands/boolean.rb +7 -0
  19. data/lib/firespring_dev_commands/common.rb +112 -0
  20. data/lib/firespring_dev_commands/daterange.rb +171 -0
  21. data/lib/firespring_dev_commands/docker/compose.rb +271 -0
  22. data/lib/firespring_dev_commands/docker/status.rb +38 -0
  23. data/lib/firespring_dev_commands/docker.rb +276 -0
  24. data/lib/firespring_dev_commands/dotenv.rb +6 -0
  25. data/lib/firespring_dev_commands/env.rb +38 -0
  26. data/lib/firespring_dev_commands/eol/product_version.rb +86 -0
  27. data/lib/firespring_dev_commands/eol.rb +58 -0
  28. data/lib/firespring_dev_commands/git/info.rb +13 -0
  29. data/lib/firespring_dev_commands/git.rb +420 -0
  30. data/lib/firespring_dev_commands/jira/issue.rb +33 -0
  31. data/lib/firespring_dev_commands/jira/project.rb +13 -0
  32. data/lib/firespring_dev_commands/jira/user/type.rb +20 -0
  33. data/lib/firespring_dev_commands/jira/user.rb +31 -0
  34. data/lib/firespring_dev_commands/jira.rb +78 -0
  35. data/lib/firespring_dev_commands/logger.rb +8 -0
  36. data/lib/firespring_dev_commands/node/audit.rb +39 -0
  37. data/lib/firespring_dev_commands/node.rb +107 -0
  38. data/lib/firespring_dev_commands/php/audit.rb +71 -0
  39. data/lib/firespring_dev_commands/php.rb +109 -0
  40. data/lib/firespring_dev_commands/rake.rb +24 -0
  41. data/lib/firespring_dev_commands/ruby/audit.rb +30 -0
  42. data/lib/firespring_dev_commands/ruby.rb +113 -0
  43. data/lib/firespring_dev_commands/second.rb +22 -0
  44. data/lib/firespring_dev_commands/tar/pax_header.rb +49 -0
  45. data/lib/firespring_dev_commands/tar/type_flag.rb +49 -0
  46. data/lib/firespring_dev_commands/tar.rb +149 -0
  47. data/lib/firespring_dev_commands/templates/aws.rb +84 -0
  48. data/lib/firespring_dev_commands/templates/base_interface.rb +54 -0
  49. data/lib/firespring_dev_commands/templates/ci.rb +138 -0
  50. data/lib/firespring_dev_commands/templates/docker/application.rb +177 -0
  51. data/lib/firespring_dev_commands/templates/docker/default.rb +200 -0
  52. data/lib/firespring_dev_commands/templates/docker/node/application.rb +145 -0
  53. data/lib/firespring_dev_commands/templates/docker/php/application.rb +190 -0
  54. data/lib/firespring_dev_commands/templates/docker/ruby/application.rb +146 -0
  55. data/lib/firespring_dev_commands/templates/eol.rb +23 -0
  56. data/lib/firespring_dev_commands/templates/git.rb +147 -0
  57. data/lib/firespring_dev_commands/version.rb +11 -0
  58. data/lib/firespring_dev_commands.rb +21 -0
  59. metadata +436 -0
@@ -0,0 +1,420 @@
1
+ require 'fileutils'
2
+ require 'git'
3
+
4
+ module Dev
5
+ # Class for performing git functions
6
+ class Git
7
+ # The default base branch to use
8
+ DEFAULT_MAIN_BRANCH = 'master'.freeze
9
+
10
+ # Config object for setting top level git config options
11
+ Config = Struct.new(:main_branch, :staging_branch, :info, :min_version, :max_version) do
12
+ def initialize
13
+ self.main_branch = DEFAULT_MAIN_BRANCH
14
+ self.staging_branch = nil
15
+ self.info = [Dev::Git::Info.new(DEV_COMMANDS_PROJECT_NAME, DEV_COMMANDS_ROOT_DIR)]
16
+ self.min_version = nil
17
+ self.max_version = nil
18
+ end
19
+ end
20
+
21
+ class << self
22
+ # Instantiates a new top level config object if one hasn't already been created
23
+ # Yields that config object to any given block
24
+ # Returns the resulting config object
25
+ def config
26
+ @config ||= Config.new
27
+ yield(@config) if block_given?
28
+ @config
29
+ end
30
+
31
+ # Alias the config method to configure for a slightly clearer access syntax
32
+ alias_method :configure, :config
33
+
34
+ # Returns the version of the git executable running on the system
35
+ def version
36
+ @version ||= ::Git::Lib.new(nil, nil).current_command_version.join('.')
37
+ end
38
+ end
39
+
40
+ attr_accessor :main_branch, :staging_branch, :release_branches, :info, :original_branches
41
+
42
+ def initialize(
43
+ main_branch: self.class.config.main_branch,
44
+ staging_branch: self.class.config.staging_branch,
45
+ info: self.class.config.info
46
+ )
47
+ @main_branch = main_branch
48
+ raise 'main branch must be configured' if main_branch.to_s.empty?
49
+
50
+ @staging_branch = staging_branch || main_branch
51
+ @info = Array(info)
52
+ raise 'git repositories must be configured' if @info.empty? || !@info.all?(Dev::Git::Info)
53
+
54
+ check_version
55
+ end
56
+
57
+ # Checks the min and max version against the current git version if they have been configured
58
+ def check_version
59
+ min_version = self.class.config.min_version
60
+ raise "requires git version >= #{min_version} (found #{self.class.version})" if min_version && !Dev::Common.new.version_greater_than(min_version, self.class.version)
61
+
62
+ max_version = self.class.config.max_version
63
+ raise "requires git version < #{max_version} (found #{self.class.version})" if max_version && Dev::Common.new.version_greater_than(max_version, self.class.version)
64
+ end
65
+
66
+ # Returns all git paths configured in our info
67
+ def project_dirs
68
+ @project_dirs ||= @info.map(&:path).sort
69
+ end
70
+
71
+ # Returns the first configured project dire
72
+ def default_project_dir
73
+ project_dirs.first
74
+ end
75
+
76
+ # Populates and returns a hash containing the original version of branches
77
+ def original_branches
78
+ @original_branches ||= current_branches
79
+ end
80
+
81
+ # Returns a hash of each project repo and the branch that is currently checked out
82
+ def current_branches
83
+ {}.tap do |hsh|
84
+ project_dirs.each do |project_dir|
85
+ next unless File.exist?(project_dir)
86
+
87
+ Dir.chdir(project_dir) do
88
+ hsh[project_dir] = branch_name(dir: project_dir)
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ # Returns the branch name associated with the given repository
95
+ # Defaults to the current directory
96
+ def branch_name(dir: default_project_dir)
97
+ return unless File.exist?(dir)
98
+
99
+ ::Git.open(dir).current_branch
100
+ end
101
+
102
+ # Returns true if the remote branch exists, false otherwise
103
+ def branch_exists?(project_dir, branch_name)
104
+ ::Git.ls_remote(project_dir)['remotes']["origin/#{branch_name}"]
105
+ end
106
+
107
+ # Prints the status of multiple repository directories and displays the results in a nice format
108
+ def status_all
109
+ @success = true
110
+ puts
111
+ puts 'Getting status in each repo'.light_yellow if project_dirs.length > 1
112
+ project_dirs.each do |project_dir|
113
+ next unless File.exist?(project_dir)
114
+
115
+ repo_basename = File.basename(File.realpath(project_dir))
116
+ header = " #{repo_basename} (#{original_branches[project_dir]}) "
117
+ puts center_pad(header).light_green
118
+ @success &= status(dir: project_dir)
119
+ puts center_pad.light_green
120
+ end
121
+ puts
122
+
123
+ raise 'Failed getting status on one or more repositories' unless @success
124
+ end
125
+
126
+ # Prints the results of the status command
127
+ # Currently running "git status" instead of using the library because it doesn't do well formatting the output
128
+ def status(dir: default_project_dir)
129
+ return unless File.exist?(dir)
130
+
131
+ # NOTE: git library doesn't have a good "status" analog. So just run the standard "git" one
132
+ # splitting and puts'ing to prefix each line with spaces...
133
+ Dir.chdir(dir) { indent `git status` }
134
+ end
135
+
136
+ # Returns the name of any repositories which have changes
137
+ def repos_with_changes
138
+ info.filter_map { |it| it.name unless changes(dir: it.path).empty? }
139
+ end
140
+
141
+ # Print the changes on the given repo
142
+ # Defaults to the current directory
143
+ def changes(dir: default_project_dir)
144
+ return unless File.exist?(dir)
145
+
146
+ Dir.chdir(dir) { `git status --porcelain | grep -v '^?'` }.split("\n").map(&:strip)
147
+ end
148
+
149
+ # Print the changes on the given repo using the ruby built-in method... which seems _REALLY_ slow compared to the porcelain version
150
+ # Defaults to the current directory
151
+ def changes_slow(dir: default_project_dir)
152
+ return unless File.exist?(dir)
153
+
154
+ s = ::Git.open(dir).status
155
+ s.added.keys.map { |it| " A #{it}" } +
156
+ s.changed.keys.map { |it| " M #{it}" } +
157
+ s.deleted.keys.map { |it| " D #{it}" }
158
+ end
159
+
160
+ # Runs a git reset on all given repositories with some additional formatting
161
+ def reset_all
162
+ puts
163
+ puts 'Resetting each repo'.light_yellow if project_dirs.length > 1
164
+ project_dirs.each do |project_dir|
165
+ next unless File.exist?(project_dir)
166
+
167
+ repo_basename = File.basename(File.realpath(project_dir))
168
+ header = " #{repo_basename} (#{original_branches[project_dir]}) "
169
+ puts center_pad(header).light_green
170
+ reset(dir: project_dir)
171
+ puts center_pad.light_green
172
+ end
173
+ puts
174
+ end
175
+
176
+ # Runs a git reset on the given repo
177
+ # Defaults to the current directory
178
+ def reset(dir: default_project_dir)
179
+ return unless File.exist?(dir)
180
+
181
+ g = ::Git.open(dir)
182
+ indent g.reset_hard
183
+ end
184
+
185
+ # Checks out the given branch in all repositories with some additional formatting
186
+ def checkout_all(branch)
187
+ @success = true
188
+ puts
189
+ puts "Checking out #{branch} in each repo".light_yellow if project_dirs.length > 1
190
+ project_dirs.each do |project_dir|
191
+ next unless File.exist?(project_dir)
192
+
193
+ repo_basename = File.basename(File.realpath(project_dir))
194
+ header = " #{repo_basename} "
195
+ puts center_pad(header).light_green
196
+ @success &= checkout(branch, dir: project_dir)
197
+ puts center_pad.light_green
198
+ end
199
+ puts
200
+
201
+ raise "Failed checking out branch #{branch} one or more repositories" unless @success
202
+ end
203
+
204
+ # Checks out the given branch in the given repo
205
+ # Defaults to the current directory
206
+ def checkout(branch, dir: default_project_dir)
207
+ raise 'branch is required' if branch.to_s.strip.empty?
208
+ return unless File.exist?(dir)
209
+
210
+ # Make sure the original branch hash has been created before we change anything
211
+ original_branches
212
+
213
+ g = ::Git.open(dir)
214
+ g.fetch('origin', prune: true)
215
+
216
+ # If the branch we are checking out doesn't exist, check out either the staging branch or the main branch
217
+ actual_branch = branch
218
+ unless branch_exists?(dir, branch)
219
+ actual_branch = [staging_branch, main_branch].uniq.find { |it| branch_exists?(dir, it) }
220
+ puts "Branch #{branch} not found, checking out #{actual_branch} instead".light_yellow
221
+ end
222
+
223
+ indent g.checkout(actual_branch)
224
+ indent g.pull('origin', actual_branch)
225
+ true
226
+ rescue ::Git::GitExecuteError => e
227
+ print_errors(e.message)
228
+ false
229
+ end
230
+
231
+ # Create the given branch in the given repo
232
+ def create_branch(branch, dir: default_project_dir)
233
+ raise 'branch is required' if branch.to_s.strip.empty?
234
+ raise "refusing to create protected branch '#{branch}'" if %w(master develop).any?(branch.to_s.strip)
235
+ return unless File.exist?(dir)
236
+
237
+ # Make sure the original branch hash has been created before we change anything
238
+ original_branches
239
+
240
+ g = ::Git.open(dir)
241
+ g.fetch('origin', prune: true)
242
+
243
+ puts "Fetching the latest changes for base branch #{staging_branch}"
244
+ g.checkout(staging_branch)
245
+ g.pull('origin', staging_branch)
246
+
247
+ puts "Creating branch #{branch}, pushing to origin, and updating remote tracking"
248
+ g.branch(branch).checkout
249
+ g.push('origin', branch)
250
+ g.config("branch.#{branch}.remote", 'origin')
251
+ g.config("branch.#{branch}.merge", "refs/heads/#{branch}")
252
+ puts
253
+ rescue ::Git::GitExecuteError => e
254
+ print_errors(e.message)
255
+ false
256
+ end
257
+
258
+ # Merge the branch into all repositories
259
+ def merge_all(branch)
260
+ @success = true
261
+ puts
262
+ puts "Merging #{branch} into each repo".light_yellow if project_dirs.length > 1
263
+ project_dirs.each do |project_dir|
264
+ next unless File.exist?(project_dir)
265
+
266
+ repo_basename = File.basename(File.realpath(project_dir))
267
+ header = " #{repo_basename} "
268
+ puts center_pad(header).light_green
269
+ @success &= merge(branch, dir: project_dir)
270
+ puts center_pad.light_green
271
+ end
272
+ puts
273
+
274
+ raise "Failed merging branch #{branch} in one or more repositories" unless @success
275
+
276
+ push_all
277
+ end
278
+
279
+ # Merge the given branch into the given repo
280
+ def merge(branch, dir: default_project_dir)
281
+ raise 'branch is required' if branch.to_s.strip.empty?
282
+ return unless File.exist?(dir)
283
+
284
+ # Make sure the original branch hash has been created before we change anything
285
+ original_branches
286
+
287
+ g = ::Git.open(dir)
288
+ g.fetch('origin', prune: true)
289
+ raise 'branch does not exist' unless branch_exists?(dir, branch)
290
+
291
+ # No need to merge into ourself
292
+ current_branch = branch_name(dir: dir)
293
+ return true if current_branch == branch
294
+
295
+ indent "Merging #{branch} into #{current_branch}"
296
+ indent g.merge(branch)
297
+ true
298
+ rescue ::Git::GitExecuteError => e
299
+ print_errors(e.message)
300
+ false
301
+ end
302
+
303
+ # Pull the latest in all repositories
304
+ def pull_all
305
+ @success = true
306
+ puts
307
+ puts 'Pulling current branch into each repo'.light_yellow if project_dirs.length > 1
308
+ project_dirs.each do |project_dir|
309
+ next unless File.exist?(project_dir)
310
+
311
+ repo_basename = File.basename(File.realpath(project_dir))
312
+ header = " #{repo_basename} "
313
+ puts center_pad(header).light_green
314
+ @success &= pull(dir: project_dir)
315
+ puts center_pad.light_green
316
+ end
317
+ puts
318
+
319
+ raise 'Failed pulling branch in one or more repositories' unless @success
320
+ end
321
+
322
+ # Pull the given repo
323
+ def pull(dir: default_project_dir)
324
+ return unless File.exist?(dir)
325
+
326
+ g = ::Git.open(dir)
327
+ g.fetch('origin', prune: true)
328
+
329
+ branch = branch_name(dir: dir)
330
+ indent "Pulling branch #{branch} from origin"
331
+ indent g.pull('origin', branch)
332
+ true
333
+ rescue ::Git::GitExecuteError => e
334
+ print_errors(e.message)
335
+ false
336
+ end
337
+
338
+ # Push to remote in all repositories
339
+ def push_all
340
+ @success = true
341
+ puts
342
+ puts 'Pushing current branch into each repo'.light_yellow if project_dirs.length > 1
343
+ project_dirs.each do |project_dir|
344
+ next unless File.exist?(project_dir)
345
+
346
+ repo_basename = File.basename(File.realpath(project_dir))
347
+ header = " #{repo_basename} "
348
+ puts center_pad(header).light_green
349
+ @success &= push(dir: project_dir)
350
+ puts center_pad.light_green
351
+ end
352
+ puts
353
+
354
+ raise 'Failed pushing branch in one or more repositories' unless @success
355
+ end
356
+
357
+ # Push the given repo
358
+ def push(dir: default_project_dir)
359
+ return unless File.exist?(dir)
360
+
361
+ g = ::Git.open(dir)
362
+ g.fetch('origin', prune: true)
363
+
364
+ branch = branch_name(dir: dir)
365
+ indent "Pushing branch #{branch} to origin"
366
+ indent g.push('origin', branch)
367
+ true
368
+ rescue ::Git::GitExecuteError => e
369
+ print_errors(e.message)
370
+ false
371
+ end
372
+
373
+ # Clones all repositories
374
+ def clone_repos
375
+ info.each { |it| clone_repo(dir: it.path, repo_name: it.name) }
376
+ end
377
+
378
+ # Clones the repo_name into the dir
379
+ # Optionally specify a repo_org
380
+ # Optionally specify a branch to check out (defaults to the repository default branch)
381
+ def clone_repo(dir:, repo_name:, repo_org: 'firespring', branch: nil)
382
+ if Dir.exist?("#{dir}/.git")
383
+ puts "#{dir} already cloned".light_green
384
+ return
385
+ end
386
+
387
+ FileUtils.mkdir_p(dir.to_s)
388
+
389
+ puts "Cloning #{dir} from #{ssh_repo_url(repo_name, repo_org)}".light_yellow
390
+
391
+ opts = {}
392
+ opts[:branch] = branch unless branch.to_s.strip.empty?
393
+ g = ::Git.clone(ssh_repo_url(repo_name, repo_org), dir, opts)
394
+ g.fetch('origin', prune: true)
395
+ end
396
+
397
+ # Builds an ssh repo URL using the org and repo name given
398
+ def ssh_repo_url(name, org)
399
+ "git@github.com:#{org}/#{name}.git"
400
+ end
401
+
402
+ # Split on newlines and add additional padding
403
+ def indent(string, padding: ' ')
404
+ string.to_s.split("\n").each { |line| puts "#{padding}#{line}" }
405
+ end
406
+
407
+ # Center the string and pad on either side with the given padding character
408
+ def center_pad(string = '', pad: '-', len: 80)
409
+ center_dash = len / 2
410
+ string = string.to_s
411
+ center_str = string.length / 2
412
+ string.rjust(center_dash + center_str - 1, pad).ljust(len - 1, pad)
413
+ end
414
+
415
+ # Exclude the command from the message and print all error lines
416
+ private def print_errors(message)
417
+ indent message.split('error:')[1..].join
418
+ end
419
+ end
420
+ end
@@ -0,0 +1,33 @@
1
+ module Dev
2
+ class Jira
3
+ # Contains information and methods representing a Jira issue
4
+ class Issue
5
+ # Issue subtypes which do not map to a story type
6
+ NON_STORY_TYPES = ['review', 'sub-task', 'code review sub-task', 'pre-deploy sub-task', 'deploy sub-task', 'devops sub-task'].freeze
7
+
8
+ attr_accessor :data, :project, :id, :title, :points, :assignee, :resolved_date
9
+
10
+ def initialize(data)
11
+ @data = data
12
+ @project = Jira::Project.new(data)
13
+ @id = data.key
14
+ @title = data.summary
15
+ @points = calculate_points(data)
16
+ @assignee = Jira::User.lookup(data.assignee&.accountId)
17
+ @resolved_date = data.resolutiondate
18
+ end
19
+
20
+ # Returns the value of the jira points field or 0 if the field is not found
21
+ def calculate_points(data)
22
+ return data.send(Dev::Jira.config.points_field_name).to_i if Dev::Jira.config.points_field_name && data.respond_to?(Dev::Jira.config.points_field_name)
23
+
24
+ 0
25
+ end
26
+
27
+ # Converts the jira issue object to a string representation
28
+ def to_s
29
+ "[#{id}] #{title} (#{points} pts) (resolved #{resolved_date}"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,13 @@
1
+ module Dev
2
+ class Jira
3
+ # Contains information on the Jira project
4
+ class Project
5
+ attr_accessor :name
6
+
7
+ def initialize(data)
8
+ @name = data.project.name
9
+ @name = @name << ' DevOps' if /devops/i.match?(data.issuetype.name)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ module Dev
2
+ class Jira
3
+ class User
4
+ # Contains constants representing valid Jira user types
5
+ class Type
6
+ # Bot type
7
+ BOT = :bot
8
+
9
+ # Developer type
10
+ DEVELOPER = :developer
11
+
12
+ # "Other" type
13
+ OTHER = :other
14
+
15
+ # Project manager type
16
+ PM = :pm
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,31 @@
1
+ module Dev
2
+ class Jira
3
+ # Contains Jira user information
4
+ # Jira does not make user information available through their normal api (they have an admin api that you can use)
5
+ # Therefore, we've provided a "lookup" method which attempts to find the jira user id in a hash of user information
6
+ # that you can configure
7
+ class User
8
+ attr_accessor :name, :email, :id, :type
9
+
10
+ def initialize(name:, email:, id:, type: Type::OTHER)
11
+ @name = name
12
+ @email = email
13
+ @id = id
14
+ @type = type
15
+ end
16
+
17
+ # Returns true if the Jira user is categorized as a developer
18
+ def developer?
19
+ type == Type::DEVELOPER
20
+ end
21
+
22
+ # Returns the Jira user object which maps to the give user id
23
+ # If none is found, it returns a Jira user object with only the id set
24
+ def self.lookup(id)
25
+ user = Dev::Jira.config.user_lookup_list&.find { |it| it.id == id }
26
+ user ||= new(name: '', email: '', id: id)
27
+ user
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,78 @@
1
+ require 'jira-ruby'
2
+ require 'base64'
3
+
4
+ module Dev
5
+ # Class which contains methods to conenct to jira and query issues
6
+ class Jira
7
+ # Config object for setting top level jira config options
8
+ # "points_field_name" is the field holding the value for points on a story. If this is not present, all points will default to 0
9
+ # "user_lookup_list" should be an array of Jira::User objects representing the usernames, ids, etc for all jira users
10
+ # This is a bit clumsy but currently the jira api only returns the user id with issues
11
+ # and there is no way to query this information from Jira directly.
12
+ Config = Struct.new(:username, :token, :url, :points_field_name, :user_lookup_list, :read_timeout, :http_debug) do
13
+ def initialize
14
+ self.username = nil
15
+ self.token = nil
16
+ self.url = nil
17
+ self.points_field_name = nil
18
+ self.user_lookup_list = []
19
+ self.read_timeout = 120
20
+ self.http_debug = false
21
+ end
22
+ end
23
+
24
+ class << self
25
+ # Instantiates a new top level config object if one hasn't already been created
26
+ # Yields that config object to any given block
27
+ # Returns the resulting config object
28
+ def config
29
+ @config ||= Config.new
30
+ yield(@config) if block_given?
31
+ @config
32
+ end
33
+
34
+ # Alias the config method to configure for a slightly clearer access syntax
35
+ alias_method :configure, :config
36
+ end
37
+
38
+ attr_accessor :username, :token, :url, :auth, :client
39
+
40
+ # Initialize a new jira client using the given inputs
41
+ def initialize(username: self.class.config.username, token: self.class.config.token, url: self.class.config.url)
42
+ @username = username
43
+ @token = token
44
+ @url = url
45
+ @auth = Base64.strict_encode64("#{@username}:#{@token}")
46
+
47
+ options = {
48
+ auth_type: :basic,
49
+ site: @url,
50
+ default_headers: {Authorization: "Basic #{@auth}"},
51
+ context_path: '',
52
+ read_timeout: self.class.config.read_timeout,
53
+ use_ssl: true,
54
+ ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER,
55
+ http_debug: self.class.config.http_debug
56
+ }
57
+
58
+ @client = JIRA::Client.new(options)
59
+ end
60
+
61
+ # Query jira using the given jql and yield each matching result
62
+ def issues(jql, &block)
63
+ start_at = 0
64
+ max_results = 100
65
+
66
+ # Query Jira and yield all issues it returns
67
+ issues = @client.Issue.jql(jql, start_at: start_at, max_results: max_results)
68
+ issues.map { |data| Issue.new(data) }.each(&block)
69
+
70
+ # If we returned the max_results then there may be more - add the max results to where we start at and query again
71
+ while issues.length >= max_results
72
+ start_at += max_results
73
+ issues = @client.Issue.jql(jql, start_at: start_at, max_results: max_results)
74
+ issues.map { |data| Issue.new(data) }.each(&block)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,8 @@
1
+ require 'logger'
2
+
3
+ # Only define LOG if it doesn't already exist
4
+ unless defined? LOG
5
+ LOG = Logger.new($stdout)
6
+ LOG.formatter = proc { |_, _, _, msg| "#{msg}\n" }
7
+ LOG.level = Logger::DEBUG
8
+ end
@@ -0,0 +1,39 @@
1
+ module Dev
2
+ class Node
3
+ # Class which contains commands and customizations for node security audit reports
4
+ class Audit
5
+ attr_accessor :data
6
+
7
+ def initialize(data)
8
+ @data = JSON.parse(Dev::Common.new.strip_non_json(data))
9
+ end
10
+
11
+ # Convert the node audit data to the standardized audit report object
12
+ def to_report
13
+ ids = Set.new
14
+
15
+ Dev::Audit::Report.new(
16
+ data['vulnerabilities'].map do |_, vulnerability|
17
+ # If the via ia a hash and the id is not already recorded, add the item to our report
18
+ vulnerability['via'].map do |it|
19
+ next unless it.is_a?(Hash)
20
+
21
+ id = it['url']&.split('/')&.last
22
+ next if ids.include?(id)
23
+
24
+ ids << id
25
+ Dev::Audit::Report::Item.new(
26
+ id: id,
27
+ name: vulnerability['name'],
28
+ title: it['title'],
29
+ url: it['url'],
30
+ severity: vulnerability['severity'],
31
+ version: it['range']
32
+ )
33
+ end
34
+ end.flatten.compact
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end