Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for docker-compose.yml files. #1862

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions docker/lib/dependabot/common/file_parser_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# frozen_string_literal: true

require "docker_registry2"

require "dependabot/dependency"
require "dependabot/errors"
require "dependabot/docker/utils/credentials_finder"

module Dependabot
module Docker
module FileParserHelper
private

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

version_from_digest(
registry: parsed_info.fetch("registry"),
image: parsed_info.fetch("image"),
digest: parsed_info.fetch("digest")
)
end

def source_from(parsed_info)
source = {}

%w(registry tag digest).each do |part|
value = parsed_info.fetch(part)
source[part.to_sym] = value if value
end

source
end

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

repo = docker_repo_name(image, registry)
client = docker_registry_client(registry)
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 if standard_registry?(registry)

raise PrivateSourceAuthenticationFailure, registry
end

def docker_repo_name(image, registry)
return image unless standard_registry?(registry)
return image unless image.split("/").count < 2

"library/#{image}"
end

def docker_registry_client(registry)
if registry
credentials = registry_credentials(registry)

DockerRegistry2::Registry.new(
"https://#{registry}",
user: credentials&.fetch("username", nil),
password: credentials&.fetch("password", nil)
)
else
DockerRegistry2::Registry.new("https://registry.hub.docker.com")
end
end

def registry_credentials(registry_url)
credentials_finder.credentials_for_registry(registry_url)
end

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

def standard_registry?(registry)
return true if registry.nil?

registry == "registry.hub.docker.com"
end
end
end
end
92 changes: 92 additions & 0 deletions docker/lib/dependabot/common/file_updater_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# frozen_string_literal: true

require "dependabot/file_updaters"
require "dependabot/file_updaters/base"
require "dependabot/errors"

module Dependabot
module Docker
module FileUpdaterHelper
private

def update_digest_and_tag(file)
old_declaration_regex = digest_and_tag_regex(old_digest(file))

file.content.gsub(old_declaration_regex) do |old_dec|
old_dec.
gsub("@#{old_digest(file)}", "@#{new_digest(file)}").
gsub(":#{dependency.previous_version}",
":#{dependency.version}")
end
end

def update_tag(file)
return unless old_tag(file)

old_declaration =
if private_registry_url(file) then "#{private_registry_url(file)}/"
else ""
end
old_declaration += "#{dependency.name}:#{old_tag(file)}"

old_declaration_regex = tag_regex(old_declaration)

file.content.gsub(old_declaration_regex) do |old_dec|
old_dec.gsub(":#{old_tag(file)}", ":#{new_tag(file)}")
end
end

def fetch_file_source(file, reqs)
reqs.
find { |req| req[:file] == file.name }.
fetch(:source)
end

def fetch_property_in_file_source(file, reqs, property)
fetch_file_source(file, reqs).fetch(property)
end

def specified_with_digest?(file)
fetch_file_source(file, dependency.requirements)[:digest]
end

def new_digest(file)
return unless specified_with_digest?(file)

fetch_property_in_file_source(file, dependency.requirements, :digest)
end

def old_digest(file)
return unless specified_with_digest?(file)

fetch_property_in_file_source(
file,
dependency.previous_requirements,
:digest
)
end

def digest(file, reqs)
return unless specified_with_digest?(file)

fetch_property_in_file_source(file, reqs, :digest)
end

def new_tag(file)
fetch_property_in_file_source(file, dependency.requirements, :tag)
end

def old_tag(file)
fetch_property_in_file_source(
file,
dependency.previous_requirements,
:tag
)
end

def private_registry_url(file)
fetch_file_source(file, dependency.requirements)[:registry]
end
end
end
end
2 changes: 2 additions & 0 deletions docker/lib/dependabot/docker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
require "dependabot/docker/requirement"
require "dependabot/docker/version"

require_relative "docker_compose"

require "dependabot/pull_request_creator/labeler"
Dependabot::PullRequestCreator::Labeler.
register_label_details("docker", name: "docker", colour: "21ceff")
Expand Down
90 changes: 3 additions & 87 deletions docker/lib/dependabot/docker/file_parser.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
# frozen_string_literal: true

require "docker_registry2"

require "dependabot/dependency"
require "dependabot/file_parsers"
require "dependabot/file_parsers/base"
require "dependabot/errors"
require "dependabot/docker/utils/credentials_finder"
require "dependabot/common/file_parser_helper"

module Dependabot
module Docker
class FileParser < Dependabot::FileParsers::Base
require "dependabot/file_parsers/base/dependency_set"

include Dependabot::Docker::FileParserHelper

# Details of Docker regular expressions is at
# https://github.com/docker/distribution/blob/master/reference/regexp.go
DOMAIN_COMPONENT =
Expand Down Expand Up @@ -72,88 +70,6 @@ def dockerfiles
dependency_files
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")
)
end

def source_from(parsed_from_line)
source = {}

if parsed_from_line.fetch("registry")
source[:registry] = parsed_from_line.fetch("registry")
end

if parsed_from_line.fetch("tag")
source[:tag] = parsed_from_line.fetch("tag")
end

if parsed_from_line.fetch("digest")
source[:digest] = parsed_from_line.fetch("digest")
end

source
end

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

repo = docker_repo_name(image, registry)
client = docker_registry_client(registry)
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 if standard_registry?(registry)

raise PrivateSourceAuthenticationFailure, registry
end

def docker_repo_name(image, registry)
return image unless standard_registry?(registry)
return image unless image.split("/").count < 2

"library/#{image}"
end

def docker_registry_client(registry)
if registry
credentials = registry_credentials(registry)

DockerRegistry2::Registry.new(
"https://#{registry}",
user: credentials&.fetch("username", nil),
password: credentials&.fetch("password", nil)
)
else
DockerRegistry2::Registry.new("https://registry.hub.docker.com")
end
end

def registry_credentials(registry_url)
credentials_finder.credentials_for_registry(registry_url)
end

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

def standard_registry?(registry)
return true if registry.nil?

registry == "registry.hub.docker.com"
end

def check_required_files
# Just check if there are any files at all.
return if dependency_files.any?
Expand Down
73 changes: 8 additions & 65 deletions docker/lib/dependabot/docker/file_updater.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
require "dependabot/file_updaters"
require "dependabot/file_updaters/base"
require "dependabot/errors"
require "dependabot/common/file_updater_helper"

module Dependabot
module Docker
class FileUpdater < Dependabot::FileUpdaters::Base
include Dependabot::Docker::FileUpdaterHelper

FROM_REGEX = /FROM/i.freeze

def self.updated_files_regex
Expand Down Expand Up @@ -59,74 +62,14 @@ def updated_dockerfile_content(file)
updated_content
end

def update_digest_and_tag(file)
old_declaration_regex = /^#{FROM_REGEX}\s+.*@#{old_digest(file)}/

file.content.gsub(old_declaration_regex) do |old_dec|
old_dec.
gsub("@#{old_digest(file)}", "@#{new_digest(file)}").
gsub(":#{dependency.previous_version}",
":#{dependency.version}")
end
def digest_and_tag_regex(digest)
/^#{FROM_REGEX}\s+.*@#{digest}/
end

def update_tag(file)
return unless old_tag(file)

old_declaration =
if private_registry_url(file) then "#{private_registry_url(file)}/"
else ""
end
old_declaration += "#{dependency.name}:#{old_tag(file)}"
escaped_declaration = Regexp.escape(old_declaration)

old_declaration_regex =
%r{^#{FROM_REGEX}\s+(docker\.io/)?#{escaped_declaration}(?=\s|$)}

file.content.gsub(old_declaration_regex) do |old_dec|
old_dec.gsub(":#{old_tag(file)}", ":#{new_tag(file)}")
end
end

def specified_with_digest?(file)
dependency.
requirements.
find { |r| r[:file] == file.name }.
fetch(:source)[:digest]
end

def new_digest(file)
return unless specified_with_digest?(file)

dependency.requirements.
find { |r| r[:file] == file.name }.
fetch(:source).fetch(:digest)
end

def old_digest(file)
return unless specified_with_digest?(file)

dependency.previous_requirements.
find { |r| r[:file] == file.name }.
fetch(:source).fetch(:digest)
end

def new_tag(file)
dependency.requirements.
find { |r| r[:file] == file.name }.
fetch(:source)[:tag]
end

def old_tag(file)
dependency.previous_requirements.
find { |r| r[:file] == file.name }.
fetch(:source)[:tag]
end
def tag_regex(declaration)
escaped_declaration = Regexp.escape(declaration)

def private_registry_url(file)
dependency.requirements.
find { |r| r[:file] == file.name }.
fetch(:source)[:registry]
%r{^#{FROM_REGEX}\s+(docker\.io/)?#{escaped_declaration}(?=\s|$)}
end
end
end
Expand Down
Loading