Files
devx/src/devx/molecule/lease.py
T

89 lines
3.0 KiB
Python

"""Emit bounded runner-resource lease env for CI molecule jobs.
Producer side of the S08 lease contract (consumer: GRM-173
``runner-cleanup.sh``). The runner's scoped cleanup honors
``org.oblachno.lease-until`` (epoch seconds) and ``org.oblachno.owner``
labels on containers, volumes, and networks. Molecule runs on the runner
host's rootless Docker socket, so objects it creates must carry the lease
labels at creation time — Docker labels cannot be added post-hoc.
This module computes ``MOLECULE_LEASE_UNTIL`` and ``MOLECULE_OWNER``,
which ``molecule.yml`` platform ``labels:`` entries consume via
``${MOLECULE_LEASE_UNTIL:-0}`` / ``${MOLECULE_OWNER:-molecule-local}``
interpolation, and appends them to ``$GITHUB_ENV`` so every later CI step
inherits them. A lease-until of ``0`` (the interpolation default for
local runs) is already expired, keeping today's unleased behavior.
"""
from __future__ import annotations
import os
import socket
import time
import click
#: Docker label contract consumed by runner-cleanup.sh (GRM-173).
LEASE_UNTIL_LABEL = "org.oblachno.lease-until"
OWNER_LABEL = "org.oblachno.owner"
#: Env vars consumed by molecule.yml platform ``labels:`` entries.
LEASE_UNTIL_ENV = "MOLECULE_LEASE_UNTIL"
OWNER_ENV = "MOLECULE_OWNER"
#: Default lease duration — covers the longest molecule job window.
DEFAULT_TTL_SECONDS = 4 * 3600
def lease_owner() -> str:
"""Identify the owning CI run for the ``org.oblachno.owner`` label.
act_runner exposes GitHub-compatible ``GITHUB_*`` env vars;
``GITEA_*`` names are checked first for forward compatibility.
"""
run_id = os.environ.get("GITEA_RUN_ID") or os.environ.get("GITHUB_RUN_ID")
job = os.environ.get("GITEA_JOB") or os.environ.get("GITHUB_JOB")
if run_id:
return f"run-{run_id}-{job or 'job'}"
return f"local-{socket.gethostname()}"
def lease_env(ttl: int = DEFAULT_TTL_SECONDS, now: int | None = None) -> dict[str, str]:
"""Return the lease env dict for the current run."""
base = int(time.time()) if now is None else now
return {
LEASE_UNTIL_ENV: str(base + ttl),
OWNER_ENV: lease_owner(),
}
def emit_github_env(env: dict[str, str]) -> int:
"""Append lease env to ``$GITHUB_ENV``; return lines written (0 if unset)."""
github_env = os.environ.get("GITHUB_ENV")
if not github_env:
return 0
with open(github_env, "a", encoding="utf-8") as f:
for key, value in env.items():
f.write(f"{key}={value}\n")
return len(env)
@click.command()
@click.option(
"--ttl",
default=DEFAULT_TTL_SECONDS,
type=int,
help=f"Lease duration in seconds (default: {DEFAULT_TTL_SECONDS}).",
)
def main(ttl: int) -> None:
"""Print lease env vars and export them to $GITHUB_ENV when set."""
env = lease_env(ttl)
for key, value in env.items():
click.echo(f"{key}={value}")
if emit_github_env(env):
click.echo(f"Exported {len(env)} lease vars to GITHUB_ENV")
if __name__ == "__main__": # pragma: no cover
main()