diff options
Diffstat (limited to '')
-rw-r--r-- | src/go/plugin/go.d/agent/vnodes/vnodes.go | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/src/go/plugin/go.d/agent/vnodes/vnodes.go b/src/go/plugin/go.d/agent/vnodes/vnodes.go new file mode 100644 index 000000000..3d332c261 --- /dev/null +++ b/src/go/plugin/go.d/agent/vnodes/vnodes.go @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package vnodes + +import ( + "io" + "io/fs" + "log/slog" + "os" + "path/filepath" + + "github.com/netdata/netdata/go/plugins/logger" + + "gopkg.in/yaml.v2" +) + +var Disabled = false // TODO: remove after Netdata v1.39.0. Fix for "from source" stable-channel installations. + +func New(confDir string) *Vnodes { + vn := &Vnodes{ + Logger: logger.New().With( + slog.String("component", "vnodes"), + ), + + confDir: confDir, + vnodes: make(map[string]*VirtualNode), + } + + vn.readConfDir() + + return vn +} + +type ( + Vnodes struct { + *logger.Logger + + confDir string + vnodes map[string]*VirtualNode + } + VirtualNode struct { + GUID string `yaml:"guid"` + Hostname string `yaml:"hostname"` + Labels map[string]string `yaml:"labels"` + } +) + +func (vn *Vnodes) Lookup(key string) (*VirtualNode, bool) { + v, ok := vn.vnodes[key] + return v, ok +} + +func (vn *Vnodes) Len() int { + return len(vn.vnodes) +} + +func (vn *Vnodes) readConfDir() { + _ = filepath.WalkDir(vn.confDir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + vn.Warning(err) + return nil + } + + if d.Type()&os.ModeSymlink != 0 { + dst, err := os.Readlink(path) + if err != nil { + vn.Warningf("failed to resolve symlink '%s': %v", path, err) + return nil + } + + if !filepath.IsAbs(dst) { + dst = filepath.Join(filepath.Dir(path), filepath.Clean(dst)) + } + + fi, err := os.Stat(dst) + if err != nil { + vn.Warningf("failed to stat resolved path '%s': %v", dst, err) + return nil + } + if !fi.Mode().IsRegular() { + vn.Debugf("'%s' is not a regular file, skipping it", dst) + return nil + } + path = dst + } else if !d.Type().IsRegular() { + vn.Debugf("'%s' is not a regular file, skipping it", path) + return nil + } + + if !isConfigFile(path) { + vn.Debugf("'%s' is not a config file (wrong extension), skipping it", path) + return nil + } + + var cfg []VirtualNode + + if err := loadConfigFile(&cfg, path); err != nil { + vn.Warning(err) + return nil + } + + for _, v := range cfg { + if v.Hostname == "" || v.GUID == "" { + vn.Warningf("skipping virtual node '%+v': some required fields are missing (%s)", v, path) + continue + } + if _, ok := vn.vnodes[v.Hostname]; ok { + vn.Warningf("skipping virtual node '%+v': duplicate node (%s)", v, path) + continue + } + + v := v + vn.Debugf("adding virtual node'%+v' (%s)", v, path) + vn.vnodes[v.Hostname] = &v + } + + return nil + }) +} + +func isConfigFile(path string) bool { + switch filepath.Ext(path) { + case ".yaml", ".yml", ".conf": + return true + default: + return false + } +} + +func loadConfigFile(conf interface{}, path string) error { + f, err := os.Open(path) + if err != nil { + return err + } + defer func() { _ = f.Close() }() + + if err := yaml.NewDecoder(f).Decode(conf); err != nil && err != io.EOF { + return err + } + + return nil +} |