Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .agents/skills/project-writing-go-modules-framework-v2/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ source files for evidence.
embedded `charts.yaml` is RECOMMENDED.
- `Collect(ctx)` MUST return `error` and write metrics to `metrix`; it MUST NOT
return a V1 `map[string]int64`.
- Collector `Cleanup(ctx)` MUST be idempotent. The framework may call it more
than once, including after partial `Init` / `Check` setup.
- Files SHOULD stay boring: public lifecycle methods in `collector.go`, setup
helpers in `init.go` when needed, orchestration in `collect.go`, distinct
upstream operations in `collect_<operation>.go`, metrics in `metrix.go` /
Expand All @@ -60,6 +62,17 @@ source files for evidence.
- If Functions exist, isolate them in a `<name>func/` subpackage with a narrow
`Deps` interface declared there. The Function package MUST NOT import the
collector package or hold `*Collector`.
- `collectorapi.Creator.InstancePolicy` defaults to
`InstancePolicyPerJob`. Use `InstancePolicySingle` only for collectors that
are intentionally one canonical job per agent. Single-instance configs MUST
use `name == module` after defaults are applied. DynCfg exposes opted-in
single-instance configs as `single` objects with the module-level collector
config ID, no collector template, and no `add`/`remove`; updates target that
single object.
- Before opting in a production collector to `InstancePolicySingle`, decide how
its initial `single` object appears in DynCfg. The framework exposes a single
object only after a config exists; it does not publish a template placeholder,
and plain stock enable failures can remove the stock object.
- Public config options SHOULD stay small and justified. A proposed config
option MUST name the concrete operator decision it enables; "operators may
want to tune it" is not enough. Internal tuning SHOULD use constants unless
Expand Down
45 changes: 42 additions & 3 deletions packaging/windows/netdata.wxs.in
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,20 @@
</InstallExecuteSequence>

<!-- Add new files (stream.conf and netdata.conf) -->
<CustomAction Id="NDCreateNewFiles" Directory="USRLIBEXECNETDATADIR" ExeCommand='powershell.exe -NoProfile -ExecutionPolicy Bypass -NonInteractive -File &quot;[USRLIBEXECNETDATADIR]\copy_files.ps1&quot;' Execute="deferred" Return="ignore" Impersonate="no"/>
<SetProperty
Id="NDCreateNewFiles"
Value="&quot;[System64Folder]WindowsPowerShell\v1.0\powershell.exe&quot; -NoProfile -ExecutionPolicy Bypass -NonInteractive -File &quot;[USRLIBEXECNETDATADIR]\copy_files.ps1&quot;"
Before="NDCreateNewFiles"
Sequence="execute"
/>
<CustomAction
Id="NDCreateNewFiles"
BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)"
DllEntry="WixQuietExec"
Execute="deferred"
Return="ignore"
Impersonate="no"
/>
<InstallExecuteSequence>
<Custom Action="NDCreateNewFiles" Before="InstallFinalize" />
</InstallExecuteSequence>
Expand All @@ -272,12 +285,38 @@
</File>
</FeatureGroup>

<CustomAction Id="DllPermission" Directory="System64Folder" ExeCommand='[System64Folder]icacls.exe &quot;[System64Folder]wevt_netdata.dll&quot; /grant &quot;NT SERVICE\EventLog&quot;:R' Execute="deferred" Return="ignore" Impersonate="no"/>
<SetProperty
Id="DllPermission"
Value="&quot;[System64Folder]icacls.exe&quot; &quot;[System64Folder]wevt_netdata.dll&quot; /grant &quot;NT SERVICE\EventLog&quot;:R"
Before="DllPermission"
Sequence="execute"
/>
<CustomAction
Id="DllPermission"
BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)"
DllEntry="WixQuietExec"
Execute="deferred"
Return="ignore"
Impersonate="no"
/>
<InstallExecuteSequence>
<Custom Action="DllPermission" After="InstallFiles" />
</InstallExecuteSequence>

<CustomAction Id="InstallManifest" Directory="System64Folder" ExeCommand='[System64Folder]wevtutil.exe im &quot;[System64Folder]wevt_netdata_manifest.xml&quot; &quot;/mf:[System64Folder]\wevt_netdata.dll&quot; &quot;/rf:[System64Folder]\wevt_netdata.dll&quot;' Execute="deferred" Return="ignore" Impersonate="no"/>
<SetProperty
Id="InstallManifest"
Value="&quot;[System64Folder]wevtutil.exe&quot; im &quot;[System64Folder]wevt_netdata_manifest.xml&quot; &quot;/mf:[System64Folder]wevt_netdata.dll&quot; &quot;/rf:[System64Folder]wevt_netdata.dll&quot;"
Before="InstallManifest"
Sequence="execute"
/>
<CustomAction
Id="InstallManifest"
BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)"
DllEntry="WixQuietExec"
Execute="deferred"
Return="ignore"
Impersonate="no"
/>
<InstallExecuteSequence>
<Custom Action="InstallManifest" After="InstallFiles" />
</InstallExecuteSequence>
Expand Down
6 changes: 5 additions & 1 deletion src/go/plugin/agent/discovery/sd/dyncfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func (cb *sdCallbacks) ExtractKey(fn dyncfg.Function) (key, name string, ok bool
return dt + ":" + name, name, true
}

func (cb *sdCallbacks) ValidateJobName(name string) error {
func (cb *sdCallbacks) ValidateConfigName(name string) error {
return dyncfg.JobNameRuleAllowDots(name)
}

Expand Down Expand Up @@ -127,6 +127,10 @@ func (cb *sdCallbacks) ConfigID(cfg sdConfig) string {
return cb.sd.dyncfgJobID(cfg.DiscovererType(), cfg.Name())
}

func (cb *sdCallbacks) ConfigType(sdConfig) dyncfg.ConfigType {
return dyncfg.ConfigTypeJob
}

// dyncfgConfig is the handler for dyncfg config commands.
// Read-only commands (schema, get, userconfig) are executed directly.
// State-changing commands are queued for serial execution.
Expand Down
10 changes: 5 additions & 5 deletions src/go/plugin/agent/discovery/sd/dyncfg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func (s *dyncfgSim) run(t *testing.T) {

Path: fmt.Sprintf(dyncfgSDPath, testPluginName),
EnableFailCode: 422,
JobCommands: []dyncfg.Command{
ConfigCommands: []dyncfg.Command{
dyncfg.CommandSchema,
dyncfg.CommandGet,
dyncfg.CommandEnable,
Expand Down Expand Up @@ -578,7 +578,7 @@ CONFIG test:sd:net_listeners:test-job status disabled
},
wantDyncfg: `
FUNCTION_RESULT_BEGIN 1-enable 404 application/json
{"status":404,"errorMessage":"job not found."}
{"status":404,"errorMessage":"config not found."}
FUNCTION_RESULT_END
`,
}
Expand Down Expand Up @@ -720,7 +720,7 @@ CONFIG test:sd:net_listeners:test-job status accepted
},
wantDyncfg: `
FUNCTION_RESULT_BEGIN 1-update 404 application/json
{"status":404,"errorMessage":"job not found."}
{"status":404,"errorMessage":"config not found."}
FUNCTION_RESULT_END
`,
}
Expand Down Expand Up @@ -831,7 +831,7 @@ CONFIG test:sd:net_listeners:test-job delete
},
wantDyncfg: `
FUNCTION_RESULT_BEGIN 1-remove 404 application/json
{"status":404,"errorMessage":"job not found."}
{"status":404,"errorMessage":"config not found."}
FUNCTION_RESULT_END
`,
}
Expand Down Expand Up @@ -1034,7 +1034,7 @@ func TestServiceDiscovery_DyncfgFileConfig(t *testing.T) {
},
wantDyncfg: `
FUNCTION_RESULT_BEGIN 1-remove 405 application/json
{"status":405,"errorMessage":"removing jobs of type 'file' is not supported, only 'dyncfg' jobs can be removed."}
{"status":405,"errorMessage":"removing configurations of source type 'file' is not supported, only 'dyncfg' configurations can be removed."}
FUNCTION_RESULT_END
`,
}
Expand Down
12 changes: 6 additions & 6 deletions src/go/plugin/agent/discovery/sd/sd.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func NewServiceDiscovery(cfg Config) (*ServiceDiscovery, error) {

Path: fmt.Sprintf(dyncfgSDPath, cfg.PluginName),
EnableFailCode: 422,
JobCommands: []dyncfg.Command{
ConfigCommands: []dyncfg.Command{
dyncfg.CommandSchema,
dyncfg.CommandGet,
dyncfg.CommandEnable,
Expand Down Expand Up @@ -229,7 +229,7 @@ func (d *ServiceDiscovery) removePipeline(conf confFile) {
d.mgr.Stop(scfg.PipelineKey())
}

d.handler.NotifyJobRemove(scfg)
d.handler.NotifyConfigRemove(scfg)
}
}

Expand Down Expand Up @@ -288,7 +288,7 @@ func (d *ServiceDiscovery) addConfig(ctx context.Context, scfg sdConfig) {
// No existing config - expose this one
d.handler.AddDiscoveredConfig(scfg, dyncfg.StatusAccepted)

d.handler.NotifyJobCreate(scfg, dyncfg.StatusAccepted)
d.handler.NotifyConfigCreate(scfg, dyncfg.StatusAccepted)
if d.runModePolicy.AutoEnableDiscovered || d.fnReg == nil || d.dyncfgCh == nil {
// Auto-enable in terminal mode and tests.
// Also auto-enable when no function registry is attached, because
Expand Down Expand Up @@ -322,8 +322,8 @@ func (d *ServiceDiscovery) addConfig(ctx context.Context, scfg sdConfig) {
d.handler.AddDiscoveredConfig(scfg, dyncfg.StatusAccepted)

// Update dyncfg (remove old, create new with new source)
d.handler.NotifyJobRemove(entry.Cfg)
d.handler.NotifyJobCreate(scfg, dyncfg.StatusAccepted)
d.handler.NotifyConfigRemove(entry.Cfg)
d.handler.NotifyConfigCreate(scfg, dyncfg.StatusAccepted)

if d.runModePolicy.AutoEnableDiscovered || d.fnReg == nil || d.dyncfgCh == nil {
d.autoEnableConfig(scfg)
Expand Down Expand Up @@ -356,7 +356,7 @@ func (d *ServiceDiscovery) removeOldConfigsFromSource(source, newKey string) {
// If it was exposed, remove from exposed cache and dyncfg.
// But DON'T stop the pipeline - let the new config's enable handle that
if _, ok := d.handler.RemoveDiscoveredConfig(oldCfg); ok {
d.handler.NotifyJobRemove(oldCfg)
d.handler.NotifyConfigRemove(oldCfg)
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/go/plugin/agent/discovery/sd/sim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func (sim *discoverySimExt) run(t *testing.T) {

Path: fmt.Sprintf(dyncfgSDPath, testPluginName),
EnableFailCode: 422,
JobCommands: []dyncfg.Command{
ConfigCommands: []dyncfg.Command{
dyncfg.CommandSchema,
dyncfg.CommandGet,
dyncfg.CommandEnable,
Expand Down Expand Up @@ -151,7 +151,7 @@ func (sim *discoverySim) run(t *testing.T) {

Path: fmt.Sprintf(dyncfgSDPath, testPluginName),
EnableFailCode: 422,
JobCommands: []dyncfg.Command{
ConfigCommands: []dyncfg.Command{
dyncfg.CommandSchema,
dyncfg.CommandGet,
dyncfg.CommandEnable,
Expand Down
2 changes: 1 addition & 1 deletion src/go/plugin/agent/discovery/sd/wait_decision_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func newWaitTestServiceDiscovery(t *testing.T) (*ServiceDiscovery, chan confFile

Path: fmt.Sprintf(dyncfgSDPath, testPluginName),
EnableFailCode: 422,
JobCommands: []dyncfg.Command{
ConfigCommands: []dyncfg.Command{
dyncfg.CommandSchema,
dyncfg.CommandGet,
dyncfg.CommandEnable,
Expand Down
20 changes: 20 additions & 0 deletions src/go/plugin/agent/jobmgr/dyncfg_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ func (m *Manager) dyncfgJobID(cfg confgroup.Config) string {
return fmt.Sprintf("%s%s:%s", m.dyncfgCollectorPrefixValue(), cfg.Module(), cfg.Name())
}

func (m *Manager) dyncfgConfigID(cfg confgroup.Config) string {
if m.isSingleInstanceCollector(cfg.Module()) {
return m.dyncfgModID(cfg.Module())
}
return m.dyncfgJobID(cfg)
}

func dyncfgCollectorModCmds() string {
return dyncfg.JoinCommands(
dyncfg.CommandAdd,
Expand All @@ -37,6 +44,19 @@ func dyncfgCollectorModCmds() string {
dyncfg.CommandUserconfig)
}

func dyncfgCollectorConfigCmds() []dyncfg.Command {
return []dyncfg.Command{
dyncfg.CommandSchema,
dyncfg.CommandGet,
dyncfg.CommandEnable,
dyncfg.CommandDisable,
dyncfg.CommandUpdate,
dyncfg.CommandRestart,
dyncfg.CommandTest,
dyncfg.CommandUserconfig,
}
}

func (m *Manager) dyncfgCollectorModuleCreate(name string) {
m.dyncfgResponder.ConfigCreate(netdataapi.ConfigOpts{
ID: m.dyncfgModID(name),
Expand Down
35 changes: 31 additions & 4 deletions src/go/plugin/agent/jobmgr/dyncfg_collector_callbacks.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,36 @@ func (cb *collectorCallbacks) ExtractKey(fn dyncfg.Function) (key, name string,
if !ok {
return "", "", false
}
if cb.mgr.isSingleInstanceCollector(mn) {
cb.mgr.Warningf("dyncfg: %s: single-instance collector %s does not support add", fn.Command(), mn)
return "", "", false
}
jn = fn.JobName()
if jn == "" {
return "", "", false
}
} else {
// For other commands: ID contains module:job.
mn, jn, ok = cb.mgr.extractModuleJobName(fn.ID())
mn, ok = cb.mgr.extractModuleName(fn.ID())
if !ok {
return "", "", false
}
if cb.mgr.isSingleInstanceCollector(mn) {
if fn.ID() != cb.mgr.dyncfgModID(mn) {
cb.mgr.Warningf("dyncfg: %s: single-instance collector %s must use config ID %s", fn.Command(), mn, cb.mgr.dyncfgModID(mn))
return "", "", false
}
jn = mn
} else {
// For per-job commands: ID contains module:job.
mn, jn, ok = cb.mgr.extractModuleJobName(fn.ID())
if !ok {
return "", "", false
}
}
}
if err := cb.mgr.validateDyncfgCollectorIdentity(mn, jn); err != nil {
cb.mgr.Warningf("dyncfg: %s: %v", fn.Command(), err)
return "", "", false
}

key = mn + "_" + jn
Expand All @@ -59,7 +79,7 @@ func (cb *collectorCallbacks) ExtractKey(fn dyncfg.Function) (key, name string,
return key, jn, true
}

func (cb *collectorCallbacks) ValidateJobName(name string) error {
func (cb *collectorCallbacks) ValidateConfigName(name string) error {
return dyncfg.JobNameRuleStrict(name)
}

Expand Down Expand Up @@ -156,7 +176,14 @@ func (cb *collectorCallbacks) OnStatusChange(entry *dyncfg.Entry[confgroup.Confi
}

func (cb *collectorCallbacks) ConfigID(cfg confgroup.Config) string {
return cb.mgr.dyncfgJobID(cfg)
return cb.mgr.dyncfgConfigID(cfg)
}

func (cb *collectorCallbacks) ConfigType(cfg confgroup.Config) dyncfg.ConfigType {
if cb.mgr.isSingleInstanceCollector(cfg.Module()) {
return dyncfg.ConfigTypeSingle
}
return dyncfg.ConfigTypeJob
}

// codedError wraps an error with an HTTP status code for the handler.
Expand Down
Loading
Loading