dependaboat 0.2.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 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: []