summaryrefslogtreecommitdiffstats
path: root/src/go/plugin/go.d/agent/vnodes/vnodes.go
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/go/plugin/go.d/agent/vnodes/vnodes.go142
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
+}