cli.administration.inventory.provision package

Submodules

cli.administration.inventory.provision.cli module

cli.administration.inventory.provision.cli.main(argv: list[str] | None = None) → int

cli.administration.inventory.provision.credentials_generator module

cli.administration.inventory.provision.credentials_generator.generate_credentials_for_roles(application_ids: list[str], roles_dir: Path, host_vars_file: Path, vault_password_file: Path, project_root: Path, env: dict[str, str] | None, workers: int = 4, app_variants: dict[str, int] | None = None) → None

cli.administration.inventory.provision.filters module

cli.administration.inventory.provision.filters.filter_dynamic_inventory(dyn_inv: dict[str, Any], include_filter: set[str] | None, exclude_filter: set[str] | None, legacy_roles_filter: set[str] | None) → dict[str, Any]
Apply include/exclude/legacy role filters in the same order as before:

include -> exclude -> legacy roles

cli.administration.inventory.provision.filters.parse_roles_list(raw_roles: list[str] | None) → set[str] | None
Parse a list of IDs supplied on the CLI. Supports:

–include web-app-nextcloud web-app-mastodon –include web-app-nextcloud,web-app-mastodon

Same logic is reused for –exclude and –roles.

cli.administration.inventory.provision.host_vars module

cli.administration.inventory.provision.host_vars.apply_vars_overrides(host_vars_file: Path, json_str: str) → None
cli.administration.inventory.provision.host_vars.apply_vars_overrides_from_file(host_vars_file: Path, vars_file: Path) → None
cli.administration.inventory.provision.host_vars.ensure_become_password(host_vars_file: Path, vault_password_file: Path, become_password: str | None) → None
cli.administration.inventory.provision.host_vars.ensure_host_vars_file(host_vars_file: Path, host: str) → None

cli.administration.inventory.provision.inventory_generator module

cli.administration.inventory.provision.inventory_generator.generate_dynamic_inventory(host: str, tmp_inventory: Path, env: dict[str, str] | None) → dict[str, Any]
Generate a dynamic inventory by executing the generator as a module:

python -m cli.administration.inventory.devices …

This avoids fragile file path construction and fixes ‘cli/cli/…’ issues.

cli.administration.inventory.provision.mirror_overrides module

cli.administration.inventory.provision.mirror_overrides.apply_mirror_overrides(host_vars_file: Path, mirrors_file: Path) → None

Apply image mirror overrides to host_vars.

See docs/contributing/artefact/mirror.md for the full architecture, format, and mirror_policy documentation.

cli.administration.inventory.provision.passwords module

cli.administration.inventory.provision.passwords.generate_declared_user_password(username, algorithm, validation)

Return a password satisfying what the user’s own declaration demands.

Parameters:
  • username – the user the password belongs to, for the failure message.

  • algorithm – a ValueGenerator algorithm name, or None for the shell-safe default every user gets when its declaration asks for nothing.

  • validation – a regular expression the value must match, or None.

An application whose registration rejects the shell-safe alphabet states that in its own meta/users.yml rather than carrying a second credential beside the account it already owns.

cli.administration.inventory.provision.passwords.generate_random_password(length: int = 64) → str
cli.administration.inventory.provision.passwords.generate_user_password(length: int = 64) → str

Return a shell-safe password for a declared user.

Parameters:

length – characters to generate, at least 16.

Returns:

letters, digits and the marks -_., never a leading -, with one of each class the Keycloak realm policy specialChars(1) counts.

cli.administration.inventory.provision.project module

cli.administration.inventory.provision.project.build_env_with_project_root(project_root: Path) → dict[str, str]

Return an environment dict where PYTHONPATH includes the project root. This makes utils and other top-level packages importable in subprocesses.

cli.administration.inventory.provision.project.detect_project_root(start_file: Path) → Path

Detect repo root by walking upwards from a file path until we find a typical repo layout.

We require:
  • cli/ directory

  • roles/ directory

  • group_vars/ directory

This avoids brittle parents[N] assumptions and fixes “cli/cli/…” path bugs.

cli.administration.inventory.provision.reset module

Rotate the credentials an inventory generated, so a redeploy has to carry them.

Provisioning fills a missing credential and leaves an existing one alone. That keeps a redeploy stable, and it also means no deploy ever runs against a secret that moved: every pass reads the value the first pass wrote. Dropping the generated values and regenerating them between two deploy passes turns the second pass into a test of the update path, where each role has to push its new secret into the application and the database it already created.

cli.administration.inventory.provision.reset.reset_credentials(application_ids: list[str], roles_dir: Path, host_vars_file: Path, vault_password_file: Path, project_root: Path, env: dict[str, str] | None, schema: bool, users: bool, exclude: set[str], workers: int = 4, app_variants: dict[str, int] | None = None, credential_ids: list[str] | None = None) → int

Replace every generated credential in host_vars_file with a fresh one.

Parameters:
  • application_ids – the roles resolved into this inventory.

  • roles_dir – directory the roles live in.

  • host_vars_file – inventory file the credentials are rewritten in.

  • vault_password_file – vault password used to encrypt each value.

  • project_root – repository root the role resolver works from.

  • env – environment the credential subprocesses inherit.

  • schema – rotate applications.<app>.credentials.*.

  • users – rotate users.<name>.password.

  • exclude – application ids and user keys to leave untouched.

  • workers – worker threads for credentials generation.

  • app_variants – variant index per app, as at provision time.

  • credential_ids – application ids whose credentials rotate; None rotates all of application_ids. User passwords ignore it and always resolve through the full application_ids.

Returns:

How many values were rotated.

cli.administration.inventory.provision.role_resolver module

cli.administration.inventory.provision.role_resolver.resolve_role_path(application_id: str, roles_dir: Path, project_root: Path, env: dict[str, str] | None) → Path | None
Resolve role path by calling:

python -m cli.meta.roles.applications.role_name <app_id> -r <roles_dir>

The helper may print:
  • bare folder name (e.g. web-app-nextcloud)

  • relative path (e.g. roles/web-app-nextcloud)

  • absolute path

Returns None when the id belongs to no application role.

cli.administration.inventory.provision.ruamel_io module

Round-trip YAML access for the inventory files this package rewrites.

The counterpart to yaml_io, which reads values. Everything here keeps comments, quoting and vault tags intact, because host_vars is hand-edited as often as it is generated.

cli.administration.inventory.provision.ruamel_io.as_commented_map(data: Any) → CommentedMap

Return data as a CommentedMap, empty when it holds no mapping.

Parameters:

data – whatever a round-trip load returned.

cli.administration.inventory.provision.ruamel_io.dump_document(path: Path, document: CommentedMap) → None

Write document to path, creating the parent directory.

Parameters:
  • path – the YAML file to write.

  • document – the round-trip document to serialise.

cli.administration.inventory.provision.ruamel_io.ensure_map(node: CommentedMap, key: str) → CommentedMap

Return node[key] as a CommentedMap, creating it when absent.

Parameters:
  • node – the parent mapping.

  • key – the child key to resolve.

cli.administration.inventory.provision.ruamel_io.load_document(path: Path) → CommentedMap

Return the round-trip document at path, empty when it does not exist.

Parameters:

path – the YAML file to read.

cli.administration.inventory.provision.ruamel_io.round_trip_yaml() → YAML

Return a YAML handler configured the way every writer here needs it.

cli.administration.inventory.provision.ruamel_io.vault_value(vault_password_file: Path | str, plain: str, name: str) → Any

Return plain as a parsed !vault node ready to assign.

Parameters:
  • vault_password_file – vault password used to encrypt.

  • plain – the secret to encrypt.

  • name – the key ansible-vault names the snippet after.

ansible-vault emits a YAML snippet rather than a value, so the encrypted node has to be read back out of it before it can be assigned anywhere.

cli.administration.inventory.provision.services_disabler module

exception cli.administration.inventory.provision.services_disabler.ServicesDisabledConflictError

Bases: RuntimeError

Raised when disable conflicts with an existing inventory.

cli.administration.inventory.provision.services_disabler.apply_services_disabled(host_vars_file: Path, services: list[str], roles_dir: Path, inventory_file: Path | None = None) → None

For every role under roles_dir whose meta/services.yml defines a service listed in services, set enabled: false and shared: false in host_vars_file under applications.<app_id>.services.<svc_name>. Missing application or services blocks are created as needed.

Per the materialised path is applications.<app>.services.<svc> (no compose.services wrapper).

If inventory_file is provided, also removes the provider role for each service from the inventory (devices.yml).

cli.administration.inventory.provision.services_disabler.apply_services_disabled_from_env(host_vars_file: Path, roles_dir: Path, inventory_file: Path | None = None) → None

Read disable from the environment and apply to host_vars and inventory.

cli.administration.inventory.provision.services_disabler.assert_services_disabled_inventory_consistency_from_env(inventory_dir: Path, roles_dir: Path) → None

Fail fast when disable conflicts with the existing inventory.

cli.administration.inventory.provision.services_disabler.expand_disabled_tokens(tokens: list[str], roles_dir: Path) → list[str]

Backwards-compatible alias for the shared utils.roles.applications.services.registry.expand_service_tokens().

cli.administration.inventory.provision.services_disabler.find_provider_roles(services: list[str], roles_dir: Path) → dict[str, str]

Return mapping of service_name -> application_id for the provider role of each requested service. Resolution goes through the canonical service registry, so provides: aliases (e.g. mailu.provides: email) and shared entity-name services are both recognized.

cli.administration.inventory.provision.services_disabler.find_roles_with_service(service_name: str, roles_dir: Path) → set[str]

Return all role IDs whose meta/services.yml declares the given service as a consumer/provider — i.e. the entry carries at least one of enabled / shared / provides. Pure metadata entries (e.g. a primary entity that only carries lifecycle/run_after) are intentionally excluded so disable doesn’t add bogus enabled: false overrides that turn metadata into spurious provider declarations downstream.

Per the file root of meta/services.yml IS the services map (no compose.services wrapper).

cli.administration.inventory.provision.services_disabler.find_services_disabled_conflicts(inventory_dir: Path, services: list[str], roles_dir: Path) → list[str]

Return human-readable conflicts when disable disagrees with the existing inventory/host_vars state.

cli.administration.inventory.provision.services_disabler.parse_services_disabled(env_value: str) → list[str]

Parse a space- or comma-separated list of service names.

cli.administration.inventory.provision.services_disabler.remove_roles_from_inventory(inventory_file: Path, application_ids: list[str]) → None

Remove the given application_ids from the inventory devices.yml.

cli.administration.inventory.provision.subprocess_runner module

cli.administration.inventory.provision.subprocess_runner.run_subprocess(cmd: list[str], capture_output: bool = False, env: dict[str, str] | None = None, ok_returncodes: tuple[int, ...] = (0,)) → CompletedProcess

Run a subprocess command and either stream output or capture it. Raise SystemExit on a return code outside ok_returncodes.

cli.administration.inventory.provision.tor_node module

Provision the node onion address into services.tor.node.

The onion identity (key files) is minted/reused by cli.administration.inventory.onion; this provisioner is the single spot that writes the resulting address into applications.svc-net-tor.services.tor.node of the host_vars — so consumers read it via lookup('config', 'svc-net-tor', 'services.tor.node') with no env indirection.

cli.administration.inventory.provision.tor_node.apply_tor_node_onion(*, host_vars_file: Path, application_ids: list[str], base_dir: Path) → None

When svc-net-tor is in the deploy, mint (or reuse) the node onion and set applications.svc-net-tor.services.tor.node in the host_vars.

cli.administration.inventory.provision.users_generator module

Pin a password on every user the resolved role set needs.

A user definition that declares no password falls back to {{ 42 | strong_password }}. That is a template, not a value: it is re-rendered at every use site, so the task that creates an account and the task that later authenticates as it read two different secrets. Inventory creation is where the value gets decided once, next to the role credentials that are already generated there.

cli.administration.inventory.provision.users_generator.generate_user_passwords(roles_dir: Path, application_ids: list[str], host_vars_file: Path, vault_password_file: Path) → int

Write a vaulted password for every required user that has none yet.

Parameters:
  • roles_dir – directory the roles live in.

  • application_ids – the roles resolved into this inventory.

  • host_vars_file – inventory file the passwords are written into.

  • vault_password_file – vault password used to encrypt each value.

Returns:

How many users received a freshly generated password.

cli.administration.inventory.provision.users_generator.required_user_policies(roles_dir: Path, application_ids: list[str]) → dict[str, dict[str, str | None]]

Return every declared username with the password policy it asks for.

Parameters:
  • roles_dir – directory the roles live in.

  • application_ids – the roles resolved into this inventory.

A role that declares no users contributes none, so the result follows the deployment rather than everything the repository could ever deploy. Two roles sharing a user must agree on its policy: the account is one thing, so silently picking one declaration would hand the other role a password its application rejects.

cli.administration.inventory.provision.yaml_io module

cli.administration.inventory.provision.yaml_io.dump_yaml(path: Path, data: dict[str, Any]) → None

Write-through wrapper around utils.cache.yaml.dump_yaml; evicts the cached entry so subsequent load_yaml(path) calls see the new content.

cli.administration.inventory.provision.yaml_io.load_yaml(path: Path) → dict[str, Any]

Cached YAML load that returns {} for missing files (legacy contract). Non-mapping roots surface as SystemExit to match the inventory CLI’s existing error UX.

Module contents

cli.administration.inventory.provision.main(argv: list[str] | None = None) → int