Files
Jason Staack da598f79a0 feat(20-01): add tod-mib-parser Go CLI binary
- Add gosmi v1.0.4 dependency for MIB parsing
- Create poller/cmd/mib-parser/ with main.go and tree.go
- CLI accepts MIB file path and optional --search-path
- Outputs JSON OID tree with oid, name, description, type, access, status, children
- Errors output as JSON {"error":"..."} to stdout (exit 0) for Python backend
- Panic recovery wraps gosmi LoadModule for malformed MIBs
- Parent-child tree built from OID hierarchy with numeric sort

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 20:22:16 -05:00

103 lines
2.7 KiB
Go

// tod-mib-parser parses vendor MIB files using gosmi and outputs a JSON OID tree.
//
// Usage:
//
// tod-mib-parser <mib-file-path> [--search-path <dir>]
//
// The binary reads a MIB file, parses it with opsbl/gosmi, and writes a JSON
// OID tree to stdout. On any parse error it outputs {"error": "..."} to stdout
// and exits 0 (the Python backend reads stdout, not exit codes).
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"github.com/opsbl/gosmi"
)
func main() {
mibPath, searchPath, err := parseArgs(os.Args[1:])
if err != nil {
writeError(err.Error())
return
}
result, err := parseMIB(mibPath, searchPath)
if err != nil {
writeError(err.Error())
return
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
if err := enc.Encode(result); err != nil {
writeError(fmt.Sprintf("json encode: %s", err.Error()))
}
}
// parseArgs extracts the MIB file path and optional --search-path from argv.
func parseArgs(args []string) (mibPath, searchPath string, err error) {
if len(args) == 0 {
return "", "", fmt.Errorf("usage: tod-mib-parser <mib-file-path> [--search-path <dir>]")
}
mibPath = args[0]
searchPath = filepath.Dir(mibPath)
for i := 1; i < len(args); i++ {
if args[i] == "--search-path" {
if i+1 >= len(args) {
return "", "", fmt.Errorf("--search-path requires a directory argument")
}
searchPath = args[i+1]
i++
}
}
if _, statErr := os.Stat(mibPath); statErr != nil {
return "", "", fmt.Errorf("cannot access MIB file: %s", statErr.Error())
}
return mibPath, searchPath, nil
}
// parseMIB loads a MIB file with gosmi and builds the OID tree. It recovers
// from gosmi panics on malformed MIBs and returns them as errors.
func parseMIB(mibPath, searchPath string) (result *ParseResult, err error) {
defer func() {
if r := recover(); r != nil {
result = nil
err = fmt.Errorf("gosmi panic: %v", r)
}
}()
inst, err := gosmi.New("tod-mib-parser")
if err != nil {
return nil, fmt.Errorf("gosmi init: %s", err.Error())
}
defer inst.Close()
// Append rather than replace so the default IETF/IANA MIB search paths
// from gosmi bootstrap are preserved. The MIB file's directory (or the
// explicit --search-path) is added so dependent MIBs co-located with the
// input file are found.
inst.AppendPath(searchPath)
moduleName, err := inst.LoadModule(filepath.Base(mibPath))
if err != nil {
return nil, fmt.Errorf("load module: %s", err.Error())
}
return BuildOIDTree(inst, moduleName)
}
// writeError outputs a JSON error object to stdout.
func writeError(msg string) {
fmt.Fprintf(os.Stderr, "tod-mib-parser: %s\n", msg)
enc := json.NewEncoder(os.Stdout)
_ = enc.Encode(map[string]string{"error": msg})
}