Plugins declare their configuration schema using YANG (RFC 7950). This guide covers the basics for plugin authors.
Schemas are declared via the Schema field of sdk.Registration, which is passed to
p.Run(). The Schema field is a pointer to sdk.SchemaDecl.
p := sdk.NewWithConn("my-plugin", conn)
p.Run(ctx, sdk.Registration{
Schema: &sdk.SchemaDecl{
Module: "ze-my-plugin",
Namespace: "urn:ze:my-plugin",
YANGText: myPluginYANG, // YANG module text (string)
Handlers: []string{"my-plugin"},
},
WantsConfig: []string{"my-plugin"},
})| Field | Type | Purpose |
|---|---|---|
Module |
string |
YANG module name |
Namespace |
string |
YANG namespace URI |
YANGText |
string |
Full YANG module text |
Handlers |
[]string |
Config path prefixes this plugin handles |
Internal plugins use //go:embed to embed YANG files at compile time:
import _ "embed"
//go:embed my-plugin.yang
var myPluginYANG stringThe embedded string is then passed as YANGText in the SchemaDecl.
For external plugins, the YANG text can come from any source (file read, constant, etc.).
module my-plugin {
namespace "urn:my-company:my-plugin";
prefix mp;
container my-plugin {
leaf enabled {
type boolean;
default true;
}
}
}module name {
namespace "urn:..."; // Unique namespace URI
prefix prefix; // Short prefix for references
// Content: containers, lists, leafs
}| YANG Type | Go Type | Example |
|---|---|---|
string |
string |
"hello" |
boolean |
bool |
true/false |
uint8/16/32/64 |
uintN |
42 |
int8/16/32/64 |
intN |
-42 |
leaf port {
type uint16 {
range "1..65535";
}
}
leaf hold-time {
type uint16 {
range "0 | 3..65535"; // 0 or 3+
}
}leaf name {
type string {
length "1..64";
}
}leaf ipv4-address {
type string {
pattern '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+';
}
}leaf action {
type enumeration {
enum allow;
enum deny;
enum log;
}
}container settings {
leaf timeout { type uint32; }
leaf retries { type uint8; }
}Config:
settings {
timeout 30;
retries 3;
}
list endpoint {
key "name";
leaf name { type string; }
leaf url { type string; }
leaf enabled { type boolean; default true; }
}Config:
endpoint api {
url "https://api.example.com";
}
endpoint backup {
url "https://backup.example.com";
enabled false;
}
leaf required-field {
type string;
mandatory true; // Must be provided
}
leaf optional-field {
type uint32;
default 100; // Used if not provided
}Reference values from other parts of config:
// In ze-bgp-conf module
list peer-group {
key "name";
leaf name { type string; }
}
// In your module - reference peer-group
leaf group {
type leafref {
path "/bgp/peer-group/name";
}
}Ze validates that referenced values exist.
- Use meaningful namespaces -- include your organization
- Add descriptions -- help users understand fields
- Set sensible defaults -- reduce required config
- Validate early -- use YANG constraints, not just code
- Use the
Handlersfield -- list the config path prefixes your plugin manages
A complete example showing YANG schema registration with the SDK:
package main
import (
"context"
_ "embed"
"log"
"os"
"os/signal"
"codeberg.org/thomas-mangin/ze/pkg/plugin/sdk"
)
//go:embed acme-monitor.yang
var acmeMonitorYANG string
func main() {
p, err := sdk.NewFromEnv("acme-monitor")
if err != nil {
log.Fatal(err)
}
p.OnConfigure(func(sections []sdk.ConfigSection) error {
for _, s := range sections {
log.Printf("config root=%s data=%s", s.Root, s.Data)
}
return nil
})
p.OnEvent(func(event string) error {
log.Printf("event: %s", event)
return nil
})
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
if err := p.Run(ctx, sdk.Registration{
Schema: &sdk.SchemaDecl{
Module: "acme-monitor",
Namespace: "urn:acme:monitor",
YANGText: acmeMonitorYANG,
Handlers: []string{"acme-monitor"},
},
WantsConfig: []string{"acme-monitor"},
}); err != nil {
log.Fatal(err)
}
}module acme-monitor {
namespace "urn:acme:monitor";
prefix acme;
description "ACME endpoint monitoring plugin";
container acme-monitor {
description "Monitor configuration";
leaf endpoint {
type string;
mandatory true;
description "HTTPS endpoint to monitor";
}
leaf interval {
type uint32 {
range "10..3600";
}
default 60;
description "Check interval in seconds";
}
list alert {
key "name";
description "Alert destinations";
leaf name {
type string {
length "1..64";
}
}
leaf email {
type string;
}
}
}
}