Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ pip install containerimage-py
1. Clone this repository
2. [Build the project from source](#build)
3. Locate the `.whl` (wheel) file in the `dist` folder
- It should be named something like so: `containerimage_py-1.1.3-py3-none-any.whl`
- It should be named something like so: `containerimage_py-1.1.4-py3-none-any.whl`
4. Run the following command from the root of the repository, replacing the name of the `.whl` file if necessary
```
pip install dist/containerimage_py-1.1.3-py3-none-any.whl
pip install dist/containerimage_py-1.1.4-py3-none-any.whl
```

## Build
Expand Down
2 changes: 1 addition & 1 deletion doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
project = 'containerimage-py'
copyright = '2025, IBM Corporation'
author = 'Ethan Balcik'
release = '1.1.3'
release = '1.1.4'

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
Expand Down
4 changes: 2 additions & 2 deletions doc/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@ Run the following command to install the latest version of this package using pi

1. Clone `the source repository <https://github.com/containers/containerimage-py>`_
2. Build the project from source following `the build instructions <Build_>`_
3. Locate the ``.whl`` (wheel) file in the ``dist`` folder. It should be named something like so: ``containerimage_py-1.1.3-py3-none-any.whl``
3. Locate the ``.whl`` (wheel) file in the ``dist`` folder. It should be named something like so: ``containerimage_py-1.1.4-py3-none-any.whl``
4. Run the following command from the root of the repository, replacing the name of the ``.whl`` file if necessary

.. code-block:: shell

pip install dist/containerimage_py-1.1.3-py3-none-any.whl
pip install dist/containerimage_py-1.1.4-py3-none-any.whl


Build
Expand Down
127 changes: 91 additions & 36 deletions image/linter.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from datetime import datetime, timezone
from image.auth import AUTH
from image.byteunit import ByteUnit
from image.client import ContainerImageRegistryClient
from image.config import ContainerImageConfig
from image.containerimage import ContainerImage
from image.errors import ContainerImageError
from image.manifest import ContainerImageManifest
from image.manifestlist import ContainerImageManifestList
from image.mediatypes import *
Expand All @@ -11,6 +13,7 @@
from lint.result import LintResult
from lint.rule import LintRule, DEFAULT_LINT_RULE_CONFIG
from lint.status import LintStatus
from typing import Union

DEFAULT_CONTAINER_IMAGE_LINTER_CONFIG = LinterConfig({
"ManifestListSupportsRequiredPlatforms": {
Expand Down Expand Up @@ -100,36 +103,51 @@ class ContainerImageManifestLinter(
pass

class ManifestListSupportsRequiredPlatforms(
LintRule[ContainerImageManifestList]
LintRule[Union[ContainerImageManifestList, ContainerImageManifest]]
):
"""
A lint rule ensuring a manifest list supports the required platforms
"""
def lint(
self,
artifact: ContainerImageManifestList,
config: LintRuleConfig=DEFAULT_LINT_RULE_CONFIG
artifact: Union[ContainerImageManifestList, ContainerImageManifest],
config: LintRuleConfig=DEFAULT_LINT_RULE_CONFIG,
**kwargs
) -> LintResult:
"""
Implementation of the ManifestListSupportsRequiredPlatforms lint rule
"""
try:
required = config.config.get("platforms", [ "linux/amd64" ])
platforms = set(
str(entry.get_platform()) for entry in artifact.get_entries()
)

# The image is actually a manifest list
if isinstance(artifact, ContainerImageManifestList):
image_type = "manifest list"
platforms = set(
str(entry.get_platform()) for entry in artifact.get_entries()
)
else:
# The image was built as a manifest
image_type = "manifest"
manifest_config = kwargs.get("manifest_config")
if not isinstance(manifest_config, ContainerImageConfig):
raise ContainerImageError(
"manifest list lint rule attempted to lint a manifest " + \
f"and no manifest config was given, got {type(manifest_config).__name__}"
)
platforms = set([str(manifest_config.get_platform())])
missing = list(set(required).difference(platforms))
if len(missing) > 0:
return LintResult(
status=LintStatus.ERROR,
message=f"({self.name()}) manifest list does not support " + \
"the following required platforms: " + \
message=f"({self.name()}) {image_type} does not " + \
"support the following required platforms: " + \
str([ str(platform) for platform in missing ])
)
return LintResult(
status=LintStatus.INFO,
message=f"({self.name()}) " + \
"manifest list supports all required platforms"
f"{image_type} supports all required platforms"
)
except Exception as e:
return LintResult(
Expand All @@ -138,57 +156,85 @@ def lint(
)

class ManifestListSupportsRequiredMediaTypes(
LintRule[ContainerImageManifestList]
LintRule[Union[ContainerImageManifestList, ContainerImageManifest]]
):
"""
A lint rule ensuring a manifest list and its manifests support the required
media types
"""
def lint(
self,
artifact: ContainerImageManifestList,
artifact: Union[ContainerImageManifestList, ContainerImageManifest],
config: LintRuleConfig=DEFAULT_LINT_RULE_CONFIG,
**kwargs
) -> LintResult:
"""
Implementation of the ManifestListSupportsRequiredMediaTypes lint rule
"""
try:
list_media_type = artifact.get_media_type()
expected_list_media_types = config.config.get(
"manifest-list-media-types",
allow_single_arch = config.config.get(
"allow-single-arch",
True
)
expected_manifest_media_types = config.config.get(
"manifest-media-types",
[
DOCKER_V2S2_LIST_MEDIA_TYPE,
OCI_INDEX_MEDIA_TYPE
DOCKER_V2S2_MEDIA_TYPE,
OCI_MANIFEST_MEDIA_TYPE
]
)
if not list_media_type in expected_list_media_types:
return LintResult(
status=LintStatus.ERROR,
message=f"({self.name()}) " + \
f"manifest list has mediaType {list_media_type}, " + \
f"expected one of {str(expected_list_media_types)}"
)
for entry in artifact.get_entries():
manifest_media_type = entry.get_media_type()
expected_media_types = config.config.get(
"manifest-media-types",

# The image is actually a manifest list
if isinstance(artifact, ContainerImageManifestList):
list_media_type = artifact.get_media_type()
expected_list_media_types = config.config.get(
"manifest-list-media-types",
[
DOCKER_V2S2_MEDIA_TYPE,
OCI_MANIFEST_MEDIA_TYPE
DOCKER_V2S2_LIST_MEDIA_TYPE,
OCI_INDEX_MEDIA_TYPE
]
)
if not manifest_media_type in expected_media_types:
if not list_media_type in expected_list_media_types:
return LintResult(
status=LintStatus.ERROR,
message=f"({self.name()}) " + \
f"manifest {entry.get_platform()} has mediaType " + \
f"{manifest_media_type}, expected one of " + \
str(expected_media_types)
f"manifest list has mediaType {list_media_type}, " + \
f"expected one of {str(expected_list_media_types)}"
)
for entry in artifact.get_entries():
manifest_media_type = entry.get_media_type()
if not manifest_media_type in expected_manifest_media_types:
return LintResult(
status=LintStatus.ERROR,
message=f"({self.name()}) " + \
f"manifest {entry.get_platform()} has mediaType " + \
f"{manifest_media_type}, expected one of " + \
str(expected_manifest_media_types)
)
return LintResult(
message=f"({self.name()}) " + \
"manifest list and manifests support expected mediaTypes"
)

# The image was built as a manifest
if not allow_single_arch:
return LintResult(
status=LintStatus.ERROR,
message=f"({self.name()}) " + \
f"got manifest, but expected manifest list"
)
manifest_media_type = artifact.get_media_type()
if not manifest_media_type in expected_manifest_media_types:
return LintResult(
status=LintStatus.ERROR,
message=f"({self.name()}) " + \
f"manifest has mediaType {manifest_media_type} " + \
f", expected one of {str(expected_manifest_media_types)}"
)
return LintResult(
status=LintStatus.INFO,
message=f"({self.name()}) " + \
"manifest list and manifests support expected maediaTypes"
"manifest supports expected mediaTypes"
)
except Exception as e:
return LintResult(
Expand All @@ -197,10 +243,12 @@ def lint(
)

class ContainerImageManifestListLinter(
Linter[ContainerImageManifestList]
Linter[Union[ContainerImageManifestList, ContainerImageManifest]]
):
"""
A linter for container image manifest lists
A linter for container image manifest lists. Can apply the same checks to
manifests in case a manifest is being built when a manifest list should be
built.
"""
pass

Expand Down Expand Up @@ -412,6 +460,13 @@ def lint(
manifest=manifest,
auth=auth
)
results.extend(
self.manifest_list_linter.lint(
manifest,
config,
manifest_config=img_config
)
)
results.extend(self.config_linter.lint(img_config, config))

# Even though it should always exist, this is protection against OOB
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "containerimage-py"
version = "1.1.3"
version = "1.1.4"
authors = [
{name = "Ethan Balcik", email="ethanbalcik@ibm.com" }
]
Expand Down
Loading