dependaboat 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 14a88024f9d6d27f1365bfbfe5575ffaf2d8f0b4e84b04eccbe1ae70489da90b
4
+ data.tar.gz: a5b7200f42a6658de3e5903c6f408097588bd486b1c09452b2762936f34ca691
5
+ SHA512:
6
+ metadata.gz: edaddab17c68a16780fd21f7a5318fea7c23d09aad516bcc5bd5b957249ce7d78e344ba9ad12d6bfff6e1d45e2340ed835a890c32e437927c1b380b2bd255e03
7
+ data.tar.gz: 416daac0c5e9d9e125649c193022c8f7eef01843f3d42013a408c8bb729bc5810ba75e6273ae8226948fee8dd4a35447454fc7aef6a3e96a4be67099964106fc
data/bin/dependaboat ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ENV["BUNDLE_GEMFILE"] = File.expand_path("../Gemfile", File.dirname(__FILE__))
4
+ require "bundler/setup"
5
+ require "dependaboat"
6
+ exitcode = Dependaboat::Cli.run
7
+ exitcode ||= 0
8
+ exit exitcode
@@ -0,0 +1,220 @@
1
+ require "optionparser"
2
+ require "yaml"
3
+ require "time"
4
+
5
+ module Dependaboat
6
+ class Cli
7
+ attr_reader :project_id, :owner, :repo, :project, :config
8
+ attr_accessor :logger
9
+
10
+ def self.run
11
+ new(*ARGV).run
12
+ end
13
+
14
+ def initialize(*argv)
15
+ @dry_run = false # Default
16
+ @logger = Dependaboat.logger
17
+ options_parser.parse(argv)
18
+ end
19
+
20
+ def run
21
+ initialize_project
22
+ fetch_and_process_alerts
23
+ logger.info "Run complete."
24
+ 0
25
+ end
26
+
27
+ private
28
+
29
+ def load_config(path)
30
+ config_file = File.expand_path(path)
31
+ raise "Could not find config file" unless File.exist?(config_file)
32
+
33
+ @config = YAML.load_file(config_file)
34
+ logger.info "Loaded config from #{config_file}"
35
+ logger.info @config
36
+
37
+ @project_id = config.dig("github", "project_id")
38
+ @owner = config.dig("github", "owner")
39
+ @repo = config.dig("github", "repo")
40
+ @assignees = config.dig("github", "issue", "assignees") || {}
41
+ end
42
+
43
+ def initialize_project
44
+ @project = GHX::Project.new(project_id)
45
+ end
46
+
47
+ def fetch_and_process_alerts
48
+ @alerts = GHX::Dependabot.get_alerts(owner: owner, repo: repo)
49
+ logger.info("Found #{@alerts.size} Dependabot alerts")
50
+
51
+ @alerts.each do |alert|
52
+ process_alert(alert)
53
+ sleep 1 # Rate limiting
54
+ end
55
+ end
56
+
57
+ def process_alert(alert)
58
+ return if issue_exists?(alert)
59
+
60
+ alert_details = extract_alert_details(alert)
61
+ create_github_issue(alert, alert_details)
62
+ rescue StandardError => e
63
+ logger.error "Error processing alert ##{alert.number}: #{e.message}"
64
+ end
65
+
66
+ def issue_exists?(alert)
67
+ existing_issue = GHX::Issue.search(owner: owner, repo: repo, query: issue_lookup_key(alert)).any?
68
+ if existing_issue
69
+ logger.info " Issue already exists for alert ##{alert.number}. Skipping."
70
+ end
71
+ existing_issue
72
+ end
73
+
74
+ def extract_alert_details(alert)
75
+ alert_number = alert.number
76
+ alert_severity = alert.security_vulnerability.severity.capitalize
77
+ alert_package_name = alert.security_vulnerability.package.name
78
+ alert_package_ecosystem = alert.security_vulnerability.package.ecosystem
79
+ alert_created_at = alert.created_at.to_date rescue Date.today
80
+
81
+ remediation_deadline = alert_created_at + config.dig("remediation_sla", alert_severity.downcase)
82
+
83
+ {
84
+ number: alert_number,
85
+ severity: alert_severity,
86
+ package_name: alert_package_name,
87
+ package_ecosystem: alert_package_ecosystem,
88
+ created_at: alert_created_at,
89
+ remediation_deadline: remediation_deadline
90
+ }
91
+ end
92
+
93
+ def create_github_issue(alert, details)
94
+ template_variable_map = build_template_variable_map(details)
95
+
96
+ logger.info "Processing alert ##{details[:number]} (#{details[:severity].upcase}) in #{details[:package_name]} (#{details[:package_ecosystem]}) created at #{details[:created_at]}"
97
+
98
+ logger.info " Creating new issue for this alert."
99
+
100
+ title = build_issue_title(alert, template_variable_map)
101
+ body = build_issue_body(template_variable_map)
102
+ labels = build_issue_labels(template_variable_map)
103
+
104
+ issue = build_github_issue(alert, title, body, labels)
105
+
106
+ if dry_run?
107
+ log_dry_run(issue)
108
+ return
109
+ end
110
+
111
+ save_issue_and_update_project(alert, issue, template_variable_map)
112
+ end
113
+
114
+ def build_template_variable_map(details)
115
+ {
116
+ "alert_number" => details[:number],
117
+ "alert_severity" => details[:severity],
118
+ "alert_package_name" => details[:package_name],
119
+ "alert_package_ecosystem" => details[:package_ecosystem],
120
+ "alert_created_at" => details[:created_at],
121
+ "remediation_deadline" => details[:remediation_deadline].strftime("%Y-%m-%d")
122
+ }
123
+ end
124
+
125
+ def build_issue_title(alert, template_variable_map)
126
+ issue_lookup_key(alert) + " " + process_templateable_string(config.dig("github", "issue", "title"), template_variable_map)
127
+ end
128
+
129
+ def build_issue_body(template_variable_map)
130
+ process_templateable_string(config.dig("github", "issue", "body"), template_variable_map)
131
+ end
132
+
133
+ def build_issue_labels(template_variable_map)
134
+ labels_config = config.dig("github", "issue", "labels") || []
135
+ labels_config.map { |template| process_templateable_string(template, template_variable_map) }
136
+ end
137
+
138
+ def build_github_issue(alert, title, body, labels)
139
+ GHX::Issue.new(
140
+ owner: owner,
141
+ repo: repo,
142
+ title: title,
143
+ body: body,
144
+ labels: labels,
145
+ assignees: assignees_for_ecosystem(alert.security_vulnerability.package.ecosystem)
146
+ )
147
+ end
148
+
149
+ def assignees_for_ecosystem(ecosystem)
150
+ assignees = Array(@assignees[ecosystem] || @assignees["other"])
151
+ assignees << @assignees["all"]
152
+ assignees.flatten.compact.uniq
153
+ end
154
+
155
+ def log_dry_run(issue)
156
+ logger.info " Dry Run: Would have created issue:"
157
+ logger.info issue.inspect
158
+ end
159
+
160
+ def save_issue_and_update_project(alert, issue, template_variable_map)
161
+ issue.save
162
+ logger.info " Created Github Issue ##{issue.number} for alert ##{alert.number}"
163
+
164
+ logger.info " Waiting for GH automation to run and create the associated GH Project Item..."
165
+ sleep 5
166
+
167
+ project_item = fetch_project_item(issue)
168
+ update_project_item(project_item, template_variable_map)
169
+ end
170
+
171
+ def fetch_project_item(issue)
172
+ project.find_item_by_issue_number(owner: owner, repo: repo, number: issue.number).tap do |project_item|
173
+ logger.info " Found project item #{project_item.id} for issue ##{issue.number}."
174
+ end
175
+ end
176
+
177
+ def update_project_item(project_item, template_variable_map)
178
+ logger.info " Updating Project Item with additional data..."
179
+
180
+ config.dig("github", "project_item", "field_map").each do |field_map|
181
+ field_name = field_map["field_name"]
182
+ field_value = field_map["field_value"]
183
+
184
+ logger.debug " #{field_name} => #{field_value}"
185
+ project_item.update(field_name => process_templateable_string(field_value, template_variable_map))
186
+ end
187
+ end
188
+
189
+ def issue_lookup_key(alert)
190
+ "[#{@repo}/DB #{alert.number}]"
191
+ end
192
+
193
+ def options_parser
194
+ OptionParser.new do |opts|
195
+ opts.banner = "Usage: dependaboat [options]"
196
+
197
+ opts.on("-cCONFIG_FILE", "--config-file=CONFIG_FILE", "The path to the config file") do |config_file|
198
+ load_config(config_file)
199
+ end
200
+
201
+ opts.on("-d", "--dry-run", "Run in dry-run mode") do
202
+ @dry_run = true
203
+ end
204
+
205
+ opts.on("-h", "--help", "Prints this help") do
206
+ puts opts
207
+ exit
208
+ end
209
+ end
210
+ end
211
+
212
+ def process_templateable_string(s, map)
213
+ map.reduce(s.dup) { |str, (key, value)| str.gsub!("{{#{key}}}", value.to_s); str }
214
+ end
215
+
216
+ def dry_run?
217
+ @dry_run
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,15 @@
1
+ require "logger"
2
+ require "ghx"
3
+
4
+ module Dependaboat
5
+ def self.logger
6
+ @logger ||= Logger.new($stdout)
7
+ end
8
+
9
+ def self.logger=(logger)
10
+ @logger = logger
11
+ end
12
+ end
13
+
14
+ require_relative "version"
15
+ require_relative "dependaboat/cli"
data/lib/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Dependaboat
2
+ VERSION = "0.2.0"
3
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dependaboat
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - CompanyCam
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-05-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ghx
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: dotenv
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 3.1.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 3.1.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: faraday
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 2.9.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 2.9.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: faraday-retry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 2.2.1
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 2.2.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: standardrb
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: debug
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: minitest
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Create GitHub Issues and GitHub Project Items from Dependabot alerts
112
+ email: jeff.mcfadden@companycam.com
113
+ executables:
114
+ - dependaboat
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - bin/dependaboat
119
+ - lib/dependaboat.rb
120
+ - lib/dependaboat/cli.rb
121
+ - lib/version.rb
122
+ homepage: https://github.com/companycam/dependaboat
123
+ licenses:
124
+ - MIT
125
+ metadata: {}
126
+ post_install_message:
127
+ rdoc_options: []
128
+ require_paths:
129
+ - lib
130
+ required_ruby_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ required_rubygems_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ requirements: []
141
+ rubygems_version: 3.5.10
142
+ signing_key:
143
+ specification_version: 4
144
+ summary: Ferry Dependabot alerts to GitHub Issues and Projects
145
+ test_files: []