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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0200408166a023ea4720b018f90dda3232304f7b5f1abec28dd8dbb9bf2b3f0e'
4
- data.tar.gz: 99f2b3c7a81588a396ca4fc38b1cb9ce9d5907f455122f7b3285fc84c92afe51
3
+ metadata.gz: 62ae318b274003dd8cb082364cb06233a54d27c993e96ebd598128b198f280e7
4
+ data.tar.gz: 3c2c803bab214190b06d60ffe76c4de7781cc81254403a616220d685f1040347
5
5
  SHA512:
6
- metadata.gz: b4eb568dd7249d487a42415564920512f63d953049334e64ec09171435385b48365e0934c8d392b8d7033d743f33b887fb4de1548e1c25db65e355cd2e9cf798
7
- data.tar.gz: 517a96c97e6b74ed9873be9d49cbdf0913204f2111f4191c18a22a9c6c9e37c9665760a70662f84e4c87b65607b710414d871109fd72dc68d64f58f0f56795ea
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] = digest_of(updated_tag) if digest || pin_digests?
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? do |req|
210
- source = req.fetch(:source)
211
- source_digest = source.fetch(:digest)
212
- source_tag = source[:tag]
213
-
214
- expected_digest =
215
- if source_tag
216
- latest_tag = latest_tag_from(source_tag)
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
- # If we can't determine an expected digest (for example if the registry does not return digests)
235
- # assume it's up to date
236
- next true if expected_digest.nil?
229
+ if source_tag
230
+ latest_tag = latest_tag_from(source_tag)
231
+ digest_only_refresh = latest_tag.name == source_tag
237
232
 
238
- source_digest == expected_digest
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: Docker::Version.new(tag.name),
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
- 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)
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
- manifest = with_retries(max_attempts: 3, errors: transient_docker_errors) do
1214
- docker_registry_client.manifest(docker_repo_name, tag_name)
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
- manifest = with_retries(max_attempts: 3, errors: transient_docker_errors) do
1271
- docker_registry_client.manifest(docker_repo_name, tag_name)
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.383.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.383.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.383.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.383.0
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: 3.7.2
284
+ rubygems_version: 4.0.14
285
285
  specification_version: 4
286
286
  summary: Provides Dependabot support for Docker
287
287
  test_files: []