dependabot-common 0.109.1 → 0.110.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 25f76fc899360534742de56a0ede99a7f87c544e7587fea6e0d76b3418c9ee8d
4
- data.tar.gz: 7ac044317f75e41360704fb51e25dd555202dc1fdac4e110b4d7b02368f9c5c7
3
+ metadata.gz: f65e5de2ce518246b0ec585237c62ac0bcc0de7392f119a4b3ffeab96ea9479f
4
+ data.tar.gz: d200fe282d5a0c10794a80d5f11526c1665519be29e41e0cc6e788f06abe5e3a
5
5
  SHA512:
6
- metadata.gz: e2e76291ffd64fd783038f274046cd10153f9952ecb285bd1c904a89f9d976225cdcdb31d1842af86dd7861d4cf029b85a5ab4d2358926839804e9aee5ee175b
7
- data.tar.gz: d2617b87ed7c3d20c5b2c9d4e272548bd1bdcdf212d639004fcc8f9519b6f43be9c146732c3f33abfd6a97aefcbd901e2ba1873904056922986660e7cbd214ae
6
+ metadata.gz: ede7202b5774b8e2ebd919d0c90a6b0010f5c9f56332ec41de1d9396c0f7617abb03825d0c02ee487077792928d24e5fc628248a20a9846ddbc2bed6aa2e6f7e
7
+ data.tar.gz: 2eefd19dbb76137dd133079840918f5a1357126d0a1d182aec15b89deb38b87c4502868c01fe9ad1d855f3181fe5ad5fd4d09ad469af4086d6e3fca3e1b37b54
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/shared_helpers"
4
+ require "excon"
5
+
6
+ module Dependabot
7
+ module Clients
8
+ class Azure
9
+ class NotFound < StandardError; end
10
+
11
+ #######################
12
+ # Constructor methods #
13
+ #######################
14
+
15
+ def self.for_source(source:, credentials:)
16
+ credential =
17
+ credentials.
18
+ select { |cred| cred["type"] == "git_source" }.
19
+ find { |cred| cred["host"] == source.hostname }
20
+
21
+ new(source, credential)
22
+ end
23
+
24
+ ##########
25
+ # Client #
26
+ ##########
27
+
28
+ def initialize(source, credentials)
29
+ @source = source
30
+ @credentials = credentials
31
+ end
32
+
33
+ def fetch_commit(_repo, branch)
34
+ response = get(source.api_endpoint +
35
+ source.organization + "/" + source.project +
36
+ "/_apis/git/repositories/" + source.unscoped_repo +
37
+ "/stats/branches?name=" + branch)
38
+
39
+ JSON.parse(response.body).fetch("commit").fetch("commitId")
40
+ end
41
+
42
+ def fetch_default_branch(_repo)
43
+ response = get(source.api_endpoint +
44
+ source.organization + "/" + source.project +
45
+ "/_apis/git/repositories/" + source.unscoped_repo)
46
+
47
+ JSON.parse(response.body).fetch("defaultBranch").gsub("refs/heads/", "")
48
+ end
49
+
50
+ def fetch_repo_contents(commit = nil, path = nil)
51
+ tree = fetch_repo_contents_treeroot(commit, path)
52
+
53
+ response = get(source.api_endpoint +
54
+ source.organization + "/" + source.project +
55
+ "/_apis/git/repositories/" + source.unscoped_repo +
56
+ "/trees/" + tree + "?recursive=false")
57
+
58
+ JSON.parse(response.body).fetch("treeEntries")
59
+ end
60
+
61
+ def fetch_repo_contents_treeroot(commit = nil, path = nil)
62
+ actual_path = path
63
+ actual_path = "/" if path.to_s.empty?
64
+
65
+ tree_url = source.api_endpoint +
66
+ source.organization + "/" + source.project +
67
+ "/_apis/git/repositories/" + source.unscoped_repo +
68
+ "/items?path=" + actual_path
69
+
70
+ unless commit.to_s.empty?
71
+ tree_url += "&versionDescriptor.versionType=commit" \
72
+ "&versionDescriptor.version=" + commit
73
+ end
74
+
75
+ tree_response = get(tree_url)
76
+
77
+ JSON.parse(tree_response.body).fetch("objectId")
78
+ end
79
+
80
+ def fetch_file_contents(commit, path)
81
+ response = get(source.api_endpoint +
82
+ source.organization + "/" + source.project +
83
+ "/_apis/git/repositories/" + source.unscoped_repo +
84
+ "/items?path=" + path +
85
+ "&versionDescriptor.versionType=commit" \
86
+ "&versionDescriptor.version=" + commit)
87
+
88
+ response.body
89
+ end
90
+
91
+ def commits(branch_name = nil)
92
+ commits_url = source.api_endpoint +
93
+ source.organization + "/" + source.project +
94
+ "/_apis/git/repositories/" + source.unscoped_repo +
95
+ "/commits"
96
+
97
+ unless branch_name.to_s.empty?
98
+ commits_url += "?searchCriteria.itemVersion.version=" + branch_name
99
+ end
100
+
101
+ response = get(commits_url)
102
+
103
+ JSON.parse(response.body).fetch("value")
104
+ end
105
+
106
+ def branch(branch_name)
107
+ response = get(source.api_endpoint +
108
+ source.organization + "/" + source.project +
109
+ "/_apis/git/repositories/" + source.unscoped_repo +
110
+ "/refs?filter=heads/" + branch_name)
111
+
112
+ JSON.parse(response.body).fetch("value").first
113
+ end
114
+
115
+ def pull_requests(source_branch, target_branch)
116
+ response = get(source.api_endpoint +
117
+ source.organization + "/" + source.project +
118
+ "/_apis/git/repositories/" + source.unscoped_repo +
119
+ "/pullrequests?searchCriteria.status=all" \
120
+ "&searchCriteria.sourceRefName=refs/heads/" + source_branch +
121
+ "&searchCriteria.targetRefName=refs/heads/" + target_branch)
122
+
123
+ JSON.parse(response.body).fetch("value")
124
+ end
125
+
126
+ def create_commit(branch_name, base_commit, commit_message, files)
127
+ content = {
128
+ refUpdates: [
129
+ { name: "refs/heads/" + branch_name, oldObjectId: base_commit }
130
+ ],
131
+ commits: [
132
+ {
133
+ comment: commit_message,
134
+ changes: files.map do |file|
135
+ {
136
+ changeType: "edit",
137
+ item: { path: file.path },
138
+ newContent: {
139
+ content: Base64.encode64(file.content),
140
+ contentType: "base64encoded"
141
+ }
142
+ }
143
+ end
144
+ }
145
+ ]
146
+ }
147
+
148
+ post(source.api_endpoint + source.organization + "/" + source.project +
149
+ "/_apis/git/repositories/" + source.unscoped_repo +
150
+ "/pushes?api-version=5.0", content.to_json)
151
+ end
152
+
153
+ def create_pull_request(pr_name, source_branch, target_branch,
154
+ pr_description, labels)
155
+ # Azure DevOps only support descriptions up to 4000 characters (https://developercommunity.visualstudio.com/content/problem/608770/remove-4000-character-limit-on-pull-request-descri.html)
156
+ azure_max_length = 3999
157
+ if pr_description.length > azure_max_length
158
+ truncated_msg = "...\n\n_Description has been truncated_"
159
+ truncate_length = azure_max_length - truncated_msg.length
160
+ pr_description = pr_description[0..truncate_length] + truncated_msg
161
+ end
162
+
163
+ content = {
164
+ sourceRefName: "refs/heads/" + source_branch,
165
+ targetRefName: "refs/heads/" + target_branch,
166
+ title: pr_name,
167
+ description: pr_description,
168
+ labels: labels.map { |label| { name: label } }
169
+ }
170
+
171
+ post(source.api_endpoint +
172
+ source.organization + "/" + source.project +
173
+ "/_apis/git/repositories/" + source.unscoped_repo +
174
+ "/pullrequests?api-version=5.0", content.to_json)
175
+ end
176
+
177
+ def get(url)
178
+ response = Excon.get(
179
+ url,
180
+ user: credentials&.fetch("username"),
181
+ password: credentials&.fetch("password"),
182
+ idempotent: true,
183
+ **SharedHelpers.excon_defaults
184
+ )
185
+ raise NotFound if response.status == 404
186
+
187
+ response
188
+ end
189
+
190
+ def post(url, json)
191
+ response = Excon.post(
192
+ url,
193
+ headers: {
194
+ "Content-Type" => "application/json"
195
+ },
196
+ body: json,
197
+ user: credentials&.fetch("username"),
198
+ password: credentials&.fetch("password"),
199
+ idempotent: true,
200
+ **SharedHelpers.excon_defaults
201
+ )
202
+ raise NotFound if response.status == 404
203
+
204
+ response
205
+ end
206
+
207
+ private
208
+
209
+ attr_reader :credentials
210
+ attr_reader :source
211
+ end
212
+ end
213
+ end
@@ -3,6 +3,7 @@
3
3
  require "dependabot/dependency_file"
4
4
  require "dependabot/source"
5
5
  require "dependabot/errors"
6
+ require "dependabot/clients/azure"
6
7
  require "dependabot/clients/github_with_retries"
7
8
  require "dependabot/clients/bitbucket_with_retries"
8
9
  require "dependabot/clients/gitlab_with_retries"
@@ -17,6 +18,7 @@ module Dependabot
17
18
  CLIENT_NOT_FOUND_ERRORS = [
18
19
  Octokit::NotFound,
19
20
  Gitlab::Error::NotFound,
21
+ Dependabot::Clients::Azure::NotFound,
20
22
  Dependabot::Clients::Bitbucket::NotFound
21
23
  ].freeze
22
24
 
@@ -144,6 +146,8 @@ module Dependabot
144
146
  _github_repo_contents(repo, path, commit)
145
147
  when "gitlab"
146
148
  _gitlab_repo_contents(repo, path, commit)
149
+ when "azure"
150
+ _azure_repo_contents(path, commit)
147
151
  when "bitbucket"
148
152
  _bitbucket_repo_contents(repo, path, commit)
149
153
  else raise "Unsupported provider '#{provider}'."
@@ -214,6 +218,25 @@ module Dependabot
214
218
  end
215
219
  end
216
220
 
221
+ def _azure_repo_contents(path, commit)
222
+ response = azure_client.fetch_repo_contents(commit, path)
223
+
224
+ response.map do |entry|
225
+ type = case entry.fetch("gitObjectType")
226
+ when "blob" then "file"
227
+ when "tree" then "dir"
228
+ else entry.fetch("gitObjectType")
229
+ end
230
+
231
+ OpenStruct.new(
232
+ name: File.basename(entry.fetch("relativePath")),
233
+ path: entry.fetch("relativePath"),
234
+ type: type,
235
+ size: entry.fetch("size")
236
+ )
237
+ end
238
+ end
239
+
217
240
  def _bitbucket_repo_contents(repo, path, commit)
218
241
  response = bitbucket_client.fetch_repo_contents(
219
242
  repo,
@@ -289,6 +312,8 @@ module Dependabot
289
312
  when "gitlab"
290
313
  tmp = gitlab_client.get_file(repo, path, commit).content
291
314
  Base64.decode64(tmp).force_encoding("UTF-8").encode
315
+ when "azure"
316
+ azure_client.fetch_file_contents(commit, path)
292
317
  when "bitbucket"
293
318
  bitbucket_client.fetch_file_contents(repo, commit, path)
294
319
  else raise "Unsupported provider '#{source.provider}'."
@@ -362,6 +387,7 @@ module Dependabot
362
387
  case source.provider
363
388
  when "github" then github_client
364
389
  when "gitlab" then gitlab_client
390
+ when "azure" then azure_client
365
391
  when "bitbucket" then bitbucket_client
366
392
  else raise "Unsupported provider '#{source.provider}'."
367
393
  end
@@ -383,6 +409,12 @@ module Dependabot
383
409
  )
384
410
  end
385
411
 
412
+ def azure_client
413
+ @azure_client ||=
414
+ Dependabot::Clients::Azure.
415
+ for_source(source: source, credentials: credentials)
416
+ end
417
+
386
418
  def bitbucket_client
387
419
  # TODO: When self-hosted Bitbucket is supported this should use
388
420
  # `Bitbucket.for_source`
@@ -4,6 +4,7 @@ require "dependabot/metadata_finders"
4
4
 
5
5
  module Dependabot
6
6
  class PullRequestCreator
7
+ require "dependabot/pull_request_creator/azure"
7
8
  require "dependabot/pull_request_creator/github"
8
9
  require "dependabot/pull_request_creator/gitlab"
9
10
  require "dependabot/pull_request_creator/message_builder"
@@ -68,6 +69,7 @@ module Dependabot
68
69
  case source.provider
69
70
  when "github" then github_creator.create
70
71
  when "gitlab" then gitlab_creator.create
72
+ when "azure" then azure_creator.create
71
73
  else raise "Unsupported provider #{source.provider}"
72
74
  end
73
75
  end
@@ -120,6 +122,20 @@ module Dependabot
120
122
  )
121
123
  end
122
124
 
125
+ def azure_creator
126
+ Azure.new(
127
+ source: source,
128
+ branch_name: branch_namer.new_branch_name,
129
+ base_commit: base_commit,
130
+ credentials: credentials,
131
+ files: files,
132
+ commit_message: message_builder.commit_message,
133
+ pr_description: message_builder.pr_message,
134
+ pr_name: message_builder.pr_name,
135
+ labeler: labeler
136
+ )
137
+ end
138
+
123
139
  def message_builder
124
140
  @message_builder ||
125
141
  MessageBuilder.new(
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/clients/azure"
4
+ require "dependabot/pull_request_creator"
5
+
6
+ module Dependabot
7
+ class PullRequestCreator
8
+ class Azure
9
+ attr_reader :source, :branch_name, :base_commit, :credentials,
10
+ :files, :commit_message, :pr_description, :pr_name,
11
+ :labeler
12
+
13
+ def initialize(source:, branch_name:, base_commit:, credentials:,
14
+ files:, commit_message:, pr_description:, pr_name:,
15
+ labeler:)
16
+ @source = source
17
+ @branch_name = branch_name
18
+ @base_commit = base_commit
19
+ @credentials = credentials
20
+ @files = files
21
+ @commit_message = commit_message
22
+ @pr_description = pr_description
23
+ @pr_name = pr_name
24
+ @labeler = labeler
25
+ end
26
+
27
+ def create
28
+ return if branch_exists? && pull_request_exists?
29
+
30
+ create_commit unless branch_exists? && commit_exists?
31
+
32
+ create_pull_request
33
+ end
34
+
35
+ private
36
+
37
+ def azure_client_for_source
38
+ @azure_client_for_source ||=
39
+ Dependabot::Clients::Azure.for_source(
40
+ source: source,
41
+ credentials: credentials
42
+ )
43
+ end
44
+
45
+ def branch_exists?
46
+ @branch_ref ||=
47
+ azure_client_for_source.branch(branch_name)
48
+
49
+ @branch_ref
50
+ rescue ::Azure::Error::NotFound
51
+ false
52
+ end
53
+
54
+ def commit_exists?
55
+ @commits ||=
56
+ azure_client_for_source.commits(branch_name)
57
+ commit_message.start_with?(@commits.first.fetch("comment"))
58
+ end
59
+
60
+ def pull_request_exists?
61
+ azure_client_for_source.pull_requests(
62
+ branch_name,
63
+ source.branch || default_branch
64
+ ).any?
65
+ end
66
+
67
+ def create_commit
68
+ azure_client_for_source.create_commit(
69
+ branch_name,
70
+ base_commit,
71
+ commit_message,
72
+ files
73
+ )
74
+ end
75
+
76
+ def create_pull_request
77
+ azure_client_for_source.create_pull_request(
78
+ pr_name,
79
+ branch_name,
80
+ source.branch || default_branch,
81
+ pr_description,
82
+ labeler.labels_for_pr
83
+ )
84
+ end
85
+
86
+ def default_branch
87
+ @default_branch ||=
88
+ azure_client_for_source.fetch_default_branch(source.repo)
89
+ end
90
+ end
91
+ end
92
+ end
@@ -221,6 +221,7 @@ module Dependabot
221
221
  case source.provider
222
222
  when "github" then fetch_github_labels
223
223
  when "gitlab" then fetch_gitlab_labels
224
+ when "azure" then fetch_azure_labels
224
225
  else raise "Unsupported provider #{source.provider}"
225
226
  end
226
227
  end
@@ -251,10 +252,19 @@ module Dependabot
251
252
  map(&:name)
252
253
  end
253
254
 
255
+ def fetch_azure_labels
256
+ langauge_name =
257
+ self.class.label_details_for_package_manager(package_manager).
258
+ fetch(:name)
259
+
260
+ @labels = [*@labels, "dependencies", "security", langauge_name].uniq
261
+ end
262
+
254
263
  def create_dependencies_label
255
264
  case source.provider
256
265
  when "github" then create_github_dependencies_label
257
266
  when "gitlab" then create_gitlab_dependencies_label
267
+ when "azure" then @labels # Azure does not have centralised labels
258
268
  else raise "Unsupported provider #{source.provider}"
259
269
  end
260
270
  end
@@ -263,6 +273,7 @@ module Dependabot
263
273
  case source.provider
264
274
  when "github" then create_github_security_label
265
275
  when "gitlab" then create_gitlab_security_label
276
+ when "azure" then @labels # Azure does not have centralised labels
266
277
  else raise "Unsupported provider #{source.provider}"
267
278
  end
268
279
  end
@@ -271,6 +282,7 @@ module Dependabot
271
282
  case source.provider
272
283
  when "github" then create_github_language_label
273
284
  when "gitlab" then create_gitlab_language_label
285
+ when "azure" then @labels # Azure does not have centralised labels
274
286
  else raise "Unsupported provider #{source.provider}"
275
287
  end
276
288
  end
@@ -417,9 +417,14 @@ module Dependabot
417
417
  end
418
418
 
419
419
  def build_details_tag(summary:, body:)
420
- msg = "\n<details>\n<summary>#{summary}</summary>\n\n"
421
- msg += body
422
- msg + "</details>"
420
+ # Azure DevOps does not support <details> tag (https://developercommunity.visualstudio.com/content/problem/608769/add-support-for-in-markdown.html)
421
+ if source.provider == "azure"
422
+ "\n\##{summary}\n\n#{body}"
423
+ else
424
+ msg = "\n<details>\n<summary>#{summary}</summary>\n\n"
425
+ msg += body
426
+ msg + "</details>"
427
+ end
423
428
  end
424
429
 
425
430
  def serialized_vulnerability_details(details)
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "dependabot/clients/azure"
3
4
  require "dependabot/clients/github_with_retries"
4
5
  require "dependabot/clients/gitlab_with_retries"
5
6
  require "dependabot/pull_request_creator"
@@ -267,10 +268,15 @@ module Dependabot
267
268
  case source.provider
268
269
  when "github" then recent_github_commit_messages
269
270
  when "gitlab" then recent_gitlab_commit_messages
271
+ when "azure" then recent_azure_commit_messages
270
272
  else raise "Unsupported provider: #{source.provider}"
271
273
  end
272
274
  end
273
275
 
276
+ def dependabot_email
277
+ "support@dependabot.com"
278
+ end
279
+
274
280
  def recent_github_commit_messages
275
281
  recent_github_commits.
276
282
  reject { |c| c.author&.type == "Bot" }.
@@ -286,18 +292,31 @@ module Dependabot
286
292
  gitlab_client_for_source.commits(source.repo)
287
293
 
288
294
  @recent_gitlab_commit_messages.
289
- reject { |c| c.author_email == "support@dependabot.com" }.
295
+ reject { |c| c.author_email == dependabot_email }.
290
296
  reject { |c| c.message&.start_with?("merge !") }.
291
297
  map(&:message).
292
298
  compact.
293
299
  map(&:strip)
294
300
  end
295
301
 
302
+ def recent_azure_commit_messages
303
+ @recent_azure_commit_messages ||=
304
+ azure_client_for_source.commits
305
+
306
+ @recent_azure_commit_messages.
307
+ reject { |c| c.fetch("author").fetch("email") == dependabot_email }.
308
+ reject { |c| c.fetch("comment")&.start_with?("Merge") }.
309
+ map { |c| c.fetch("comment") }.
310
+ compact.
311
+ map(&:strip)
312
+ end
313
+
296
314
  def last_dependabot_commit_message
297
315
  @last_dependabot_commit_message ||=
298
316
  case source.provider
299
317
  when "github" then last_github_dependabot_commit_message
300
318
  when "gitlab" then last_gitlab_dependabot_commit_message
319
+ when "azure" then last_azure_dependabot_commit_message
301
320
  else raise "Unsupported provider: #{source.provider}"
302
321
  end
303
322
  end
@@ -323,7 +342,17 @@ module Dependabot
323
342
  gitlab_client_for_source.commits(source.repo)
324
343
 
325
344
  @recent_gitlab_commit_messages.
326
- find { |c| c.author_email == "support@dependabot.com" }&.
345
+ find { |c| c.author_email == dependabot_email }&.
346
+ message&.
347
+ strip
348
+ end
349
+
350
+ def last_azure_dependabot_commit_message
351
+ @recent_azure_commit_messages ||=
352
+ azure_client_for_source.commits
353
+
354
+ @recent_azure_commit_messages.
355
+ find { |c| c.fetch("author").fetch("email") == dependabot_email }&.
327
356
  message&.
328
357
  strip
329
358
  end
@@ -344,6 +373,14 @@ module Dependabot
344
373
  )
345
374
  end
346
375
 
376
+ def azure_client_for_source
377
+ @azure_client_for_source ||=
378
+ Dependabot::Clients::Azure.for_source(
379
+ source: source,
380
+ credentials: credentials
381
+ )
382
+ end
383
+
347
384
  def package_manager
348
385
  @package_manager ||= dependencies.first.package_manager
349
386
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dependabot
4
- VERSION = "0.109.1"
4
+ VERSION = "0.110.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-common
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.109.1
4
+ version: 0.110.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-26 00:00:00.000000000 Z
11
+ date: 2019-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-ecr
@@ -311,6 +311,7 @@ extra_rdoc_files: []
311
311
  files:
312
312
  - bin/git-credential-store-immutable
313
313
  - lib/dependabot.rb
314
+ - lib/dependabot/clients/azure.rb
314
315
  - lib/dependabot/clients/bitbucket.rb
315
316
  - lib/dependabot/clients/bitbucket_with_retries.rb
316
317
  - lib/dependabot/clients/github_with_retries.rb
@@ -338,6 +339,7 @@ files:
338
339
  - lib/dependabot/metadata_finders/base/commits_finder.rb
339
340
  - lib/dependabot/metadata_finders/base/release_finder.rb
340
341
  - lib/dependabot/pull_request_creator.rb
342
+ - lib/dependabot/pull_request_creator/azure.rb
341
343
  - lib/dependabot/pull_request_creator/branch_namer.rb
342
344
  - lib/dependabot/pull_request_creator/commit_signer.rb
343
345
  - lib/dependabot/pull_request_creator/github.rb