11from datetime import datetime , timezone
22from image .auth import AUTH
33from image .byteunit import ByteUnit
4+ from image .client import ContainerImageRegistryClient
45from image .config import ContainerImageConfig
56from image .containerimage import ContainerImage
7+ from image .errors import ContainerImageError
68from image .manifest import ContainerImageManifest
79from image .manifestlist import ContainerImageManifestList
810from image .mediatypes import *
1113from lint .result import LintResult
1214from lint .rule import LintRule , DEFAULT_LINT_RULE_CONFIG
1315from lint .status import LintStatus
16+ from typing import Union
1417
1518DEFAULT_CONTAINER_IMAGE_LINTER_CONFIG = LinterConfig ({
1619 "ManifestListSupportsRequiredPlatforms" : {
@@ -100,36 +103,51 @@ class ContainerImageManifestLinter(
100103 pass
101104
102105class ManifestListSupportsRequiredPlatforms (
103- LintRule [ContainerImageManifestList ]
106+ LintRule [Union [ ContainerImageManifestList , ContainerImageManifest ] ]
104107 ):
105108 """
106109 A lint rule ensuring a manifest list supports the required platforms
107110 """
108111 def lint (
109112 self ,
110- artifact : ContainerImageManifestList ,
111- config : LintRuleConfig = DEFAULT_LINT_RULE_CONFIG
113+ artifact : Union [ContainerImageManifestList , ContainerImageManifest ],
114+ config : LintRuleConfig = DEFAULT_LINT_RULE_CONFIG ,
115+ ** kwargs
112116 ) -> LintResult :
113117 """
114118 Implementation of the ManifestListSupportsRequiredPlatforms lint rule
115119 """
116120 try :
117121 required = config .config .get ("platforms" , [ "linux/amd64" ])
118- platforms = set (
119- str (entry .get_platform ()) for entry in artifact .get_entries ()
120- )
122+
123+ # The image is actually a manifest list
124+ if isinstance (artifact , ContainerImageManifestList ):
125+ image_type = "manifest list"
126+ platforms = set (
127+ str (entry .get_platform ()) for entry in artifact .get_entries ()
128+ )
129+ else :
130+ # The image was built as a manifest
131+ image_type = "manifest"
132+ manifest_config = kwargs .get ("manifest_config" )
133+ if not isinstance (manifest_config , ContainerImageConfig ):
134+ raise ContainerImageError (
135+ "manifest list lint rule attempted to lint a manifest " + \
136+ f"and no manifest config was given, got { type (manifest_config ).__name__ } "
137+ )
138+ platforms = set ([str (manifest_config .get_platform ())])
121139 missing = list (set (required ).difference (platforms ))
122140 if len (missing ) > 0 :
123141 return LintResult (
124142 status = LintStatus .ERROR ,
125- message = f"({ self .name ()} ) manifest list does not support " + \
126- "the following required platforms: " + \
143+ message = f"({ self .name ()} ) { image_type } does not " + \
144+ "support the following required platforms: " + \
127145 str ([ str (platform ) for platform in missing ])
128146 )
129147 return LintResult (
130148 status = LintStatus .INFO ,
131149 message = f"({ self .name ()} ) " + \
132- "manifest list supports all required platforms"
150+ f" { image_type } supports all required platforms"
133151 )
134152 except Exception as e :
135153 return LintResult (
@@ -138,57 +156,85 @@ def lint(
138156 )
139157
140158class ManifestListSupportsRequiredMediaTypes (
141- LintRule [ContainerImageManifestList ]
159+ LintRule [Union [ ContainerImageManifestList , ContainerImageManifest ] ]
142160 ):
143161 """
144162 A lint rule ensuring a manifest list and its manifests support the required
145163 media types
146164 """
147165 def lint (
148166 self ,
149- artifact : ContainerImageManifestList ,
167+ artifact : Union [ ContainerImageManifestList , ContainerImageManifest ] ,
150168 config : LintRuleConfig = DEFAULT_LINT_RULE_CONFIG ,
151169 ** kwargs
152170 ) -> LintResult :
153171 """
154172 Implementation of the ManifestListSupportsRequiredMediaTypes lint rule
155173 """
156174 try :
157- list_media_type = artifact .get_media_type ()
158- expected_list_media_types = config .config .get (
159- "manifest-list-media-types" ,
175+ allow_single_arch = config .config .get (
176+ "allow-single-arch" ,
177+ True
178+ )
179+ expected_manifest_media_types = config .config .get (
180+ "manifest-media-types" ,
160181 [
161- DOCKER_V2S2_LIST_MEDIA_TYPE ,
162- OCI_INDEX_MEDIA_TYPE
182+ DOCKER_V2S2_MEDIA_TYPE ,
183+ OCI_MANIFEST_MEDIA_TYPE
163184 ]
164185 )
165- if not list_media_type in expected_list_media_types :
166- return LintResult (
167- status = LintStatus .ERROR ,
168- message = f"({ self .name ()} ) " + \
169- f"manifest list has mediaType { list_media_type } , " + \
170- f"expected one of { str (expected_list_media_types )} "
171- )
172- for entry in artifact .get_entries ():
173- manifest_media_type = entry .get_media_type ()
174- expected_media_types = config .config .get (
175- "manifest-media-types" ,
186+
187+ # The image is actually a manifest list
188+ if isinstance (artifact , ContainerImageManifestList ):
189+ list_media_type = artifact .get_media_type ()
190+ expected_list_media_types = config .config .get (
191+ "manifest-list-media-types" ,
176192 [
177- DOCKER_V2S2_MEDIA_TYPE ,
178- OCI_MANIFEST_MEDIA_TYPE
193+ DOCKER_V2S2_LIST_MEDIA_TYPE ,
194+ OCI_INDEX_MEDIA_TYPE
179195 ]
180196 )
181- if not manifest_media_type in expected_media_types :
197+ if not list_media_type in expected_list_media_types :
182198 return LintResult (
183199 status = LintStatus .ERROR ,
184200 message = f"({ self .name ()} ) " + \
185- f"manifest { entry .get_platform ()} has mediaType " + \
186- f"{ manifest_media_type } , expected one of " + \
187- str (expected_media_types )
201+ f"manifest list has mediaType { list_media_type } , " + \
202+ f"expected one of { str (expected_list_media_types )} "
188203 )
204+ for entry in artifact .get_entries ():
205+ manifest_media_type = entry .get_media_type ()
206+ if not manifest_media_type in expected_manifest_media_types :
207+ return LintResult (
208+ status = LintStatus .ERROR ,
209+ message = f"({ self .name ()} ) " + \
210+ f"manifest { entry .get_platform ()} has mediaType " + \
211+ f"{ manifest_media_type } , expected one of " + \
212+ str (expected_manifest_media_types )
213+ )
214+ return LintResult (
215+ message = f"({ self .name ()} ) " + \
216+ "manifest list and manifests support expected mediaTypes"
217+ )
218+
219+ # The image was built as a manifest
220+ if not allow_single_arch :
221+ return LintResult (
222+ status = LintStatus .ERROR ,
223+ message = f"({ self .name ()} ) " + \
224+ f"got manifest, but expected manifest list"
225+ )
226+ manifest_media_type = artifact .get_media_type ()
227+ if not manifest_media_type in expected_manifest_media_types :
228+ return LintResult (
229+ status = LintStatus .ERROR ,
230+ message = f"({ self .name ()} ) " + \
231+ f"manifest has mediaType { manifest_media_type } " + \
232+ f", expected one of { str (expected_manifest_media_types )} "
233+ )
189234 return LintResult (
235+ status = LintStatus .INFO ,
190236 message = f"({ self .name ()} ) " + \
191- "manifest list and manifests support expected maediaTypes "
237+ "manifest supports expected mediaTypes "
192238 )
193239 except Exception as e :
194240 return LintResult (
@@ -197,10 +243,12 @@ def lint(
197243 )
198244
199245class ContainerImageManifestListLinter (
200- Linter [ContainerImageManifestList ]
246+ Linter [Union [ ContainerImageManifestList , ContainerImageManifest ] ]
201247 ):
202248 """
203- A linter for container image manifest lists
249+ A linter for container image manifest lists. Can apply the same checks to
250+ manifests in case a manifest is being built when a manifest list should be
251+ built.
204252 """
205253 pass
206254
@@ -412,6 +460,13 @@ def lint(
412460 manifest = manifest ,
413461 auth = auth
414462 )
463+ results .extend (
464+ self .manifest_list_linter .lint (
465+ manifest ,
466+ config ,
467+ manifest_config = img_config
468+ )
469+ )
415470 results .extend (self .config_linter .lint (img_config , config ))
416471
417472 # Even though it should always exist, this is protection against OOB
0 commit comments