Skip to content

Commit ffee40b

Browse files
committed
docker: add metadata finding support for org.opencontainers.image.version and org.opencontainers.image.revision
Extends metadata extraction to handle digest-only image references, where no tag is present. This new logic will run when one of the following additional OCI metadata fields are present along with the digest: - `org.opencontainers.image.version`: Packaged software version. This can sometimes be a tag - `org.opencontainers.image.revision`: Source control revision identifier for the packaged software
1 parent a789dd4 commit ffee40b

File tree

2 files changed

+113
-7
lines changed

2 files changed

+113
-7
lines changed

docker/lib/dependabot/docker/metadata_finder.rb

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,84 @@ class MetadataFinder < Dependabot::MetadataFinders::Base
1313

1414
private
1515

16+
# Finds the repository for the Docker image using OCI annotations.
17+
# @see https://specs.opencontainers.org/image-spec/annotations/
1618
sig { override.returns(T.nilable(Dependabot::Source)) }
1719
def look_up_source
1820
return if dependency.requirements.empty?
1921

2022
new_source = dependency.requirements.first&.fetch(:source)
21-
return unless new_source && new_source[:registry] && new_source[:tag]
23+
return unless new_source && new_source[:registry] && (new_source[:tag] || new_source[:digest])
2224

23-
image_ref = "#{new_source[:registry]}/#{dependency.name}:#{new_source[:tag]}"
24-
image_details_output = SharedHelpers.run_shell_command("regctl image inspect #{image_ref}")
25-
image_details = JSON.parse(image_details_output)
26-
image_source = image_details.dig("config", "Labels", "org.opencontainers.image.source")
25+
details = image_details(new_source)
26+
image_source = details.dig("config", "Labels", "org.opencontainers.image.source")
27+
# If the tag is present, we can use it directly to build the source
2728
return unless image_source
2829

29-
Dependabot::Source.from_url(image_source)
30+
return Dependabot::Source.from_url(image_source) if new_source[:tag]
31+
32+
build_source_from_image_version(image_source, details)
3033
rescue StandardError => e
3134
Dependabot.logger.warn("Error looking up Docker source: #{e.message}")
3235
nil
3336
end
37+
38+
sig do
39+
params(
40+
source: T::Hash[Symbol, T.untyped]
41+
).returns(
42+
T::Hash[String, T.untyped]
43+
)
44+
end
45+
def image_details(source)
46+
registry = source[:registry]
47+
tag = source[:tag]
48+
digest = source[:digest]
49+
50+
image_ref =
51+
# If both tag and digest are present, use the digest as docker ignores the tag when a digest is present
52+
if digest
53+
"#{registry}/#{dependency.name}@sha256:#{digest}"
54+
else
55+
"#{registry}/#{dependency.name}:#{tag}"
56+
end
57+
58+
Dependabot.logger.info("Looking up Docker source #{image_ref}")
59+
output = SharedHelpers.run_shell_command("regctl image inspect #{image_ref}")
60+
JSON.parse(output)
61+
end
62+
63+
# Builds a Dependabot::Source object using the OCI image version label.
64+
#
65+
# This is used as a fallback when an image is referenced by digest rather than a tag
66+
sig do
67+
params(
68+
image_source: String,
69+
details: T::Hash[String, T.untyped]
70+
).returns(T.nilable(Dependabot::Source))
71+
end
72+
def build_source_from_image_version(image_source, details)
73+
image_version = details.dig("config", "Labels", "org.opencontainers.image.version")
74+
revision = details.dig("config", "Labels", "org.opencontainers.image.revision")
75+
# Sometimes the versions are not tags (e.g., "24.04")
76+
# We only want to build a source if the version looks like a tag (starts with "v")
77+
is_tag = image_version&.start_with?("v")
78+
79+
return unless is_tag || revision
80+
81+
parsed_source = Dependabot::Source.from_url(image_source)
82+
return unless parsed_source
83+
84+
Dependabot.logger.info "Building source with branch '#{image_version}' and commit '#{revision}'"
85+
86+
Dependabot::Source.new(
87+
provider: parsed_source.provider,
88+
repo: parsed_source.repo,
89+
directory: parsed_source.directory,
90+
branch: image_version,
91+
commit: revision
92+
)
93+
end
3494
end
3595
end
3696
end

docker/spec/dependabot/docker/metadata_finder_spec.rb

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,53 @@
7373
requirement: nil,
7474
groups: [],
7575
source: { registry: "ghcr.io",
76-
digest: "sha256:389a5a9a5457ed237b05d623ddc31a42fa97811051dcd02d7ca4ad46bd3edd3e" }
76+
digest: "389a5a9a5457ed237b05d623ddc31a42fa97811051dcd02d7ca4ad46bd3edd3e" }
77+
}],
78+
package_manager: "docker"
79+
)
80+
end
81+
82+
it "doesn't find the repository" do
83+
expect(finder.source_url).to be_nil
84+
end
85+
end
86+
87+
context "with a docker image without a tag but with org.opencontainers.image.version populated" do
88+
let(:dependency) do
89+
Dependabot::Dependency.new(
90+
name: "regclient/regctl",
91+
version: "",
92+
requirements: [{
93+
file: "Dockerfile",
94+
requirement: nil,
95+
groups: [],
96+
source: { registry: "ghcr.io",
97+
digest: "a734f285c0962e46557bff24489fa0b0521455733f72d9eb30c4f7a5027aeed6" }
98+
}],
99+
package_manager: "docker"
100+
)
101+
end
102+
103+
it "finds the repository" do
104+
expect(finder.source_url).to eq "https://github.com/regclient/regclient"
105+
expect(finder.send(:source).branch).to eq "v0.11.1"
106+
expect(finder.send(:source).commit).to eq "bf3bcfc47173b49ee8000d1d3a1ac15036e83cf0"
107+
end
108+
end
109+
110+
context "with a docker image without a tag but without a proper tag format or revision" do
111+
# The image used here has org.opencontainers.image.version set to "24.04"
112+
# which refers to the Ubuntu version rather than a tag
113+
let(:dependency) do
114+
Dependabot::Dependency.new(
115+
name: "maven",
116+
version: "",
117+
requirements: [{
118+
file: "Dockerfile",
119+
requirement: nil,
120+
groups: [],
121+
source: { registry: "docker.io",
122+
digest: "800a33a4cb190082c47abcd57944c852e1dece834f92c0aef65bea6336c52a72" }
77123
}],
78124
package_manager: "docker"
79125
)

0 commit comments

Comments
 (0)