dependabot-common 0.109.1 → 0.110.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: 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