Metadata-Version: 2.4
Name: infraclass
Version: 1.1.0
Summary: Infraclass is a lightweight, zero-dependency, and highly secure hierarchical inventory compiler for Python automation engines (like ansible and pyinfra). It allows you to build inventories using a top-down class inheritance layout, natively supporting encrypted secrets using age.
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: pyyaml>=6.0

# Infraclass

Infraclass is a lightweight, zero-dependency inventory compiler. You describe your infrastructure as a hierarchy of YAML files — nodes, and reusable "classes" they inherit from — and infraclass merges them into one flat set of config values per node, decrypting any `age`-encrypted secrets along the way.

Infraclass has no knowledge of pyinfra, Ansible, or any other automation tool. It only produces data — a Python dict, or YAML on stdout. Feeding that data into whatever automation engine you use is a separate step you write yourself (see "Using the compiled data" below).

## Project Directory Structure

Rename your data directory to `infraclass/` to align with the framework:

```text
automation/
├── your_inventory_script.py   # Your own integration script — see "Using the compiled data"
├── infraclass/                 # Your hierarchical data directory
│   ├── classes/                # Reusable configuration blueprints
│   │   ├── components/
│   │   ├── platform/
│   │   │   └── init.yml        # Standard shared properties & secrets
│   │   └── roles/
│   └── nodes/                  # Machine-specific inventory targets
│       └── node1.example.com.yml
└── vault/
    └── vault.age                # Encrypted secrets store
```

## Setup

### Install the package
```text
uv tool install infraclass
```

### Point it at your age key
By default, infraclass looks for your decryption key at `~/.age/identity.age`. Put your private key there, or tell infraclass to use a different path:
```text
export INFRACLASS_AGE_KEY_FILE="$HOME/.age/identity.age"
```

## Systemd / Credentials Directory Resolution

When running inside a systemd service (e.g. a CI/CD runner) that provides the vault key via `LoadCredentialEncrypted`, infraclass finds it automatically — no config changes needed. It looks in `$CREDENTIALS_DIRECTORY` for a file matching `age-key*` or `age-pq-key*`:

```text
import os
import glob

def _get_age_key_path():
    """Resolves the age encryption key path based on systemic environment tags."""
    creds_dir = os.environ.get("CREDENTIALS_DIRECTORY")
    if creds_dir:
        pattern = os.path.join(creds_dir, "age-[kp]*")
        matches = glob.glob(pattern)
        if matches:
            return sorted(matches)[0]

    return os.environ.get("INFRACLASS_AGE_KEY_FILE", os.path.expanduser("~/.age/identity.age"))
```

## Trying it out

### From the command line
```text
infraclass show node1.example.com
```
Prints that node's fully compiled configuration as YAML. This is the quickest way to sanity-check that a node compiles, and to see exactly what data it produces.

By default (or with `--mask-secret-values`, the same thing spelled out explicitly), the vault isn't decrypted at all — every `!secret` reference is shown as a `<SECRET: id>` placeholder, and no passphrase/Touch ID prompt appears. Add `--reveal-secret-values` to decrypt the vault and print the real secret values instead.

Vault/secret management lives under its own `vault` command — `infraclass vault add`, `infraclass vault remove <id>`, `infraclass vault show <id>`, `infraclass vault audit`, `infraclass vault prune`, `infraclass vault re-encrypt`. Run `infraclass vault --help` for the full list.

### Calling it from a script instead of the CLI

If you need the compiled data inside a script rather than as YAML from the CLI, call the compiler function directly:

```text
import infraclass

node_data = infraclass.compile_node_data("node1.example.com", base_dir="infraclass")

node_data["parameters"]    # merged config for this node, secrets decrypted
node_data["classes"]       # every class this node inherited from
node_data["applications"]  # any "applications" lists it picked up
```

This is exactly what `inventory.py` (in `pyinfra`) can do: it can loop over every node file, calling `compile_node_data` for each one, and builds pyinfra's host/group inventory out of the result.
