YAML inventory

Define services and hosts, merge tag variables, resolve paths, and select assignments.

Validate and view the supplied two-host example without network access:

go run . inventory validate -i examples/inventory/richt.yaml
go run . inventory show -i examples/inventory/richt.yaml
go run . inventory show -i examples/inventory/richt.yaml --tag hypervisor --service vmango

validate resolves the selected scope and reports errors. show performs the same checks and writes the resolved inventory as indented JSON. Neither command contacts hosts, opens templates, reads secret source values, or changes host state. The example describes a local host and an illustrative SSH host; execution supports both transport descriptions. See SSH and parallel apply for connection settings and noninteractive authentication.

The example resolves gena.vmango.cloud_name to Home and lanst.vmango.cloud_name to Net. Both retain listen = 127.0.0.1:8080 and libvirt_uri = qemu:///system. Each host’s services are ordered vmango-repository (10), then vmango (20), independent of YAML listing order.

Schema and file references

Inventory contains tags, services, hosts, and optional secret_sources. Names of hosts, tags, services and components start with a letter or number and contain only letters, numbers, hyphens and underscores. Component names are stable keys, independent of resource paths, package names and unit names. Unknown fields, duplicate YAML keys, non-string mapping keys, and multiple YAML documents are errors. All definitions and references are validated, including definitions outside the selected scope.

tags:
  shared:
    order: 10
    services:
      app: {}
    vars:
      app:
        listen: "127.0.0.1:8080"
services:
  app:
    order: 10
    file: services/app.yaml
hosts:
  - name: example
    tags: [shared]
    transport: {type: local}
secret_sources:
  default: {type: yaml, file: secrets/default.yaml}
  production: {type: yaml, file: secrets/production.yaml}

A service requires an integer order, including when it is not assigned to any host. Explicit zero is valid; omission is an error. Components can be defined inline or by file, exclusively, with order beside either form. Referenced files contain one bare service definition (binaries, configs, processes), without another services wrapper or recursive file references. Service file paths are relative to the inventory file. Template paths are relative to the service file declaring them; inline services use the inventory file as their base. Absolute local file paths are also accepted. URL sources are unsupported. Resolved JSON includes absolute source/template paths and normalized absolute target paths.

A service must contain at least one component; binary-only, config-only and process-only services are accepted. Tags assign services through a mapping keyed by service name. Each value contains only optional state: present|absent; {} defaults to present. Hosts receive services from all their tags and do not declare assignments themselves. A host without assignments has an empty service list. Component parameters belong to the service definition, with no assignment or host overrides.

Each service must occur at most once on a host, even when two tags assign it with identical settings. Errors identify the host, service and both source tags. Different effective services on a host must have different service orders. Tags or services that are never combined on a host may reuse assignments or order values. Services execute in ascending service order, including absent assignments; tag order does not set execution order. Removing a tag or assignment does not request resource removal: use explicit state: absent.

Components

binaries:
  package:
    type: rpm
    manager: dnf              # dnf (default) or yum
    package: app
    version: "0:1.2-3.noarch" # optional exact epoch:version-release.arch
configs:
  main:
    path: /etc/app.conf
    owner: root
    group: root
    mode: "0644"
    selinux: system_u:object_r:etc_t:s0
    sensitive: false
    template:
      file: templates/app.gotmpl
processes:
  web:
    type: systemd
    unit: app.service
    state: running           # running (default) or stopped
    enabled: true            # optional
    timeout: 5s              # positive Go duration; default 5s
    restart_on: [binary.package]
    reload_on: [config.main]
    after: []

Configuration targets must be absolute file paths. mode is an octal string. template requires exactly one of file or inline; an empty inline string is valid. Templates receive the complete merged host variables. Missing variables, file permissions, actual owners/groups and SELinux application are checked when preparing or executing a plan, beyond inventory-only validation.

restart_on and reload_on reference binary.<name> or config.<name> in the same service. after references process names in the same service. Unknown, duplicate or cross-service references and cycles are errors. Processes use systemd .service unit names. Reload support depends on host capabilities and is checked during plan preparation.

An optional process unit_file and named overrides use the same fields as configs. Their target paths may be omitted and are then derived as shown:

processes:
  web:
    type: systemd
    unit: app.service
    unit_file:
      template: {file: templates/app.service.gotmpl}
    overrides:
      10-runtime.conf:
        template: {inline: "[Service]\nRestart=always\n"}

The unit target is /etc/systemd/system/app.service; the override target is /etc/systemd/system/app.service.d/10-runtime.conf. Explicit paths must match these locations. Override names are plain .conf filenames. Template paths still use the declaring service file as their base.

Variable merging

Tags are processed by ascending order, which defaults to zero when omitted. All tags of an order are checked as a group before that group is merged with earlier orders. Host tag listing order has no effect.

  • Scalars and lists replace the lower-order value completely.
  • Maps merge recursively; a map and a non-map replace each other at higher order.
  • At equal order, different keys and compatible nested maps merge. Identical values are accepted. Different scalar/list values or incompatible types on the same path fail, even when a higher-order tag would replace that path.
  • Errors identify the host, variable path, both tag names and common order.

For a reproducible conflict, add home to lanst.tags in a copy of the example: home and net both set vmango.cloud_name at order 20. Validation fails at vars.vmango.cloud_name, including when --service vmango-repository is used.

Resource ownership

Before service filtering, every assignment of each selected host is checked. Different services cannot own the same explicitly declared RPM package, normalized absolute file path, or systemd unit, even with identical settings or different present/absent states. DNF and YUM share the RPM package namespace. Managed unit files and drop-ins also claim their unit, including ordinary configs under standard systemd system directories. Exact duplicate file/package components within one service and two processes managing the same unit are also rejected. A process may own its unit file and multiple drop-ins.

For a reproducible file conflict, change the repository config target to /etc/vmango.conf in a copy of the example. Validation identifies the host and both vmango-repository/config.repository and vmango/config.main, including with --service vmango.

Selection

--host, --tag, and --service can each repeat. Matching is exact and case sensitive. Unknown names produce errors; repeated selectors are harmless.

  • Within the host group, host names and tags form a union: --host gena --tag net selects gena plus every host tagged net.
  • Repeated service names form a union of already assigned services.
  • The host group and service group intersect. With no selectors in a group, that group selects everything.
  • Selecting a service never assigns it to new hosts or adds dependencies. Selected hosts with no matching assignments remain visible with an empty services list.
  • Hosts are displayed by name; their assignments are displayed by increasing service order. --tag selects hosts, and every selected host still receives services and variables from all its tags. Duplicate assignment, service-order, ownership and variable conflicts on unselected hosts are outside the scope; service selection cannot hide those conflicts on selected hosts. Definitions and references, including assignments in unused tags, are always validated.
# All services on one host
go run . inventory show -i examples/inventory/richt.yaml --host gena
# One service on every host
go run . inventory show -i examples/inventory/richt.yaml --service vmango
# A union of hosts, intersected with a union of services
go run . inventory show -i examples/inventory/richt.yaml \
  --host gena --tag net --service vmango-repository --service vmango

Secret declarations

Named sources currently accept only {type: yaml, file: <local path>}. Paths are relative to the inventory file. Multiple independent YAML sources are allowed. A default source is required only when a template calls secret without naming a source. Inventory commands show declarations and never read the files or their values, even if a file exists; missing source files therefore do not fail inventory validation. Secret lookup and template secret evaluation belong to plan preparation. Sources are read locally on the machine running diff, including for SSH targets. An absent service does not render templates or read secret sources. See YAML secrets and sensitive files for the value schema, function arguments, caching, diagnostics, and saved-plan behavior.