-
Notifications
You must be signed in to change notification settings - Fork 1.4k
docker: add support for org.opencontainers.image.version and org.opencontainers.image.revision
#13855
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
docker: add support for org.opencontainers.image.version and org.opencontainers.image.revision
#13855
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,24 +13,89 @@ class MetadataFinder < Dependabot::MetadataFinders::Base | |
|
|
||
| private | ||
|
|
||
| # Finds the repository for the Docker image using OCI annotations. | ||
| # @see https://specs.opencontainers.org/image-spec/annotations/ | ||
| sig { override.returns(T.nilable(Dependabot::Source)) } | ||
| def look_up_source | ||
| return if dependency.requirements.empty? | ||
|
|
||
| new_source = dependency.requirements.first&.fetch(:source) | ||
| return unless new_source && new_source[:registry] && new_source[:tag] | ||
| return unless new_source && new_source[:registry] && (new_source[:tag] || new_source[:digest]) | ||
|
|
||
| image_ref = "#{new_source[:registry]}/#{dependency.name}:#{new_source[:tag]}" | ||
| image_details_output = SharedHelpers.run_shell_command("regctl image inspect #{image_ref}") | ||
| image_details = JSON.parse(image_details_output) | ||
| image_source = image_details.dig("config", "Labels", "org.opencontainers.image.source") | ||
| details = image_details(new_source) | ||
| image_source = details.dig("config", "Labels", "org.opencontainers.image.source") | ||
| # Return early if the org.opencontainers.image.source label is not present | ||
| return unless image_source | ||
|
|
||
| Dependabot::Source.from_url(image_source) | ||
| # If we have a tag, return the source directly without additional version metadata | ||
| return Dependabot::Source.from_url(image_source) if new_source[:tag] | ||
|
|
||
| # If we only have a digest, we need to look for the version label to build the source | ||
| build_source_from_image_version(image_source, details) | ||
| rescue StandardError => e | ||
| Dependabot.logger.warn("Error looking up Docker source: #{e.message}") | ||
| nil | ||
| end | ||
|
|
||
| sig do | ||
| params( | ||
| source: T::Hash[Symbol, T.untyped] | ||
| ).returns( | ||
| T::Hash[String, T.untyped] | ||
| ) | ||
| end | ||
| def image_details(source) | ||
| registry = source[:registry] | ||
| tag = source[:tag] | ||
| digest = source[:digest] | ||
|
|
||
| image_ref = | ||
| # If both tag and digest are present, use the digest as docker ignores the tag when a digest is present | ||
| if digest | ||
| "#{registry}/#{dependency.name}@sha256:#{digest}" | ||
| else | ||
| "#{registry}/#{dependency.name}:#{tag}" | ||
| end | ||
|
|
||
| Dependabot.logger.info("Looking up Docker source #{image_ref}") | ||
| output = SharedHelpers.run_shell_command("regctl image inspect #{image_ref}") | ||
| JSON.parse(output) | ||
| end | ||
|
|
||
| # Builds a Dependabot::Source object using the OCI image version label. | ||
| # | ||
| # This is used as a fallback when an image is referenced by digest rather than a tag | ||
| sig do | ||
| params( | ||
| image_source: String, | ||
| details: T::Hash[String, T.untyped] | ||
| ).returns(T.nilable(Dependabot::Source)) | ||
| end | ||
| def build_source_from_image_version(image_source, details) | ||
| image_version = details.dig("config", "Labels", "org.opencontainers.image.version") | ||
| revision = details.dig("config", "Labels", "org.opencontainers.image.revision") | ||
| # Sometimes the versions are not tags (e.g., "24.04") | ||
| # We only want to build a source if the version looks like a tag (starts with "v") | ||
| # This is a safeguard for a first iteration. We may adjust this later based on user feedback. | ||
| tag_like = image_version&.start_with?("v") | ||
|
kbukum1 marked this conversation as resolved.
|
||
|
|
||
| return unless tag_like || revision | ||
|
kbukum1 marked this conversation as resolved.
|
||
|
|
||
| parsed_source = Dependabot::Source.from_url(image_source) | ||
| return unless parsed_source | ||
|
|
||
| branch_info = image_version ? "image version '#{image_version}'" : "unknown image version" | ||
| commit_info = revision ? "revision '#{revision}'" : "no commit" | ||
| Dependabot.logger.info "Building source with #{branch_info} and #{commit_info}" | ||
|
|
||
| Dependabot::Source.new( | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure if there is any easier way to do this other than to change how |
||
| provider: parsed_source.provider, | ||
| repo: parsed_source.repo, | ||
| directory: parsed_source.directory, | ||
| branch: image_version, | ||
| commit: revision | ||
| ) | ||
| end | ||
| end | ||
| end | ||
| end | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,6 +55,53 @@ | |
| end | ||
| end | ||
|
|
||
| context "with a docker image with both tag and sha that has an OCI source annotation" do | ||
| let(:dependency_with_tag_and_sha_source) do | ||
| Dependabot::Dependency.new( | ||
| name: "dependabot-fixtures/docker-with-source", | ||
| version: "v0.0.2", | ||
| requirements: [{ | ||
| file: "Dockerfile", | ||
| requirement: nil, | ||
| groups: [], | ||
| source: { registry: "ghcr.io", | ||
| digest: "389a5a9a5457ed237b05d623ddc31a42fa97811051dcd02d7ca4ad46bd3edd3e", | ||
| tag: "v0.0.2" } | ||
| }], | ||
| package_manager: "docker" | ||
| ) | ||
| end | ||
|
|
||
| let(:dependency) { dependency_with_tag_and_sha_source } | ||
|
|
||
| it "finds the repository" do | ||
| expect(finder.source_url).to eq "https://github.com/dependabot-fixtures/docker-with-source" | ||
| end | ||
| end | ||
|
|
||
| context "with a digest but no tag or revision data" do | ||
| let(:dependency_with_sha_no_tag) do | ||
| Dependabot::Dependency.new( | ||
| name: "dependabot/dependabot-updater-npm", | ||
| version: "", | ||
| requirements: [{ | ||
| file: "Dockerfile", | ||
| requirement: nil, | ||
| groups: [], | ||
| source: { registry: "ghcr.io", | ||
| digest: "74c21f5886502d754c47a163975062e0d3065e3d19f43c8f48c9dbeb2126767e" } | ||
| }], | ||
| package_manager: "docker" | ||
| ) | ||
| end | ||
|
|
||
| let(:dependency) { dependency_with_sha_no_tag } | ||
|
|
||
| it "does not find the repository" do | ||
| expect(finder.source_url).to be_nil | ||
| end | ||
| end | ||
|
|
||
| context "with a docker image that lacks an OCI source annotation" do | ||
| let(:dependency) { dependency_without_source } | ||
|
|
||
|
|
@@ -73,7 +120,57 @@ | |
| requirement: nil, | ||
| groups: [], | ||
| source: { registry: "ghcr.io", | ||
| digest: "sha256:389a5a9a5457ed237b05d623ddc31a42fa97811051dcd02d7ca4ad46bd3edd3e" } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The only change I applied to this test is to remove the |
||
| digest: "389a5a9a5457ed237b05d623ddc31a42fa97811051dcd02d7ca4ad46bd3edd3e" } | ||
|
kbukum1 marked this conversation as resolved.
|
||
| }], | ||
| package_manager: "docker" | ||
| ) | ||
| end | ||
|
|
||
| it "doesn't find the repository" do | ||
| expect(finder.source_url).to be_nil | ||
| end | ||
| end | ||
|
|
||
| context "with a docker image without a tag but with org.opencontainers.image.version populated" do | ||
| let(:dependency) do | ||
| Dependabot::Dependency.new( | ||
| name: "regclient/regctl", | ||
| version: "", | ||
| requirements: [{ | ||
| file: "Dockerfile", | ||
| requirement: nil, | ||
| groups: [], | ||
| source: { registry: "ghcr.io", | ||
| digest: "a734f285c0962e46557bff24489fa0b0521455733f72d9eb30c4f7a5027aeed6" } | ||
| }], | ||
| package_manager: "docker" | ||
| ) | ||
| end | ||
|
|
||
| it "finds the repository" do | ||
| expect(finder.source_url).to eq "https://github.com/regclient/regclient" | ||
| # Normally, accessing private methods in tests is discouraged. | ||
| # In this case, we need to verify the branch and commit derived from the image within the source | ||
| # to ensure the source construction logic is correct. This access is for internal validation only. | ||
| # Exposing the source publicly only for this test would be less desirable. | ||
| expect(finder.send(:source).branch).to eq "v0.11.1" | ||
| expect(finder.send(:source).commit).to eq "bf3bcfc47173b49ee8000d1d3a1ac15036e83cf0" | ||
|
kbukum1 marked this conversation as resolved.
|
||
| end | ||
|
kbukum1 marked this conversation as resolved.
|
||
| end | ||
|
|
||
| context "with a docker image without a tag but without a proper tag format or revision" do | ||
| # The image used here has org.opencontainers.image.version set to "24.04" | ||
| # which refers to the Ubuntu version rather than a tag | ||
| let(:dependency) do | ||
| Dependabot::Dependency.new( | ||
| name: "maven", | ||
| version: "", | ||
| requirements: [{ | ||
| file: "Dockerfile", | ||
| requirement: nil, | ||
| groups: [], | ||
| source: { registry: "docker.io", | ||
| digest: "800a33a4cb190082c47abcd57944c852e1dece834f92c0aef65bea6336c52a72" } | ||
| }], | ||
| package_manager: "docker" | ||
| ) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.