dependabot-go_modules 0.362.0 → 0.364.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: 6e4a9975da382d57158ea52ad6a4d47299daa3f2e99f7133f68923563ca14c42
4
- data.tar.gz: 9b1f7a4d3bc51dfbf2b4a8c3f42d90ea833ffe787ba336d3014da205507f19cf
3
+ metadata.gz: '0549b6632e12248926b3a3b507c6ef80e389fba47bde1cf5886f0b0493a0faad'
4
+ data.tar.gz: 3928257803da819ec010c35031c94ad78eeacf6effc8f495b257d9b4d95b91cd
5
5
  SHA512:
6
- metadata.gz: fd095f5658455e70a0651ced2d747f0332e545ceb007e5158058e713bf89ceea86fb2a396fdc73dd058ec0534aa087dfe0052a3f34bc9ac3ba87ad8f8d257b23
7
- data.tar.gz: fe1970687918ba78de5b069a52e2273fdbf96dbfa91049b3be3e5e216ddd8ee51993421df887137fed648a64d4d6c554a930882dc78f1af9fbafa7beda501674
6
+ metadata.gz: 9ae6dd6cca4e9a0dc9e7598c44978f89fcee9fae236b852957718ef0282b7d24f0578f5ef959b37790b16168ea223c78d7fb18a42a4a50fd29cd7b6aff3ef13a
7
+ data.tar.gz: f9e66da88dd36e4f2da7412f276edbbe296a7745034e4c6ded7d0ec5b46b6071618c21f19b7fe57a1f24087a8dd92b007b5a9b0bc3d4012255c1d6271b633ea7
data/helpers/go.mod CHANGED
@@ -1,8 +1,8 @@
1
1
  module github.com/dependabot/dependabot-core/go_modules/helpers
2
2
 
3
- go 1.23.0
3
+ go 1.24.0
4
4
 
5
5
  require (
6
6
  github.com/Masterminds/vcs v1.13.3
7
- golang.org/x/mod v0.27.0
7
+ golang.org/x/mod v0.33.0
8
8
  )
data/helpers/go.sum CHANGED
@@ -1,4 +1,4 @@
1
1
  github.com/Masterminds/vcs v1.13.3 h1:IIA2aBdXvfbIM+yl/eTnL4hb1XwdpvuQLglAix1gweE=
2
2
  github.com/Masterminds/vcs v1.13.3/go.mod h1:TiE7xuEjl1N4j016moRd6vezp6e6Lz23gypeXfzXeW8=
3
- golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
4
- golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
3
+ golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
4
+ golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
@@ -1,6 +1,7 @@
1
1
  package importresolver
2
2
 
3
3
  import (
4
+ "net/url"
4
5
  "os"
5
6
  "strings"
6
7
 
@@ -20,6 +21,8 @@ func VCSRemoteForImport(args *Args) (interface{}, error) {
20
21
  remote = "https://" + remote
21
22
  }
22
23
 
24
+ remote = normalizeAzureDevOpsURL(remote)
25
+
23
26
  local, err := os.MkdirTemp("", "unused-vcs-local-dir")
24
27
  if err != nil {
25
28
  return nil, err
@@ -34,3 +37,60 @@ func VCSRemoteForImport(args *Args) (interface{}, error) {
34
37
  }()
35
38
  return repo.Remote(), nil
36
39
  }
40
+
41
+ func normalizeAzureDevOpsURL(remote string) string {
42
+ uri, err := url.Parse(remote)
43
+ if err != nil {
44
+ return remote
45
+ }
46
+
47
+ host := strings.ToLower(uri.Host)
48
+ if host != "dev.azure.com" {
49
+ return remote
50
+ }
51
+
52
+ segments := strings.Split(strings.TrimPrefix(uri.Path, "/"), "/")
53
+ if len(segments) < 3 {
54
+ return remote
55
+ }
56
+
57
+ normalizedSegments := segments
58
+ removeGitSuffix := false
59
+ if !strings.Contains(uri.Path, "/_git/") {
60
+ if len(segments) < 3 {
61
+ return remote
62
+ }
63
+
64
+ // Azure DevOps paths are /{org}/{project}/_git/{repo}[/{subdir}...].
65
+ // Insert "_git" after the first two segments and preserve the rest.
66
+ normalizedSegments = make([]string, 0, len(segments)+1)
67
+ normalizedSegments = append(normalizedSegments, segments[:2]...)
68
+ normalizedSegments = append(normalizedSegments, "_git")
69
+ normalizedSegments = append(normalizedSegments, segments[2:]...)
70
+ removeGitSuffix = true
71
+ }
72
+
73
+ if !removeGitSuffix {
74
+ uri.Path = "/" + strings.Join(normalizedSegments, "/")
75
+
76
+ return uri.String()
77
+ }
78
+
79
+ for i := range normalizedSegments {
80
+ if normalizedSegments[i] != "_git" {
81
+ continue
82
+ }
83
+
84
+ repoIndex := i + 1
85
+ if repoIndex >= len(normalizedSegments) {
86
+ return remote
87
+ }
88
+
89
+ normalizedSegments[repoIndex] = strings.TrimSuffix(normalizedSegments[repoIndex], ".git")
90
+ break
91
+ }
92
+
93
+ uri.Path = "/" + strings.Join(normalizedSegments, "/")
94
+
95
+ return uri.String()
96
+ }
@@ -13,3 +13,51 @@ func TestVCSRemoteForImport(t *testing.T) {
13
13
  t.Fatalf("failed to get VCS remote for import %s: %v", args.Import, err)
14
14
  }
15
15
  }
16
+
17
+ func TestNormalizeAzureDevOpsURL(t *testing.T) {
18
+ tests := []struct {
19
+ name string
20
+ input string
21
+ want string
22
+ }{
23
+ {
24
+ name: "adds _git segment when missing and removes .git suffix",
25
+ input: "https://dev.azure.com/VaronisIO/da-cloud/be-protobuf.git",
26
+ want: "https://dev.azure.com/VaronisIO/da-cloud/_git/be-protobuf",
27
+ },
28
+ {
29
+ name: "preserves existing _git segment",
30
+ input: "https://dev.azure.com/VaronisIO/da-cloud/_git/be-protobuf",
31
+ want: "https://dev.azure.com/VaronisIO/da-cloud/_git/be-protobuf",
32
+ },
33
+ {
34
+ name: "retains .git suffix when _git already exists",
35
+ input: "https://dev.azure.com/VaronisIO/da-cloud/_git/be-protobuf.git",
36
+ want: "https://dev.azure.com/VaronisIO/da-cloud/_git/be-protobuf.git",
37
+ },
38
+ {
39
+ name: "retains .git suffix and subdirectory when _git already exists",
40
+ input: "https://dev.azure.com/VaronisIO/da-cloud/_git/be-protobuf.git/submodule",
41
+ want: "https://dev.azure.com/VaronisIO/da-cloud/_git/be-protobuf.git/submodule",
42
+ },
43
+ {
44
+ name: "preserves subdirectory while removing .git suffix",
45
+ input: "https://dev.azure.com/VaronisIO/da-cloud/be-protobuf.git/submodule",
46
+ want: "https://dev.azure.com/VaronisIO/da-cloud/_git/be-protobuf/submodule",
47
+ },
48
+ {
49
+ name: "ignores non azure hosts",
50
+ input: "https://github.com/dependabot/dependabot-core",
51
+ want: "https://github.com/dependabot/dependabot-core",
52
+ },
53
+ }
54
+
55
+ for _, test := range tests {
56
+ t.Run(test.name, func(t *testing.T) {
57
+ got := normalizeAzureDevOpsURL(test.input)
58
+ if got != test.want {
59
+ t.Fatalf("normalizeAzureDevOpsURL(%q) = %q, want %q", test.input, got, test.want)
60
+ }
61
+ })
62
+ }
63
+ }
@@ -0,0 +1,27 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ module Dependabot
7
+ module GoModules
8
+ module AzureDevopsPathNormalizer
9
+ extend T::Sig
10
+
11
+ sig { params(name: String).returns(String) }
12
+ def self.normalize(name)
13
+ return name unless name.start_with?("dev.azure.com/")
14
+
15
+ segments = name.split("/")
16
+ return name if segments.length < 4
17
+ return name if segments[3] == "_git"
18
+
19
+ normalized_segments = segments.dup
20
+ normalized_segments.insert(3, "_git")
21
+ normalized_segments[4] = normalized_segments.fetch(4).delete_suffix(".git")
22
+
23
+ normalized_segments.join("/")
24
+ end
25
+ end
26
+ end
27
+ end
@@ -57,6 +57,8 @@ module Dependabot
57
57
  /go(?: get)?: .*: unknown revision/m,
58
58
  # Package pointing to a proxy that 404s
59
59
  /go(?: get)?: .*: unrecognized import path/m,
60
+ # Private repository cannot be fetched over a secure protocol
61
+ Dependabot::GoModules::ResolvabilityErrors::INSECURE_PROTOCOL_REPOSITORY_REGEX,
60
62
  # Package not being referenced correctly
61
63
  /go:.*imports.*package.+is not in std/m,
62
64
  # Invalid version due to missing go.mod files at specified revision
@@ -94,12 +96,22 @@ module Dependabot
94
96
  T::Array[Regexp]
95
97
  )
96
98
 
99
+ PATH_DEPENDENCY_ERROR_REGEXES = T.let(
100
+ [
101
+ /replaced by (?<path>[^)\s]+)\): reading .*go\.mod: open .*: no such file or directory/
102
+ ].freeze,
103
+ T::Array[Regexp]
104
+ )
105
+
97
106
  GO_LANG = "Go"
98
107
 
99
108
  AMBIGUOUS_ERROR_MESSAGE = /ambiguous import: found package (?<package>.*) in multiple modules/
100
109
 
101
110
  GO_VERSION_MISMATCH = /requires go (?<current_ver>.*) .*running go (?<req_ver>.*);/
102
111
 
112
+ GITHUB_403_REGEX =
113
+ %r{https://github\.com/(?<repo>[^/'\s]+/[^/'\s]+)/?': The requested URL returned error: 403}
114
+
103
115
  GO_MOD_VERSION = /^go 1\.\d+(\.\d+)?$/
104
116
 
105
117
  sig do
@@ -326,11 +338,8 @@ module Dependabot
326
338
  def handle_subprocess_error(stderr) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
327
339
  stderr = stderr.gsub(Dir.getwd, "")
328
340
 
329
- go_mod_parse_error_regex = GO_MOD_PARSE_ERROR_REGEXES.find { |r| stderr =~ r }
330
- if go_mod_parse_error_regex
331
- error_message = filter_error_message(message: stderr, regex: go_mod_parse_error_regex)
332
- raise Dependabot::DependencyFileNotParseable.new(go_mod_path, error_message)
333
- end
341
+ raise_for_go_mod_parse_error(stderr)
342
+ raise_for_path_dependency_error(stderr)
334
343
 
335
344
  # Package version doesn't match the module major version
336
345
  error_regex = RESOLVABILITY_ERROR_REGEXES.find { |r| stderr =~ r }
@@ -343,8 +352,12 @@ module Dependabot
343
352
  raise Dependabot::PrivateSourceAuthenticationFailure, matches[:url]
344
353
  end
345
354
 
355
+ if github_credentials_configured? && (matches = stderr.match(GITHUB_403_REGEX))
356
+ raise Dependabot::PrivateSourceAuthenticationFailure, "https://github.com/#{matches[:repo]}"
357
+ end
358
+
346
359
  repo_error_regex = REPO_RESOLVABILITY_ERROR_REGEXES.find { |r| stderr =~ r }
347
- ResolvabilityErrors.handle(stderr) if repo_error_regex
360
+ Dependabot::GoModules::ResolvabilityErrors.handle(stderr) if repo_error_regex
348
361
 
349
362
  path_regex = MODULE_PATH_MISMATCH_REGEXES.find { |r| stderr =~ r }
350
363
  if path_regex
@@ -387,6 +400,44 @@ module Dependabot
387
400
  message.match(regex).to_s
388
401
  end
389
402
 
403
+ sig { params(message: String).returns(T.nilable(String)) }
404
+ def extract_replacement_path(message)
405
+ PATH_DEPENDENCY_ERROR_REGEXES.each do |regex|
406
+ match = regex.match(message)
407
+ return match[:path] if match
408
+ end
409
+
410
+ nil
411
+ end
412
+
413
+ sig { returns(T::Boolean) }
414
+ def github_credentials_configured?
415
+ credentials.any? do |credential|
416
+ credential["type"] == "git_source" && credential["host"] == "github.com"
417
+ end
418
+ end
419
+
420
+ sig { params(stderr: String).void }
421
+ def raise_for_go_mod_parse_error(stderr)
422
+ go_mod_parse_error_regex = GO_MOD_PARSE_ERROR_REGEXES.find { |r| stderr =~ r }
423
+ return unless go_mod_parse_error_regex
424
+
425
+ error_message = filter_error_message(message: stderr, regex: go_mod_parse_error_regex)
426
+ raise Dependabot::DependencyFileNotParseable.new(go_mod_path, error_message)
427
+ end
428
+
429
+ sig { params(stderr: String).void }
430
+ def raise_for_path_dependency_error(stderr)
431
+ path_error_regex = PATH_DEPENDENCY_ERROR_REGEXES.find { |r| stderr =~ r }
432
+ return unless path_error_regex
433
+
434
+ dependency_path = extract_replacement_path(stderr)
435
+ raise Dependabot::PathDependenciesNotReachable, [dependency_path] if dependency_path
436
+
437
+ error_message = filter_error_message(message: stderr, regex: path_error_regex)
438
+ raise Dependabot::DependencyFileNotResolvable, error_message
439
+ end
440
+
390
441
  sig { returns(String) }
391
442
  def go_mod_path
392
443
  return "go.mod" if directory == "/"
@@ -10,6 +10,7 @@ require "dependabot/shared_helpers"
10
10
  require "dependabot/errors"
11
11
  require "dependabot/go_modules/requirement"
12
12
  require "dependabot/go_modules/resolvability_errors"
13
+ require "dependabot/go_modules/azure_devops_path_normalizer"
13
14
 
14
15
  module Dependabot
15
16
  module GoModules
@@ -77,8 +78,9 @@ module Dependabot
77
78
  end
78
79
 
79
80
  # Turn off the module proxy for private dependencies
81
+ dependency_name = AzureDevopsPathNormalizer.normalize(dependency.name)
80
82
  versions_json = SharedHelpers.run_shell_command(
81
- "go list -m -versions -json #{dependency.name}",
83
+ "go list -m -versions -json #{dependency_name}",
82
84
  fingerprint: "go list -m -versions -json <dependency_name>"
83
85
  )
84
86
  version_strings = JSON.parse(versions_json)["Versions"]
@@ -156,7 +156,7 @@ module Dependabot
156
156
  def convert_caret_req(req_string)
157
157
  version = req_string.gsub(/^\^?v?/, "")
158
158
  parts = version.split(".")
159
- upper_bound = [parts.first.to_i + 1, 0, 0, "a"].map(&:to_s).join(".")
159
+ upper_bound = [parts.first.to_i + 1, 0, 0, "a"].join(".")
160
160
 
161
161
  [">= #{version}", "< #{upper_bound}"]
162
162
  end
@@ -8,27 +8,41 @@ module Dependabot
8
8
  module ResolvabilityErrors
9
9
  extend T::Sig
10
10
 
11
- GITHUB_REPO_REGEX = %r{github.com/[^:@ ]*}
11
+ GITHUB_REPO_REGEX = T.let(%r{github.com/[^:@ '\n]*}, Regexp)
12
+ INSECURE_PROTOCOL_REPOSITORY_REGEX = T.let(
13
+ /go(?: get)?: .*: no secure protocol found for repository/m,
14
+ Regexp
15
+ )
16
+ GO_MODULE_WITH_VERSION_REGEX = T.let(/go(?: get)?:\s*(?<module>[^\s@]+)@/, Regexp)
17
+ GO_PREFIXED_HOSTED_REPO_REGEX = T.let(
18
+ %r{(?:^|\n)\s*go(?: get)?:\s*(?<repo>[a-z0-9.-]+\.[a-z]{2,}/[^:@\s]+)(?:[:\s]|$)}i,
19
+ Regexp
20
+ )
21
+ REACHABILITY_CHECK_HINTS = T.let(
22
+ [
23
+ /If this is a private repository/i,
24
+ /Write access to repository not granted/i,
25
+ /Authentication failed/i
26
+ ].freeze,
27
+ T::Array[Regexp]
28
+ )
12
29
 
13
30
  sig { params(message: String).void }
14
31
  def self.handle(message)
15
- mod_path = message.scan(GITHUB_REPO_REGEX).last
16
- unless mod_path && message.include?("If this is a private repository")
17
- raise Dependabot::DependencyFileNotResolvable, message
32
+ mod_path = extract_module_path(message)
33
+
34
+ if mod_path && insecure_protocol_repo_error?(message)
35
+ raise Dependabot::GitDependenciesNotReachable, [repo_path_for(mod_path)]
18
36
  end
19
37
 
20
- # Module not found on github.com - query for _any_ version to know if it
21
- # doesn't exist (or is private) or we were just given a bad revision by this manifest
38
+ raise Dependabot::DependencyFileNotResolvable, message unless mod_path && requires_reachability_check?(message)
39
+
40
+ # Module not found in the module repository (e.g., GitHub, Gerrit) - query for _any_ version
41
+ # to know if it doesn't exist (or is private) or we were just given a bad revision by this manifest
22
42
  SharedHelpers.in_a_temporary_directory do
23
43
  File.write("go.mod", "module dummy\n")
24
44
 
25
- mod_path = T.cast(mod_path, String)
26
- mod_split = mod_path.split("/")
27
- repo_path = if mod_split.size > 3
28
- T.must(mod_split[0..2]).join("/")
29
- else
30
- mod_path
31
- end
45
+ repo_path = repo_path_for(mod_path)
32
46
 
33
47
  _, _, status = Open3.capture3(SharedHelpers.escape_command("go list -m -versions #{repo_path}"))
34
48
  raise Dependabot::DependencyFileNotResolvable, message if status.success?
@@ -36,6 +50,46 @@ module Dependabot
36
50
  raise Dependabot::GitDependenciesNotReachable, [repo_path]
37
51
  end
38
52
  end
53
+
54
+ sig { params(message: String).returns(T.nilable(String)) }
55
+ def self.extract_module_path(message)
56
+ github_repo_paths = T.let(
57
+ message.scan(GITHUB_REPO_REGEX).filter_map do |match|
58
+ next match if match.is_a?(String)
59
+
60
+ match.first
61
+ end,
62
+ T::Array[String]
63
+ )
64
+ return github_repo_paths.last&.delete_suffix("/") if github_repo_paths.any?
65
+
66
+ module_with_version_match = message.match(GO_MODULE_WITH_VERSION_REGEX)
67
+ return module_with_version_match[:module] if module_with_version_match
68
+
69
+ hosted_repo_match = message.match(GO_PREFIXED_HOSTED_REPO_REGEX)
70
+ return hosted_repo_match[:repo] if hosted_repo_match
71
+
72
+ nil
73
+ end
74
+
75
+ sig { params(message: String).returns(T::Boolean) }
76
+ def self.requires_reachability_check?(message)
77
+ REACHABILITY_CHECK_HINTS.any? { |regex| message.match?(regex) }
78
+ end
79
+
80
+ sig { params(message: String).returns(T::Boolean) }
81
+ def self.insecure_protocol_repo_error?(message)
82
+ message.match?(INSECURE_PROTOCOL_REPOSITORY_REGEX)
83
+ end
84
+
85
+ sig { params(mod_path: String).returns(String) }
86
+ def self.repo_path_for(mod_path)
87
+ normalized_mod_path = mod_path.delete_suffix("/")
88
+ mod_split = normalized_mod_path.split("/")
89
+ return normalized_mod_path unless mod_split.first == "github.com" && mod_split.size > 3
90
+
91
+ T.must(mod_split[0..2]).join("/")
92
+ end
39
93
  end
40
94
  end
41
95
  end
@@ -11,6 +11,7 @@ require "dependabot/errors"
11
11
  require "dependabot/go_modules/requirement"
12
12
  require "dependabot/go_modules/resolvability_errors"
13
13
  require "dependabot/go_modules/package/package_details_fetcher"
14
+ require "dependabot/go_modules/azure_devops_path_normalizer"
14
15
  require "dependabot/package/package_latest_version_finder"
15
16
 
16
17
  module Dependabot
@@ -198,8 +199,9 @@ module Dependabot
198
199
  sig { params(release: Dependabot::Package::PackageRelease).returns(T::Boolean) }
199
200
  def in_cooldown_period?(release)
200
201
  begin
202
+ dependency_name = AzureDevopsPathNormalizer.normalize(dependency.name)
201
203
  release_info = SharedHelpers.run_shell_command(
202
- "go list -m -json #{dependency.name}@#{release.details.[]('version_string')}",
204
+ "go list -m -json #{dependency_name}@#{release.details.[]('version_string')}",
203
205
  fingerprint: "go list -m -json <dependency_name>"
204
206
  )
205
207
  rescue Dependabot::SharedHelpers::HelperSubprocessFailed => e
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-go_modules
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.362.0
4
+ version: 0.364.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.362.0
18
+ version: 0.364.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.362.0
25
+ version: 0.364.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -85,14 +85,14 @@ dependencies:
85
85
  requirements:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
- version: '1.3'
88
+ version: '2.0'
89
89
  type: :development
90
90
  prerelease: false
91
91
  version_requirements: !ruby/object:Gem::Requirement
92
92
  requirements:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
- version: '1.3'
95
+ version: '2.0'
96
96
  - !ruby/object:Gem::Dependency
97
97
  name: rspec-sorbet
98
98
  requirement: !ruby/object:Gem::Requirement
@@ -250,6 +250,7 @@ files:
250
250
  - helpers/main.go
251
251
  - helpers/version_test.go
252
252
  - lib/dependabot/go_modules.rb
253
+ - lib/dependabot/go_modules/azure_devops_path_normalizer.rb
253
254
  - lib/dependabot/go_modules/dependency_grapher.rb
254
255
  - lib/dependabot/go_modules/file_fetcher.rb
255
256
  - lib/dependabot/go_modules/file_parser.rb
@@ -273,7 +274,7 @@ licenses:
273
274
  - MIT
274
275
  metadata:
275
276
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
276
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.362.0
277
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.364.0
277
278
  rdoc_options: []
278
279
  require_paths:
279
280
  - lib