Files
nmon/internal/simagent/agentfile.go

136 lines
3.8 KiB
Go

package simagent
import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
"gopkg.in/yaml.v3"
"github.com/v10networks/nmon/internal/snmp"
)
// AgentFile is the sidecar `<name>.agent.yaml` that says how a fixture's
// recording is served.
//
// recording: renumber.a # file stem in the same directory; default: the sidecar's own stem
// community: public
// version: v2c # v1, v2c or v3
// v3: {user: nmon, auth_proto: SHA256, auth_pass: ..., priv_proto: AES, priv_pass: ...}
// latency: 3s
// drop_every: 3
// toobig_above: 20
type AgentFile struct {
Recording string `yaml:"recording,omitempty"`
Community string `yaml:"community,omitempty"`
Version string `yaml:"version,omitempty"`
V3 *V3Config `yaml:"v3,omitempty"`
Latency string `yaml:"latency,omitempty"`
DropEvery int `yaml:"drop_every,omitempty"`
TooBigAbove int `yaml:"toobig_above,omitempty"`
// Dir and Stem say where the sidecar was read from.
Dir string `yaml:"-"`
Stem string `yaml:"-"`
}
// V3Config is a USM user in a sidecar.
type V3Config struct {
User string `yaml:"user,omitempty"`
AuthProto string `yaml:"auth_proto,omitempty"`
AuthPass string `yaml:"auth_pass,omitempty"`
PrivProto string `yaml:"priv_proto,omitempty"`
PrivPass string `yaml:"priv_pass,omitempty"`
}
// LoadAgentFile reads path. A missing file yields the defaults for the stem
// (v2c, public, no faults), with ok false.
func LoadAgentFile(path string) (af *AgentFile, ok bool, err error) {
af = &AgentFile{Dir: filepath.Dir(path)}
af.Stem = strings.TrimSuffix(filepath.Base(path), ".agent.yaml")
b, err := os.ReadFile(path)
if os.IsNotExist(err) {
return af.defaults(), false, nil
}
if err != nil {
return nil, false, err
}
dec := yaml.NewDecoder(strings.NewReader(string(b)))
dec.KnownFields(true)
if err := dec.Decode(af); err != nil {
return nil, false, fmt.Errorf("%s: %w", path, err)
}
return af.defaults(), true, nil
}
func (a *AgentFile) defaults() *AgentFile {
if a.Recording == "" {
a.Recording = a.Stem
}
if a.Community == "" {
a.Community = "public"
}
if a.Version == "" {
a.Version = "v2c"
if a.V3 != nil {
a.Version = "v3"
}
}
return a
}
// RecordingPath is the .snmprec the sidecar serves.
func (a *AgentFile) RecordingPath() string { return filepath.Join(a.Dir, a.Recording+".snmprec") }
// Config builds the agent configuration; Addr is left for the caller.
func (a *AgentFile) Config() (Config, error) {
rec, err := LoadRecording(a.RecordingPath())
if err != nil {
return Config{}, err
}
return a.ConfigFor(rec)
}
// ConfigFor is Config with an explicit recording.
func (a *AgentFile) ConfigFor(rec *Recording) (Config, error) {
cfg := Config{Recording: rec, Community: a.Community, Behaviour: Behaviour{DropEvery: a.DropEvery, TooBigAbove: a.TooBigAbove}}
if a.Latency != "" {
d, err := time.ParseDuration(a.Latency)
if err != nil {
return Config{}, fmt.Errorf("latency: %w", err)
}
cfg.Behaviour.Latency = d
}
if a.V3 != nil {
ap, err := snmp.ParseAuthProto(orStr(a.V3.AuthProto, "SHA256"))
if err != nil {
return Config{}, err
}
pp, err := snmp.ParsePrivProto(orStr(a.V3.PrivProto, "AES"))
if err != nil {
return Config{}, err
}
cfg.V3 = []V3User{{Name: a.V3.User, AuthProto: ap, AuthPass: a.V3.AuthPass, PrivProto: pp, PrivPass: a.V3.PrivPass}}
}
return cfg, nil
}
// Credential is what a client needs to talk to the agent.
func (a *AgentFile) Credential() snmp.Credential {
c := snmp.Credential{Version: strings.TrimPrefix(a.Version, "v"), Community: a.Community}
if a.V3 != nil {
c.V3User, c.V3AuthProto, c.V3AuthPass = a.V3.User, orStr(a.V3.AuthProto, "SHA256"), a.V3.AuthPass
c.V3PrivProto, c.V3PrivPass = orStr(a.V3.PrivProto, "AES"), a.V3.PrivPass
}
return c
}
func orStr(s, d string) string {
if s == "" {
return d
}
return s
}