Declarative manifests¶
Otherix supports a kubectl-apply-style workflow: describe your resources in
YAML, then create or delete the whole set with one command. Manifests are an
alternative to the imperative otherix network create / pool create /
vm create flags - the same Control Plane endpoints, driven from a file.
otherix create -f cluster.yaml # create everything in the file
otherix delete -f cluster.yaml --force # tear it back down
Manifest shape¶
Every document carries an apiVersion, a kind, a metadata.name, and a
spec. The only accepted apiVersion is otherix/v1. The three supported
kinds are Network, StoragePool, and VM (case-sensitive).
apiVersion: otherix/v1
kind: Network
metadata:
name: demo-net
spec:
type: bridge
bridgeName: otdemo0
A single file may hold many documents separated by the YAML --- marker. You
can also pass -f more than once, or read from stdin with -f -:
Unknown fields are rejected
A typo in a spec field (for example bridge_name instead of bridgeName)
fails the whole command at the CLI edge rather than being silently dropped.
Field names are camelCase.
Apply ordering¶
On create, document order does not matter. A VM references a network and a
storage pool by name, but VM admission resolves those references lazily and
never fails on a missing or not-yet-ready dependency, so a manifest that lists
a VM before its network or pool applies fine. otherix create -f still sorts
the documents Network -> StoragePool -> VM, but that ordering is cosmetic
(deterministic, readable logs) rather than a correctness requirement.
On delete, order does matter: otherix delete -f runs the reverse order
(VM -> StoragePool -> Network) so dependents are torn down before the pools
and networks they use.
Both commands are best-effort: each document is reported independently and the command exits non-zero if any document failed. A network that still has VM NICs attached is reported as blocked and skipped, never forced.
A full example¶
This manifest declares a managed bridge network and a VM attached to it, with inline cloud-init:
apiVersion: otherix/v1
kind: Network
metadata:
name: demo-net
spec:
type: bridge
managed: true
bridgeName: otdemo0
mtu: 1500
---
apiVersion: otherix/v1
kind: VM
metadata:
name: demo-vm
spec:
imageURL: https://cloud-images.ubuntu.com/minimal/releases/noble/release/ubuntu-24.04-minimal-cloudimg-arm64.img
arch: arm64
network: demo-net
vcpus: 2
memoryMB: 2048
diskGiB: 20
# inline cloud-config, sent as user_data at create time:
userData: |
#cloud-config
package_update: true
users:
- name: otherix
sudo: ALL=(ALL) NOPASSWD:ALL
Apply it, blocking until the VM create task and any pool reconciliation finish:
--wait blocks on the asynchronous resources (VM create tasks and storage-pool
reconciliation); networks are created synchronously and need no wait.
--wait-timeout defaults to 5m. Use --dry-run to print the resolved plan
without contacting the server:
Spec fields by kind¶
| Kind | Required | Optional |
|---|---|---|
Network |
type |
bridgeName, managed, egress, subnet, gateway, mtu, vlan |
StoragePool |
path, one of node / nodeList |
type |
VM |
imageURL, arch |
imageSHA256, firmware / firmwareID, format, diskGiB, vcpus, memoryMB, pool, network, node, userData, networkConfig / cloudInitDisabled |
Within a kind, a few fields are mutually exclusive: node and nodeList
(StoragePool); firmware and firmwareID, and userData /
networkConfig each with cloudInitDisabled (VM). When vcpus or memoryMB is omitted the CLI applies
the same defaults as vm create (2 vCPUs, 2048 MiB). See
Networks, Storage pools, and
Create and manage VMs for the meaning of each field.
StoragePool nodeList fan-out¶
A storage-pool name is a per-node concept (see Storage pools).
A single StoragePool document can fan out to one instance per node with
spec.nodeList:
apiVersion: otherix/v1
kind: StoragePool
metadata:
name: default
spec:
type: local_dir
path: /var/lib/otherix/pools/default
nodeList:
- node-a
- node-b
- node-c
This is expanded to one create (or delete) per listed node. Use spec.node for
a single instance. Duplicate node names in nodeList are de-duplicated.
Round-trip with get -o yaml¶
Every resource type can project a live object back to an apply-ready manifest
with get -o yaml, so you can capture current state into a file:
otherix network get demo-net -o yaml > demo-net.yaml
otherix pool get default -o yaml
otherix vm get demo-vm -o yaml
The projection deliberately omits server-assigned fields (id, timestamps, status, owner, reconciliation) so the output re-applies cleanly.
Some fields do not round-trip
The API view does not surface every create-time field, so a
get -o yaml | create -f round-trip is not always lossless. Keep your
source manifest as the record of what you applied.
- VM:
userData(user_data),networkConfig(network_config),cloudInitDisabled,firmware/firmwareID, anddiskGiBare consumed at create time and are not in the view, so the projection omits them and re-applying reverts those to server defaults. Only the first NIC is projected (the v1 VM schema attaches a singlenetwork); a multi-NIC VM loses the extras. - Network: bridge networks round-trip in full. An overlay network
projects only
type+subnet(the create API forbids the server-derivedbridgeName/mtu/vlan), so re-applying allocates a fresh VNI rather than preserving the original. - StoragePool: round-trips except the operator-settable
configblob. A multi-node pool projects as a singlenodeListdocument when every instance shares a path, or as one document per instance when paths differ, so each node keeps its own path on re-apply.
The config blob on both Network and StoragePool is not yet
manifest-expressible (there is no config field in the v1 schema) and is
dropped on projection.
Deleting from a manifest¶
otherix delete -f reads the same files and removes the named resources in
reverse create order. It prompts for confirmation when stdin is a terminal;
--force skips the prompt and --dry-run prints the plan:
otherix delete -f cluster.yaml # prompts when interactive
otherix delete -f cluster.yaml --force # no prompt
otherix delete -f cluster.yaml --dry-run
Existing delete blockers (a network still in use, a pool with disks) are
reported and skipped - delete -f never force-deletes a resource that has
dependents.