dependabot-docker 0.383.0 → 0.384.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 +4 -4
- data/lib/dependabot/docker/update_checker.rb +293 -53
- data/lib/dependabot/shared/shared_file_parser.rb +1 -0
- metadata +5 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 62ae318b274003dd8cb082364cb06233a54d27c993e96ebd598128b198f280e7
|
|
4
|
+
data.tar.gz: 3c2c803bab214190b06d60ffe76c4de7781cc81254403a616220d685f1040347
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 91b741842929d3bb485325f0315a71defae699ea33b16e0c4fe7f605dfc3e3caeb4db99c2751ccb170059d3e7a741d6c1cc77483ff79ac0e52c18d16be6b6141
|
|
7
|
+
data.tar.gz: f3d93ecfb28273c0af1fe959cc830b67121c20ec8010f086865bbcf3191bbff301da0e406b7305a609e946ae6342898a50b815eea776bf782f09512f3dccc585
|
|
@@ -60,6 +60,17 @@ module Dependabot
|
|
|
60
60
|
T::Array[String]
|
|
61
61
|
)
|
|
62
62
|
|
|
63
|
+
# Media types returned for single-platform (non manifest-list) images.
|
|
64
|
+
# Used to cheaply rule out manifest-list comparison via a HEAD request's
|
|
65
|
+
# negotiated Content-Type, avoiding a full manifest GET for these images.
|
|
66
|
+
SINGLE_PLATFORM_MANIFEST_TYPES = T.let(
|
|
67
|
+
[
|
|
68
|
+
"application/vnd.docker.distribution.manifest.v2+json",
|
|
69
|
+
"application/vnd.oci.image.manifest.v1+json"
|
|
70
|
+
].freeze,
|
|
71
|
+
T::Array[String]
|
|
72
|
+
)
|
|
73
|
+
|
|
63
74
|
# Tolerance window for platform timestamp comparison.
|
|
64
75
|
# Multi-arch CI builds may finish platforms at slightly different times.
|
|
65
76
|
PLATFORM_TIMESTAMP_TOLERANCE_SECONDS = T.let(3 * 60 * 60, Integer)
|
|
@@ -141,9 +152,9 @@ module Dependabot
|
|
|
141
152
|
if tag
|
|
142
153
|
updated_tag = latest_version_from(tag)
|
|
143
154
|
updated_source[:tag] = updated_tag
|
|
144
|
-
updated_source[:digest] =
|
|
155
|
+
updated_source[:digest] = resolved_digest_for(tag, updated_tag, digest) if digest || pin_digests?
|
|
145
156
|
elsif digest
|
|
146
|
-
updated_source[:digest] = digest_of("latest")
|
|
157
|
+
updated_source[:digest] = digest_within_cooldown?("latest") ? digest : digest_of("latest")
|
|
147
158
|
end
|
|
148
159
|
|
|
149
160
|
req.merge(source: updated_source)
|
|
@@ -206,37 +217,51 @@ module Dependabot
|
|
|
206
217
|
|
|
207
218
|
sig { returns(T::Boolean) }
|
|
208
219
|
def digest_up_to_date?
|
|
209
|
-
digest_requirements.all?
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
# When digest-only updates are suppressed and the tag hasn't changed,
|
|
219
|
-
# treat the digest as up-to-date to avoid proposing a PR that only
|
|
220
|
-
# bumps the digest without a corresponding version change.
|
|
221
|
-
# Only apply to comparable (versioned) tags — non-comparable tags like
|
|
222
|
-
# "latest" or distro codenames should still get digest updates.
|
|
223
|
-
if Dependabot::Experiments.enabled?(:docker_digest_only_update_suppression) &&
|
|
224
|
-
Tag.new(source_tag).comparable? &&
|
|
225
|
-
latest_tag.name == source_tag
|
|
226
|
-
next true
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
digest_of(latest_tag.name)
|
|
230
|
-
else
|
|
231
|
-
updated_digest
|
|
232
|
-
end
|
|
220
|
+
digest_requirements.all? { |req| digest_requirement_up_to_date?(req) }
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
sig { params(req: T::Hash[Symbol, T.untyped]).returns(T::Boolean) }
|
|
224
|
+
def digest_requirement_up_to_date?(req)
|
|
225
|
+
source = req.fetch(:source)
|
|
226
|
+
source_digest = source.fetch(:digest)
|
|
227
|
+
source_tag = source[:tag]
|
|
233
228
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
229
|
+
if source_tag
|
|
230
|
+
latest_tag = latest_tag_from(source_tag)
|
|
231
|
+
digest_only_refresh = latest_tag.name == source_tag
|
|
237
232
|
|
|
238
|
-
|
|
233
|
+
# Digest-only refresh (tag unchanged): respect the cooldown window
|
|
234
|
+
# so a freshly-pushed digest isn't proposed before it matures. This
|
|
235
|
+
# covers both comparable and non-comparable tags (e.g. "alpine"),
|
|
236
|
+
# which the version-tag cooldown (apply_cooldown) never reaches.
|
|
237
|
+
return true if digest_only_refresh && digest_within_cooldown?(source_tag)
|
|
238
|
+
|
|
239
|
+
# When digest-only updates are suppressed and the tag hasn't changed,
|
|
240
|
+
# treat the digest as up-to-date to avoid proposing a PR that only
|
|
241
|
+
# bumps the digest without a corresponding version change.
|
|
242
|
+
# Only apply to comparable (versioned) tags — non-comparable tags like
|
|
243
|
+
# "latest" or distro codenames should still get digest updates.
|
|
244
|
+
return true if digest_only_update_suppressed?(source_tag, latest_tag)
|
|
245
|
+
|
|
246
|
+
expected_digest = digest_of(latest_tag.name)
|
|
247
|
+
else
|
|
248
|
+
digest_only_refresh = true
|
|
249
|
+
# Pure digest pin (no tag): the proposed digest resolves from the
|
|
250
|
+
# "latest" tag, so gate it on the same cooldown window.
|
|
251
|
+
return true if digest_within_cooldown?("latest")
|
|
252
|
+
|
|
253
|
+
expected_digest = updated_digest
|
|
239
254
|
end
|
|
255
|
+
|
|
256
|
+
# If we can't determine an expected digest (for example if the registry does not return digests)
|
|
257
|
+
# assume it's up to date
|
|
258
|
+
return true if expected_digest.nil?
|
|
259
|
+
|
|
260
|
+
# Multi-arch no-op: a digest-only refresh whose index digest changed only
|
|
261
|
+
# because an unconsumed platform was rebuilt should not open a PR.
|
|
262
|
+
return true if digest_refresh_is_noop?(source, source_digest, expected_digest, digest_only_refresh)
|
|
263
|
+
|
|
264
|
+
source_digest == expected_digest
|
|
240
265
|
end
|
|
241
266
|
|
|
242
267
|
sig { params(version: String).returns(String) }
|
|
@@ -446,7 +471,7 @@ module Dependabot
|
|
|
446
471
|
published_date = last_modified ? Time.parse(last_modified) : nil
|
|
447
472
|
|
|
448
473
|
Dependabot::Package::PackageRelease.new(
|
|
449
|
-
version:
|
|
474
|
+
version: release_version_for(tag),
|
|
450
475
|
released_at: published_date,
|
|
451
476
|
latest: false,
|
|
452
477
|
yanked: false,
|
|
@@ -932,6 +957,130 @@ module Dependabot
|
|
|
932
957
|
Dependabot::UpdateCheckers::CooldownCalculation.within_cooldown_window?(release_date, days)
|
|
933
958
|
end
|
|
934
959
|
|
|
960
|
+
# Builds the PackageRelease version for a tag. Non-comparable tags (e.g.
|
|
961
|
+
# "alpine") have no semver, so Docker::Version.new would raise. The version
|
|
962
|
+
# is only consumed by semver-aware version-tag cooldown; the digest cooldown
|
|
963
|
+
# path relies solely on the release date, so a sentinel keeps PackageRelease
|
|
964
|
+
# construction safe for non-comparable tags.
|
|
965
|
+
sig { params(tag: Dependabot::Docker::Tag).returns(Dependabot::Version) }
|
|
966
|
+
def release_version_for(tag)
|
|
967
|
+
Docker::Version.new(tag.name)
|
|
968
|
+
rescue ArgumentError, TypeError
|
|
969
|
+
Dependabot::Version.new("0")
|
|
970
|
+
end
|
|
971
|
+
|
|
972
|
+
# Whether the digest currently served for the given tag was published too
|
|
973
|
+
# recently to satisfy the configured cooldown window. Digest-only refreshes
|
|
974
|
+
# don't change the version string, so the default cooldown window applies
|
|
975
|
+
# (semver-specific windows require a version delta, and the tag may be
|
|
976
|
+
# non-comparable like "alpine"). Fails open (returns false) when the
|
|
977
|
+
# publication date can't be determined, so a missing Last-Modified header
|
|
978
|
+
# never permanently blocks an update.
|
|
979
|
+
sig { params(tag_name: String).returns(T::Boolean) }
|
|
980
|
+
def digest_within_cooldown?(tag_name)
|
|
981
|
+
return false if should_skip_cooldown?
|
|
982
|
+
|
|
983
|
+
cooldown = @update_cooldown
|
|
984
|
+
return false unless cooldown
|
|
985
|
+
|
|
986
|
+
released_at = publication_detail(Tag.new(tag_name))&.released_at
|
|
987
|
+
return false unless released_at
|
|
988
|
+
|
|
989
|
+
Dependabot::UpdateCheckers::CooldownCalculation.within_cooldown_window?(
|
|
990
|
+
released_at, cooldown.default_days
|
|
991
|
+
)
|
|
992
|
+
end
|
|
993
|
+
|
|
994
|
+
# Resolves the digest to write for a tag during a requirement update.
|
|
995
|
+
# Keeps the existing digest for a digest-only refresh that's still within
|
|
996
|
+
# the cooldown window; otherwise resolves the latest digest for the tag.
|
|
997
|
+
sig do
|
|
998
|
+
params(
|
|
999
|
+
current_tag: String,
|
|
1000
|
+
updated_tag: String,
|
|
1001
|
+
current_digest: T.nilable(String)
|
|
1002
|
+
).returns(T.nilable(String))
|
|
1003
|
+
end
|
|
1004
|
+
def resolved_digest_for(current_tag, updated_tag, current_digest)
|
|
1005
|
+
# Keep the existing digest for a digest-only refresh that's still within
|
|
1006
|
+
# the cooldown window; otherwise resolve the latest digest for the tag.
|
|
1007
|
+
return current_digest if current_digest && updated_tag == current_tag &&
|
|
1008
|
+
digest_within_cooldown?(updated_tag)
|
|
1009
|
+
|
|
1010
|
+
digest_of(updated_tag)
|
|
1011
|
+
end
|
|
1012
|
+
|
|
1013
|
+
# Whether a digest-only update for an unchanged comparable tag is suppressed
|
|
1014
|
+
# by the docker_digest_only_update_suppression experiment. Non-comparable
|
|
1015
|
+
# tags like "latest" or distro codenames are excluded so they still update.
|
|
1016
|
+
sig do
|
|
1017
|
+
params(source_tag: String, latest_tag: Dependabot::Docker::Tag).returns(T::Boolean)
|
|
1018
|
+
end
|
|
1019
|
+
def digest_only_update_suppressed?(source_tag, latest_tag)
|
|
1020
|
+
Dependabot::Experiments.enabled?(:docker_digest_only_update_suppression) &&
|
|
1021
|
+
Tag.new(source_tag).comparable? &&
|
|
1022
|
+
latest_tag.name == source_tag
|
|
1023
|
+
end
|
|
1024
|
+
|
|
1025
|
+
# Whether a digest-only refresh is a no-op for the platform(s) the user
|
|
1026
|
+
# actually consumes. Only applies when the tag is unchanged and the index
|
|
1027
|
+
# digest moved; otherwise this is a genuine version/digest change.
|
|
1028
|
+
sig do
|
|
1029
|
+
params(
|
|
1030
|
+
source: T::Hash[Symbol, T.untyped],
|
|
1031
|
+
current_digest: String,
|
|
1032
|
+
expected_digest: String,
|
|
1033
|
+
digest_only_refresh: T::Boolean
|
|
1034
|
+
).returns(T::Boolean)
|
|
1035
|
+
end
|
|
1036
|
+
def digest_refresh_is_noop?(source, current_digest, expected_digest, digest_only_refresh)
|
|
1037
|
+
digest_only_refresh &&
|
|
1038
|
+
current_digest != expected_digest &&
|
|
1039
|
+
multiarch_noop?(source, current_digest, expected_digest)
|
|
1040
|
+
end
|
|
1041
|
+
|
|
1042
|
+
# Compares the per-platform manifest digests of the currently-pinned index
|
|
1043
|
+
# against the candidate index. When the Dockerfile pins `--platform`, only
|
|
1044
|
+
# that platform is compared; otherwise every platform must be identical.
|
|
1045
|
+
# Fails open (returns false) whenever a confident comparison isn't possible.
|
|
1046
|
+
sig do
|
|
1047
|
+
params(
|
|
1048
|
+
source: T::Hash[Symbol, T.untyped],
|
|
1049
|
+
current_digest: String,
|
|
1050
|
+
candidate_digest: String
|
|
1051
|
+
).returns(T::Boolean)
|
|
1052
|
+
end
|
|
1053
|
+
def multiarch_noop?(source, current_digest, candidate_digest)
|
|
1054
|
+
current = platform_digests("sha256:#{current_digest}")
|
|
1055
|
+
candidate = platform_digests("sha256:#{candidate_digest}")
|
|
1056
|
+
|
|
1057
|
+
# Either reference is single-platform or couldn't be fetched: can't prove a no-op.
|
|
1058
|
+
return false if current.nil? || candidate.nil?
|
|
1059
|
+
|
|
1060
|
+
pinned = pinned_platform_key(source[:platform])
|
|
1061
|
+
if pinned
|
|
1062
|
+
current_platform = current[pinned]
|
|
1063
|
+
candidate_platform = candidate[pinned]
|
|
1064
|
+
return false if current_platform.nil? || candidate_platform.nil?
|
|
1065
|
+
|
|
1066
|
+
current_platform == candidate_platform
|
|
1067
|
+
else
|
|
1068
|
+
# No platform pin: only a no-op if nothing changed for any platform.
|
|
1069
|
+
current == candidate
|
|
1070
|
+
end
|
|
1071
|
+
end
|
|
1072
|
+
|
|
1073
|
+
# Normalizes a Dockerfile `--platform` value into a manifest platform key
|
|
1074
|
+
# (e.g. "linux/amd64", "linux/arm64/v8"). Returns nil for build-arg
|
|
1075
|
+
# placeholders like `$BUILDPLATFORM` so we fall back to the strict check.
|
|
1076
|
+
sig { params(platform: T.nilable(String)).returns(T.nilable(String)) }
|
|
1077
|
+
def pinned_platform_key(platform)
|
|
1078
|
+
return nil if platform.nil?
|
|
1079
|
+
return nil unless platform.match?(%r{\A[a-z0-9]+/[a-z0-9]+(?:/[a-z0-9]+)?\z})
|
|
1080
|
+
|
|
1081
|
+
platform
|
|
1082
|
+
end
|
|
1083
|
+
|
|
935
1084
|
# Fetches the "created" timestamp from the image config blob for a given tag.
|
|
936
1085
|
# This represents the actual build time, which is more reliable than semver
|
|
937
1086
|
# for determining which image is truly newer.
|
|
@@ -1122,6 +1271,10 @@ module Dependabot
|
|
|
1122
1271
|
# with matching per-platform manifest digests. For single-platform images this
|
|
1123
1272
|
# returns false (empty digest map), so this check is effectively a no-op and
|
|
1124
1273
|
# we fall back to the existing semver/precision selection logic.
|
|
1274
|
+
#
|
|
1275
|
+
# Single-platform current tags are ruled out up front with a cheap HEAD
|
|
1276
|
+
# request, so the default path doesn't pay for a full manifest GET that
|
|
1277
|
+
# would only ever return an empty digest map.
|
|
1125
1278
|
sig do
|
|
1126
1279
|
params(
|
|
1127
1280
|
candidate_tag: Dependabot::Docker::Tag,
|
|
@@ -1129,6 +1282,12 @@ module Dependabot
|
|
|
1129
1282
|
).returns(T::Boolean)
|
|
1130
1283
|
end
|
|
1131
1284
|
def same_image_contents?(candidate_tag, current_tag)
|
|
1285
|
+
# Only manifest lists can match here, so confirm the current tag is
|
|
1286
|
+
# multi-arch with a cheap HEAD request before paying for the full
|
|
1287
|
+
# manifest GET that fetch_platform_digests performs. Single-platform
|
|
1288
|
+
# images (the default path) short-circuit without an extra manifest GET.
|
|
1289
|
+
return false if single_platform_image?(current_tag.name)
|
|
1290
|
+
|
|
1132
1291
|
current_digests = fetch_platform_digests(current_tag.name)
|
|
1133
1292
|
return false if current_digests.empty?
|
|
1134
1293
|
|
|
@@ -1141,6 +1300,74 @@ module Dependabot
|
|
|
1141
1300
|
current_digests == candidate_digests
|
|
1142
1301
|
end
|
|
1143
1302
|
|
|
1303
|
+
# Returns true when a tag points at a single-platform image rather than a
|
|
1304
|
+
# multi-arch manifest list. Uses a HEAD request — the registry reports the
|
|
1305
|
+
# negotiated media type in the Content-Type header — so we avoid the more
|
|
1306
|
+
# expensive full-manifest GET that fetch_platform_digests performs.
|
|
1307
|
+
#
|
|
1308
|
+
# Reuses a manifest list already fetched for the tag when available, and
|
|
1309
|
+
# only treats positively recognised single-platform media types as
|
|
1310
|
+
# single-platform. An ambiguous, missing or errored response returns false
|
|
1311
|
+
# so the caller falls back to the per-platform comparison rather than risk
|
|
1312
|
+
# skipping a real manifest list.
|
|
1313
|
+
sig { params(tag_name: String).returns(T::Boolean) }
|
|
1314
|
+
def single_platform_image?(tag_name)
|
|
1315
|
+
return manifest_list_cache[tag_name].nil? if manifest_list_cache.key?(tag_name)
|
|
1316
|
+
return T.must(single_platform_cache[tag_name]) if single_platform_cache.key?(tag_name)
|
|
1317
|
+
|
|
1318
|
+
single_platform_cache[tag_name] = fetch_single_platform_flag(tag_name)
|
|
1319
|
+
end
|
|
1320
|
+
|
|
1321
|
+
sig { params(tag_name: String).returns(T::Boolean) }
|
|
1322
|
+
def fetch_single_platform_flag(tag_name)
|
|
1323
|
+
response = with_retries(max_attempts: 3, errors: transient_docker_errors) do
|
|
1324
|
+
docker_registry_client.dohead("v2/#{docker_repo_name}/manifests/#{tag_name}")
|
|
1325
|
+
end
|
|
1326
|
+
|
|
1327
|
+
media_type = response.headers[:content_type]&.split(";")&.first&.strip
|
|
1328
|
+
SINGLE_PLATFORM_MANIFEST_TYPES.include?(media_type)
|
|
1329
|
+
rescue DockerRegistry2::Exception => e
|
|
1330
|
+
Dependabot.logger.info(
|
|
1331
|
+
"Failed to determine manifest media type for #{docker_repo_name}:#{tag_name}, " \
|
|
1332
|
+
"falling back to per-platform comparison: #{e.class} - #{e.message}"
|
|
1333
|
+
)
|
|
1334
|
+
false
|
|
1335
|
+
end
|
|
1336
|
+
|
|
1337
|
+
# Fetches a tag's manifest list (multi-arch image index) and returns the
|
|
1338
|
+
# array of per-platform manifest entries, or nil for single-platform images
|
|
1339
|
+
# (not a manifest list). Cached so the platform-inspection methods below
|
|
1340
|
+
# share a single registry GET per tag.
|
|
1341
|
+
sig { params(tag_name: String).returns(T.nilable(T::Array[T::Hash[String, T.untyped]])) }
|
|
1342
|
+
def fetch_manifest_list(tag_name)
|
|
1343
|
+
return manifest_list_cache[tag_name] if manifest_list_cache.key?(tag_name)
|
|
1344
|
+
|
|
1345
|
+
manifest_list_cache[tag_name] = fetch_manifest_list_from_registry(tag_name)
|
|
1346
|
+
rescue DockerRegistry2::Exception, JSON::ParserError => e
|
|
1347
|
+
# Do NOT cache fetch failures. A cached nil is reserved for "definitely
|
|
1348
|
+
# not a manifest list" (single-platform), which single_platform_image?
|
|
1349
|
+
# relies on. Caching a transient error as nil would misclassify the tag
|
|
1350
|
+
# as single-platform and permanently short-circuit same-content
|
|
1351
|
+
# suppression, even if a later manifest fetch would succeed. Returning
|
|
1352
|
+
# nil without caching lets a subsequent call retry.
|
|
1353
|
+
Dependabot.logger.info(
|
|
1354
|
+
"Failed to fetch manifest list for #{docker_repo_name}:#{tag_name}: #{e.message}"
|
|
1355
|
+
)
|
|
1356
|
+
nil
|
|
1357
|
+
end
|
|
1358
|
+
|
|
1359
|
+
sig { params(tag_name: String).returns(T.nilable(T::Array[T::Hash[String, T.untyped]])) }
|
|
1360
|
+
def fetch_manifest_list_from_registry(tag_name)
|
|
1361
|
+
manifest = with_retries(max_attempts: 3, errors: transient_docker_errors) do
|
|
1362
|
+
docker_registry_client.manifest(docker_repo_name, tag_name)
|
|
1363
|
+
end
|
|
1364
|
+
|
|
1365
|
+
media_type = manifest["mediaType"] || manifest[:mediaType]
|
|
1366
|
+
return nil unless MANIFEST_LIST_TYPES.include?(media_type)
|
|
1367
|
+
|
|
1368
|
+
manifest["manifests"] || manifest[:manifests] || []
|
|
1369
|
+
end
|
|
1370
|
+
|
|
1144
1371
|
# Fetches a map of platform key (e.g. "linux/amd64") to platform manifest
|
|
1145
1372
|
# digest for a tag's manifest list. Returns an empty hash for single-platform
|
|
1146
1373
|
# images or when the manifest can't be fetched.
|
|
@@ -1162,14 +1389,9 @@ module Dependabot
|
|
|
1162
1389
|
|
|
1163
1390
|
sig { params(tag_name: String).returns(T::Hash[String, String]) }
|
|
1164
1391
|
def fetch_platform_digests_from_registry(tag_name)
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
end
|
|
1168
|
-
|
|
1169
|
-
media_type = manifest["mediaType"] || manifest[:mediaType]
|
|
1170
|
-
return {} unless MANIFEST_LIST_TYPES.include?(media_type)
|
|
1392
|
+
manifests = fetch_manifest_list(tag_name)
|
|
1393
|
+
return {} unless manifests
|
|
1171
1394
|
|
|
1172
|
-
manifests = manifest["manifests"] || manifest[:manifests] || []
|
|
1173
1395
|
collect_platform_digests(manifests)
|
|
1174
1396
|
end
|
|
1175
1397
|
|
|
@@ -1210,19 +1432,26 @@ module Dependabot
|
|
|
1210
1432
|
|
|
1211
1433
|
sig { params(tag_name: String).returns(T.nilable(T::Array[T::Hash[String, T.untyped]])) }
|
|
1212
1434
|
def fetch_manifest_platforms_from_registry(tag_name)
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
end
|
|
1216
|
-
|
|
1217
|
-
media_type = manifest["mediaType"] || manifest[:mediaType]
|
|
1218
|
-
return nil unless MANIFEST_LIST_TYPES.include?(media_type)
|
|
1219
|
-
|
|
1220
|
-
manifests = manifest["manifests"] || manifest[:manifests] || []
|
|
1435
|
+
manifests = fetch_manifest_list(tag_name)
|
|
1436
|
+
return nil unless manifests
|
|
1221
1437
|
|
|
1222
1438
|
# Filter to actual image manifests (exclude attestations/signatures)
|
|
1223
1439
|
manifests.filter_map { |m| extract_platform(m) }
|
|
1224
1440
|
end
|
|
1225
1441
|
|
|
1442
|
+
# Maps each platform in a manifest list to its per-platform manifest digest,
|
|
1443
|
+
# e.g. { "linux/amd64" => "sha256:…", "linux/arm64/v8" => "sha256:…" }.
|
|
1444
|
+
# Accepts a tag name or a digest reference (e.g. "sha256:…"). Returns nil
|
|
1445
|
+
# for single-platform images or when the manifest can't be fetched, so
|
|
1446
|
+
# callers fail open rather than treat an unknown image as a no-op.
|
|
1447
|
+
sig { params(reference: String).returns(T.nilable(T::Hash[String, String])) }
|
|
1448
|
+
def platform_digests(reference)
|
|
1449
|
+
digests = fetch_platform_digests(reference)
|
|
1450
|
+
return nil if digests.empty?
|
|
1451
|
+
|
|
1452
|
+
digests
|
|
1453
|
+
end
|
|
1454
|
+
|
|
1226
1455
|
sig { params(manifest_entry: T.untyped).returns(T.nilable(T::Hash[String, T.untyped])) }
|
|
1227
1456
|
def extract_platform(manifest_entry)
|
|
1228
1457
|
platform = manifest_entry["platform"] || manifest_entry[:platform]
|
|
@@ -1267,14 +1496,9 @@ module Dependabot
|
|
|
1267
1496
|
|
|
1268
1497
|
sig { params(tag_name: String).returns(T::Hash[String, T.nilable(Time)]) }
|
|
1269
1498
|
def fetch_all_platform_timestamps_from_registry(tag_name)
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
end
|
|
1273
|
-
|
|
1274
|
-
media_type = manifest["mediaType"] || manifest[:mediaType]
|
|
1275
|
-
return {} unless MANIFEST_LIST_TYPES.include?(media_type)
|
|
1499
|
+
manifests = fetch_manifest_list(tag_name)
|
|
1500
|
+
return {} unless manifests
|
|
1276
1501
|
|
|
1277
|
-
manifests = manifest["manifests"] || manifest[:manifests] || []
|
|
1278
1502
|
collect_platform_timestamps(manifests)
|
|
1279
1503
|
end
|
|
1280
1504
|
|
|
@@ -1309,6 +1533,22 @@ module Dependabot
|
|
|
1309
1533
|
parse_created_from_config_blob(config_digest)
|
|
1310
1534
|
end
|
|
1311
1535
|
|
|
1536
|
+
sig { returns(T::Hash[String, T.nilable(T::Array[T::Hash[String, T.untyped]])]) }
|
|
1537
|
+
def manifest_list_cache
|
|
1538
|
+
@manifest_list_cache ||= T.let(
|
|
1539
|
+
{},
|
|
1540
|
+
T.nilable(T::Hash[String, T.nilable(T::Array[T::Hash[String, T.untyped]])])
|
|
1541
|
+
)
|
|
1542
|
+
end
|
|
1543
|
+
|
|
1544
|
+
sig { returns(T::Hash[String, T::Boolean]) }
|
|
1545
|
+
def single_platform_cache
|
|
1546
|
+
@single_platform_cache ||= T.let(
|
|
1547
|
+
{},
|
|
1548
|
+
T.nilable(T::Hash[String, T::Boolean])
|
|
1549
|
+
)
|
|
1550
|
+
end
|
|
1551
|
+
|
|
1312
1552
|
sig { returns(T::Hash[String, T.nilable(T::Array[T::Hash[String, T.untyped]])]) }
|
|
1313
1553
|
def manifest_platforms_cache
|
|
1314
1554
|
@manifest_platforms_cache ||= T.let(
|
|
@@ -43,6 +43,7 @@ module Dependabot
|
|
|
43
43
|
source[:registry] = parsed_line.fetch("registry") if parsed_line.fetch("registry")
|
|
44
44
|
source[:tag] = parsed_line.fetch("tag") if parsed_line.fetch("tag")
|
|
45
45
|
source[:digest] = parsed_line.fetch("digest") if parsed_line.fetch("digest")
|
|
46
|
+
source[:platform] = parsed_line["platform"] if parsed_line["platform"]
|
|
46
47
|
|
|
47
48
|
source
|
|
48
49
|
end
|
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.
|
|
4
|
+
version: 0.384.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.
|
|
18
|
+
version: 0.384.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.
|
|
25
|
+
version: 0.384.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.
|
|
269
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.384.0
|
|
270
270
|
rdoc_options: []
|
|
271
271
|
require_paths:
|
|
272
272
|
- lib
|
|
@@ -281,7 +281,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
281
281
|
- !ruby/object:Gem::Version
|
|
282
282
|
version: 3.3.0
|
|
283
283
|
requirements: []
|
|
284
|
-
rubygems_version:
|
|
284
|
+
rubygems_version: 4.0.14
|
|
285
285
|
specification_version: 4
|
|
286
286
|
summary: Provides Dependabot support for Docker
|
|
287
287
|
test_files: []
|