Skip to content

yang model

Thomas Mangin edited this page Jul 25, 2026 · 2 revisions

Pre-Alpha. This page describes behavior that may change.

Every field in Ze comes from a YANG schema. The CLI prompts, the web UI forms, the REST API URLs, the MCP tool signatures, the validation error messages, and the tab completion candidates all derive from the same schemas. Changing a schema updates every surface at once. This page covers how to read the schemas, how to map them to config paths, and what happens when validation fails.

Where the schemas live

The schemas are split between the engine and the plugins.

Engine schemas. Each top-level container lives in its own component directory: BGP at internal/component/bgp/schema/, system at internal/component/config/system/schema/, environment at internal/component/hub/schema/, telemetry at internal/component/telemetry/schema/. These define the top-level containers (bgp, system, environment, telemetry), the ze-specific extensions (ze:required, ze:suggest, ze:decorate), and the cross-cutting types.

Subsystem schemas. Each subsystem has its own schema directory. The BGP subsystem has one under main/internal/component/bgp/schema/, and each BGP plugin has its own under main/internal/component/bgp/plugins/<plugin>/schema/. Interface management has one under main/internal/component/iface/schema/. The web UI has one. The system block has one.

Plugin schemas. Every plugin that ships config declares a schema. The schema is embedded into the binary with //go:embed at compile time.

Reading a schema from a running daemon

The fastest way to look at a schema is through ze schema, which reads the in-memory schemas directly.

ze schema list                        # Every registered module
ze schema show ze-bgp-conf            # Full YANG text of a module
ze schema methods ze-bgp-conf         # Every RPC the module declares
ze schema events                      # Every notification type
ze schema handlers                    # Which handler serves which module
ze schema protocol                    # Protocol version and wire format

The output of ze schema show is the same YANG text that lives in the source file. It cannot go out of sync with the code because it is the same bytes.

How a YANG path maps to a config path

The YANG path and the config path are the same thing, with minor syntactic adjustments.

YANG:    /bgp/peer[name='upstream']/timer/receive-hold-time
CLI:     bgp peer upstream timer receive-hold-time
File:    bgp { peer upstream { timer { receive-hold-time 180; } } }
URL:     /show/bgp/peer/upstream/timer/receive-hold-time

The file format is hierarchical with curly-brace containers. The CLI flattens the path with spaces. The web URL uses slashes. All three resolve to the same YANG node.

When ze schema show shows you a container, a file-format block is the way to write it: foo { ... }. A list is the same but keyed, so every entry takes a key: list peer { key name; } becomes peer <name> { ... } in the file. A leaf is a single value: leaf receive-hold-time { type uint16; } becomes receive-hold-time <value>;.

The ze extensions

Ze adds a small set of YANG extensions on top of the standard types. The ones you will see most are ze:required, ze:suggest, and ze:decorate.

ze:required. Marks a leaf that must have a value after inheritance is resolved. The BGP peer's connection/remote/ip, session/asn/local, and session/asn/remote all carry this extension. Validation runs at ze config validate, at editor commit, and at daemon startup. A missing required field rejects the commit with a message that names the field.

ze:suggest. Marks a leaf that is not mandatory but should be offered in the web UI creation form with inherited defaults. Used for fields that have a sensible default but that operators typically override per peer.

ze:decorate. Attaches a decorator to a leaf so the web UI can show enriched display text. AS numbers carry a decorator that looks up the organisation name via Team Cymru DNS, which is why the web UI shows AS30740 (Exa Networks) instead of just 30740.

Validation runs everywhere

Every place that touches the config tree runs the same validators. That includes commit on the SSH CLI, save in the web UI, a commit from the REST API, a commit from an MCP tool, and ze config validate on a file. There is one set of rules, and it is enforced in one place: the YANG parser and the validators that sit behind it.

Validation covers the obvious things (type checks, ranges, regex patterns, enum values, length limits) and a few of the less obvious ones. The ze:required extension checks for presence after inheritance. Leafrefs check that the referenced value exists. Custom validators (IP address, MAC address, prefix) check format and range. Unknown keys are rejected with a suggestion for the closest valid key.

Standard YANG cardinality is enforced too. A leaf-list declared min-elements 1 is rejected when it is absent and when it is present but empty, which is what catches a VRRP group configured with no virtual address. Absence is the case worth calling out: goyang never marks a leaf-list mandatory, so the schema walk scans for a min-bounded leaf-list that never appeared rather than relying on the mandatory check.

A minimal schema example

module ze-my-plugin {
    namespace "urn:ze:my-plugin";
    prefix ze-mp;

    container my-plugin {
        description "Configuration for the my-plugin feature";

        leaf enabled {
            type boolean;
            default true;
            description "Turn the plugin on or off";
        }

        list endpoint {
            key "name";
            description "Endpoints this plugin watches";

            leaf name {
                type string { length "1..64"; }
            }

            leaf url {
                type string;
                mandatory true;
            }

            leaf timeout {
                type uint32 { range "1..3600"; }
                default 30;
            }
        }
    }
}

The file-format config that validates against this:

my-plugin {
    enabled true;
    endpoint api {
        url     "https://api.example.com";
        timeout 60;
    }
    endpoint backup {
        url "https://backup.example.com";
    }
}

The backup endpoint uses the timeout default of 30 because it does not set its own value.

When validation fails

ze# set bgp peer upstream timer receive-hold-time 2
error: leaf 'receive-hold-time': value 2 out of range (expected 0 or 3..65535)

ze# set bgp peer upstream remote ip 300.0.0.1
error: leaf 'ip': '300.0.0.1' is not a valid IPv4 address

ze# commit
error: bgp/peer[name='new-peer']: required field 'remote/as' is missing

Every validation error names the path and the specific rule that was violated. The CLI shows them inline; the web UI shows them as red toasts; ze config validate prints them with the file and line number; the REST API returns them as JSON with a structured error body.

See also

Adapted from main/docs/guide/configuration.md and main/docs/architecture/config/.

Home

About

First Steps

Configuration

Operation

Interfaces

Plugins

Plugin Development

Chaos Testing

Blueprints

Development

Reference

Clone this wiki locally