dependabot-docker 0.212.0 → 0.214.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: 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