Skip to content

Commit 3c9844c

Browse files
Merge pull request #74 from containers/catch-single-arch
fix(lint): extend manifest list linter to manifests
2 parents 1f67f08 + b9d9b9c commit 3c9844c

1 file changed

Lines changed: 91 additions & 36 deletions

File tree

image/linter.py

Lines changed: 91 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from datetime import datetime, timezone
22
from image.auth import AUTH
33
from image.byteunit import ByteUnit
4+
from image.client import ContainerImageRegistryClient
45
from image.config import ContainerImageConfig
56
from image.containerimage import ContainerImage
7+
from image.errors import ContainerImageError
68
from image.manifest import ContainerImageManifest
79
from image.manifestlist import ContainerImageManifestList
810
from image.mediatypes import *
@@ -11,6 +13,7 @@
1113
from lint.result import LintResult
1214
from lint.rule import LintRule, DEFAULT_LINT_RULE_CONFIG
1315
from lint.status import LintStatus
16+
from typing import Union
1417

1518
DEFAULT_CONTAINER_IMAGE_LINTER_CONFIG = LinterConfig({
1619
"ManifestListSupportsRequiredPlatforms": {
@@ -100,36 +103,51 @@ class ContainerImageManifestLinter(
100103
pass
101104

102105
class 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

140158
class 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

199245
class 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

Comments
 (0)