Skip to content

Commit

Permalink
Add support for docker-compose.yml files. Closes dependabot#390
Browse files Browse the repository at this point in the history
  • Loading branch information
Pedro Pombeiro committed Nov 5, 2019
1 parent 509bafd commit fba0088
Show file tree
Hide file tree
Showing 39 changed files with 2,558 additions and 802 deletions.
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

0 comments on commit fba0088

Please sign in to comment.