Skip to content
Merged
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
13 changes: 9 additions & 4 deletions docker/lib/dependabot/docker/file_fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module Docker
class FileFetcher < Dependabot::FileFetchers::Base
YAML_REGEXP = /^[^\.]+\.ya?ml$/i.freeze
DOCKER_REGEXP = /dockerfile/i.freeze
HELM_REGEXP = /values[\-a-zA-Z_0-9]*\.yaml/i.freeze

def self.required_files_in?(filenames)
filenames.any? { |f| f.match?(DOCKER_REGEXP) } or
Expand Down Expand Up @@ -85,10 +86,14 @@ def likely_kubernetes_resource?(resource)
def correctly_encoded_yamlfiles
candidate_files = yamlfiles.select { |f| f.content.valid_encoding? }
candidate_files.select do |f|
# This doesn't handle multi-resource files, but it shouldn't matter, since the first resource
# in a multi-resource file had better be a valid k8s resource
content = ::YAML.safe_load(f.content, aliases: true)
likely_kubernetes_resource?(content)
if f.type == "file" && f.name.match?(HELM_REGEXP)
true
else
# This doesn't handle multi-resource files, but it shouldn't matter, since the first resource
# in a multi-resource file had better be a valid k8s resource
content = ::YAML.safe_load(f.content, aliases: true)
likely_kubernetes_resource?(content)
end
rescue ::Psych::Exception
false
end
Expand Down
18 changes: 18 additions & 0 deletions docker/lib/dependabot/docker/file_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ def deep_fetch_images_from_hash(json_object)
images =
if !img.nil? && img.is_a?(String) && !img.empty?
[img]
elsif !img.nil? && img.is_a?(Hash) && !img.empty?
parse_helm(img)
else
[]
end
Expand All @@ -225,6 +227,22 @@ def manifest_files
# Dependencies include both Dockerfiles and yaml, select yaml.
dependency_files.select { |f| f.type == "file" && f.name.match?(/^[^\.]+\.ya?ml/i) }
end

def parse_helm(img_hash)
repo = img_hash.fetch("repository", nil)
tag = img_hash.key?("tag") ? img_hash.fetch("tag", nil) : img_hash.fetch("version", nil)
registry = img_hash.fetch("registry", nil)

if !repo.nil? && !registry.nil? && !tag.nil?
["#{registry}/#{repo}:#{tag}"]
elsif !repo.nil? && !tag.nil?
["#{repo}:#{tag}"]
elsif !repo.nil?
[repo]
else
[]
end
end
end
end
end
Expand Down
40 changes: 34 additions & 6 deletions docker/lib/dependabot/docker/file_updater.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ def self.updated_files_regex

def updated_dependency_files
updated_files = []

dependency_files.each do |file|
next unless requirement_changed?(file, dependency)

Expand Down Expand Up @@ -153,13 +152,29 @@ def private_registry_url(file)
end

def updated_yaml_content(file)
updated_content = update_image(file)
updated_content = file.name == "values.yaml" ? update_helm(file) : update_image(file)

raise "Expected content to change!" if updated_content == file.content

updated_content
end

def update_helm(file)
# TODO: this won't work if two images have the same tag version
old_tags = old_helm_tags(file)
return if old_tags.empty?

modified_content = file.content

old_tags.each do |old_tag|
old_tag_regex = /^\s+(?:-\s)?(?:tag|version):\s+#{old_tag}(?=\s|$)/
modified_content = modified_content.gsub(old_tag_regex) do |old_img_tag|
old_img_tag.gsub(old_tag.to_s, new_yaml_tag(file).to_s)
end
end
modified_content
end

def update_image(file)
old_images = old_yaml_images(file)
return if old_images.empty?
Expand All @@ -176,13 +191,18 @@ def update_image(file)
end

def new_yaml_image(file)
elt = dependency.requirements.find { |r| r[:file] == file.name }
prefix = elt.fetch(:source)[:registry] ? "#{elt.fetch(:source)[:registry]}/" : ""
digest = elt.fetch(:source)[:digest] ? "@#{elt.fetch(:source)[:digest]}" : ""
tag = elt.fetch(:source)[:tag] ? ":#{elt.fetch(:source)[:tag]}" : ""
element = dependency.requirements.find { |r| r[:file] == file.name }
prefix = element.fetch(:source)[:registry] ? "#{element.fetch(:source)[:registry]}/" : ""
digest = element.fetch(:source)[:digest] ? "@#{element.fetch(:source)[:digest]}" : ""
tag = element.fetch(:source)[:tag] ? ":#{element.fetch(:source)[:tag]}" : ""
"#{prefix}#{dependency.name}#{tag}#{digest}"
end

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

def old_yaml_images(file)
dependency.
previous_requirements.
Expand All @@ -193,6 +213,14 @@ def old_yaml_images(file)
"#{prefix}#{dependency.name}#{tag}#{digest}"
end
end

def old_helm_tags(file)
dependency.
previous_requirements.
select { |r| r[:file] == file.name }.map do |r|
r.fetch(:source)[:tag] || ""
end
end
end
end
end
Expand Down
53 changes: 53 additions & 0 deletions docker/spec/dependabot/docker/file_fetcher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -268,5 +268,58 @@
to match_array(%w(pod.yaml))
end
end

context "with a Helm values file" do
before do
stub_request(:get, url + "?ref=sha").
with(headers: { "Authorization" => "token token" }).
to_return(
status: 200,
body: fixture("github", "contents_helm_repo.json"),
headers: { "content-type" => "application/json" }
)

stub_request(:get, File.join(url, "values.yaml?ref=sha")).
with(headers: { "Authorization" => "token token" }).
to_return(
status: 200,
body: values_fixture,
headers: { "content-type" => "application/json" }
)

stub_request(:get, File.join(url, "other-values.yaml?ref=sha")).
with(headers: { "Authorization" => "token token" }).
to_return(
status: 200,
body: values_fixture,
headers: { "content-type" => "application/json" }
)

stub_request(:get, File.join(url, "values_other.yaml?ref=sha")).
with(headers: { "Authorization" => "token token" }).
to_return(
status: 200,
body: values_fixture,
headers: { "content-type" => "application/json" }
)

stub_request(:get, File.join(url, "values2.yaml?ref=sha")).
with(headers: { "Authorization" => "token token" }).
to_return(
status: 200,
body: values_fixture,
headers: { "content-type" => "application/json" }
)
end

let(:values_fixture) { fixture("github", "contents_values_yaml.json") }
let(:options) { { kubernetes_updates: true } }

it "fetches the values.yaml" do
expect(file_fetcher_instance.files.count).to eq(4)
expect(file_fetcher_instance.files.map(&:name)).
to match_array(["other-values.yaml", "values.yaml", "values2.yaml", "values_other.yaml"])
end
end
end
end
107 changes: 107 additions & 0 deletions docker/spec/dependabot/docker/file_parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1318,4 +1318,111 @@
end
end
end

let(:helmfiles) { [helmfile] }
let(:helmfile) do
Dependabot::DependencyFile.new(name: helmfile_fixture_name, content: helmfile_body)
end
let(:helmfile_body) do
fixture("helm", "yaml", helmfile_fixture_name)
end
let(:helmfile_fixture_name) { "values.yaml" }
let(:helm_parser) { described_class.new(dependency_files: helmfiles, source: source) }

describe "YAML parse" do
subject(:dependencies) { helm_parser.parse }

its(:length) { is_expected.to eq(1) }

describe "the first dependency" do
subject(:dependency) { dependencies.first }
let(:expected_requirements) do
[{
requirement: nil,
groups: [],
file: "values.yaml",
source: { registry: "registry.example.com", tag: "1.14.2" }
}]
end

it "has the right details" do
expect(dependency).to be_a(Dependabot::Dependency)
expect(dependency.name).to eq("nginx")
expect(dependency.version).to eq("1.14.2")
expect(dependency.requirements).to eq(expected_requirements)
end
end

context "with no image" do
let(:helmfile_fixture_name) { "empty.yaml" }
its(:length) { is_expected.to eq(0) }
end

context "with no registry" do
let(:helmfile_fixture_name) { "no-registry.yaml" }
its(:length) { is_expected.to eq(1) }

describe "the first dependency" do
subject(:dependency) { dependencies.first }
let(:expected_requirements) do
[{
requirement: nil,
groups: [],
file: "no-registry.yaml",
source: { registry: "mcr.microsoft.com", tag: "v1.2.3" }
}]
end

it "has the right details" do
expect(dependency).to be_a(Dependabot::Dependency)
expect(dependency.name).to eq("sql/sql")
expect(dependency.version).to eq("v1.2.3")
expect(dependency.requirements).to eq(expected_requirements)
end
end
end

context "with multiple images" do
let(:helmfile_fixture_name) { "multi-image.yaml" }
its(:length) { is_expected.to eq(2) }

describe "the first dependency" do
subject(:dependency) { dependencies.first }
let(:expected_requirements) do
[{
requirement: nil,
groups: [],
file: "multi-image.yaml",
source: { registry: "burns.azurecr.io", tag: "1.14.2" }
}]
end

it "has the right details" do
expect(dependency).to be_a(Dependabot::Dependency)
expect(dependency.name).to eq("nginx")
expect(dependency.version).to eq("1.14.2")
expect(dependency.requirements).to eq(expected_requirements)
end
end

describe "the second dependency" do
subject(:dependency) { dependencies.last }
let(:expected_requirements) do
[{
requirement: nil,
groups: [],
file: "multi-image.yaml",
source: { tag: "18.04" }
}]
end

it "has the right details" do
expect(dependency).to be_a(Dependabot::Dependency)
expect(dependency.name).to eq("canonical/ubuntu")
expect(dependency.version).to eq("18.04")
expect(dependency.requirements).to eq(expected_requirements)
end
end
end
end
end
Loading