@@ -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
3696end
0 commit comments