Public Access
DEVX-14: feat: fix molecule platforms to use sleep infinity, add --platforms-file
Post-merge / detect-type (push) Successful in 10s
Post-merge / validate-commit-msg (push) Successful in 10s
Post-merge / configure-repo (push) Successful in 20s
Post-merge / release (push) Successful in 47s
Post-merge / vikunja (push) Successful in 20s
Post-merge / sync-wiki (push) Successful in 50s
Post-merge / badges (push) Successful in 1m0s
Post-merge / detect-type (push) Successful in 10s
Post-merge / validate-commit-msg (push) Successful in 10s
Post-merge / configure-repo (push) Successful in 20s
Post-merge / release (push) Successful in 47s
Post-merge / vikunja (push) Successful in 20s
Post-merge / sync-wiki (push) Successful in 50s
Post-merge / badges (push) Successful in 1m0s
This commit was merged in pull request #26.
This commit is contained in:
@@ -25,7 +25,7 @@ from pathlib import Path
|
||||
import click
|
||||
|
||||
from devx.i18n import _
|
||||
from devx.molecule.platforms import PLATFORMS
|
||||
from devx.molecule.platforms import PLATFORMS, load_platforms
|
||||
|
||||
DEFAULT_MAX_RUNNERS = 3
|
||||
MOLECULE_ROOT = Path("ansible/roles/gitea-runner/molecule")
|
||||
@@ -238,6 +238,13 @@ def _write_github_env(key: str, value: str) -> None:
|
||||
help="Roles directory for multi-role discovery (scans */molecule/*/). "
|
||||
"Use this for projects with multiple Ansible roles. Default: disabled (single-role mode).",
|
||||
)
|
||||
@click.option(
|
||||
"--platforms-file",
|
||||
type=click.Path(exists=True, file_okay=True, path_type=Path),
|
||||
default=None,
|
||||
help="JSON file with custom platform list (each entry: name, image, command). "
|
||||
"Overrides the default platform matrix. Useful for projects with custom test images.",
|
||||
)
|
||||
def cli(
|
||||
runner_index: int | None,
|
||||
max_runners: int,
|
||||
@@ -247,7 +254,9 @@ def cli(
|
||||
skip_if_excess: bool,
|
||||
molecule_root: Path | None,
|
||||
roles_root: Path | None,
|
||||
platforms_file: Path | None,
|
||||
) -> None:
|
||||
platforms = load_platforms(platforms_file)
|
||||
# Multi-role mode: discover (role, scenario) pairs across all roles
|
||||
if roles_root is not None:
|
||||
role_scenarios = discover_multi_role_scenarios(roles_root)
|
||||
@@ -256,10 +265,10 @@ def cli(
|
||||
click.echo(f"{role}|{scenario}")
|
||||
return
|
||||
if list_platforms:
|
||||
for p in PLATFORMS:
|
||||
for p in platforms:
|
||||
click.echo(f"{p['name']}|{p['image']}|{p['command']}")
|
||||
return
|
||||
pairs_mr = build_multi_role_pairs(role_scenarios)
|
||||
pairs_mr = build_multi_role_pairs(role_scenarios, platforms)
|
||||
if runner_index is None:
|
||||
groups = distribute_multi_role(pairs_mr, max_runners)
|
||||
for i, group in enumerate(groups):
|
||||
@@ -291,10 +300,10 @@ def cli(
|
||||
click.echo(s)
|
||||
return
|
||||
if list_platforms:
|
||||
for p in PLATFORMS:
|
||||
for p in platforms:
|
||||
click.echo(f"{p['name']}|{p['image']}|{p['command']}")
|
||||
return
|
||||
pairs = build_pairs(scenarios)
|
||||
pairs = build_pairs(scenarios, platforms)
|
||||
if runner_index is None:
|
||||
groups = distribute(pairs, max_runners)
|
||||
for i, group in enumerate(groups):
|
||||
|
||||
@@ -10,13 +10,39 @@ dev tools and CI scripts.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
#: Supported OS platform matrix.
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
#: Default supported OS platform matrix.
|
||||
#: Each entry maps a short name to (image, command).
|
||||
#: The command must be systemd since rootless Docker requires
|
||||
#: loginctl/systemctl --user.
|
||||
#: Uses the project's pre-built molecule-test-base image with
|
||||
#: ``sleep infinity`` (NOT systemd) to avoid cgroup v2 failures.
|
||||
PLATFORMS: list[dict[str, str]] = [
|
||||
{"name": "ubuntu-2204", "image": "geerlingguy/docker-ubuntu2204-ansible:latest", "command": "/lib/systemd/systemd"},
|
||||
{"name": "ubuntu-2404", "image": "geerlingguy/docker-ubuntu2404-ansible:latest", "command": "/lib/systemd/systemd"},
|
||||
{"name": "debian-12", "image": "geerlingguy/docker-debian12-ansible:latest", "command": "/lib/systemd/systemd"},
|
||||
{"name": "archlinux", "image": "marcstraube/archlinux-ansible:latest", "command": "/usr/lib/systemd/systemd"},
|
||||
{
|
||||
"name": "ubuntu-2604",
|
||||
"image": "git.oblachno.oblachno.fyi/oblachno/molecule-test-base:latest",
|
||||
"command": "sleep infinity",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def load_platforms(platforms_file: str | Path | None = None) -> list[dict[str, str]]:
|
||||
"""Load platforms from a JSON file, falling back to PLATFORMS.
|
||||
|
||||
Args:
|
||||
platforms_file: Path to a JSON file with a list of platform dicts.
|
||||
Each dict must have ``name``, ``image``, and ``command`` keys.
|
||||
|
||||
Returns:
|
||||
List of platform dictionaries.
|
||||
"""
|
||||
if platforms_file is None:
|
||||
return PLATFORMS
|
||||
path = Path(platforms_file)
|
||||
if not path.is_file():
|
||||
return PLATFORMS
|
||||
with path.open() as f:
|
||||
data = json.load(f)
|
||||
if not isinstance(data, list) or not data:
|
||||
return PLATFORMS
|
||||
return data
|
||||
|
||||
@@ -163,10 +163,7 @@ class TestCli:
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ["--list-platforms"])
|
||||
assert result.exit_code == 0
|
||||
assert "ubuntu-2204" in result.output
|
||||
assert "ubuntu-2404" in result.output
|
||||
assert "debian-12" in result.output
|
||||
assert "archlinux" in result.output
|
||||
assert "ubuntu-2604" in result.output
|
||||
|
||||
def test_no_runner_index_prints_all_groups(self, tmp_path: Path) -> None:
|
||||
from click.testing import CliRunner
|
||||
@@ -198,7 +195,7 @@ class TestCli:
|
||||
assert result.exit_code == 0
|
||||
# Output should contain encoded pairs with platform info
|
||||
assert "alpha|" in result.output
|
||||
assert "ubuntu-2204" in result.output
|
||||
assert "ubuntu-2604" in result.output
|
||||
|
||||
|
||||
class TestGithubEnv:
|
||||
@@ -409,7 +406,7 @@ class TestCliMultiRole:
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ["--roles-root", str(roles), "--list-platforms"])
|
||||
assert result.exit_code == 0
|
||||
assert "ubuntu-2204" in result.output
|
||||
assert "ubuntu-2604" in result.output
|
||||
|
||||
def test_roles_root_no_runner_index_prints_groups(self, tmp_path: Path) -> None:
|
||||
"""--roles-root without --runner-index prints all groups."""
|
||||
@@ -422,6 +419,27 @@ class TestCliMultiRole:
|
||||
assert "Runner 0:" in result.output
|
||||
assert "Runner 1:" in result.output
|
||||
|
||||
def test_platforms_file_overrides_default(self, tmp_path: Path) -> None:
|
||||
"""--platforms-file loads custom platforms from JSON."""
|
||||
import json
|
||||
|
||||
from click.testing import CliRunner
|
||||
|
||||
from devx.molecule.distribute_molecule import cli
|
||||
|
||||
roles = tmp_path / "roles"
|
||||
(roles / "role-a" / "molecule" / "default").mkdir(parents=True)
|
||||
platforms_file = tmp_path / "platforms.json"
|
||||
custom = [{"name": "custom-os", "image": "custom:latest", "command": "sleep infinity"}]
|
||||
platforms_file.write_text(json.dumps(custom))
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
cli, ["--roles-root", str(roles), "--platforms-file", str(platforms_file), "--list-platforms"]
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert "custom-os" in result.output
|
||||
assert "custom:latest" in result.output
|
||||
|
||||
def test_roles_root_skips_non_dir_role(self, tmp_path: Path) -> None:
|
||||
"""Non-directory entries in roles root are skipped."""
|
||||
roles = tmp_path / "roles"
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
"""Unit tests for scripts/ci/platforms.py."""
|
||||
"""Unit tests for devx.molecule.platforms."""
|
||||
|
||||
from devx.molecule.platforms import PLATFORMS
|
||||
import json
|
||||
|
||||
from devx.molecule.platforms import PLATFORMS, load_platforms
|
||||
|
||||
|
||||
class TestPlatforms:
|
||||
def test_platforms_not_empty(self) -> None:
|
||||
assert len(PLATFORMS) >= 4
|
||||
assert len(PLATFORMS) >= 1
|
||||
|
||||
def test_each_platform_has_required_keys(self) -> None:
|
||||
for p in PLATFORMS:
|
||||
@@ -17,9 +19,40 @@ class TestPlatforms:
|
||||
names = [p["name"] for p in PLATFORMS]
|
||||
assert len(names) == len(set(names))
|
||||
|
||||
def test_platforms_use_sleep_infinity(self) -> None:
|
||||
"""All default platforms must use sleep infinity, not systemd."""
|
||||
for p in PLATFORMS:
|
||||
assert p["command"] == "sleep infinity", f"Platform {p['name']} uses {p['command']}"
|
||||
|
||||
def test_known_platforms_present(self) -> None:
|
||||
names = {p["name"] for p in PLATFORMS}
|
||||
assert "ubuntu-2204" in names
|
||||
assert "ubuntu-2404" in names
|
||||
assert "debian-12" in names
|
||||
assert "archlinux" in names
|
||||
assert "ubuntu-2604" in names
|
||||
|
||||
|
||||
class TestLoadPlatforms:
|
||||
def test_load_platforms_default(self, tmp_path) -> None: # type: ignore[no-untyped-def]
|
||||
"""load_platforms with no file returns PLATFORMS."""
|
||||
result = load_platforms(None)
|
||||
assert result == PLATFORMS
|
||||
|
||||
def test_load_platforms_from_file(self, tmp_path) -> None: # type: ignore[no-untyped-def]
|
||||
"""load_platforms reads custom platforms from JSON file."""
|
||||
custom = [
|
||||
{"name": "custom-os", "image": "custom:latest", "command": "sleep infinity"},
|
||||
]
|
||||
f = tmp_path / "platforms.json"
|
||||
f.write_text(json.dumps(custom))
|
||||
result = load_platforms(f)
|
||||
assert result == custom
|
||||
|
||||
def test_load_platforms_missing_file_falls_back(self, tmp_path) -> None: # type: ignore[no-untyped-def]
|
||||
"""load_platforms falls back to PLATFORMS when file doesn't exist."""
|
||||
result = load_platforms(tmp_path / "nonexistent.json")
|
||||
assert result == PLATFORMS
|
||||
|
||||
def test_load_platforms_empty_list_falls_back(self, tmp_path) -> None: # type: ignore[no-untyped-def]
|
||||
"""load_platforms falls back to PLATFORMS when file has empty list."""
|
||||
f = tmp_path / "platforms.json"
|
||||
f.write_text("[]")
|
||||
result = load_platforms(f)
|
||||
assert result == PLATFORMS
|
||||
|
||||
Reference in New Issue
Block a user