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

This commit was merged in pull request #26.
This commit is contained in:
2026-06-23 18:10:19 +00:00
parent 9a60009d29
commit 2ead959fcf
5 changed files with 112 additions and 26 deletions
+14 -5
View File
@@ -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):
+33 -7
View File
@@ -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