dependabot-docker 0.381.0 → 0.383.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: 594eace484f3785999d308bfa51c89852a3c8a9ad4a3df078d6877da95911011
4
- data.tar.gz: 02c6632d9724e223b2e7759c861274d05f76bcdf387598faa248450132c6e118
3
+ metadata.gz: '0200408166a023ea4720b018f90dda3232304f7b5f1abec28dd8dbb9bf2b3f0e'
4
+ data.tar.gz: 99f2b3c7a81588a396ca4fc38b1cb9ce9d5907f455122f7b3285fc84c92afe51
5
5
  SHA512:
6
- metadata.gz: 3a1ad82c40987ad25327373dca49c81d604f14aee3a0c1e4751821677e7bb0e48e7d324366f8d500b07148d70e17774e4b39b7d08af9d095e461450c74bbbc88
7
- data.tar.gz: c5c315b5b419d28fe8bec64a25383e9149d7d2d267c945da70a20f9c9dd592e9e7ac20ec0fb9b3325c8690489d04e571a49b1185a1c2538d3511660019539040
6
+ metadata.gz: b4eb568dd7249d487a42415564920512f63d953049334e64ec09171435385b48365e0934c8d392b8d7033d743f33b887fb4de1548e1c25db65e355cd2e9cf798
7
+ data.tar.gz: 517a96c97e6b74ed9873be9d49cbdf0913204f2111f4191c18a22a9c6c9e37c9665760a70662f84e4c87b65607b710414d871109fd72dc68d64f58f0f56795ea
@@ -130,9 +130,9 @@ module Dependabot
130
130
  dependency.version
131
131
  end
132
132
 
133
- sig { override.returns(T::Array[T::Hash[Symbol, T.untyped]]) }
133
+ sig { override.returns(T::Array[Dependabot::DependencyRequirement]) }
134
134
  def updated_requirements
135
- dependency.requirements.map do |req|
135
+ updated = dependency.requirements.map do |req|
136
136
  updated_source = req.fetch(:source).dup
137
137
 
138
138
  tag = req[:source][:tag]
@@ -148,6 +148,7 @@ module Dependabot
148
148
 
149
149
  req.merge(source: updated_source)
150
150
  end
151
+ wrap_requirements(updated)
151
152
  end
152
153
 
153
154
  private
@@ -262,6 +263,7 @@ module Dependabot
262
263
  # (which requires a call to the registry for each tag, so can be slow)
263
264
  candidate_tags = comparable_tags_from_registry(version_tag)
264
265
  candidate_tags = remove_version_downgrades(candidate_tags, version_tag)
266
+ candidate_tags = remove_more_precise_tags(candidate_tags, version_tag)
265
267
  candidate_tags = remove_prereleases(candidate_tags, version_tag)
266
268
  candidate_tags = filter_ignored(candidate_tags)
267
269
  candidate_tags = sort_tags(candidate_tags, version_tag)
@@ -284,14 +286,26 @@ module Dependabot
284
286
  candidate_tags.reverse_each do |candidate|
285
287
  selected = select_tag_with_precision(candidate, same_precision_tags, version_tag)
286
288
 
289
+ # Content check (runs regardless of experiments): if the candidate tag
290
+ # resolves to the exact same image contents as the current tag, there
291
+ # is no real update to make. This catches rolling tags (e.g. "9.0")
292
+ # that point to the same image as a pinned tag (e.g. "9.0.11").
293
+ if selected.name != version_tag.name && same_image_contents?(selected, version_tag)
294
+ Dependabot.logger.info(
295
+ "Digest check: #{selected.name} has the same image contents as #{version_tag.name} " \
296
+ "— no update needed, staying on #{version_tag.name}"
297
+ )
298
+ next
299
+ end
300
+
287
301
  if Dependabot::Experiments.enabled?(:docker_created_timestamp_validation) &&
288
302
  selected.name != version_tag.name
289
303
  if validation_attempts >= MAX_PLATFORM_VALIDATION_ATTEMPTS
290
304
  Dependabot.logger.info(
291
305
  "Platform validation: reached max attempts (#{MAX_PLATFORM_VALIDATION_ATTEMPTS}), " \
292
- "accepting #{selected.name} without timestamp check"
306
+ "skipping #{selected.name} continuing to find a validated candidate"
293
307
  )
294
- return selected
308
+ next
295
309
  end
296
310
  validation_attempts += 1
297
311
  end
@@ -561,6 +575,26 @@ module Dependabot
561
575
  end
562
576
  end
563
577
 
578
+ # When the current tag pins only the major or major.minor version (e.g.
579
+ # "9.0"), a candidate that additionally specifies a patch version (e.g.
580
+ # "9.0.17") is not a wanted upgrade: pinning the less precise tag opts into
581
+ # a rolling tag, so the patch should not be specified for them. Only true
582
+ # semver tags (":normal" format) are filtered here — build-number and date
583
+ # based formats have their own comparability rules and are left untouched.
584
+ sig do
585
+ params(
586
+ candidate_tags: T::Array[Dependabot::Docker::Tag],
587
+ version_tag: Dependabot::Docker::Tag
588
+ )
589
+ .returns(T::Array[Dependabot::Docker::Tag])
590
+ end
591
+ def remove_more_precise_tags(candidate_tags, version_tag)
592
+ return candidate_tags unless version_tag.format == :normal
593
+ return candidate_tags if version_tag.precision > 2
594
+
595
+ candidate_tags.select { |tag| tag.precision <= version_tag.precision }
596
+ end
597
+
564
598
  sig do
565
599
  params(
566
600
  candidate_tags: T::Array[Dependabot::Docker::Tag],
@@ -1082,6 +1116,80 @@ module Dependabot
1082
1116
  candidate_created > current_created
1083
1117
  end
1084
1118
 
1119
+ # Returns true when the candidate tag and current tag reference the exact
1120
+ # same image contents. Only multi-arch (manifest list) images are compared
1121
+ # here, by checking that both tags expose the identical set of platforms
1122
+ # with matching per-platform manifest digests. For single-platform images this
1123
+ # returns false (empty digest map), so this check is effectively a no-op and
1124
+ # we fall back to the existing semver/precision selection logic.
1125
+ sig do
1126
+ params(
1127
+ candidate_tag: Dependabot::Docker::Tag,
1128
+ current_tag: Dependabot::Docker::Tag
1129
+ ).returns(T::Boolean)
1130
+ end
1131
+ def same_image_contents?(candidate_tag, current_tag)
1132
+ current_digests = fetch_platform_digests(current_tag.name)
1133
+ return false if current_digests.empty?
1134
+
1135
+ candidate_digests = fetch_platform_digests(candidate_tag.name)
1136
+ return false if candidate_digests.empty?
1137
+
1138
+ # The contents are unchanged only when both tags expose the exact same set
1139
+ # of platforms with identical per-platform manifest digests. A candidate
1140
+ # with extra platforms is a different image and must not match.
1141
+ current_digests == candidate_digests
1142
+ end
1143
+
1144
+ # Fetches a map of platform key (e.g. "linux/amd64") to platform manifest
1145
+ # digest for a tag's manifest list. Returns an empty hash for single-platform
1146
+ # images or when the manifest can't be fetched.
1147
+ sig { params(tag_name: String).returns(T::Hash[String, String]) }
1148
+ def fetch_platform_digests(tag_name)
1149
+ return T.must(platform_digests_cache[tag_name]) if platform_digests_cache.key?(tag_name)
1150
+
1151
+ digests = fetch_platform_digests_from_registry(tag_name)
1152
+ platform_digests_cache[tag_name] = digests
1153
+ digests
1154
+ rescue *transient_docker_errors, DockerRegistry2::RegistryAuthenticationException,
1155
+ RestClient::Forbidden, RestClient::TooManyRequests, JSON::ParserError => e
1156
+ Dependabot.logger.info(
1157
+ "Failed to fetch platform digests for #{docker_repo_name}:#{tag_name}: #{e.message}"
1158
+ )
1159
+ platform_digests_cache[tag_name] = {}
1160
+ {}
1161
+ end
1162
+
1163
+ sig { params(tag_name: String).returns(T::Hash[String, String]) }
1164
+ def fetch_platform_digests_from_registry(tag_name)
1165
+ manifest = with_retries(max_attempts: 3, errors: transient_docker_errors) do
1166
+ docker_registry_client.manifest(docker_repo_name, tag_name)
1167
+ end
1168
+
1169
+ media_type = manifest["mediaType"] || manifest[:mediaType]
1170
+ return {} unless MANIFEST_LIST_TYPES.include?(media_type)
1171
+
1172
+ manifests = manifest["manifests"] || manifest[:manifests] || []
1173
+ collect_platform_digests(manifests)
1174
+ end
1175
+
1176
+ sig { params(manifests: T.untyped).returns(T::Hash[String, String]) }
1177
+ def collect_platform_digests(manifests)
1178
+ digests = {}
1179
+
1180
+ manifests.each do |m|
1181
+ platform = extract_platform(m)
1182
+ next unless platform
1183
+
1184
+ digest = m["digest"] || m[:digest]
1185
+ next unless digest
1186
+
1187
+ digests[platform_key(platform)] = digest
1188
+ end
1189
+
1190
+ digests
1191
+ end
1192
+
1085
1193
  # Fetches the platform entries from a manifest list for a given tag.
1086
1194
  # Returns nil if the tag is a single-platform image (not a manifest list).
1087
1195
  sig { params(tag_name: String).returns(T.nilable(T::Array[T::Hash[String, T.untyped]])) }
@@ -1217,6 +1325,14 @@ module Dependabot
1217
1325
  )
1218
1326
  end
1219
1327
 
1328
+ sig { returns(T::Hash[String, T::Hash[String, String]]) }
1329
+ def platform_digests_cache
1330
+ @platform_digests_cache ||= T.let(
1331
+ {},
1332
+ T.nilable(T::Hash[String, T::Hash[String, String]])
1333
+ )
1334
+ end
1335
+
1220
1336
  sig { returns(T::Hash[String, T.nilable(Time)]) }
1221
1337
  def config_created_timestamps
1222
1338
  @config_created_timestamps ||= T.let(
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-docker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.381.0
4
+ version: 0.383.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.381.0
18
+ version: 0.383.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.381.0
25
+ version: 0.383.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -266,7 +266,7 @@ licenses:
266
266
  - MIT
267
267
  metadata:
268
268
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
269
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.381.0
269
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.383.0
270
270
  rdoc_options: []
271
271
  require_paths:
272
272
  - lib