dependabot-docker 0.212.0 → 0.214.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: 8a77135a48c7aab9a2b1bb6069cdae9de09c7ee2d9d2b5eb8cc30e4fc4cfbb1a
4
- data.tar.gz: 0b92a7eed035d963f08b99026a671f7201d2074de87692ada394eedbc373717f
3
+ metadata.gz: b7aabb905dbb5a64638f936079c477b091dd8b2a05aad7cc070c10e11977cc97
4
+ data.tar.gz: 4cc6bbe0291ded5c2e975f2256561044700a6a5903f1d110815de0ff5a0da281
5
5
  SHA512:
6
- metadata.gz: b70a2a9fc4b2e1b0605ba08197407c1bb93af05bd9cb3a105805545cb8809659da347c0048c2442f3a8a24b10e926aa785704eaf3cd947997e5fc888864b0f87
7
- data.tar.gz: 02a76d37235c6bb35d157ec86d63b974d2a1f678149bf781d1d745d8424cd762bb8b3d72d9b6076fea4bb79cb729a11f30d161273672924346b5da6cde1e5297
6
+ metadata.gz: d2a1629644bf4dcc1a523aaef694b2601f0008361468dbde35008396bb9dfe6c225857946bdad4f6dad384f11b7c763357a50a8f27ab02fb8cde23f9ce6f7271
7
+ data.tar.gz: b5a6df5e91b49e9faa2d972ee74b4aba244f83051e076cf33dda73dc9f55deee7895f8ffc4e5e13ef4d309ae33e1d9a9ef0e710bf6b12dd35545d524ec7dca7b
@@ -1,13 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "dependabot/docker/utils/helpers"
4
+ require "dependabot/experiments"
3
5
  require "dependabot/file_fetchers"
4
6
  require "dependabot/file_fetchers/base"
5
7
 
6
8
  module Dependabot
7
9
  module Docker
8
10
  class FileFetcher < Dependabot::FileFetchers::Base
9
- YAML_REGEXP = /^[^\.]+\.ya?ml$/i.freeze
10
- DOCKER_REGEXP = /dockerfile/i.freeze
11
+ YAML_REGEXP = /^[^\.]+\.ya?ml$/i
12
+ DOCKER_REGEXP = /dockerfile/i
11
13
 
12
14
  def self.required_files_in?(filenames)
13
15
  filenames.any? { |f| f.match?(DOCKER_REGEXP) } or
@@ -20,26 +22,18 @@ module Dependabot
20
22
 
21
23
  private
22
24
 
23
- def kubernetes_enabled?
24
- options.key?(:kubernetes_updates) && options[:kubernetes_updates]
25
- end
26
-
27
25
  def fetch_files
28
26
  fetched_files = []
29
27
  fetched_files += correctly_encoded_dockerfiles
30
- fetched_files += correctly_encoded_yamlfiles if kubernetes_enabled?
28
+ fetched_files += correctly_encoded_yamlfiles
31
29
 
32
30
  return fetched_files if fetched_files.any?
33
31
 
34
- if !kubernetes_enabled? && incorrectly_encoded_dockerfiles.none?
32
+ if incorrectly_encoded_dockerfiles.none? && incorrectly_encoded_yamlfiles.none?
35
33
  raise(
36
34
  Dependabot::DependencyFileNotFound,
37
- File.join(directory, "Dockerfile")
38
- )
39
- elsif incorrectly_encoded_dockerfiles.none? && incorrectly_encoded_yamlfiles.none?
40
- raise(
41
- Dependabot::DependabotError,
42
- "Found neither Kubernetes YAML nor Dockerfiles in #{directory}"
35
+ File.join(directory, "Dockerfile"),
36
+ "No Dockerfiles nor Kubernetes YAML found in #{directory}"
43
37
  )
44
38
  elsif incorrectly_encoded_dockerfiles.none?
45
39
  raise(
@@ -84,10 +78,14 @@ module Dependabot
84
78
  def correctly_encoded_yamlfiles
85
79
  candidate_files = yamlfiles.select { |f| f.content.valid_encoding? }
86
80
  candidate_files.select do |f|
87
- # This doesn't handle multi-resource files, but it shouldn't matter, since the first resource
88
- # in a multi-resource file had better be a valid k8s resource
89
- content = ::YAML.safe_load(f.content, aliases: true)
90
- likely_kubernetes_resource?(content)
81
+ if f.type == "file" && Utils.likely_helm_chart?(f)
82
+ true
83
+ else
84
+ # This doesn't handle multi-resource files, but it shouldn't matter, since the first resource
85
+ # in a multi-resource file had better be a valid k8s resource
86
+ content = ::YAML.safe_load(f.content, aliases: true)
87
+ likely_kubernetes_resource?(content)
88
+ end
91
89
  rescue ::Psych::Exception
92
90
  false
93
91
  end
@@ -15,27 +15,25 @@ module Dependabot
15
15
 
16
16
  # Details of Docker regular expressions is at
17
17
  # https://github.com/docker/distribution/blob/master/reference/regexp.go
18
- DOMAIN_COMPONENT =
19
- /(?:[[:alnum:]]|[[:alnum:]][[[:alnum:]]-]*[[:alnum:]])/.freeze
20
- DOMAIN = /(?:#{DOMAIN_COMPONENT}(?:\.#{DOMAIN_COMPONENT})+)/.freeze
21
- REGISTRY = /(?<registry>#{DOMAIN}(?::\d+)?)/.freeze
22
-
23
- NAME_COMPONENT = /(?:[a-z\d]+(?:(?:[._]|__|[-]*)[a-z\d]+)*)/.freeze
24
- IMAGE = %r{(?<image>#{NAME_COMPONENT}(?:/#{NAME_COMPONENT})*)}.freeze
25
-
26
- FROM = /FROM/i.freeze
27
- PLATFORM = /--platform\=(?<platform>\S+)/.freeze
28
- TAG = /:(?<tag>[\w][\w.-]{0,127})/.freeze
29
- DIGEST = /@(?<digest>[^\s]+)/.freeze
30
- NAME = /\s+AS\s+(?<name>[\w-]+)/.freeze
18
+ DOMAIN_COMPONENT = /(?:[[:alnum:]]|[[:alnum:]][[[:alnum:]]-]*[[:alnum:]])/
19
+ DOMAIN = /(?:#{DOMAIN_COMPONENT}(?:\.#{DOMAIN_COMPONENT})+)/
20
+ REGISTRY = /(?<registry>#{DOMAIN}(?::\d+)?)/
21
+
22
+ NAME_COMPONENT = /(?:[a-z\d]+(?:(?:[._]|__|[-]*)[a-z\d]+)*)/
23
+ IMAGE = %r{(?<image>#{NAME_COMPONENT}(?:/#{NAME_COMPONENT})*)}
24
+
25
+ FROM = /FROM/i
26
+ PLATFORM = /--platform\=(?<platform>\S+)/
27
+ TAG = /:(?<tag>[\w][\w.-]{0,127})/
28
+ DIGEST = /@(?<digest>[^\s]+)/
29
+ NAME = /\s+AS\s+(?<name>[\w-]+)/
31
30
  FROM_LINE =
32
31
  %r{^#{FROM}\s+(#{PLATFORM}\s+)?(#{REGISTRY}/)?
33
- #{IMAGE}#{TAG}?#{DIGEST}?#{NAME}?}x.freeze
32
+ #{IMAGE}#{TAG}?#{DIGEST}?#{NAME}?}x
34
33
 
35
- AWS_ECR_URL = /dkr\.ecr\.(?<region>[^.]+)\.amazonaws\.com/.freeze
34
+ AWS_ECR_URL = /dkr\.ecr\.(?<region>[^.]+)\.amazonaws\.com/
36
35
 
37
- IMAGE_SPEC =
38
- %r{^(#{REGISTRY}/)?#{IMAGE}#{TAG}?#{DIGEST}?#{NAME}?}x.freeze
36
+ IMAGE_SPEC = %r{^(#{REGISTRY}/)?#{IMAGE}#{TAG}?#{DIGEST}?#{NAME}?}x
39
37
 
40
38
  def parse
41
39
  dependency_set = DependencySet.new
@@ -103,8 +101,9 @@ module Dependabot
103
101
  def version_from_digest(registry:, image:, digest:)
104
102
  return unless digest
105
103
 
106
- repo = docker_repo_name(image, registry)
107
- client = docker_registry_client(registry)
104
+ registry_details = fetch_registry_details(registry)
105
+ repo = docker_repo_name(image, registry_details["registry"])
106
+ client = docker_registry_client(registry_details["registry"], registry_details["credentials"])
108
107
  client.tags(repo, auto_paginate: true).fetch("tags").find do |tag|
109
108
  digest == client.digest(repo, tag)
110
109
  rescue DockerRegistry2::NotFound
@@ -114,46 +113,44 @@ module Dependabot
114
113
  end
115
114
  rescue DockerRegistry2::RegistryAuthenticationException,
116
115
  RestClient::Forbidden
117
- raise if standard_registry?(registry)
116
+ raise PrivateSourceAuthenticationFailure, registry_details["registry"]
117
+ rescue RestClient::Exceptions::OpenTimeout,
118
+ RestClient::Exceptions::ReadTimeout
119
+ raise if credentials_finder.using_dockerhub?(registry_details["registry"])
118
120
 
119
- raise PrivateSourceAuthenticationFailure, registry
121
+ raise PrivateSourceTimedOut, registry_details["registry"]
120
122
  end
121
123
 
122
124
  def docker_repo_name(image, registry)
123
- return image unless standard_registry?(registry)
124
- return image unless image.split("/").count < 2
125
+ return image if image.include? "/"
126
+ return "library/#{image}" if credentials_finder.using_dockerhub?(registry)
125
127
 
126
- "library/#{image}"
128
+ image
127
129
  end
128
130
 
129
- def docker_registry_client(registry)
130
- if registry
131
- credentials = registry_credentials(registry)
132
-
133
- DockerRegistry2::Registry.new(
134
- "https://#{registry}",
135
- user: credentials&.fetch("username", nil),
136
- password: credentials&.fetch("password", nil)
137
- )
138
- else
139
- DockerRegistry2::Registry.new("https://registry.hub.docker.com")
131
+ def docker_registry_client(registry_hostname, registry_credentials)
132
+ unless credentials_finder.using_dockerhub?(registry_hostname)
133
+ return DockerRegistry2::Registry.new("https://#{registry_hostname}")
140
134
  end
135
+
136
+ DockerRegistry2::Registry.new(
137
+ "https://#{registry_hostname}",
138
+ user: registry_credentials&.fetch("username", nil),
139
+ password: registry_credentials&.fetch("password", nil),
140
+ read_timeout: 10
141
+ )
141
142
  end
142
143
 
143
- def registry_credentials(registry_url)
144
- credentials_finder.credentials_for_registry(registry_url)
144
+ def fetch_registry_details(registry)
145
+ registry ||= credentials_finder.base_registry
146
+ credentials = credentials_finder.credentials_for_registry(registry)
147
+ { "registry" => registry, "credentials" => credentials }
145
148
  end
146
149
 
147
150
  def credentials_finder
148
151
  @credentials_finder ||= Utils::CredentialsFinder.new(credentials)
149
152
  end
150
153
 
151
- def standard_registry?(registry)
152
- return true if registry.nil?
153
-
154
- registry == "registry.hub.docker.com"
155
- end
156
-
157
154
  def check_required_files
158
155
  # Just check if there are any files at all.
159
156
  return if dependency_files.any?
@@ -214,6 +211,8 @@ module Dependabot
214
211
  images =
215
212
  if !img.nil? && img.is_a?(String) && !img.empty?
216
213
  [img]
214
+ elsif !img.nil? && img.is_a?(Hash) && !img.empty?
215
+ parse_helm(img)
217
216
  else
218
217
  []
219
218
  end
@@ -225,6 +224,22 @@ module Dependabot
225
224
  # Dependencies include both Dockerfiles and yaml, select yaml.
226
225
  dependency_files.select { |f| f.type == "file" && f.name.match?(/^[^\.]+\.ya?ml/i) }
227
226
  end
227
+
228
+ def parse_helm(img_hash)
229
+ repo = img_hash.fetch("repository", nil)
230
+ tag = img_hash.key?("tag") ? img_hash.fetch("tag", nil) : img_hash.fetch("version", nil)
231
+ registry = img_hash.fetch("registry", nil)
232
+
233
+ if !repo.nil? && !registry.nil? && !tag.nil?
234
+ ["#{registry}/#{repo}:#{tag}"]
235
+ elsif !repo.nil? && !tag.nil?
236
+ ["#{repo}:#{tag}"]
237
+ elsif !repo.nil?
238
+ [repo]
239
+ else
240
+ []
241
+ end
242
+ end
228
243
  end
229
244
  end
230
245
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "dependabot/docker/utils/helpers"
3
4
  require "dependabot/file_updaters"
4
5
  require "dependabot/file_updaters/base"
5
6
  require "dependabot/errors"
@@ -7,7 +8,7 @@ require "dependabot/errors"
7
8
  module Dependabot
8
9
  module Docker
9
10
  class FileUpdater < Dependabot::FileUpdaters::Base
10
- FROM_REGEX = /FROM(\s+--platform\=\S+)?/i.freeze
11
+ FROM_REGEX = /FROM(\s+--platform\=\S+)?/i
11
12
 
12
13
  def self.updated_files_regex
13
14
  [
@@ -18,7 +19,6 @@ module Dependabot
18
19
 
19
20
  def updated_dependency_files
20
21
  updated_files = []
21
-
22
22
  dependency_files.each do |file|
23
23
  next unless requirement_changed?(file, dependency)
24
24
 
@@ -153,13 +153,29 @@ module Dependabot
153
153
  end
154
154
 
155
155
  def updated_yaml_content(file)
156
- updated_content = update_image(file)
156
+ updated_content = Utils.likely_helm_chart?(file) ? update_helm(file) : update_image(file)
157
157
 
158
158
  raise "Expected content to change!" if updated_content == file.content
159
159
 
160
160
  updated_content
161
161
  end
162
162
 
163
+ def update_helm(file)
164
+ # TODO: this won't work if two images have the same tag version
165
+ old_tags = old_helm_tags(file)
166
+ return if old_tags.empty?
167
+
168
+ modified_content = file.content
169
+
170
+ old_tags.each do |old_tag|
171
+ old_tag_regex = /^\s+(?:-\s)?(?:tag|version):\s+#{old_tag}(?=\s|$)/
172
+ modified_content = modified_content.gsub(old_tag_regex) do |old_img_tag|
173
+ old_img_tag.gsub(old_tag.to_s, new_yaml_tag(file).to_s)
174
+ end
175
+ end
176
+ modified_content
177
+ end
178
+
163
179
  def update_image(file)
164
180
  old_images = old_yaml_images(file)
165
181
  return if old_images.empty?
@@ -176,13 +192,18 @@ module Dependabot
176
192
  end
177
193
 
178
194
  def new_yaml_image(file)
179
- elt = dependency.requirements.find { |r| r[:file] == file.name }
180
- prefix = elt.fetch(:source)[:registry] ? "#{elt.fetch(:source)[:registry]}/" : ""
181
- digest = elt.fetch(:source)[:digest] ? "@#{elt.fetch(:source)[:digest]}" : ""
182
- tag = elt.fetch(:source)[:tag] ? ":#{elt.fetch(:source)[:tag]}" : ""
195
+ element = dependency.requirements.find { |r| r[:file] == file.name }
196
+ prefix = element.fetch(:source)[:registry] ? "#{element.fetch(:source)[:registry]}/" : ""
197
+ digest = element.fetch(:source)[:digest] ? "@#{element.fetch(:source)[:digest]}" : ""
198
+ tag = element.fetch(:source)[:tag] ? ":#{element.fetch(:source)[:tag]}" : ""
183
199
  "#{prefix}#{dependency.name}#{tag}#{digest}"
184
200
  end
185
201
 
202
+ def new_yaml_tag(file)
203
+ element = dependency.requirements.find { |r| r[:file] == file.name }
204
+ element.fetch(:source)[:tag] || ""
205
+ end
206
+
186
207
  def old_yaml_images(file)
187
208
  dependency.
188
209
  previous_requirements.
@@ -193,6 +214,14 @@ module Dependabot
193
214
  "#{prefix}#{dependency.name}#{tag}#{digest}"
194
215
  end
195
216
  end
217
+
218
+ def old_helm_tags(file)
219
+ dependency.
220
+ previous_requirements.
221
+ select { |r| r[:file] == file.name }.map do |r|
222
+ r.fetch(:source)[:tag] || ""
223
+ end
224
+ end
196
225
  end
197
226
  end
198
227
  end
@@ -6,13 +6,17 @@ module Dependabot
6
6
  module Docker
7
7
  # Lifted from the bundler package manager
8
8
  class Requirement < Gem::Requirement
9
- # For consistency with other langauges, we define a requirements array.
9
+ # For consistency with other languages, we define a requirements array.
10
10
  # Ruby doesn't have an `OR` separator for requirements, so it always
11
11
  # contains a single element.
12
12
  def self.requirements_array(requirement_string)
13
13
  [new(requirement_string)]
14
14
  end
15
15
 
16
+ def satisfied_by?(version)
17
+ super(version.release_part)
18
+ end
19
+
16
20
  # Patches Gem::Requirement to make it accept requirement strings like
17
21
  # "~> 4.2.5, >= 4.2.5.1" without first needing to split them.
18
22
  def initialize(*requirements)
@@ -42,22 +42,19 @@ end
42
42
  module Dependabot
43
43
  module Docker
44
44
  class UpdateChecker < Dependabot::UpdateCheckers::Base
45
- VERSION_REGEX =
46
- /v?(?<version>[0-9]+(?:(?:\.[a-z0-9]+)|(?:-(?:kb)?[0-9]+))*)/i.freeze
47
- VERSION_WITH_SFX = /^#{VERSION_REGEX}(?<suffix>-[a-z0-9.\-]+)?$/i.freeze
48
- VERSION_WITH_PFX = /^(?<prefix>[a-z0-9.\-]+-)?#{VERSION_REGEX}$/i.freeze
49
- VERSION_WITH_PFX_AND_SFX =
50
- /^(?<prefix>[a-z\-]+-)?#{VERSION_REGEX}(?<suffix>-[a-z\-]+)?$/i.
51
- freeze
45
+ VERSION_REGEX = /v?(?<version>[0-9]+(?:\.[0-9]+)*(?:_[0-9]+|\.[a-z0-9]+|-(?:kb)?[0-9]+)*)/i
46
+ VERSION_WITH_SFX = /^#{VERSION_REGEX}(?<suffix>-[a-z][a-z0-9.\-]*)?$/i
47
+ VERSION_WITH_PFX = /^(?<prefix>[a-z][a-z0-9.\-]*-)?#{VERSION_REGEX}$/i
48
+ VERSION_WITH_PFX_AND_SFX = /^(?<prefix>[a-z\-]+-)?#{VERSION_REGEX}(?<suffix>-[a-z\-]+)?$/i
52
49
  NAME_WITH_VERSION =
53
50
  /
54
51
  #{VERSION_WITH_PFX}|
55
52
  #{VERSION_WITH_SFX}|
56
53
  #{VERSION_WITH_PFX_AND_SFX}
57
- /x.freeze
54
+ /x
58
55
 
59
56
  def latest_version
60
- fetch_latest_version(dependency.version)
57
+ latest_version_from(dependency.version)
61
58
  end
62
59
 
63
60
  def latest_resolvable_version
@@ -74,7 +71,7 @@ module Dependabot
74
71
  dependency.requirements.map do |req|
75
72
  updated_source = req.fetch(:source).dup
76
73
  updated_source[:digest] = updated_digest if req[:source][:digest]
77
- updated_source[:tag] = fetch_latest_version(req[:source][:tag]) if req[:source][:tag]
74
+ updated_source[:tag] = latest_version_from(req[:source][:tag]) if req[:source][:tag]
78
75
 
79
76
  req.merge(source: updated_source)
80
77
  end
@@ -120,7 +117,7 @@ module Dependabot
120
117
  # Check the precision of the potentially higher tag is the same as the
121
118
  # one it would replace. In the event that it's not the same, check the
122
119
  # digests are also unequal. Avoids 'updating' ruby-2 -> ruby-2.5.1
123
- return false if old_v.split(".").count == latest_v.split(".").count
120
+ return false if precision_of(old_v) == precision_of(latest_v)
124
121
 
125
122
  digest_of(version) == digest_of(latest_version)
126
123
  end
@@ -134,33 +131,38 @@ module Dependabot
134
131
  end
135
132
  end
136
133
 
137
- # NOTE: It's important that this *always* returns a version (even if
138
- # it's the existing one) as it is what we later check the digest of.
139
- def fetch_latest_version(version)
134
+ def latest_version_from(version)
140
135
  @versions ||= {}
141
136
  return @versions[version] if @versions.key?(version)
142
137
 
143
- @versions[version] = begin
144
- return version unless version.match?(NAME_WITH_VERSION)
145
-
146
- # Prune out any downgrade tags before checking for pre-releases
147
- # (which requires a call to the registry for each tag, so can be slow)
148
- candidate_tags = comparable_tags_from_registry(version)
149
- candidate_tags = remove_version_downgrades(candidate_tags, version)
138
+ @versions[version] = fetch_latest_version(version)
139
+ end
150
140
 
151
- unless prerelease?(version)
152
- candidate_tags =
153
- candidate_tags.
154
- reject { |tag| prerelease?(tag) }
141
+ # NOTE: It's important that this *always* returns a version (even if
142
+ # it's the existing one) as it is what we later check the digest of.
143
+ def fetch_latest_version(version)
144
+ return version unless version.match?(NAME_WITH_VERSION)
145
+
146
+ # Prune out any downgrade tags before checking for pre-releases
147
+ # (which requires a call to the registry for each tag, so can be slow)
148
+ candidate_tags = comparable_tags_from_registry(version)
149
+ candidate_tags = remove_version_downgrades(candidate_tags, version)
150
+ candidate_tags = remove_prereleases(candidate_tags, version)
151
+ candidate_tags = filter_ignored(candidate_tags)
152
+ candidate_tags = sort_tags(candidate_tags, version)
153
+
154
+ latest_tag = candidate_tags.last
155
+
156
+ if latest_tag && same_precision?(latest_tag, version)
157
+ latest_tag
158
+ else
159
+ latest_same_precision_tag = remove_precision_changes(candidate_tags, version).last
160
+
161
+ if latest_same_precision_tag && digest_of(latest_same_precision_tag) == digest_of(latest_tag)
162
+ latest_same_precision_tag
163
+ else
164
+ latest_tag || version
155
165
  end
156
-
157
- latest_tag =
158
- filter_ignored(candidate_tags).
159
- max_by do |tag|
160
- [version_class.new(numeric_version_from(tag)), tag.length]
161
- end
162
-
163
- latest_tag || version
164
166
  end
165
167
  end
166
168
 
@@ -169,28 +171,37 @@ module Dependabot
169
171
  original_suffix = suffix_of(version)
170
172
  original_format = format_of(version)
171
173
 
172
- tags_from_registry.
174
+ candidate_tags =
175
+ tags_from_registry.
173
176
  select { |tag| tag.match?(NAME_WITH_VERSION) }.
174
177
  select { |tag| prefix_of(tag) == original_prefix }.
175
- select { |tag| suffix_of(tag) == original_suffix || commit_sha_suffix?(tag) }.
176
178
  select { |tag| format_of(tag) == original_format }
179
+ return candidate_tags if original_format == :sha_suffixed
180
+
181
+ candidate_tags.select { |tag| suffix_of(tag) == original_suffix }
177
182
  end
178
183
 
179
184
  def remove_version_downgrades(candidate_tags, version)
180
185
  candidate_tags.select do |tag|
181
- version_class.new(numeric_version_from(tag)) >=
182
- version_class.new(numeric_version_from(version))
186
+ comparable_version_from(tag) >=
187
+ comparable_version_from(version)
183
188
  end
184
189
  end
185
190
 
186
- def commit_sha_suffix?(tag)
187
- # Some people suffix their versions with commit SHAs. Dependabot
188
- # can't order on those but will try to, so instead we should exclude
189
- # them (unless there's a `latest` version pushed to the registry, in
190
- # which case we'll use that to find the latest version)
191
- return false unless tag.match?(/(^|\-g?)[0-9a-f]{7,}$/)
191
+ def remove_prereleases(candidate_tags, version)
192
+ return candidate_tags if prerelease?(version)
192
193
 
193
- !tag.match?(/(^|\-)\d+$/)
194
+ candidate_tags.reject { |tag| prerelease?(tag) }
195
+ end
196
+
197
+ def remove_precision_changes(candidate_tags, version)
198
+ candidate_tags.select do |tag|
199
+ same_precision?(tag, version)
200
+ end
201
+ end
202
+
203
+ def same_precision?(tag, another_tag)
204
+ precision_of(numeric_version_from(tag)) == precision_of(numeric_version_from(another_tag))
194
205
  end
195
206
 
196
207
  def version_of_latest_tag
@@ -199,13 +210,13 @@ module Dependabot
199
210
  candidate_tag =
200
211
  tags_from_registry.
201
212
  select { |tag| canonical_version?(tag) }.
202
- sort_by { |t| version_class.new(numeric_version_from(t)) }.
213
+ sort_by { |t| comparable_version_from(t) }.
203
214
  reverse.
204
215
  find { |t| digest_of(t) == latest_digest }
205
216
 
206
217
  return unless candidate_tag
207
218
 
208
- version_class.new(numeric_version_from(candidate_tag))
219
+ comparable_version_from(candidate_tag)
209
220
  end
210
221
 
211
222
  def canonical_version?(tag)
@@ -295,6 +306,7 @@ module Dependabot
295
306
 
296
307
  return :year_month if version.match?(/^[12]\d{3}(?:[.\-]|$)/)
297
308
  return :year_month_day if version.match?(/^[12]\d{5}(?:[.\-]|$)/)
309
+ return :sha_suffixed if tag.match?(/(^|\-g?)[0-9a-f]{7,}$/)
298
310
  return :build_num if version.match?(/^\d+$/)
299
311
 
300
312
  :normal
@@ -309,7 +321,11 @@ module Dependabot
309
321
  return false unless latest_digest
310
322
  return false unless version_of_latest_tag
311
323
 
312
- version_class.new(numeric_version_from(tag)) > version_of_latest_tag
324
+ comparable_version_from(tag) > version_of_latest_tag
325
+ end
326
+
327
+ def comparable_version_from(tag)
328
+ version_class.new(numeric_version_from(tag))
313
329
  end
314
330
 
315
331
  def numeric_version_from(tag)
@@ -319,8 +335,9 @@ module Dependabot
319
335
  end
320
336
 
321
337
  def registry_hostname
322
- dependency.requirements.first[:source][:registry] ||
323
- "registry.hub.docker.com"
338
+ return dependency.requirements.first[:source][:registry] if dependency.requirements.first[:source][:registry]
339
+
340
+ credentials_finder.base_registry
324
341
  end
325
342
 
326
343
  def using_dockerhub?
@@ -347,15 +364,36 @@ module Dependabot
347
364
  DockerRegistry2::Registry.new(
348
365
  "https://#{registry_hostname}",
349
366
  user: registry_credentials&.fetch("username", nil),
350
- password: registry_credentials&.fetch("password", nil)
367
+ password: registry_credentials&.fetch("password", nil),
368
+ read_timeout: 10
351
369
  )
352
370
  end
353
371
 
372
+ def sort_tags(candidate_tags, version)
373
+ candidate_tags.sort do |tag_a, tag_b|
374
+ if comparable_version_from(tag_a) > comparable_version_from(tag_b)
375
+ 1
376
+ elsif comparable_version_from(tag_a) < comparable_version_from(tag_b)
377
+ -1
378
+ elsif same_precision?(tag_a, version)
379
+ 1
380
+ elsif same_precision?(tag_b, version)
381
+ -1
382
+ else
383
+ 0
384
+ end
385
+ end
386
+ end
387
+
388
+ def precision_of(version)
389
+ version.split(/[.-]/).length
390
+ end
391
+
354
392
  def filter_ignored(candidate_tags)
355
393
  filtered =
356
394
  candidate_tags.
357
395
  reject do |tag|
358
- version = version_class.new(numeric_version_from(tag))
396
+ version = comparable_version_from(tag)
359
397
  ignore_requirements.any? { |r| r.satisfied_by?(version) }
360
398
  end
361
399
  if @raise_on_ignored && filter_lower_versions(filtered).empty? && filter_lower_versions(candidate_tags).any?
@@ -366,9 +404,9 @@ module Dependabot
366
404
  end
367
405
 
368
406
  def filter_lower_versions(tags)
369
- versions_array = tags.map { |tag| version_class.new(numeric_version_from(tag)) }
407
+ versions_array = tags.map { |tag| comparable_version_from(tag) }
370
408
  versions_array.
371
- select { |version| version > version_class.new(numeric_version_from(dependency.version)) }
409
+ select { |version| version > comparable_version_from(dependency.version) }
372
410
  end
373
411
  end
374
412
  end
@@ -9,7 +9,8 @@ module Dependabot
9
9
  module Docker
10
10
  module Utils
11
11
  class CredentialsFinder
12
- AWS_ECR_URL = /dkr\.ecr\.(?<region>[^.]+)\.amazonaws\.com/.freeze
12
+ AWS_ECR_URL = /dkr\.ecr\.(?<region>[^.]+)\.amazonaws\.com/
13
+ DEFAULT_DOCKER_HUB_REGISTRY = "registry.hub.docker.com"
13
14
 
14
15
  def initialize(credentials)
15
16
  @credentials = credentials
@@ -26,6 +27,18 @@ module Dependabot
26
27
  build_aws_credentials(registry_details)
27
28
  end
28
29
 
30
+ def base_registry
31
+ @base_registry ||= credentials.find do |cred|
32
+ cred["type"] == "docker_registry" && cred["replaces-base"] == true
33
+ end
34
+ @base_registry ||= { "registry" => DEFAULT_DOCKER_HUB_REGISTRY, "credentials" => nil }
35
+ @base_registry["registry"]
36
+ end
37
+
38
+ def using_dockerhub?(registry)
39
+ registry == DEFAULT_DOCKER_HUB_REGISTRY
40
+ end
41
+
29
42
  private
30
43
 
31
44
  attr_reader :credentials
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dependabot
4
+ module Docker
5
+ module Utils
6
+ HELM_REGEXP = /values[\-a-zA-Z_0-9]*\.ya?ml$/i
7
+
8
+ def self.likely_helm_chart?(file)
9
+ file.name.match?(HELM_REGEXP)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -4,7 +4,29 @@ require "dependabot/utils"
4
4
 
5
5
  module Dependabot
6
6
  module Docker
7
+ # In the special case of Java, the version string may also contain
8
+ # optional "update number" and "identifier" components.
9
+ # See https://www.oracle.com/java/technologies/javase/versioning-naming.html
10
+ # for a description of Java versions.
11
+ #
7
12
  class Version < Gem::Version
13
+ def initialize(version)
14
+ release_part, update_part = version.split("_", 2)
15
+
16
+ @release_part = Gem::Version.new(release_part.tr("-", "."))
17
+
18
+ @update_part = Gem::Version.new(update_part&.start_with?(/[0-9]/) ? update_part : 0)
19
+ end
20
+
21
+ attr_reader :release_part
22
+
23
+ def <=>(other)
24
+ sort_criteria <=> other.sort_criteria
25
+ end
26
+
27
+ def sort_criteria
28
+ [@release_part, @update_part]
29
+ end
8
30
  end
9
31
  end
10
32
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-docker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.212.0
4
+ version: 0.214.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-06 00:00:00.000000000 Z
11
+ date: 2022-12-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dependabot-common
@@ -16,42 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.212.0
19
+ version: 0.214.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 0.212.0
27
- - !ruby/object:Gem::Dependency
28
- name: debase
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - '='
32
- - !ruby/object:Gem::Version
33
- version: 0.2.3
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - '='
39
- - !ruby/object:Gem::Version
40
- version: 0.2.3
41
- - !ruby/object:Gem::Dependency
42
- name: debase-ruby_core_source
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - '='
46
- - !ruby/object:Gem::Version
47
- version: 0.10.16
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - '='
53
- - !ruby/object:Gem::Version
54
- version: 0.10.16
26
+ version: 0.214.0
55
27
  - !ruby/object:Gem::Dependency
56
28
  name: debug
57
29
  requirement: !ruby/object:Gem::Requirement
@@ -86,14 +58,14 @@ dependencies:
86
58
  requirements:
87
59
  - - "~>"
88
60
  - !ruby/object:Gem::Version
89
- version: 3.12.0
61
+ version: 4.0.0
90
62
  type: :development
91
63
  prerelease: false
92
64
  version_requirements: !ruby/object:Gem::Requirement
93
65
  requirements:
94
66
  - - "~>"
95
67
  - !ruby/object:Gem::Version
96
- version: 3.12.0
68
+ version: 4.0.0
97
69
  - !ruby/object:Gem::Dependency
98
70
  name: rake
99
71
  requirement: !ruby/object:Gem::Requirement
@@ -142,42 +114,28 @@ dependencies:
142
114
  requirements:
143
115
  - - "~>"
144
116
  - !ruby/object:Gem::Version
145
- version: 1.36.0
117
+ version: 1.39.0
146
118
  type: :development
147
119
  prerelease: false
148
120
  version_requirements: !ruby/object:Gem::Requirement
149
121
  requirements:
150
122
  - - "~>"
151
123
  - !ruby/object:Gem::Version
152
- version: 1.36.0
124
+ version: 1.39.0
153
125
  - !ruby/object:Gem::Dependency
154
126
  name: rubocop-performance
155
127
  requirement: !ruby/object:Gem::Requirement
156
128
  requirements:
157
129
  - - "~>"
158
130
  - !ruby/object:Gem::Version
159
- version: 1.14.2
160
- type: :development
161
- prerelease: false
162
- version_requirements: !ruby/object:Gem::Requirement
163
- requirements:
164
- - - "~>"
165
- - !ruby/object:Gem::Version
166
- version: 1.14.2
167
- - !ruby/object:Gem::Dependency
168
- name: ruby-debug-ide
169
- requirement: !ruby/object:Gem::Requirement
170
- requirements:
171
- - - "~>"
172
- - !ruby/object:Gem::Version
173
- version: 0.7.3
131
+ version: 1.15.0
174
132
  type: :development
175
133
  prerelease: false
176
134
  version_requirements: !ruby/object:Gem::Requirement
177
135
  requirements:
178
136
  - - "~>"
179
137
  - !ruby/object:Gem::Version
180
- version: 0.7.3
138
+ version: 1.15.0
181
139
  - !ruby/object:Gem::Dependency
182
140
  name: simplecov
183
141
  requirement: !ruby/object:Gem::Requirement
@@ -263,6 +221,7 @@ files:
263
221
  - lib/dependabot/docker/requirement.rb
264
222
  - lib/dependabot/docker/update_checker.rb
265
223
  - lib/dependabot/docker/utils/credentials_finder.rb
224
+ - lib/dependabot/docker/utils/helpers.rb
266
225
  - lib/dependabot/docker/version.rb
267
226
  homepage: https://github.com/dependabot/dependabot-core
268
227
  licenses:
@@ -276,14 +235,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
276
235
  requirements:
277
236
  - - ">="
278
237
  - !ruby/object:Gem::Version
279
- version: 2.7.0
238
+ version: 3.1.0
280
239
  required_rubygems_version: !ruby/object:Gem::Requirement
281
240
  requirements:
282
241
  - - ">="
283
242
  - !ruby/object:Gem::Version
284
- version: 2.7.0
243
+ version: 3.1.0
285
244
  requirements: []
286
- rubygems_version: 3.1.6
245
+ rubygems_version: 3.3.7
287
246
  signing_key:
288
247
  specification_version: 4
289
248
  summary: Docker support for dependabot-common