Skip to content
Open
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
21 changes: 21 additions & 0 deletions base/images/images.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,35 @@ runtime-package-management = true
[images.container-distroless-minimal]
description = "Container Distroless Minimal Image"
definition = { type = "kiwi", path = "container-base/container-base.kiwi", profile = "distroless-minimal" }
tests.test-suites = [{ name = "static-image-checks" }]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the metadata updates here; their omission on the distroless containers was an oversight.


[images.container-distroless-minimal.capabilities]
machine-bootable = false
container = true
systemd = false
runtime-package-management = false

[images.container-distroless-base]
description = "Container Distroless Base Image"
definition = { type = "kiwi", path = "container-base/container-base.kiwi", profile = "distroless-base" }
tests.test-suites = [{ name = "static-image-checks" }]

[images.container-distroless-base.capabilities]
machine-bootable = false
container = true
systemd = false
runtime-package-management = false

[images.container-distroless-debug]
description = "Container Distroless Debug Image"
definition = { type = "kiwi", path = "container-base/container-base.kiwi", profile = "distroless-debug" }
tests.test-suites = [{ name = "static-image-checks" }]

[images.container-distroless-debug.capabilities]
machine-bootable = false
container = true
systemd = false
runtime-package-management = false

# ---- wsl ---------------------------------------------------------------

Expand Down
2 changes: 2 additions & 0 deletions base/images/tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ base/images/
│ └── tools.py # Native-tool registry
└── cases/ # Test cases
├── test_os_release.py # Shared: /etc/os-release
├── test_oci_config.py # Shared (container): OCI Config.User unset
├── test_packages.py # Shared: rpm-db checks (capability-gated)
├── vm-base/ # VM-specific tests (auto-restricted to the vm-base family — vm-base, vm-base-dev)
│ ├── test_kernel.py
Expand All @@ -111,6 +112,7 @@ base/images/
| `capabilities` | session | `set[str]` | Parsed `--capabilities` |
| `workdir` | session | `Path` | Working directory for mounts/extractions |
| `rootfs` | session | `Path` | Mounted/extracted root filesystem |
| `oci_image_config` | session | `dict[str, object]` | Parsed `skopeo inspect --config` output (use with `@pytest.mark.require_capability("container")`) |
| `os_release` | session | `dict[str, str]` | Parsed `/etc/os-release` |
| `installed_packages` | session | `set[str]` | Installed RPM names (`rpm --root`) |
| `disk_info` | session | `DiskInfo \| None` | VM only |
Expand Down
36 changes: 36 additions & 0 deletions base/images/tests/cases/test_oci_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# SPDX-License-Identifier: MIT
"""OCI image-config validation (container images).

Shared test (not under a ``cases/<family>/`` directory) so it applies to
every container image family — both ``core`` (container-base) and the
``distroless-*`` variants. Gated on the ``container`` capability via
``@pytest.mark.require_capability`` so it only runs for container images
(VM images, which declare ``container = false``, are skipped).
"""

from __future__ import annotations

import pytest


@pytest.mark.require_capability("container")
def test_no_explicit_config_user(oci_image_config: dict[str, object]) -> None:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use a marker, e.g.: @pytest.mark.require_capability with an appropriate capability if there is one.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the container capability

"""OCI ``Config.User`` must be unset.

Azure Linux base/distroless images intentionally leave ``Config.User``
unset (matching AZL 3.0 and mainstream base images such as Debian,
Ubuntu, Alpine, UBI, Fedora). The OCI runtime default for an unset
user is uid 0, so this does not change effective runtime behavior, but
explicitly declaring a user diverges from that convention. An explicit
empty string still counts as "set" and must therefore also fail.
"""
config = oci_image_config.get("config")
assert isinstance(config, dict), (
f"OCI image config has no 'config' object (got {type(config).__name__}); "
f"cannot validate Config.User. Full inspect output: {oci_image_config!r}"
)
assert "User" not in config, (
"OCI Config.User must be unset, but the image explicitly declares "
f"User={config['User']!r}. Remove the user attribute from the kiwi "
"<containerconfig> so the published manifest leaves Config.User unset."
)
14 changes: 14 additions & 0 deletions base/images/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

from utils.disk import inspect_disk
from utils.extract import (
inspect_oci_config,
mount_container_image,
mount_vm_image,
unmount_container_image,
Expand Down Expand Up @@ -160,6 +161,19 @@ def rootfs(image_path: Path, image_type: str, workdir: Path) -> Path:
unmount_container_image(container_dir)


@pytest.fixture(scope="session")
def oci_image_config(image_path: Path) -> dict[str, object]:
"""Return the parsed OCI image config.

Only meaningful for container images; tests using this fixture gate on
the ``container`` capability via
``@pytest.mark.require_capability("container")``.
"""
config = inspect_oci_config(image_path)
logger.info("OCI image config.config keys: %s", sorted(config.get("config", {})))
return config


@pytest.fixture(scope="session")
def disk_info(image_path: Path, image_type: str) -> DiskInfo | None:
"""Partition/filesystem info — ``None`` for container images."""
Expand Down
23 changes: 23 additions & 0 deletions base/images/tests/utils/extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@

from __future__ import annotations

import json
import logging
import os
import subprocess
from pathlib import Path
from typing import Any

from .tools import NativeTool

Expand Down Expand Up @@ -202,3 +204,24 @@ def unmount_container_image(extract_dir: Path) -> None:
result.returncode,
result.stderr.strip(),
)


def inspect_oci_config(image_path: Path) -> dict[str, Any]:
"""Return the OCI image configuration for a container archive.

Runs ``skopeo inspect --config`` against the OCI archive and parses
the resulting JSON (the OCI image config, which carries the
``config`` object with ``User``, ``Cmd``, ``WorkingDir``, etc.).
Unlike rootfs extraction this needs only ``skopeo`` — no umoci unpack.
"""
image_path = image_path.resolve()
logger.info("Inspecting OCI image config: %s", image_path)
result = _run(
[
SKOPEO.name,
"inspect",
"--config",
f"oci-archive:{image_path}",
]
)
return json.loads(result.stdout)
Loading