-
Notifications
You must be signed in to change notification settings - Fork 3
commands
Pre-Alpha. This page describes behavior that may change.
A plugin exposes commands by declaring them in its Registration at startup and handling them with OnExecuteCommand at runtime. Once declared, the command is visible on every operator surface automatically: the CLI shell, ze cli -c, the MCP server, the REST API, and the Looking Glass (if it is read-only). You do not wire it up five times. You declare it once.
This page covers the author side. The consumer side (how commands flow from the CLI through the dispatcher to the plugin) is in the architecture reference.
Commands go in the Commands field of sdk.Registration. The engine learns about them during stage 1 (declare-registration).
err := p.Run(ctx, sdk.Registration{
Commands: []sdk.CommandDecl{
{Name: "my-plugin status", Description: "Show current status"},
{Name: "my-plugin check", Description: "Trigger immediate check", Args: []string{"target"}},
},
})| Field | Purpose |
|---|---|
Name |
The full command name. Use spaces to group (my-plugin status). |
Description |
One line shown in help and command-list. |
Args |
The expected positional arguments. Used for tab completion and help text. |
Register OnExecuteCommand before calling Run(). The handler receives the command serial (a correlation id for the request), the command name, the arguments, and the peer selector if one was provided.
p.OnExecuteCommand(func(serial, command string, args []string, peer string) (status, data string, err error) {
switch command {
case "my-plugin status":
return "done", `{"status":"running","uptime":3600}`, nil
case "my-plugin check":
if len(args) < 1 {
return "error", "usage: my-plugin check <target>", nil
}
result := performCheck(args[0])
return "done", result, nil
default:
return "error", "unknown command: " + command, nil
}
})OnExecuteCommand returns (status, data, error).
Success with data. Return a short status string ("done" is the usual choice) and the structured result as a JSON string.
return "done", `{"count":42,"items":["a","b"]}`, nilSuccess without data. Return "done" and an empty data string.
return "done", "", nilHandler error. Return a non-nil error. The SDK sends an error response and the status and data values are ignored.
return "", "", fmt.Errorf("operation failed: database timeout")User-facing validation failure. Return "error" as the status with an explanatory message in data, and nil for the error. The engine treats the response as a successful RPC but the operator sees the error message.
return "error", "usage: my-plugin check <target>", nilThe distinction matters: a Go error from the handler is a plugin bug, and the engine logs it. A "error" status is an expected failure that the user should see.
Commands are delivered as execute-command RPCs over the plugin connection.
#17 ze-plugin-callback:execute-command {"serial":"abc123","command":"my-plugin status","args":[],"peer":""}
#17 ok {"status":"done","data":"{\"status\":\"running\"}"}
On a handler error:
#17 error {"message":"operation failed: database timeout"}
| Field | Type | Purpose |
|---|---|---|
serial |
string | Correlation id for the request. |
command |
string | Command name (e.g. "my-plugin status"). |
args |
[]string |
Additional arguments. May be empty. |
peer |
string | Peer selector. May be empty. |
The peer field is the selector from the CLI (e.g. *, a peer name, or an IP). Use it to narrow your command's effect to the peers the operator targeted.
Ze matches the operator's positional tokens against the declared ArgDef list before your handler runs.
- Every argument kind takes part in the match, typed kinds included. A
uint16port and a plain string are both offered a token, soshow tcp-check 127.0.0.1 1 timeout 2sbinds1to the port instead of skipping it and then failing withrequired argument missing: port. - Mandatory definitions are offered a token before optional ones. An optional pattern-less string can no longer take the value a required argument needed.
- A single spare token becomes the peer selector when the command declares that it requires one, no selector arrived out of band, and there is exactly one spare token. That is what makes
delete bgp peer 127.0.0.1work whenpeeris the last key word of the command. A command that takes one token AND declares a mandatory selector, such asannounce, is not affected: the one-spare-token fence keeps its argument out of the selector slot. - A validation failure is reported at the position where it was found, so the operator-visible message order does not change.
A command that acts on ONE peer resolves its selector through the shared resolver. It accepts a name, an address, an ASN (as65001), or a prefix, and it refuses two forms:
| Selector | Verdict |
|---|---|
* or empty |
Refused: the command requires one specific peer |
!edge1, !as65001, !10.0.0.0/24
|
Refused: an exclusion selector cannot name one peer |
| A selector matching several peers | Refused as ambiguous, never resolved to a guess |
| A selector matching nothing | Refused, with the selector quoted |
A filter for a show command is different: a complement is a good answer when the command narrows a list rather than acting on one target.
A plugin-proxied command is registered in both the dispatcher and the plugin command registry, deliberately, so the dispatcher can route to the plugin. The merged command list deduplicates on the name both sources key on, keeps the dispatcher entry, fills an empty dispatcher help text from the plugin description, and sorts by name. Consumers of the list (the MCP tool schema, the API command lister) therefore see one stable entry per command rather than two whose content depended on map iteration order.
Forward to a worker. The handler runs on the plugin's event loop. If the command does real work, push the request onto a channel and return a status that says "accepted" or "in progress". The operator can poll for the result with a separate command.
JSON all the way. data is a string, but everything else in Ze treats the contents as JSON. Return JSON even when the output is trivial. It plays better with ze cli ... | json and with the MCP tools.
Tab completion. The Args field gives Ze a hint for tab completion. For dynamic completion (peer names, family names), the engine generates candidates from its own registry, so you usually do not need to do anything extra.
-
Handlers for the full
OnExecuteCommandsignature. - Go plugins for the surrounding SDK.
- Interfaces: CLI for the operator side of the pipe.
- In-tree commands reference for the full version.
Adapted from main/docs/plugin-development/commands.md.
Unreviewed draft. This wiki was authored in bulk and has not been reviewed. File corrections on the issue tracker.
- Overview
- YANG Model
- Editor Workflow
- Archive and Rollback
- System
- Interfaces
- VRRP
- BFD
- FIB
- OSPF
- IS-IS
- MPLS / LDP / RSVP-TE
- RSVP-TE
- SRv6
- Static Routes
- Policy Routing
- Firewall
- Traffic Control
- Class of Service
- L2TP/PPP
- PPPoE
- VPP Data Plane
- RPKI
- IPsec VPN
- TACACS+ AAA
- RADIUS AAA
- AS112 DNS
- DNS
- Authorization
- Fleet
- BGP
- Starting and Stopping
- Show Commands
- Monitoring
- Flow Export
- DDoS Mitigation
- Anomaly Detection
- Health Checks
- Audit Trail
- Production Diagnostics
- Logging
- Operational Reports
- Healthcheck
- Self-Update
- Zero-Touch Provisioning
- MRT Analysis
- Upgrade and Restart
- Storage
- Policy
- Core
- Resilience
- Validation
- Capabilities
- Address Families
- Protocol
- Subsystems
- Infrastructure
- Route Server at an IXP
- Transit Edge with RPKI
- Public Looking Glass
- ExaBGP Migration Walkthrough
- FlowSpec Injection
- Chaos-Tested Peering
- AS Path Topology