EventSource
Keep event configuration in git as YAML and push it to Horizon declaratively.
The loop has three steps: event-source convert brings existing XML in, onmsctl apply -f ships edits out, and event-source export snapshots the server back to YAML.
YAML
metadata.name becomes the server's stored source name verbatim.
Horizon derives vendor as the prefix before the first . in that name: cisco.foo becomes vendor cisco.
See Field semantics below for the full per-field schema and Example for a complete document.
Apply
onmsctl apply -f cisco.foo.yaml --diff # push (diff to stderr)
apply is idempotent: Horizon's upsert replaces events under an existing basename.
Inspect and delete
onmsctl event-source list # filter / sort / page
onmsctl event-source get <id>
onmsctl event-source download <id|name> -O out.xml # raw XML for one source
onmsctl event list --source <id> # events for a source
download and export accept a selector that is either a numeric id or an exact source name.
Snapshot server sources back to git-managed YAML, the reverse of apply:
onmsctl event-source export cisco.foo # one source → stdout (YAML)
onmsctl event-source export cisco.foo --out ./s/ # one source → ./s/cisco.foo.yaml
onmsctl event-source export --out ./sources/ # every source → one file each
Export routes through the convert migrator, so it carries the same EC### findings, on stderr.
In bulk it is continue-on-error: warnings are written, blocking and download-failed sources are reported and counted, and the exit code reflects the highest severity seen.
--out validates every target filename before writing anything: it is all-or-nothing.
Read/raw verbs stay imperative: event-source list|get|names|names-and-ids, event list (including cross-source --uei/--vendor), and the raw-XML event-source upload|download round trip.
Use the raw-XML round trip for full fidelity when a source carries fields the YAML model doesn't represent yet.
Convert
# Single file to stdout
onmsctl event-source convert mevents.xml > mevents.yaml
# Read from stdin (single input; --name required)
cat mevents.xml | onmsctl event-source convert - --name my-source > my-source.yaml
# Batch a directory into per-input YAML files
onmsctl event-source convert ./xml/*.xml --output-dir ./yaml/
Conversion emits EC###-coded findings on stderr for anything the YAML model doesn't represent.
Run onmsctl event-source convert --explain <code> for the rationale behind any finding.
See Convert findings below for the full catalog.
Convert findings
event-source convert parses each event against the local EventSource schema and emits findings on stderr.
Example:
EC004 error event missing required field: uei
At: bad.events.xml:14:5 (event[3])
Fix: Add the required uei to the event in the source XML.
For the full rationale: onmsctl event-source convert --explain EC004
Finding codes (EC001-EC008) are stable across releases; --explain <code> prints the full rationale for any of them.
| Code | Severity | Meaning |
|---|---|---|
| EC001 | warning | Unmodeled direct-child element under <event> dropped on conversion. |
| EC002 | error | Source has zero events. |
| EC003 | error | Reserved metadata.name. |
| EC004 | error | Event missing a required field. |
| EC005 | warning | Severity case normalized (e.g. WARNING to Warning). |
| EC006 | error | Post-conversion validation failed (catch-all). |
| EC007 | error | alarm-type outside the accepted set {1, 2, 3}. |
| EC008 | error | Invalid metadata.name (disallowed characters). |
Exit codes: 0 clean, 1 warnings (YAML written), 2 blocking findings (no YAML).
Flags:
| Flag | Purpose |
|---|---|
--format json | CI envelope with output path and yaml body. |
--max-bytes 64M | Override the 16 MiB input cap. |
--max-findings 0 | Disable the 1000-finding EC001 cap (set <n> for any other limit). |
--force | Overwrite existing output. |
--explain <code> | Print the full rationale for a finding code and exit. |
EC001 is the permanent forward-compatibility surface: any direct-child element under <event> that the YAML schema doesn't model fires EC001 rather than silently losing data.
The early modeling gaps (<snmp>, <parameter>, <forward>, <script>, <filters>) are now first-class.
Remaining unmodeled XSD elements (<priority>, <autoaction>, <operaction>, <loggroup>, vendor extensions) keep firing EC001 until modeled.
For full fidelity today, keep the XML alongside the YAML and use event-source upload.
EC001 is structural-only: it does not detect attribute extensions on modeled elements or enum-value drift on modeled fields.
Field semantics
alarmType
spec.events[].alarmData.alarmType accepts the three known states, in either symbolic (Web UI) form or the integer it maps to:
| Symbolic | Integer |
|---|---|
raise | 1 |
resolution | 2 |
unresolvable | 3 |
Symbolic input is case-insensitive on parse; canonical YAML output is always lowercase.
Anything else fails immediately: unknown symbolic strings ("problem", the alarmd Java alias) or integers outside {1, 2, 3}.
YAML inputs reject at deserialize time; eventconf XML inputs produce an EC007 finding (error, exit 2).
snmp
spec.events[].snmp mirrors the eventconf XSD's <snmp> element.
Every sub-field is optional.
Practical numeric ranges are documented but not enforced: out-of-range integers round-trip verbatim.
String fields are rejected when empty or whitespace-only.
id: enterprise OID, a free string with no OID-format validation.idtext: vendor-supplied textual label.version: common valuesv1/v2c/v3(free string; variants likev3-auth-privaccepted verbatim).generic:0..=6per RFC 1157.specific:>= 0.community: typicallypublic.
parameters
spec.events[].parameters mirrors <parameter name value expand/>: static per-event configuration eventd attaches to fired events.
Each entry requires name and value; expand is optional and controls whether eventd substitutes %parm[#N]%-style placeholders at fire time.
Document order is preserved.
This is distinct from parmCollection on a fired event instance, a runtime field on the JSON wire that is not modeled here.
The names are similar but the domains differ; do not conflate them.
forwards and scripts
spec.events[].forwards mirrors <forward state mechanism>target</forward>: eventd's forwarding directives, validated against the XSD-closed sets:
stateis one ofon,off.mechanismis one ofsnmpudp,snmptcp,xmltcp,xmludp.
Values outside these sets are rejected locally (otherwise Horizon returns a 400).
An empty forwards: [{}] entry is rejected; at least one of state/mechanism/target must be set.
spec.events[].scripts mirrors <script language>body</script>: embedded executable logic (typically BeanShell) that eventd runs on event arrival.
language is required; body is optional and preserved byte-for-byte (use YAML's | literal block for multi-line bodies).
Shipping executable code via onmsctl apply lowers the friction for deploying server-side code execution on Horizon.
The threat surface already exists at the raw eventconf-XML upload path; modeling <script> in YAML adds no new authority.
Ensure RBAC on eventconf write access reflects it: anyone who can upload an event source can run code on the Horizon JVM.
filters
spec.events[].filters mirrors <filters><filter eventparm pattern replacement/></filters>.
Each entry is a regex-replacement rule eventd applies to a named event parameter at fire time:
Pattern.compile(pattern).matcher(parmValue).replaceAll(replacement)
All three fields are required.
pattern is Java regex; replacement supports $1/$2 backreferences.
The YAML is flat (filters: directly on the event); the <filters> wrapper materializes only on XML render.
<mask> versus <filters>: <mask> selects which events a source applies to (SNMP PDU shape matching on id / generic / specific / varbind values, each an OR-matched value list).
<filters> operates after selection, rewriting parameter values on the fired event.
These are two different layers.
Apply-time limitations
Run onmsctl apply --help for the full text.
| # | Limitation | Workaround |
|---|---|---|
| 1 | description not persisted server-side through apply. | Carry intent in the YAML and git review; the field round-trips locally. |
| 2 | Disabled-state apply has a bounded enabled-flap window. | --verbose warns when this runs. |
| 3 | vendor is filename-derived, not declared. | Encode as the prefix before the first . in metadata.name. |
| 4 | fileOrder is server-managed. | Deferred to a future kind: EventConfMaster. |
Example
The examples directory also ships event-source-severities.yaml and event-source-disabled.yaml.
# Minimal EventSource — the smallest document that `onmsctl apply`
# accepts. metadata.name MUST contain at least one '.' (the prefix before
# the first '.' becomes the server-derived vendor: here, "vendor-example").
#
# Apply against a configured context:
#
# onmsctl apply -f examples/minimal.yaml --diff
#
apiVersion: eventconf.opennms.org/v1
kind: EventSource
metadata:
name: vendor-example.cold-start
spec:
enabled: true
events:
- uei: uei.opennms.org/vendor-example/cold-start
label: "Vendor Example Cold Start"
severity: Warning
description: |
Demonstrates the minimum viable EventSource document.
# Comprehensive EventSource — exercises every nested type the EventSource
# model supports. Use this as a reference when authoring your own
# EventSource documents.
#
# Style note: optional fields are OMITTED rather than set to `null`. The
# schema treats absence and `null` equivalently, but keeping the document
# minimal matches what `config view` would round-trip back.
apiVersion: eventconf.opennms.org/v1
kind: EventSource
metadata:
name: cisco.foo
spec:
enabled: true
events:
- uei: uei.opennms.org/vendor/cisco/foo/coldStart
label: "Cisco Foo Cold Start"
severity: Warning
description: |
Foo device performed a cold start (re-initialized configuration).
operinstruct: "Investigate device boot logs."
mouseovertext: "ifIndex=%parm[ifIndex]%, type=%parm[linkType]%"
logmsg:
# logndisplay | logonly | displayonly | suppress | donotpersist | discardtraps
dest: logndisplay
text: "%nodelabel% performed a cold start"
alarmData:
reductionKey: "%uei%:%dpname%:%nodeid%"
# alarmType accepts symbolic (`raise` / `resolution` / `unresolvable`)
# or integer (1 / 2 / 3) forms. Symbolic is recommended for readability.
alarmType: raise
autoClean: false
mask:
# The mask matches an INCOMING event (typically an SNMP trap) to this
# definition. Recognized element names: uei / source / nodeid / host /
# interface / snmphost / service / id (enterprise OID) / trapoid /
# generic / specific / community. A maskelement matches when the event
# value equals ANY listed value (OR within an element, AND across
# elements). `severity` is NOT a mask field — severity is assigned by
# this definition, not matched on.
elements:
- name: id
values: [".1.3.6.1.4.1.9.9.41.2"] # enterprise OID
- name: generic
values: ["6"] # enterprise-specific trap
- name: specific
values: ["1", "2"] # multi-value: specific 1 OR 2
# A varbind matches by position (vbnumber) or OID (vboid). Like
# maskelement values, `values` is a list matched with OR, and each
# value may be plain (exact), `~`-prefixed (regex), or `%`-suffixed
# (prefix / starts-with).
varbinds:
- vbnumber: 1 # positional match
values: ["3", "5"] # OR-matched: 3 or 5
- vboid: ".1.3.6.1.4.1.9.9.41.1.1.1.1.0" # OID-based match
values: ["~^crit.*"] # regex (leading `~`)
autoacknowledge:
state: "off"
tticket:
state: "off"
correlation:
state: "off"
# cmin / cmax are typed as Option<String> (event-correlation
# parameter strings, not raw integers) — quote to keep the
# fixture portable across YAML and JSON loaders.
cmin: "0"
cmax: "0"
cuei: []
varbindsdecode:
- parmid: "1"
decode:
- value: "0"
label: "off(0)"
- value: "1"
label: "on(1)"
snmp:
# Per RFC 1157: generic ∈ 0..=6, specific ≥ 0.
# `version` is a free string; common values are v1 / v2c / v3
# but `v3-auth-priv` and other Horizon-version-specific variants
# round-trip verbatim.
id: ".1.3.6.1.4.1.9.1.13"
idtext: "Cisco"
version: v2c
generic: 6
specific: 1
community: public
parameters:
# Static per-event configuration; distinct from runtime
# parmCollection (which is per-fired-event, not eventconf XML).
# eventd evaluates entries in document order. `expand` is
# optional — absent means the eventd default applies. The
# absent vs explicit `true`/`false` distinction round-trips.
- name: endpoint
value: "/var/log/foo"
- name: context
value: "%parm[#1]%"
expand: true
forwards:
# eventd forwarding directives. state is {on, off};
# mechanism is one of the XSD-closed set:
# snmpudp / snmptcp / xmltcp / xmludp
# Anything outside the closed sets is rejected at parse so
# Horizon doesn't reject the upload with a server-side 400.
- state: "on"
mechanism: snmpudp
target: "alarmcentral:162"
scripts:
# SECURITY NOTE: shipping executable code via `apply`
# requires RBAC on eventconf write access in Horizon. The
# threat surface already exists at the raw XML upload path;
# modeling here just lowers the friction.
- language: beanshell
body: |
// Multi-line BeanShell preserved byte-for-byte.
do_thing();
another_thing();
filters:
# eventd regex-replacement rules on named event parameters.
# `pattern` is Java regex; `replacement` is Matcher.replaceAll
# syntax (use $1 / $2 for capture-group backreferences).
# NOT a suppression filter — these rewrite parm values at
# event-fire time.
- eventparm: trapMsg
pattern: '\bWARN\b'
replacement: "WARNING"
- eventparm: ifAlias
pattern: '^old-(.*)$'
replacement: "new-$1"