dependabot-docker 0.396.0 → 0.397.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: d4dfb9902bcedec29154319ac165b61f4919234b9e14a5bb52b43c942f51a1e5
4
- data.tar.gz: 6c7e1b398f047fdf203ca9a21d8802eb999e573273e72c76576f8f028e89544a
3
+ metadata.gz: b43ff0be6b23394fc47285dce19edb7b8e90bcc6a859243315758559dbd5fac4
4
+ data.tar.gz: 0b12ca42143a2d08547b575d4b4373ce4cf0490f22fc41f8c2fb2ff4a944ef4f
5
5
  SHA512:
6
- metadata.gz: af5fe84bcc77eab88b77e70e2f659159698f1bcec99cb6d114dccc3a5a3f325859a8b1bb8d9573eb20c46c3994d731805ef8f88083408ac882bb30fa2935f5db
7
- data.tar.gz: 9f3ab430f720059323379a4900c9d27a782ee4c6d9ba1381a4eb801dc4c417a5858a820d427a955686e4d10d391f4f2e6901e4b1db9dbdf40ac36a06a515d026
6
+ metadata.gz: 338d342f4d99ee355295af0581139e6a121c580bcc636d54cd8bde653b4169b569e80b5371ab1b0bd321e421df07fb13f4275eea8e4aebe2384a55afc014e18d
7
+ data.tar.gz: a7feda400c5b1fc35e0ac60b36d37c7bfa57295416ff1e230fe656311700573b4fae9d0ac0097702e8325d94a2f245ba56f668ddca145c2df3f63c8f49ef8bee
@@ -145,7 +145,11 @@ module Dependabot
145
145
  repo = img_hash.fetch("repository", nil)
146
146
  return [] unless repo.is_a?(String)
147
147
 
148
- match = tag_value.to_s.match(TAG_WITH_DIGEST)
148
+ tag = tag_value.to_s
149
+ # Helm template expressions cannot be resolved without rendering the chart.
150
+ return [] if tag.include?("{{")
151
+
152
+ match = tag.match(TAG_WITH_DIGEST)
149
153
  return [] unless match
150
154
 
151
155
  tag_details = match.named_captures
@@ -2,7 +2,10 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "docker_registry2"
5
+ require "excon"
6
+ require "json"
5
7
  require "sorbet-runtime"
8
+ require "uri"
6
9
 
7
10
  begin
8
11
  require "rest-client"
@@ -36,6 +39,7 @@ end
36
39
  require "dependabot/update_checkers"
37
40
  require "dependabot/update_checkers/base"
38
41
  require "dependabot/update_checkers/cooldown_calculation"
42
+ require "dependabot/registry_client"
39
43
  require "dependabot/errors"
40
44
  require "dependabot/docker/tag"
41
45
  require "dependabot/docker/file_parser"
@@ -471,25 +475,8 @@ module Dependabot
471
475
 
472
476
  sig { params(tag: Dependabot::Docker::Tag).returns(T.nilable(Dependabot::Package::PackageRelease)) }
473
477
  def get_tag_publication_details(tag)
474
- digest_info = with_retries(max_attempts: 3, errors: transient_docker_errors) do
475
- client = docker_registry_client
476
- client.digest(docker_repo_name, tag.name)
477
- end
478
-
479
- first_digest = extract_digest_from_response(digest_info, tag)
480
- return nil unless first_digest
481
-
482
- # When digest_info is an Array the registry returned a manifest list
483
- # (OCI image index) and the extracted digest points at a platform-
484
- # specific *manifest*, not a blob. Use the correct endpoint so the
485
- # HEAD request succeeds on registries like ghcr.io.
486
- endpoint = digest_info.is_a?(Array) ? "manifests" : "blobs"
487
- head_response = with_retries(max_attempts: 3, errors: transient_docker_errors) do
488
- client = docker_registry_client
489
- client.dohead "v2/#{docker_repo_name}/#{endpoint}/#{first_digest}"
490
- end
491
-
492
- published_date = published_date_from_response_headers(head_response.headers, tag.name)
478
+ published_date = registry_tag_release_date(tag)
479
+ return nil unless published_date
493
480
 
494
481
  Dependabot::Package::PackageRelease.new(
495
482
  version: release_version_for(tag),
@@ -499,58 +486,75 @@ module Dependabot
499
486
  url: nil,
500
487
  package_type: "docker"
501
488
  )
502
- rescue *transient_docker_errors,
489
+ end
490
+
491
+ sig { params(tag: Dependabot::Docker::Tag).returns(T.nilable(Time)) }
492
+ def registry_tag_release_date(tag)
493
+ return docker_hub_tag_release_date(tag) if using_dockerhub?
494
+
495
+ Dependabot.logger.info(
496
+ "No verified registry publication date source for #{registry_hostname}; " \
497
+ "skipping cooldown for #{docker_repo_name}:#{tag.name}"
498
+ )
499
+ nil
500
+ rescue JSON::ParserError, ArgumentError, TypeError, Excon::Error::Socket, Excon::Error::Timeout,
501
+ RegistryError, PrivateSourceBadResponse, PrivateSourceAuthenticationFailure,
502
+ *transient_docker_errors,
503
503
  DockerRegistry2::RegistryAuthenticationException,
504
504
  DockerRegistry2::RegistryAuthorizationException,
505
505
  RestClient::Forbidden,
506
506
  RestClient::TooManyRequests => e
507
- Dependabot.logger.warn(
508
- "Failed to fetch publication details for #{docker_repo_name}:#{tag.name}, " \
509
- "skipping cooldown: #{e.class} - #{e.message}"
507
+ Dependabot.logger.info(
508
+ "Failed to fetch registry tag metadata for #{docker_repo_name}:#{tag.name}: #{e.message}"
510
509
  )
511
510
  nil
512
511
  end
513
512
 
514
- sig { params(headers: T::Hash[Symbol, String], tag_name: String).returns(T.nilable(Time)) }
515
- def published_date_from_response_headers(headers, tag_name)
516
- last_modified = headers[:last_modified]
517
- return nil unless last_modified
513
+ sig { params(tag: Dependabot::Docker::Tag).returns(T.nilable(Time)) }
514
+ def docker_hub_tag_release_date(tag)
515
+ namespace, repository = docker_repo_name.split("/", 2)
516
+ return unless namespace && repository
518
517
 
519
- Time.parse(last_modified)
520
- rescue ArgumentError, TypeError => e
521
- Dependabot.logger.info(
522
- "Invalid Last-Modified header for #{docker_repo_name}:#{tag_name}: #{e.message}"
518
+ digest = digest_of(tag.name)
519
+ return unless digest
520
+
521
+ response = Dependabot::RegistryClient.get(
522
+ url: docker_hub_tag_url(namespace, repository, tag.name),
523
+ headers: { "Accept" => "application/json" },
524
+ options: {
525
+ connect_timeout: docker_open_timeout_in_seconds,
526
+ read_timeout: docker_read_timeout_in_seconds,
527
+ write_timeout: docker_read_timeout_in_seconds
528
+ }
523
529
  )
524
- nil
530
+ return unless response.status == 200
531
+
532
+ metadata = JSON.parse(response.body)
533
+ return unless metadata.is_a?(Hash)
534
+ return unless docker_hub_digest_matches_update?(metadata["digest"], digest)
535
+
536
+ timestamp = metadata["tag_last_pushed"]
537
+ Time.iso8601(timestamp) if timestamp.is_a?(String)
525
538
  end
526
539
 
527
- sig do
528
- params(
529
- digest_info: Object,
530
- tag: Dependabot::Docker::Tag
531
- ).returns(T.nilable(String))
540
+ sig { params(metadata_digest: Object, digest: String).returns(T::Boolean) }
541
+ def docker_hub_digest_matches_update?(metadata_digest, digest)
542
+ return digest_requirements.empty? && !pin_digests? if metadata_digest.nil?
543
+
544
+ matching_digest?(metadata_digest, digest)
532
545
  end
533
- def extract_digest_from_response(digest_info, tag)
534
- # digest_info can be either a String or an Array depending on the registry response
535
- case digest_info
536
- when Array
537
- if digest_info.empty?
538
- Dependabot.logger.warn(
539
- "Empty digest_info array for #{docker_repo_name}:#{tag.name}"
540
- )
541
- return nil
542
- end
543
- digest = digest_info.first&.fetch("digest")
544
- digest if digest.is_a?(String)
545
- when String
546
- digest_info
547
- else
548
- Dependabot.logger.warn(
549
- "Unexpected digest_info type for #{docker_repo_name}:#{tag.name}: " \
550
- "#{digest_info.class} (expected String or Array)"
551
- )
552
- nil
553
- end
546
+
547
+ sig { params(namespace: String, repository: String, tag: String).returns(String) }
548
+ def docker_hub_tag_url(namespace, repository, tag)
549
+ escaped = [namespace, repository, tag].map { |value| URI.encode_www_form_component(value) }
550
+ "https://hub.docker.com/v2/namespaces/#{escaped[0]}/repositories/#{escaped[1]}/tags/#{escaped[2]}"
551
+ end
552
+
553
+ sig { params(first: Object, second: Object).returns(T::Boolean) }
554
+ def matching_digest?(first, second)
555
+ return false unless first.is_a?(String) && second.is_a?(String)
556
+
557
+ first.delete_prefix("sha256:").casecmp?(second.delete_prefix("sha256:")) || false
554
558
  end
555
559
 
556
560
  sig do
@@ -1088,8 +1092,7 @@ module Dependabot
1088
1092
  # don't change the version string, so the default cooldown window applies
1089
1093
  # (semver-specific windows require a version delta, and the tag may be
1090
1094
  # non-comparable like "alpine"). Fails open (returns false) when the
1091
- # publication date can't be determined, so a missing Last-Modified header
1092
- # never permanently blocks an update.
1095
+ # publication date can't be determined and records an unavailable-date warning.
1093
1096
  sig { params(tag_name: String).returns(T::Boolean) }
1094
1097
  def digest_within_cooldown?(tag_name)
1095
1098
  return false if should_skip_cooldown?
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.396.0
4
+ version: 0.397.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.396.0
18
+ version: 0.397.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.396.0
25
+ version: 0.397.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.396.0
269
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.397.0
270
270
  rdoc_options: []
271
271
  require_paths:
272
272
  - lib