Using the components in other projects¶
PyrixOS is built from separate packages, and three of them know nothing of PyrixOS. Any Python program can use them, in the way Starlark is used outside Bazel.
| PyPI package | Import name | Version | What it gives a program |
|---|---|---|---|
pybonsai-lang |
pybonsai |
1.2 | Configuration files in Python syntax that cannot read files, the network, the clock or the environment, and always finish with the same result |
cloche |
cloche |
1.1 | Running a program in an unprivileged Linux sandbox that sees only the paths it is given, with no network, and with memory, CPU and process limits in a delegated cgroup |
trellis-kconfig |
trellis |
1.0 | A Kconfig-style option tree whose choices a SAT solver checks, with explanations of conflicts |
pyrixos-lattice |
lattice |
0.12 | The PyrixOS package manager; its interface may change until PyrixOS 1.0 |
Install¶
In a project's pyproject.toml, allow every release of the same major version:
Within a major version, the names in each package's __all__, the fields of its data models and the behaviour its README promises do not change in a way that breaks a caller. For PyBonsai, a file that evaluates under 1.0 gives the same values under every 1.x release. A name to be removed first gives a DeprecationWarning for at least one minor release.
PyBonsai runs on any operating system. Cloche needs Linux with unprivileged user namespaces; cloche status reports whether a machine has them and, if not, why. On Ubuntu 23.10 and later, AppArmor restricts them unless kernel.apparmor_restrict_unprivileged_userns is 0 or the program has an AppArmor profile that allows them.
PyBonsai and Cloche together¶
A common use is a tool whose users describe jobs in a file, and which runs each job without trusting it. PyBonsai keeps the description from doing anything but describe, and Cloche keeps the job from touching anything it was not given.
The description, jobs.pyb, is ordinary Python syntax. Functions, comprehensions and string operations are allowed; imports, while, mutation and I/O are not.
# A list of jobs, each a program and its arguments.
tools = ["/usr", "/lib", "/lib64"]
def shell(name, script):
return {"name": name, "program": "/usr/bin/sh", "args": ["-c", script], "binds": tools}
jobs = [
shell("greet", "echo hello > /build/out"),
shell("count", "/usr/bin/seq " + str(3) + " > /build/out"),
]
The program evaluates the file, turns each job into a Cloche Command, and runs it:
import sys
import tempfile
import cloche
import pybonsai
def load_jobs(path: str) -> list[cloche.Command]:
names = pybonsai.evaluate_file(path)
return [
cloche.Command(
name=job["name"],
program=job["program"],
args=job["args"],
binds=tuple(cloche.Bind(source=p, destination=p) for p in job["binds"]),
outputs={"out": "/build/out"},
)
for job in names["jobs"]
]
def main(path: str) -> int:
try:
commands = load_jobs(path)
except pybonsai.PyBonsaiError as error:
print(f"{path}: {error}", file=sys.stderr)
return 2
for command in commands:
try:
outcome = cloche.run(command, tempfile.mkdtemp(), cloche.Limits(timeout=60))
except cloche.ClocheError as error:
print(error, file=sys.stderr)
return 1
with open(outcome.outputs["out"]) as output:
print(command.name, output.read().split())
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1]))
A file that steps outside the language is refused before anything runs, with its file and line:
$ python runner.py bad.pyb
bad.pyb: Import is not permitted: imports bring in code outside the standard library (bad.pyb, line 1)
Inside the sandbox, PATH is set to /path-not-set, so a job names its programs by absolute path. Everything outside the binds, /build, /proc and a minimal /dev is absent, including /etc.
Giving files your own functions¶
Without a host, a PyBonsai file sees only the pure built-in functions such as len, range and sorted. A program adds its own through a Host:
from pybonsai import Builtin, evaluate_source, make_host
host = make_host(names={"double": Builtin("double", lambda x: 2 * x)}, types={})
assert evaluate_source("y = double(21)", host=host)["y"] == 42
A host function must not read files, the clock or anything else outside its arguments, or the result of a file would no longer depend on the file alone. Host types must be immutable and hashable, and list the attributes a file may read.
Errors¶
| Package | Base class | Raised for |
|---|---|---|
| PyBonsai | PyBonsaiError |
RejectedSyntax for a construct outside the language, EvaluationError for a failure while evaluating, and LimitExceeded when a size, depth or step limit is reached |
| Cloche | ClocheError |
InvalidCommand for a command that does not fit together, SandboxUnavailable when the machine cannot run the backend, and RunFailed when the program fails, with the last lines of its log |
| Trellis | ConfigError |
ParseError and RejectedConstruct for a Kconfig file outside the subset, TreeError for a tree that breaks a rule, InvalidValue for a value an option cannot take, ConflictError for choices that conflict, with the choices and rules involved, and SolverUnavailable for a missing solver backend |
More¶
Each package's README lists its whole interface: PyBonsai, Cloche and Trellis. The PyBonsai, Build Sandbox and Trellis pages explain how each works.