Skip to content

Commit

Permalink
Add support for helm files.
Browse files Browse the repository at this point in the history
  • Loading branch information
dependabot-ci committed Sep 16, 2022
1 parent 9e15f16 commit 30cb2b0
Show file tree
Hide file tree
Showing 12 changed files with 603 additions and 6 deletions.
12 changes: 8 additions & 4 deletions docker/lib/dependabot/docker/file_fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,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 == "values.yaml"
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?
parseHelm(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 parseHelm(imgHash)
repo = imgHash.fetch("repository", nil)
tag = imgHash.has_key?("tag") ? imgHash.fetch("tag", nil) : imgHash.fetch("version", nil)
registry = imgHash.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
32 changes: 30 additions & 2 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 @@ -183,6 +198,11 @@ def new_yaml_image(file)
"#{prefix}#{dependency.name}#{tag}#{digest}"
end

def new_yaml_tag(file)
elt = dependency.requirements.find { |r| r[:file] == file.name }
elt.fetch(:source)[:tag] ? elt.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] ? r.fetch(:source)[:tag] : ""
end
end
end
end
end
Expand Down
29 changes: 29 additions & 0 deletions docker/spec/dependabot/docker/file_fetcher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -264,5 +264,34 @@
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" }
)
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(1)
expect(file_fetcher_instance.files.map(&:name)).
to match_array(%w(values.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

0 comments on commit 30cb2b0

Please sign in to comment.