github.com/netdata/go.d.plugin@v0.58.1/agent/discovery/dyncfg/dyncfg.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package dyncfg
     4  
     5  import (
     6  	"bytes"
     7  	"context"
     8  	"fmt"
     9  	"log/slog"
    10  	"strings"
    11  	"sync"
    12  
    13  	"github.com/netdata/go.d.plugin/agent/confgroup"
    14  	"github.com/netdata/go.d.plugin/agent/functions"
    15  	"github.com/netdata/go.d.plugin/agent/module"
    16  	"github.com/netdata/go.d.plugin/logger"
    17  
    18  	"gopkg.in/yaml.v2"
    19  )
    20  
    21  const dynCfg = "dyncfg"
    22  
    23  func NewDiscovery(cfg Config) (*Discovery, error) {
    24  	if err := validateConfig(cfg); err != nil {
    25  		return nil, err
    26  	}
    27  
    28  	mgr := &Discovery{
    29  		Logger: logger.New().With(
    30  			slog.String("component", "discovery dyncfg"),
    31  		),
    32  		Plugin:               cfg.Plugin,
    33  		API:                  cfg.API,
    34  		Modules:              cfg.Modules,
    35  		ModuleConfigDefaults: nil,
    36  		mux:                  &sync.Mutex{},
    37  		configs:              make(map[string]confgroup.Config),
    38  	}
    39  
    40  	mgr.registerFunctions(cfg.Functions)
    41  
    42  	return mgr, nil
    43  }
    44  
    45  type Discovery struct {
    46  	*logger.Logger
    47  
    48  	Plugin               string
    49  	API                  NetdataDyncfgAPI
    50  	Modules              module.Registry
    51  	ModuleConfigDefaults confgroup.Registry
    52  
    53  	in chan<- []*confgroup.Group
    54  
    55  	mux     *sync.Mutex
    56  	configs map[string]confgroup.Config
    57  }
    58  
    59  func (d *Discovery) String() string {
    60  	return d.Name()
    61  }
    62  
    63  func (d *Discovery) Name() string {
    64  	return "dyncfg discovery"
    65  }
    66  
    67  func (d *Discovery) Run(ctx context.Context, in chan<- []*confgroup.Group) {
    68  	d.Info("instance is started")
    69  	defer func() { d.Info("instance is stopped") }()
    70  
    71  	d.in = in
    72  
    73  	if reload, ok := ctx.Value("reload").(bool); ok && reload {
    74  		_ = d.API.DynCfgReset()
    75  	}
    76  
    77  	_ = d.API.DynCfgEnable(d.Plugin)
    78  
    79  	for k := range d.Modules {
    80  		_ = d.API.DyncCfgRegisterModule(k)
    81  	}
    82  
    83  	<-ctx.Done()
    84  }
    85  
    86  func (d *Discovery) registerFunctions(r FunctionRegistry) {
    87  	r.Register("get_plugin_config", d.getPluginConfig)
    88  	r.Register("get_plugin_config_schema", d.getModuleConfigSchema)
    89  	r.Register("set_plugin_config", d.setPluginConfig)
    90  
    91  	r.Register("get_module_config", d.getModuleConfig)
    92  	r.Register("get_module_config_schema", d.getModuleConfigSchema)
    93  	r.Register("set_module_config", d.setModuleConfig)
    94  
    95  	r.Register("get_job_config", d.getJobConfig)
    96  	r.Register("get_job_config_schema", d.getJobConfigSchema)
    97  	r.Register("set_job_config", d.setJobConfig)
    98  	r.Register("delete_job", d.deleteJobName)
    99  }
   100  
   101  func (d *Discovery) getPluginConfig(fn functions.Function)       { d.notImplemented(fn) }
   102  func (d *Discovery) getPluginConfigSchema(fn functions.Function) { d.notImplemented(fn) }
   103  func (d *Discovery) setPluginConfig(fn functions.Function)       { d.notImplemented(fn) }
   104  
   105  func (d *Discovery) getModuleConfig(fn functions.Function)       { d.notImplemented(fn) }
   106  func (d *Discovery) getModuleConfigSchema(fn functions.Function) { d.notImplemented(fn) }
   107  func (d *Discovery) setModuleConfig(fn functions.Function)       { d.notImplemented(fn) }
   108  
   109  func (d *Discovery) getJobConfig(fn functions.Function) {
   110  	if err := d.verifyFn(fn, 2); err != nil {
   111  		d.apiReject(fn, err.Error())
   112  		return
   113  	}
   114  
   115  	moduleName, jobName := fn.Args[0], fn.Args[1]
   116  
   117  	bs, err := d.getConfigBytes(moduleName + "_" + jobName)
   118  	if err != nil {
   119  		d.apiReject(fn, err.Error())
   120  		return
   121  	}
   122  
   123  	d.apiSuccessYAML(fn, string(bs))
   124  }
   125  
   126  func (d *Discovery) getJobConfigSchema(fn functions.Function) {
   127  	if err := d.verifyFn(fn, 1); err != nil {
   128  		d.apiReject(fn, err.Error())
   129  		return
   130  	}
   131  
   132  	name := fn.Args[0]
   133  
   134  	v, ok := d.Modules[name]
   135  	if !ok {
   136  		msg := jsonErrorf("module %s is not registered", name)
   137  		d.apiReject(fn, msg)
   138  		return
   139  	}
   140  
   141  	d.apiSuccessJSON(fn, v.JobConfigSchema)
   142  }
   143  
   144  func (d *Discovery) setJobConfig(fn functions.Function) {
   145  	if err := d.verifyFn(fn, 2); err != nil {
   146  		d.apiReject(fn, err.Error())
   147  		return
   148  	}
   149  
   150  	var cfg confgroup.Config
   151  	if err := yaml.NewDecoder(bytes.NewBuffer(fn.Payload)).Decode(&cfg); err != nil {
   152  		d.apiReject(fn, err.Error())
   153  		return
   154  	}
   155  
   156  	modName, jobName := fn.Args[0], fn.Args[1]
   157  	def, _ := d.ModuleConfigDefaults.Lookup(modName)
   158  	src := source(modName, jobName)
   159  
   160  	cfg.SetProvider(dynCfg)
   161  	cfg.SetSource(src)
   162  	cfg.SetModule(modName)
   163  	cfg.SetName(jobName)
   164  	cfg.Apply(def)
   165  
   166  	d.in <- []*confgroup.Group{
   167  		{
   168  			Configs: []confgroup.Config{cfg},
   169  			Source:  src,
   170  		},
   171  	}
   172  
   173  	d.apiSuccessJSON(fn, "")
   174  }
   175  
   176  func (d *Discovery) deleteJobName(fn functions.Function) {
   177  	if err := d.verifyFn(fn, 2); err != nil {
   178  		d.apiReject(fn, err.Error())
   179  		return
   180  	}
   181  
   182  	modName, jobName := fn.Args[0], fn.Args[1]
   183  
   184  	cfg, ok := d.getConfig(modName + "_" + jobName)
   185  	if !ok {
   186  		d.apiReject(fn, jsonErrorf("module '%s' job '%s': not registered", modName, jobName))
   187  		return
   188  	}
   189  	if cfg.Provider() != dynCfg {
   190  		d.apiReject(fn, jsonErrorf("module '%s' job '%s': can't remove non Dyncfg job", modName, jobName))
   191  		return
   192  	}
   193  
   194  	d.in <- []*confgroup.Group{
   195  		{
   196  			Configs: []confgroup.Config{},
   197  			Source:  source(modName, jobName),
   198  		},
   199  	}
   200  
   201  	d.apiSuccessJSON(fn, "")
   202  }
   203  
   204  func (d *Discovery) apiSuccessJSON(fn functions.Function, payload string) {
   205  	_ = d.API.FunctionResultSuccess(fn.UID, "application/json", payload)
   206  }
   207  
   208  func (d *Discovery) apiSuccessYAML(fn functions.Function, payload string) {
   209  	_ = d.API.FunctionResultSuccess(fn.UID, "application/x-yaml", payload)
   210  }
   211  
   212  func (d *Discovery) apiReject(fn functions.Function, msg string) {
   213  	_ = d.API.FunctionResultReject(fn.UID, "application/json", msg)
   214  }
   215  
   216  func (d *Discovery) notImplemented(fn functions.Function) {
   217  	d.Infof("not implemented: '%s'", fn.String())
   218  	msg := jsonErrorf("function '%s' is not implemented", fn.Name)
   219  	d.apiReject(fn, msg)
   220  }
   221  
   222  func (d *Discovery) verifyFn(fn functions.Function, wantArgs int) error {
   223  	if got := len(fn.Args); got != wantArgs {
   224  		msg := jsonErrorf("wrong number of arguments: want %d, got %d (args: '%v')", wantArgs, got, fn.Args)
   225  		return fmt.Errorf(msg)
   226  	}
   227  
   228  	if isSetFunction(fn) && len(fn.Payload) == 0 {
   229  		msg := jsonErrorf("no payload")
   230  		return fmt.Errorf(msg)
   231  	}
   232  
   233  	return nil
   234  }
   235  
   236  func jsonErrorf(format string, a ...any) string {
   237  	msg := fmt.Sprintf(format, a...)
   238  	msg = strings.ReplaceAll(msg, "\n", " ")
   239  
   240  	return fmt.Sprintf(`{ "error": "%s" }`+"\n", msg)
   241  }
   242  
   243  func source(modName, jobName string) string {
   244  	return fmt.Sprintf("%s/%s/%s", dynCfg, modName, jobName)
   245  }
   246  
   247  func cfgJobName(cfg confgroup.Config) string {
   248  	if strings.HasPrefix(cfg.Source(), "dyncfg") {
   249  		return cfg.Name()
   250  	}
   251  	return cfg.NameWithHash()
   252  }
   253  
   254  func isSetFunction(fn functions.Function) bool {
   255  	return strings.HasPrefix(fn.Name, "set_")
   256  }