host.product¶
Product lifecycle strategy for hosts.
A Product is a unit of software-under-test deployed to a host — the
lifecycle analog of BinaryLoader. It is a
behavior contract (an ABC): projects subclass it and inject instances
via products. The host orchestrates; the
product knows how to stage/install/uninstall/check itself.
It is intentionally not a pydantic model — that would force every project
product into pydantic and diverge from the sibling host strategies
(CommandFrame, BinaryLoader, EmbeddedFileSystem).
Concrete subclasses pick their own data representation (@dataclass or an
OttoModel).
Products are behavior, so they are customized in code, not lab data: a
product repo registers a register_product_provider() callback from a
.otto init module, and otto applies it to each host as it is ingested (see
apply_product_providers()). Lab data stays product-agnostic and evolves
independently of product code; declaring products in lab data is deliberately
not supported.
- class otto.host.product.Product¶
Bases:
ABCA unit of software-under-test deployed to a host (behavior contract).
- name : str¶
Logical identity — used for logging,
is_installedlookups, and dedup. Not a file path: a product may be multi-file or installed from a repo.
- abstract async stage(host: Host) tuple[Status, str]¶
Transfer/place this product’s artifacts onto host (no install).
- class otto.host.product.FileProduct(artifact: ~pathlib.Path, name: str = '', dest_dir: ~pathlib.Path = <factory>)¶
Bases:
ProductConvenience base for a product that is a single artifact file.
stage()transfers the artifact viaput().namedefaults to the artifact’s basename.install/uninstall/is_installedremain abstract — they are inherently project-specific. Once the remote file-ops phase lands, the naturalis_installedisawait host.exists(self.dest_dir / self.artifact.name).
- otto.host.product.ProductProvider¶
A function that, given a host, returns the products it should carry.
Registered from a
.ottoinit module viaregister_product_provider()and run once per lab-ingested host. All product knowledge stays in product-repo code; lab data never names a product.
- otto.host.product.register_product_provider(provider: Callable[[Host], Iterable[Product] | None]) None¶
Register a function that decides which products a host carries.
Call from an init module listed in
.otto/settings.toml— the same extension hook the other host strategies use. The provider runs once per lab-ingested host; inspect the host’s product-agnostic attributes (element,element_id,os_type,id,ip,resources) and return the products that host should carry (orNone/[]for none). Behavior lives in code; lab data stays product-agnostic.
- otto.host.product.apply_product_providers(host: Host) None¶
Run every registered provider against host, attaching their products.
Called at the single lab-ingest chokepoint (
otto.storage.factory.create_host_from_dict()). Providers run in registration order and their results are concatenated ontohost.products. A product whoseProduct.namealready appears on the host is skipped (deduplication guards two overlapping providers). A provider that raises propagates — a misconfigured provider fails ingest loudly.