Skip to content

Commit

Permalink
Don't resolve digest to versions unnecessarily
Browse files Browse the repository at this point in the history
* If the Dockerfile specifies a version, we don't need to do this,
  since we already have a previous version.

* If the Dockerfile only specifies a SHA, I don't think we need it
  either because we will be updating to another SHA, so the user seems
  uninterested in specific versions. And this case doesn't really work
  in most cases anyways because of how inefficient this is.

With this patch, we can find updates to Dockerfiles specifiying only
SHAs much faster.
  • Loading branch information
deivid-rodriguez committed Dec 7, 2022
1 parent 3005635 commit 1cd6b45
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 673 deletions.
62 changes: 1 addition & 61 deletions docker/lib/dependabot/docker/file_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
require "dependabot/file_parsers"
require "dependabot/file_parsers/base"
require "dependabot/errors"
require "dependabot/docker/utils/credentials_finder"

module Dependabot
module Docker
Expand Down Expand Up @@ -77,13 +76,7 @@ def dockerfiles
end

def version_from(parsed_from_line)
return parsed_from_line.fetch("tag") if parsed_from_line.fetch("tag")

version_from_digest(
registry: parsed_from_line.fetch("registry"),
image: parsed_from_line.fetch("image"),
digest: parsed_from_line.fetch("digest")
)
parsed_from_line.fetch("tag") || parsed_from_line.fetch("digest")
end

def source_from(parsed_from_line)
Expand All @@ -98,59 +91,6 @@ def source_from(parsed_from_line)
source
end

def version_from_digest(registry:, image:, digest:)
return unless digest

registry_details = fetch_registry_details(registry)
repo = docker_repo_name(image, registry_details["registry"])
client = docker_registry_client(registry_details["registry"], registry_details["credentials"])
client.tags(repo, auto_paginate: true).fetch("tags").find do |tag|
digest == client.digest(repo, tag)
rescue DockerRegistry2::NotFound
# Shouldn't happen, but it does. Example of existing tag with
# no manifest is "library/python", "2-windowsservercore".
false
end
rescue DockerRegistry2::RegistryAuthenticationException,
RestClient::Forbidden
raise PrivateSourceAuthenticationFailure, registry_details["registry"]
rescue RestClient::Exceptions::OpenTimeout,
RestClient::Exceptions::ReadTimeout
raise if credentials_finder.using_dockerhub?(registry_details["registry"])

raise PrivateSourceTimedOut, registry_details["registry"]
end

def docker_repo_name(image, registry)
return image if image.include? "/"
return "library/#{image}" if credentials_finder.using_dockerhub?(registry)

image
end

def docker_registry_client(registry_hostname, registry_credentials)
unless credentials_finder.using_dockerhub?(registry_hostname)
return DockerRegistry2::Registry.new("https://#{registry_hostname}")
end

DockerRegistry2::Registry.new(
"https://#{registry_hostname}",
user: registry_credentials&.fetch("username", nil),
password: registry_credentials&.fetch("password", nil),
read_timeout: 10
)
end

def fetch_registry_details(registry)
registry ||= credentials_finder.base_registry
credentials = credentials_finder.credentials_for_registry(registry)
{ "registry" => registry, "credentials" => credentials }
end

def credentials_finder
@credentials_finder ||= Utils::CredentialsFinder.new(credentials)
end

def check_required_files
# Just check if there are any files at all.
return if dependency_files.any?
Expand Down
14 changes: 10 additions & 4 deletions docker/lib/dependabot/docker/update_checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class UpdateChecker < Dependabot::UpdateCheckers::Base
#{VERSION_WITH_SFX}|
#{VERSION_WITH_PFX_AND_SFX}
/x
DIGEST = /^sha256:[0-9a-f]{64}$/

def latest_version
latest_version_from(dependency.version)
Expand Down Expand Up @@ -124,10 +125,10 @@ def version_tag_up_to_date?(version)

def digest_up_to_date?
dependency.requirements.all? do |req|
next true unless req.fetch(:source)[:digest]
next true unless (new_digest = digest_of(dependency.version))
next true unless (digest = req.fetch(:source)[:digest])
next true unless (new_digest = updated_digest)

req.fetch(:source).fetch(:digest) == new_digest
digest == new_digest
end
end

Expand All @@ -141,6 +142,7 @@ def latest_version_from(version)
# NOTE: It's important that this *always* returns a version (even if
# it's the existing one) as it is what we later check the digest of.
def fetch_latest_version(version)
return latest_digest if version.match?(DIGEST)
return version unless version.match?(NAME_WITH_VERSION)

# Prune out any downgrade tags before checking for pre-releases
Expand Down Expand Up @@ -230,7 +232,11 @@ def canonical_version?(tag)
end

def updated_digest
@updated_digest ||= digest_of(latest_version)
@updated_digest ||= if latest_version.match?(DIGEST)
latest_digest
else
digest_of(latest_version)
end
end

def tags_from_registry
Expand Down
Loading

0 comments on commit 1cd6b45

Please sign in to comment.