A kubectl-style command-line interface for OpenNMS Horizon —
declarative apply -f for event configuration and provisioning
requisitions, XML→YAML migrators for both legacy eventconf and
provision.pl-shape requisition files, and imperative verbs for
source / event / requisition management.
Pre-stability notice. Releases on
v0.x.ymay break CLI flags, the configuration schema, and theEventSourceYAML schema between minor versions. Surfaces stabilize atv1.0.0.
Pre-compiled binaries are published as GitHub Releases for every v*.*.*
tag. Each release ships per-target binaries, per-binary SHA256 checksums,
an aggregate SHA256SUMS file, and Sigstore (cosign) keyless signatures
- certificates for every asset.
| Target | Asset suffix |
|---|---|
| Linux x86_64 | x86_64-unknown-linux-gnu |
| Linux aarch64 | aarch64-unknown-linux-gnu |
| macOS x86_64 (Intel) | x86_64-apple-darwin |
| macOS aarch64 (Apple Silicon) | aarch64-apple-darwin |
Windows is not yet in the release matrix; Windows users build from source (below).
VERSION=v0.1.0
TARGET=x86_64-apple-darwin # or one of the rows above
curl -fL -O https://github.com/no42-org/onmsctl/releases/download/${VERSION}/onmsctl-${VERSION}-${TARGET}
curl -fL -O https://github.com/no42-org/onmsctl/releases/download/${VERSION}/onmsctl-${VERSION}-${TARGET}.sha256
shasum -a 256 -c onmsctl-${VERSION}-${TARGET}.sha256
chmod +x onmsctl-${VERSION}-${TARGET}
sudo mv onmsctl-${VERSION}-${TARGET} /usr/local/bin/onmsctl
onmsctl versionVerify the cosign signature (recommended). Every release asset is signed via Sigstore's keyless OIDC flow. Verifying ties the binary to a specific GitHub Actions workflow run on this repository, with no long-lived key:
cosign verify-blob \
--certificate-identity-regexp "^https://github.com/no42-org/onmsctl/.github/workflows/release.yml@" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate onmsctl-${VERSION}-${TARGET}.pem \
--signature onmsctl-${VERSION}-${TARGET}.sig \
onmsctl-${VERSION}-${TARGET}macOS Gatekeeper. Binaries are Sigstore-signed but not Apple-notarized. On first run macOS may quarantine the file:
xattr -d com.apple.quarantine /usr/local/bin/onmsctlor approve once via System Settings → Privacy & Security.
For development, unreleased commits, or platforms outside the release
matrix. Requires the toolchain pinned in rust-toolchain.toml (currently
Rust 1.95):
git clone https://github.com/no42-org/onmsctl
cd onmsctl
make build # debug build → target/debug/onmsctl
cargo build --release # → target/release/onmsctl
cargo install --path crates/onmsctl # → ~/.cargo/binVerify with onmsctl version.
onmsctl is one binary that statically links every capability crate
in the workspace. Run onmsctl version to see the binary version
alongside each linked capability:
$ onmsctl version
onmsctl 0.0.1
capabilities:
- eventconf 0.0.1
- provisioning 0.0.1
Each capability owns its own subcommand tree (source / event,
requisition, etc.) and ships its own JSON Schemas, validators, and
HTTP client wrappers. New capabilities are added as workspace
members; this list grows verbatim from onmsctl_<cap>::{CAPABILITY_NAME, VERSION}.
kubectl pattern: one config file, one or more named contexts, one currently-active context.
| OS | Default path |
|---|---|
| Linux | $XDG_CONFIG_HOME/onmsctl/config.yaml (typically ~/.config/onmsctl/config.yaml) |
| macOS | ~/Library/Application Support/org.no42-org.onmsctl/config.yaml |
| Windows | %APPDATA%\no42-org\onmsctl\config\config.yaml |
Override with --config <path> or $ONMSCTL_CONFIG.
current-context: dev
contexts:
- name: dev
server:
url: https://horizon.dev.lab/opennms
auth:
basic:
username: admin
password: adminDon't commit inline secrets. Use one of the credential references below.
auth.basic accepts exactly one of password / password-file /
keyring. auth.bearer accepts the same shapes as token /
token-file / keyring.
| Field | Notes |
|---|---|
password / token |
Inline plain-text. Convenient, leaks if the config leaks. |
password-file / token-file |
Mode 0600 recommended; trailing newlines stripped. |
keyring |
OS keyring. macOS Keychain and Windows Credential Manager work out of the box. On Linux the default build links only the kernel keyutils backend; for GNOME Keyring / KWallet support rebuild with --features keyring/sync-secret-service (adds a libdbus-1 build dependency). |
Resolution at request time:
env ($ONMS_PASSWORD / $ONMS_TOKEN) > keyring > file > inline
onmsctl config view # current config (secrets redacted)
onmsctl config use-context staging # atomic rewrite of current-contextconfig view redacts inline password / token strings; file and
keyring references remain visible (they are pointers, not secrets).
config use-context resolves symlinks before writing so a symlinked
config.yaml writes through to the upstream file.
flags (--url, --user, --context) > env (ONMS_URL, ONMS_USER, ONMSCTL_CONTEXT) > active context > built-in default
Keep event configuration in git as YAML; push to Horizon declaratively.
Two commands carry the loop — source convert brings existing XML in,
source apply ships edits out.
Pure local file transform; no Horizon contact required.
onmsctl source convert /opt/opennms/etc/events/cisco.foo.events.xml
onmsctl source convert --output-dir yaml/ /opt/opennms/etc/events/*.events.xmlRule violations emit stable, file:line-anchored findings on stderr (see
source convert reference for the finding-code
catalog, exit codes, flags, and the unmodeled-element policy).
onmsctl source apply -f cisco.foo.yaml --diffFetches the server's current state, prints a UEI-bucketed diff to
stderr, uploads only when changes exist. Add --dry-run to simulate.
source apply -f accepts a single file, a directory of YAML files, or
a glob pattern (quote it so the shell doesn't pre-expand):
onmsctl source apply -f sources/ # directory
onmsctl source apply -f 'sources/cisco-*.yaml' # globWith multiple files, each is applied in alphabetical (byte-wise) order
with continue-on-error semantics — failures are collected and the
exit code is non-zero if any file failed, but later files still run.
--diff is single-file-only, but a glob that matches exactly one
file collapses to single-file mode so --diff still applies.
metadata.name becomes the server's stored source name verbatim;
Horizon derives vendor server-side as the prefix before the first .
(so metadata.name: cisco.foo → source name cisco.foo, vendor cisco).
The examples/ directory ships fixtures: minimal.yaml, full.yaml
(every nested type), severities.yaml, disabled.yaml. Full schema
detail in the EventSource YAML reference.
Edit YAML in git, push through review, run source apply -f again. The
diff display flags changes the upload would make. --dry-run is safe
for any branch; source apply itself is idempotent (Horizon's upsert
path replaces events under an existing basename).
JSON Schemas (draft 2020-12) live under schemas/, one per
capability YAML kind. One line at the top of your YAML enables in-editor
validation via yaml-language-server:
# For kind: EventSource
# yaml-language-server: $schema=https://raw.githubusercontent.com/no42-org/onmsctl/main/schemas/event-source.schema.json
# For kind: Requisition
# yaml-language-server: $schema=https://raw.githubusercontent.com/no42-org/onmsctl/main/schemas/requisition.schema.jsonPin to a release tag for stability or reference a clone with
./schemas/<name>.schema.json. Regenerate with make schema; CI
fails if any committed artifact lags.
Project-specific extension — the requisition schema annotates list
fields with x-onmsctl-list-kind: ordered|set so downstream diff
tooling distinguishes ordered sequences (detectors, policies —
order is semantically meaningful in provisiond) from sets
(categories, services — order is ignored). Editors that don't
understand the extension ignore it harmlessly.
Manage Horizon requisitions (the provision.pl-shape data) declaratively
from git. Three verbs carry the loop — requisition convert brings
existing XML in, requisition apply ships edits out, requisition status / import cover the lifecycle.
Pure local file transform; no Horizon contact required.
# Single pair: requisition XML + matching foreign-source XML
onmsctl requisition convert \
--from /opt/opennms/etc/imports/ \
--foreign-sources-dir /opt/opennms/etc/foreign-sources/ \
--out yaml/
# Stream to stdout (one document per requisition, separated by `---`)
onmsctl requisition convert --from /opt/opennms/etc/imports/Rule violations emit PR###-coded findings on stderr. Run
onmsctl requisition convert --explain PR001 to see the rationale
for any code. PR001 / PR002 / PR003 are Warnings (exit 1), PR004 is
Info (exit 0), and parse failures exit 2.
# Single file — preview without writing. --diff goes to stderr,
# structured outcome to stdout. Safe against any context.
onmsctl requisition apply -f acme-prod.yaml --dry-run --diff
# Single file — real apply. With --wait, blocks until import
# completes (or --timeout fires for exit 10).
onmsctl requisition apply -f acme-prod.yaml --wait --timeout 5m
# Directory mode — every *.yaml / *.yml under the path is applied
# in alphabetical order. Phase 1 cross-file collision check runs
# first (duplicate metadata.name aborts before any writes;
# duplicate foreignId warns and continues). --stop-on-error
# (kubectl-style) halts phase 2 after the first per-file failure;
# default is continue-on-error.
onmsctl requisition apply -f ./requisitions/ --dry-run
onmsctl requisition apply -f ./requisitions/ --stop-on-error --waitThe apply path computes a three-level diff (canonical-bytes / per-node
/ per-leaf) and auto-decides rescanExisting from the scan-relevance
of what changed. Override with --rescan-existing true|false.
In directory mode, --wait polls per-file after each successful
apply (not as one batch wait). --diff is single-file only — for
directory previews use --dry-run -o yaml to see the structured
outcomes per file.
Edit YAML in git, push through review, run requisition apply -f
again. --dry-run is safe for any branch; --diff prints the diff to
stderr so -o json/-o yaml consumers on stdout stay clean.
The composite kind: Requisition document carries spec.foreignSource
optionally. Two operator styles:
- Pinned style — include
spec.foreignSourcewith detectors and policies. The YAML is the source of truth for both the requisition and its foreign-source.applycreates / updates the custom foreign-source alongside the requisition. - Portable style — omit
spec.foreignSourceentirely. On apply, Horizon's default foreign-source is inherited (no custom detectors, no custom policies). Useful for stamping out cookie-cutter requisitions that all share the site-wide default scan settings. If the server previously had a custom foreign-source for this name,applywill delete it and the--diffoutput enumerates the displaced detectors and policies so the operator sees the blast radius.
The examples/requisition-acme-prod.yaml
fixture demonstrates the pinned style with every modeled field.
provision.pl |
onmsctl |
|---|---|
provision.pl requisition add <fs> |
onmsctl requisition apply -f <fs>.yaml (with an empty nodes: [] payload) |
provision.pl requisition remove <fs> |
onmsctl requisition delete <fs> --yes (issues both DELETE /rest/requisitions/<fs> AND DELETE /rest/requisitions/deployed/<fs> in one call; idempotent on both — 404 on either snapshot is treated as success. --yes is required in non-TTY contexts (CI / scripting); TTY contexts prompt interactively. Remove the local YAML separately) |
provision.pl requisition import <fs> |
onmsctl requisition import <fs> (PUT-only, no re-POST; add --wait to block until completion) |
provision.pl requisition list |
onmsctl requisition list (wraps GET /rest/requisitionNames; respects -o table / json / yaml) |
provision.pl node add <fs> <foreign-id> <node-label> |
Recommended: edit YAML, onmsctl requisition apply -f <fs>.yaml. Imperative escape-hatch: onmsctl requisition node add <fs> <foreign-id> --label <node-label> (the imperative path stages the change in the pending requisition; run requisition import <fs> to take effect, or apply -f to also resync the rest of the YAML) |
provision.pl interface add <fs> <foreign-id> <ip> |
Recommended: edit YAML, onmsctl requisition apply -f <fs>.yaml. Imperative escape-hatch: onmsctl requisition interface add <fs> <foreign-id> <ip> --snmp-primary P|S|N [--status 1|3] [--descr ...]. Sibling verbs: interface list / get / set / remove. Stages the change in the pending requisition; run requisition import <fs> to take effect. Note: --status and --descr are wire-only — apply→export→apply silently drops those values |
provision.pl service add <fs> <foreign-id> <ip> <svc> |
Recommended: edit YAML, onmsctl requisition apply -f <fs>.yaml. Imperative escape-hatch: onmsctl requisition service add <fs> <foreign-id> <ip> <svc>. Sibling verbs: service list / remove. Stages the change in the pending requisition; run requisition import <fs> to take effect |
provision.pl category add <fs> <foreign-id> <cat> |
Recommended: edit YAML, onmsctl requisition apply -f <fs>.yaml. Imperative escape-hatch: onmsctl requisition category add <fs> <foreign-id> <cat>. Sibling verbs: category list / remove. Stages the change in the pending requisition; run requisition import <fs> to take effect |
provision.pl asset add <fs> <foreign-id> <name> <value> |
Recommended (requisition-time): edit YAML's spec.nodes[].assets, onmsctl requisition apply -f <fs>.yaml. Imperative escape-hatch (post-import, takes effect immediately): onmsctl requisition asset set <db-id> <field> <value>. Sibling verbs: asset list / get. Misfit: keyed by integer database node ID, not foreign-id — resolve via curl /opennms/rest/nodes?foreignId=<fid> before running |
The migration philosophy reverses provision.pl's shell-automation
model. Where provision.pl ran one mutation per invocation,
onmsctl requisition apply ships the desired state and lets the
three-level diff figure out what to mutate.
Recommended once-per-site recipe (per design D11):
- Convert existing XML to YAML with
onmsctl requisition convert --from /opt/opennms/etc/imports/ --foreign-sources-dir /opt/opennms/etc/foreign-sources/ --out repo/yaml/. Review the stderr findings; resolve PR001 / PR002 by editing the source XML (rare) or accepting the documented data-loss (most common — see the per-code--explaintext). - Commit the YAML directory to git as the new source of truth.
- Rewrite the existing
provision.plshell scripts asonmsctl requisition apply -f <fs>.yamlinvocations. The YAML carries desired state; the legacy "step-by-step mutation" pattern collapses to one apply per requisition. - Schedule the apply via CI / cron.
--dry-run --diffis the review gate; the actual apply runs only after review.
For ongoing sync after the initial migration — operators that edit
requisitions in Horizon's UI and want to pull the changes back into
git — use requisition export:
# Export every requisition on the server, per-file into a directory.
onmsctl requisition export --out repo/yaml/
# Export a single requisition to stdout.
onmsctl requisition export acme-prod
# Inline Horizon's default-FS into the YAML when the requisition has
# no custom one. Adds a snapshot-timestamp comment so the operator
# sees what defaults were in effect at export time.
onmsctl requisition export --include-defaults --out repo/yaml/Without --include-defaults, the exported YAML omits
spec.foreignSource when the server has no custom FS — i.e. produces
the portable style described above. With --include-defaults, the
default-FS is inlined alongside a snapshot comment; the inlined block
is a point-in-time copy that does NOT stay in sync with Horizon's
default after export.
See the editor integration section above for
the yaml-language-server directive for kind: Requisition — the
schema lives at schemas/requisition.schema.json and ships with the
same x-onmsctl-list-kind annotations the diff engine uses to
distinguish ordered lists (detectors / policies) from sets
(categories / services).
The verb purges both pending and deployed snapshots in a single call, which has a wider blast radius than any other Write verb in the CLI. Starting in v0.1.1 it refuses to run without explicit operator acknowledgement:
- Interactive (TTY) shells: the verb shows the requisition name
- node count + last-import timestamp (ISO-8601 UTC) and prompts
for
yesory(case-insensitive). Any other input — includingno, empty line, or Ctrl-D / EOF — aborts with a non-zero exit so calling scripts can distinguish cancellation from success.
- node count + last-import timestamp (ISO-8601 UTC) and prompts
for
- Non-interactive contexts (CI, scripted pipelines, redirected
stderr): the verb refuses with a clear error pointing at
--yes. There is no "auto-confirm because non-interactive" path. Both stdin AND stderr must be TTYs for the interactive prompt to fire.
CI fix recipe: add --yes (or -y) to existing invocations:
# Before:
onmsctl requisition delete acme-prod
# After:
onmsctl requisition delete acme-prod --yesIf the requisition already doesn't exist on the server (both snapshots return 404), the verb is a no-op and skips the prompt.
Every capability registers a short alias so the most common verbs stay terse:
| Long form | Alias | Notes |
|---|---|---|
onmsctl source ... |
onmsctl src ... |
eventconf source verbs |
onmsctl event ... |
onmsctl evt ... |
eventconf event verbs |
onmsctl requisition ... |
onmsctl req ... |
provisioning verbs |
onmsctl config ... |
onmsctl cfg ... |
local config management |
Both forms appear in --help output so the alias is discoverable.
Mark a context read-only: true to refuse every write verb locally
before any HTTP call. This is defense-in-depth on top of the server's
role checks — useful for "look but don't touch" credentials.
contexts:
- name: prod-readonly
server:
url: https://horizon.prod.example/opennms
auth:
basic:
username: viewer
keyring: prod-readonly
read-only: trueVerbs classified WriteCmd at compile time (source create / delete / apply, event add / update / delete, requisition apply / import)
refuse with exit code 12 against a read-only context. Reads pass
through. The --read-only flag and $ONMSCTL_READ_ONLY env var
force the flag on regardless of context — precedence is flag > env >
context > default false, and the flag is one-way (no --no-read-only
escape hatch — context can never un-set it).
For ad-hoc work outside the GitOps loop:
# sources
onmsctl source list # -o table | -o yaml | -o json
onmsctl source get 42
onmsctl source create --name acme.widget --description "Acme widget events"
onmsctl source delete 42 43
onmsctl source enable 42 --cascade
onmsctl source disable 42
onmsctl source names # name-only listing
onmsctl source names-and-ids
# events (refs are <source-id>/<event-id>)
onmsctl event list --source 42
onmsctl event list --uei "uei.opennms.org/vendor/cisco/.*" # cross-source
onmsctl event list --vendor cisco
onmsctl event add --source 42 -f examples/single-event.yaml
onmsctl event update 42/108 -f event.yaml --enabled true # --enabled required
onmsctl event delete 42/108 42/109
onmsctl event enable 42/108
onmsctl event disable 42/108
# raw XML round-trip
onmsctl source upload cisco.foo.events.xml acme.widget.events.xml
onmsctl source download 42 -O cisco.foo.events.xml
onmsctl source download 42 --format yaml -O cisco.foo.yaml # convert inlineevent add / event update expect a single Event payload; see
examples/single-event.yaml. source download → edit → apply may drop
server-only fields not modeled locally — keep the XML alongside for
full fidelity.
| Server | Status |
|---|---|
| OpenNMS Horizon 35+ | Primary target. EventConf REST endpoints are reproducible on 35.0.5 and 36.0.0. |
Five /eventconf/* quirks reproducible on Horizon 35.0.5 and 36.0.0;
all tracked upstream. onmsctl works around NMS-19813 and the
filename-stripper quirk client-side.
| # | Endpoint | Symptom | Upstream |
|---|---|---|---|
| 1 | GET /eventconf/filter/sources |
returns empty eventConfSourceList despite non-zero totalRecords |
NMS-19810 |
| 2 | GET /eventconf/filter/{id}/events |
HTTP 500 NPE when offset is omitted |
NMS-19811 |
| 3 | /eventconf/filter/* |
paging-parameter requirements differ per endpoint, undocumented | NMS-19812 |
| 4 | POST /eventconf/upload |
HTTP 400 (empty body) unless the multipart field name is literally upload — CXF @Multipart("upload") qualifier on the JAX-RS interface |
NMS-19813 |
| 5 | POST /eventconf/upload |
EventConfRestService.stripPathAndExtension derives the source name via lastIndexOf('.') — strips only the final extension. Uploading Cisco.events.xml produces stored source name Cisco.events. |
(no ticket yet) |
User-visible cascade:
source listprints empty even when sources exist (NMS-19810); usesource names-and-idsas a working alternative.find_source_by_namealways reportsAbsent(NMS-19810), sosource apply --diffshows the whole local document as "added" instead of a true delta. The upload itself still succeeds — Horizon's upsert path replaces events under an existing basename — so the source materializes correctly; treat the diff display as advisory until NMS-19810 is fixed upstream.source applyandsource uploadwork today: onmsctl sendsname="upload"on every multipart part (NMS-19813 workaround).source applyuploads as{metadata.name}.xml(not.events.xml) so Horizon's naive filename stripper produces a source name equal tometadata.nameverbatim.
source convert parses each event against the local EventSource
schema and emits findings on stderr. Example finding:
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 source convert --explain EC004
Finding codes. EC001–EC008 are stable across releases. Read any
rule's rationale with onmsctl source convert --explain <code>.
| 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 → Warning). |
| EC006 | error | Post-conversion validation failed (catch-all for rules not specifically modeled). |
| 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. |
Unmodeled elements. EC001 is the permanent forward-compatibility
surface: any direct-child element under <event> that onmsctl's YAML
schema doesn't model fires EC001 rather than silently losing data.
The v0.1 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 they're modeled too. For
full fidelity today, keep the XML alongside the YAML and use
source upload.
EC001 is structural-only — it does not detect attribute extensions
on modeled elements or enum-value drift on modeled fields.
spec.events[].alarmData.alarmType strictly 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; the canonical YAML output
is always lowercase. Anything else — unknown symbolic strings
("problem", the alarmd Java alias) OR integers outside {1, 2, 3} —
fails immediately. YAML inputs reject at deserialize time; eventconf
XML inputs to source convert produce an EC007 finding at Error
severity (no YAML written, exit 2).
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 set to empty or whitespace-only.
id— enterprise OID; free string, no OID-format validation.idtext— vendor-supplied textual label.version— common valuesv1/v2c/v3(free string;v3-auth-privand other variants accepted verbatim).generic—0..=6per RFC 1157.specific—>= 0.community— typicallypublic.
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, not modeled here). The two share
similar names but live in different domains and MUST NOT be conflated.
spec.events[].forwards mirrors <forward state="..." mechanism="..."> target</forward> — eventd's forwarding directives. The local schema
validates against the XSD-closed sets:
state∈{on, off}mechanism∈{snmpudp, snmptcp, xmltcp, xmludp}
Values outside these sets are rejected locally (otherwise Horizon
returns a server-side 400). An empty forwards: [{}] entry is rejected
too — 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 per the XSD; body is optional
and preserved byte-for-byte. Use YAML's | literal block for
multi-line bodies — clip mode (|) keeps one trailing newline, strip
mode (|-) keeps none.
Security note for
scripts:. Shipping executable code viasource applylowers the friction for deploying server-side code execution on Horizon. The underlying threat surface already exists at the raw eventconf-XML upload path — modeling<script>in YAML does not introduce new authority. Operators should ensure RBAC on eventconf write access in Horizon reflects this: anyone who can upload an event source can run code on the Horizon JVM.
spec.events[].filters mirrors <filters><filter eventparm="..." pattern="..." replacement="..."/></filters>. Each entry is a regex-
replacement rule that eventd applies to a named event parameter at
fire time:
Pattern.compile(pattern)
.matcher(parmValue)
.replaceAll(replacement)
All three fields are required. pattern uses Java regex syntax;
replacement supports $1/$2-style backreferences. The YAML is
flat — operators write filters: directly on the event; the
<filters> wrapper materializes only on XML render.
<mask> vs <filters>. <mask> selects which events a source
applies to (SNMP PDU shape matching: id / generic / specific / varbind
values). <filters> operates after selection, transforming
parameter values on the fired event. Two different layers — <mask>
is selection, <filters> is post-selection parameter rewrite.
(source apply --help for full text.)
| # | Limitation | Workaround |
|---|---|---|
| 1 | description not set/preserved through apply. |
source create --description ... at first creation. |
| 2 | Disabled-state apply has a bounded enabled-flap window. |
Imperative path for strict avoidance; --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 in v0.1. |
Deferred to a future kind: EventConfMaster. |
Every list/get accepts -o table (default), -o yaml, -o json.
Tables go through comfy-table; structured outputs use serde_json
and serde_norway.
Stable per cli-core spec §4.5; safe for scripting:
| Code | Meaning |
|---|---|
| 0 | success |
| 1 | HTTP non-success / partial-failure batch / post-upload state-sync failed |
| 2 | misuse / config error / generic internal |
| 3 | reserved (unassigned in v0.1) |
| 4 | DNS resolution failure |
| 5 | connection refused |
| 6 | timeout |
| 7 | TLS handshake failed |
| 8 | redirect loop |
| 9 | unsupported authentication scheme |
# Bash
onmsctl completion bash > /etc/bash_completion.d/onmsctl
# Fish
onmsctl completion fish > ~/.config/fish/completions/onmsctl.fishZsh target depends on your setup:
| Setup | Target |
|---|---|
| Oh My Zsh | ~/.oh-my-zsh/custom/completions/_onmsctl |
| Homebrew zsh (macOS) | "$(brew --prefix)/share/zsh/site-functions/_onmsctl" |
| System-wide on Linux | /usr/local/share/zsh/site-functions/_onmsctl |
| Plain user-local | ~/.zsh/completions/_onmsctl (then add the dir to $fpath) |
# Oh My Zsh
mkdir -p ~/.oh-my-zsh/custom/completions
onmsctl completion zsh > ~/.oh-my-zsh/custom/completions/_onmsctlOh My Zsh does not pick up $ZSH_CUSTOM/completions/ automatically.
Add this to ~/.zshrc above source $ZSH/oh-my-zsh.sh:
fpath=("$ZSH_CUSTOM/completions" $fpath)Then start a new shell or run compinit.
Avoid > "${fpath[1]}/_onmsctl" — on Oh My Zsh ${fpath[1]} is
typically ~/.oh-my-zsh/plugins/git, which is both wrong and confusing.
Renaming: the generated script targets the literal binary name
onmsctl. Post-process with e.g. sed -e 's/onmsctl/<name>/g' if
you've repackaged or symlinked under another name.
server.insecure-skip-tls-verify: true (or --insecure-tls) disables
certificate verification. Every outgoing request then emits:
warning: TLS certificate verification is disabled (insecure-skip-tls-verify) for GET request. Use only on trusted networks.
Keep this off in production. The path is intentionally omitted from the warning to avoid log-cardinality explosion and accidental PII retention.
Apache-2.0. Third-party crate licenses inventoried in
THIRD-PARTY-LICENSES.md (regenerated by make licenses).
See CONTRIBUTING.md. Implementations work from the OpenAPI document
and black-box observation of a Horizon instance — never from Horizon's
server source — so the result remains an Apache-2.0 clean-room.