Ansible YAML Inventory Builder

Ansible YAML Inventory Builder cover

Project Overview

This interactive Python script simplifies creating valid Ansible YAML inventories, especially in airgapped environments. It guides you through defining groups, hosts (supporting formats like hostname=ip, hostname-only, or IP-only), and optional child groups. The output is clean, Ansible-compliant YAML ready for immediate use with ansible or ansible-playbook commands.

Built quickly with GenAI assistance—perfect for labs, automation pipelines, or teaching Ansible basics. It eliminates manual YAML errors and enforces best practices.

Full Code

Security Note: Always review code before executing. SHA256 Hash: 51ea38c9041b22416e305b7abeb27ccf8d0ebc13c2d71b424a6cc7dd6c039a30/p>


import argparse
import sys
import yaml

def main():
    parser = argparse.ArgumentParser(
        description="Interactively build an Ansible YAML inventory (official format)."
    )
    parser.add_argument(
        "--output",
        default=None,
        help="Output file for the inventory (skips filename prompt)"
    )
    args = parser.parse_args()

    # Internal structure:
    # groupname: {
    #   'hosts': {hostname: {vars}},
    #   'children': set()
    # }
    groups = {}

    print("\nInteractive Ansible YAML Inventory Builder")
    print("Rules:")
    print("- Top-level keys are group names")
    print("- Hosts may be 'hostname=ip', hostname-only, or ip-only")
    print("- Child groups are optional\n")

    while True:
        group_name = input("Enter group name (or 'done' to finish): ").strip()
        if group_name.lower() == "done":
            break
        if not group_name:
            print("Group name cannot be empty.\n")
            continue

        group = groups.setdefault(
            group_name,
            {"hosts": {}, "children": set()}
        )

        hosts_input = input(
            f"Hosts for '{group_name}' "
            "(comma-separated, empty for none): "
        ).strip()

        if hosts_input:
            for item in hosts_input.split(","):
                item = item.strip()
                if not item:
                    continue

                # hostname=ip format
                if "=" in item:
                    hostname, addr = item.split("=", 1)
                    hostname = hostname.strip()
                    addr = addr.strip()

                    if not hostname or not addr:
                        print(f"Ignoring invalid host entry: {item}")
                        continue

                    group["hosts"][hostname] = {
                        "ansible_host": addr
                    }
                else:
                    # hostname-only OR ip-only
                    group["hosts"][item] = {}

        children_input = input(
            f"Child groups for '{group_name}' "
            "(comma-separated, empty for none): "
        ).strip()

        if children_input:
            for child in children_input.split(","):
                child = child.strip()
                if not child:
                    continue
                if child == group_name:
                    print("A group cannot be its own child — ignored.")
                else:
                    group["children"].add(child)

        print()

    if not groups:
        print("No groups defined. Exiting.")
        sys.exit(0)

    # Validate referenced children
    defined_groups = set(groups.keys())
    undefined_children = {
        child
        for g in groups.values()
        for child in g["children"]
        if child not in defined_groups
    }

    if undefined_children:
        print("\nWarning: These child groups are referenced but not defined:")
        for name in sorted(undefined_children):
            print(f"  - {name}")
        if input("Proceed anyway? (y/n): ").strip().lower() != "y":
            print("Aborted.")
            sys.exit(1)

    # Build final inventory
    inventory = {}

    for group_name in sorted(groups):
        data = groups[group_name]
        entry = {}

        # Hosts (always emitted)
        entry["hosts"] = dict(sorted(data["hosts"].items()))

        # Children (only if defined)
        if data["children"]:
            entry["children"] = {child: {} for child in sorted(data["children"])}

        inventory[group_name] = entry

    # Output filename
    if args.output:
        output_file = args.output
    else:
        while True:
            output_file = input("Output filename (inventory.yml): ").strip()
            if output_file:
                break

    # Write YAML
    try:
        with open(output_file, "w") as f:
            yaml.safe_dump(
                inventory,
                f,
                indent=2,
                sort_keys=False,
                default_flow_style=False
            )
    except Exception as e:
        print(f"Error writing file: {e}")
        sys.exit(1)

    print(f"\nInventory written to {output_file}\n")

    print("Preview:")
    print("-" * 40)
    with open(output_file) as f:
        print(f.read())

if __name__ == "__main__":
    main()
            

Execution Demo

Running the script and generating an inventory:

Script execution in terminal

Verifying the generated inventory with Ansible:

ansible-inventory --list output

Conclusion

This lightweight tool makes Ansible inventory creation fast, error-free, and repeatable—ideal for airgapped setups or quick prototyping. Save as yml-create.py, make executable if desired, and start building inventories effortlessly.