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 }