@cryptotaxi247 / netdata-1 / commits / d1696cde8

refactor(go.d/azure_monitor): replace profiles magic keyword (#22088)

Ilya Mashchenko committed Mar 30, 2026 at 21:09 UTC d1696cde89d3452a42083cf0040081cc552d1e3a
12 files changed +312 -161
src/go/plugin/go.d/collector/azure_monitor/collector.go
+10 -9
@@ -35,15 +35,16 @@ func New() *Collector {
35 store := metrix.NewCollectorStore()
36 c := &Collector{
37 Config: Config{
38 - UpdateEvery: defaultUpdateEvery,
39 - AutoDetectionRetry: defaultAutoDetectRetry,
40 - Cloud: defaultCloud,
41 - DiscoveryEvery: defaultDiscoveryEvery,
42 - QueryOffset: defaultQueryOffset,
43 - Timeout: defaultTimeout,
44 - MaxConcurrency: defaultMaxConcurrency,
45 - MaxBatchResources: defaultMaxBatchResource,
46 - MaxMetricsPerQuery: defaultMaxMetricsQuery,
38 + UpdateEvery: defaultUpdateEvery,
39 + AutoDetectionRetry: defaultAutoDetectRetry,
40 + Cloud: defaultCloud,
41 + DiscoveryEvery: defaultDiscoveryEvery,
42 + QueryOffset: defaultQueryOffset,
43 + Timeout: defaultTimeout,
44 + MaxConcurrency: defaultMaxConcurrency,
45 + MaxBatchResources: defaultMaxBatchResource,
46 + MaxMetricsPerQuery: defaultMaxMetricsQuery,
47 + ProfileSelectionMode: profileSelectionModeAuto,
48 },
49 store: store,
50 now: time.Now,
src/go/plugin/go.d/collector/azure_monitor/collector_runtime.go
+4
@@ -74,6 +74,10 @@ type resourceInfo struct {
74 Region string
75 }
76
77 +func (r resourceInfo) String() string {
78 + return r.Name + " (" + r.Type + ")"
79 +}
80 +
81 type queryBatch struct {
82 Profile *profileRuntime
83 Metrics []*metricRuntime
src/go/plugin/go.d/collector/azure_monitor/collector_test.go
+45 -69
@@ -60,9 +60,12 @@ func TestCollector_Init(t *testing.T) {
60 },
61 "fails on negative timeout": {
62 cfg: Config{
63 - SubscriptionID: "sub-1",
64 - Timeout: confopt.Duration(-time.Second),
65 - Profiles: []string{"postgres_flexible"},
63 + SubscriptionID: "sub-1",
64 + Timeout: confopt.Duration(-time.Second),
65 + ProfileSelectionMode: profileSelectionModeExact,
66 + ProfileSelectionModeExact: &ProfileSelectionModeExactConfig{
67 + Profiles: []string{"postgres_flexible"},
68 + },
69 Auth: cloudauth.AzureADAuthConfig{
70 Mode: cloudauth.AzureADAuthModeDefault,
71 },
@@ -150,7 +153,8 @@ func TestCollector_UsesConfiguredTimeout(t *testing.T) {
153 setup: func(c *Collector, rg *mockResourceGraph, mx *mockMetricsClient) {
154 c.Config = testConfig()
155 c.Config.Timeout = confopt.Duration(timeout)
153 - c.Config.Profiles = []string{"auto"}
156 + c.Config.ProfileSelectionMode = profileSelectionModeAuto
157 + c.Config.ProfileSelectionModeExact = nil
158 rg.resources = []map[string]any{
159 {"type": "microsoft.dbforpostgresql/flexibleservers", "count_": int64(1)},
160 }
@@ -357,7 +361,7 @@ func TestCollector_TimeGrainScheduling(t *testing.T) {
361 }
362
363 cfg := testConfig()
360 - cfg.Profiles = []string{"storage_slow"}
364 + cfg.ProfileSelectionModeExact = &ProfileSelectionModeExactConfig{Profiles: []string{"storage_slow"}}
365
366 catalog := mustLoadStockCatalog(t, map[string]string{
367 "storage_slow.yaml": `
@@ -424,7 +428,8 @@ func TestCollector_InitAutoDiscover(t *testing.T) {
428
429 c := New()
430 c.Config = testConfig()
427 - c.Config.Profiles = []string{"auto"}
431 + c.Config.ProfileSelectionMode = profileSelectionModeAuto
432 + c.Config.ProfileSelectionModeExact = nil
433 c.newResourceGraph = func(string, azcore.TokenCredential, azcloud.Configuration) (resourceGraphClient, error) {
434 return rg, nil
435 }
@@ -433,7 +438,8 @@ func TestCollector_InitAutoDiscover(t *testing.T) {
438 }
439
440 require.NoError(t, c.Init(context.Background()))
436 - assert.Contains(t, c.Config.Profiles, "postgres_flexible")
441 + require.NotEmpty(t, c.runtime.Profiles)
442 + assert.Equal(t, "postgres_flexible", c.runtime.Profiles[0].ID)
443 }
444
445 func TestCollector_InitAutoDiscoverWithExplicit(t *testing.T) {
@@ -445,7 +451,11 @@ func TestCollector_InitAutoDiscoverWithExplicit(t *testing.T) {
451
452 c := New()
453 c.Config = testConfig()
448 - c.Config.Profiles = []string{"auto", "cosmos_db"}
454 + c.Config.ProfileSelectionMode = profileSelectionModeCombined
455 + c.Config.ProfileSelectionModeExact = nil
456 + c.Config.ProfileSelectionModeCombined = &ProfileSelectionModeCombinedConfig{
457 + Profiles: []string{"cosmos_db"},
458 + }
459 c.newResourceGraph = func(string, azcore.TokenCredential, azcloud.Configuration) (resourceGraphClient, error) {
460 return rg, nil
461 }
@@ -454,14 +464,19 @@ func TestCollector_InitAutoDiscoverWithExplicit(t *testing.T) {
464 }
465
466 require.NoError(t, c.Init(context.Background()))
457 - assert.Contains(t, c.Config.Profiles, "cosmos_db")
458 - assert.Contains(t, c.Config.Profiles, "postgres_flexible")
467 + var ids []string
468 + for _, p := range c.runtime.Profiles {
469 + ids = append(ids, p.ID)
470 + }
471 + assert.Contains(t, ids, "cosmos_db")
472 + assert.Contains(t, ids, "postgres_flexible")
473 }
474
461 -func TestCollector_InitEmptyProfilesDefaultsToAuto(t *testing.T) {
475 +func TestCollector_InitDefaultModeIsAuto(t *testing.T) {
476 c := New()
477 c.Config = testConfig()
464 - c.Config.Profiles = []string{}
478 + c.Config.ProfileSelectionMode = ""
479 + c.Config.ProfileSelectionModeExact = nil
480 c.newResourceGraph = func(string, azcore.TokenCredential, azcloud.Configuration) (resourceGraphClient, error) {
481 return &mockResourceGraph{}, nil
482 }
@@ -483,7 +498,8 @@ func TestCollector_InitAutoDiscoverNoMatchFails(t *testing.T) {
498
499 c := New()
500 c.Config = testConfig()
486 - c.Config.Profiles = []string{"auto"}
501 + c.Config.ProfileSelectionMode = profileSelectionModeAuto
502 + c.Config.ProfileSelectionModeExact = nil
503 c.newResourceGraph = func(string, azcore.TokenCredential, azcloud.Configuration) (resourceGraphClient, error) {
504 return rg, nil
505 }
@@ -496,48 +512,6 @@ func TestCollector_InitAutoDiscoverNoMatchFails(t *testing.T) {
512 assert.Contains(t, err.Error(), "auto-discovery found no Azure resources")
513 }
514
499 -func TestExtractAutoKeyword(t *testing.T) {
500 - tests := map[string]struct {
501 - input []string
502 - wantAuto bool
503 - wantRemain []string
504 - }{
505 - "auto only": {
506 - input: []string{"auto"},
507 - wantAuto: true,
508 - wantRemain: []string{},
509 - },
510 - "auto with explicit": {
511 - input: []string{"auto", "vm", "sql"},
512 - wantAuto: true,
513 - wantRemain: []string{"vm", "sql"},
514 - },
515 - "no auto": {
516 - input: []string{"vm", "sql"},
517 - wantAuto: false,
518 - wantRemain: []string{"vm", "sql"},
519 - },
520 - "empty": {
521 - input: []string{},
522 - wantAuto: false,
523 - wantRemain: []string{},
524 - },
525 - "auto case insensitive": {
526 - input: []string{"AUTO"},
527 - wantAuto: true,
528 - wantRemain: []string{},
529 - },
530 - }
531 -
532 - for name, tc := range tests {
533 - t.Run(name, func(t *testing.T) {
534 - gotAuto, gotRemain := extractAutoKeyword(tc.input)
535 - assert.Equal(t, tc.wantAuto, gotAuto)
536 - assert.Equal(t, tc.wantRemain, gotRemain)
537 - })
538 - }
539 -}
540 -
515 func TestMergeProfileIDs(t *testing.T) {
516 tests := map[string]struct {
517 explicit []string
@@ -570,8 +544,7 @@ func TestMergeProfileIDs(t *testing.T) {
544 }
545
546 func TestBuildCollectorRuntime_DetectsChartIDCollision(t *testing.T) {
573 - cfg := testConfig()
574 - cfg.Profiles = []string{"redis_upper", "redis_lower"}
547 + profileIDs := []string{"redis_upper", "redis_lower"}
548
549 catalog := mustLoadStockCatalog(t, map[string]string{
550 "redis_upper.yaml": `
@@ -628,23 +601,26 @@ template:
601 `,
602 })
603
631 - _, err := buildCollectorRuntimeFromConfig(cfg, catalog)
604 + _, err := buildCollectorRuntimeFromConfig(profileIDs, catalog)
605 require.Error(t, err)
606 }
607
608 func testConfig() Config {
609 return Config{
637 - UpdateEvery: 60,
638 - AutoDetectionRetry: 0,
639 - SubscriptionID: "sub-1",
640 - Cloud: "public",
641 - DiscoveryEvery: 300,
642 - QueryOffset: 180,
643 - Timeout: defaultTimeout,
644 - MaxConcurrency: 4,
645 - MaxBatchResources: 50,
646 - MaxMetricsPerQuery: 20,
647 - Profiles: []string{"postgres_flexible"},
610 + UpdateEvery: 60,
611 + AutoDetectionRetry: 0,
612 + SubscriptionID: "sub-1",
613 + Cloud: "public",
614 + DiscoveryEvery: 300,
615 + QueryOffset: 180,
616 + Timeout: defaultTimeout,
617 + MaxConcurrency: 4,
618 + MaxBatchResources: 50,
619 + MaxMetricsPerQuery: 20,
620 + ProfileSelectionMode: profileSelectionModeExact,
621 + ProfileSelectionModeExact: &ProfileSelectionModeExactConfig{
622 + Profiles: []string{"postgres_flexible"},
623 + },
624 Auth: cloudauth.AzureADAuthConfig{
625 Mode: cloudauth.AzureADAuthModeDefault,
626 },
src/go/plugin/go.d/collector/azure_monitor/config.go
+60 -23
@@ -24,7 +24,11 @@ const (
24 defaultMaxMetricsQuery = 20
25 )
26
27 -const profileAutoKeyword = "auto"
27 +const (
28 + profileSelectionModeAuto = "auto"
29 + profileSelectionModeExact = "exact"
30 + profileSelectionModeCombined = "combined"
31 +)
32
33 const (
34 cloudPublic = "public"
@@ -33,20 +37,30 @@ const (
37 )
38
39 type Config struct {
36 - Vnode string `yaml:"vnode,omitempty" json:"vnode"`
37 - UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
38 - AutoDetectionRetry int `yaml:"autodetection_retry,omitempty" json:"autodetection_retry"`
39 - SubscriptionID string `yaml:"subscription_id" json:"subscription_id"`
40 - Cloud string `yaml:"cloud,omitempty" json:"cloud"`
41 - DiscoveryEvery int `yaml:"discovery_every,omitempty" json:"discovery_every"`
42 - QueryOffset int `yaml:"query_offset,omitempty" json:"query_offset"`
43 - Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
44 - MaxConcurrency int `yaml:"max_concurrency,omitempty" json:"max_concurrency"`
45 - MaxBatchResources int `yaml:"max_batch_resources,omitempty" json:"max_batch_resources"`
46 - MaxMetricsPerQuery int `yaml:"max_metrics_per_query,omitempty" json:"max_metrics_per_query"`
47 - ResourceGroups []string `yaml:"resource_groups,omitempty" json:"resource_groups"`
48 - Profiles []string `yaml:"profiles,omitempty" json:"profiles"`
49 - Auth cloudauth.AzureADAuthConfig `yaml:"auth" json:"auth"`
40 + Vnode string `yaml:"vnode,omitempty" json:"vnode"`
41 + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
42 + AutoDetectionRetry int `yaml:"autodetection_retry,omitempty" json:"autodetection_retry"`
43 + SubscriptionID string `yaml:"subscription_id" json:"subscription_id"`
44 + Cloud string `yaml:"cloud,omitempty" json:"cloud"`
45 + DiscoveryEvery int `yaml:"discovery_every,omitempty" json:"discovery_every"`
46 + QueryOffset int `yaml:"query_offset,omitempty" json:"query_offset"`
47 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
48 + MaxConcurrency int `yaml:"max_concurrency,omitempty" json:"max_concurrency"`
49 + MaxBatchResources int `yaml:"max_batch_resources,omitempty" json:"max_batch_resources"`
50 + MaxMetricsPerQuery int `yaml:"max_metrics_per_query,omitempty" json:"max_metrics_per_query"`
51 + ResourceGroups []string `yaml:"resource_groups,omitempty" json:"resource_groups"`
52 + ProfileSelectionMode string `yaml:"profile_selection_mode,omitempty" json:"profile_selection_mode"`
53 + ProfileSelectionModeExact *ProfileSelectionModeExactConfig `yaml:"profile_selection_mode_exact,omitempty" json:"profile_selection_mode_exact,omitempty"`
54 + ProfileSelectionModeCombined *ProfileSelectionModeCombinedConfig `yaml:"profile_selection_mode_combined,omitempty" json:"profile_selection_mode_combined,omitempty"`
55 + Auth cloudauth.AzureADAuthConfig `yaml:"auth" json:"auth"`
56 +}
57 +
58 +type ProfileSelectionModeExactConfig struct {
59 + Profiles []string `yaml:"profiles" json:"profiles"`
60 +}
61 +
62 +type ProfileSelectionModeCombinedConfig struct {
63 + Profiles []string `yaml:"profiles" json:"profiles"`
64 }
65
66 func (c *Config) applyDefaults() {
@@ -77,8 +91,8 @@ func (c *Config) applyDefaults() {
91 if c.MaxMetricsPerQuery <= 0 {
92 c.MaxMetricsPerQuery = defaultMaxMetricsQuery
93 }
80 - if len(c.Profiles) == 0 {
81 - c.Profiles = []string{"auto"}
94 + if strings.TrimSpace(c.ProfileSelectionMode) == "" {
95 + c.ProfileSelectionMode = profileSelectionModeAuto
96 }
97 }
98
@@ -120,19 +134,42 @@ func (c Config) validate() error {
134 errs = append(errs, err)
135 }
136
123 - seenProfiles := map[string]struct{}{}
124 - for _, name := range c.Profiles {
137 + switch strings.ToLower(strings.TrimSpace(c.ProfileSelectionMode)) {
138 + case profileSelectionModeAuto:
139 + case profileSelectionModeExact:
140 + if c.ProfileSelectionModeExact == nil || len(c.ProfileSelectionModeExact.Profiles) == 0 {
141 + errs = append(errs, errors.New("'profile_selection_mode_exact.profiles' must not be empty when mode is 'exact'"))
142 + } else {
143 + errs = append(errs, validateProfilesList(c.ProfileSelectionModeExact.Profiles)...)
144 + }
145 + case profileSelectionModeCombined:
146 + if c.ProfileSelectionModeCombined == nil || len(c.ProfileSelectionModeCombined.Profiles) == 0 {
147 + errs = append(errs, errors.New("'profile_selection_mode_combined.profiles' must not be empty when mode is 'combined'"))
148 + } else {
149 + errs = append(errs, validateProfilesList(c.ProfileSelectionModeCombined.Profiles)...)
150 + }
151 + default:
152 + errs = append(errs, fmt.Errorf("'profile_selection_mode' must be one of: %s, %s, %s",
153 + profileSelectionModeAuto, profileSelectionModeExact, profileSelectionModeCombined))
154 + }
155 +
156 + return errors.Join(errs...)
157 +}
158 +
159 +func validateProfilesList(profiles []string) []error {
160 + var errs []error
161 + seen := map[string]struct{}{}
162 + for _, name := range profiles {
163 n := strings.TrimSpace(name)
164 if n == "" {
165 errs = append(errs, errors.New("'profiles' contains an empty value"))
166 continue
167 }
168 norm := stringsLowerTrim(n)
131 - if _, ok := seenProfiles[norm]; ok {
169 + if _, ok := seen[norm]; ok {
170 errs = append(errs, fmt.Errorf("'profiles' contains duplicate value '%s'", n))
171 }
134 - seenProfiles[norm] = struct{}{}
172 + seen[norm] = struct{}{}
173 }
136 -
137 - return errors.Join(errs...)
174 + return errs
175 }
src/go/plugin/go.d/collector/azure_monitor/config_schema.json
+100 -16
@@ -91,19 +91,16 @@
91 "type": "string"
92 }
93 },
94 - "profiles": {
95 - "title": "Profiles",
96 - "description": "Profile ids to enable. Use 'auto' for automatic discovery based on resource types in the subscription. Combine with explicit ids: ['auto', 'custom_profile']. Empty list disables collection.",
97 - "type": [
98 - "array",
99 - "null"
94 + "profile_selection_mode": {
95 + "title": "Profile selection mode",
96 + "description": "How profiles are selected for monitoring.",
97 + "type": "string",
98 + "enum": [
99 + "auto",
100 + "exact",
101 + "combined"
102 ],
101 - "items": {
102 - "type": "string"
103 - },
104 - "default": [
105 - "auto"
106 - ]
103 + "default": "auto"
104 },
105 "auth": {
106 "title": "Authentication",
@@ -205,7 +202,78 @@
202 "required": [
203 "subscription_id",
204 "auth"
208 - ]
205 + ],
206 + "dependencies": {
207 + "profile_selection_mode": {
208 + "oneOf": [
209 + {
210 + "properties": {
211 + "profile_selection_mode": {
212 + "const": "auto"
213 + }
214 + }
215 + },
216 + {
217 + "properties": {
218 + "profile_selection_mode": {
219 + "const": "exact"
220 + },
221 + "profile_selection_mode_exact": {
222 + "title": "Exact profile selection",
223 + "description": "Settings for exact profile selection mode.",
224 + "type": "object",
225 + "properties": {
226 + "profiles": {
227 + "title": "Profiles",
228 + "description": "Profile ids to enable.",
229 + "type": "array",
230 + "items": {
231 + "type": "string"
232 + },
233 + "minItems": 1
234 + }
235 + },
236 + "required": [
237 + "profiles"
238 + ]
239 + }
240 + },
241 + "required": [
242 + "profile_selection_mode_exact"
243 + ]
244 + },
245 + {
246 + "properties": {
247 + "profile_selection_mode": {
248 + "const": "combined"
249 + },
250 + "profile_selection_mode_combined": {
251 + "title": "Combined profile selection",
252 + "description": "Settings for combined profile selection mode. Listed profiles are merged with auto-discovered profiles.",
253 + "type": "object",
254 + "properties": {
255 + "profiles": {
256 + "title": "Profiles",
257 + "description": "Profile ids to merge with auto-discovered profiles.",
258 + "type": "array",
259 + "items": {
260 + "type": "string"
261 + },
262 + "minItems": 1
263 + }
264 + },
265 + "required": [
266 + "profiles"
267 + ]
268 + }
269 + },
270 + "required": [
271 + "profile_selection_mode_combined"
272 + ]
273 + }
274 + ]
275 + }
276 + }
277 },
278 "uiSchema": {
279 "uiOptions": {
@@ -214,8 +282,22 @@
282 "resource_groups": {
283 "ui:listFlavour": "list"
284 },
217 - "profiles": {
218 - "ui:listFlavour": "list"
285 + "profile_selection_mode": {
286 + "ui:widget": "radio",
287 + "ui:options": {
288 + "inline": true
289 + },
290 + "ui:help": "Choose how profiles are selected.\n\n- `auto`: Automatically discovers resource types in the subscription via Azure Resource Graph and enables matching profiles.\n- `exact`: Uses only the explicitly listed profile ids.\n- `combined`: Merges explicitly listed profile ids with auto-discovered profiles."
291 + },
292 + "profile_selection_mode_exact": {
293 + "profiles": {
294 + "ui:listFlavour": "list"
295 + }
296 + },
297 + "profile_selection_mode_combined": {
298 + "profiles": {
299 + "ui:listFlavour": "list"
300 + }
301 },
302 "timeout": {
303 "ui:help": "Accepts decimals for sub-second granularity (for example, `0.5` for 500ms)."
@@ -270,7 +352,9 @@
352 {
353 "title": "Profiles",
354 "fields": [
273 - "profiles",
355 + "profile_selection_mode",
356 + "profile_selection_mode_exact",
357 + "profile_selection_mode_combined",
358 "resource_groups"
359 ]
360 },
src/go/plugin/go.d/collector/azure_monitor/discover.go
+5
@@ -5,6 +5,7 @@ package azure_monitor
5 import (
6 "context"
7 "fmt"
8 + "slices"
9 "strings"
10 "time"
11
@@ -23,6 +24,10 @@ func (c *Collector) refreshDiscovery(ctx context.Context, force bool) ([]resourc
24 return nil, err
25 }
26
27 + if !slices.Equal(resources, c.discovery.Resources) {
28 + c.Infof("discovered %d resources: %v", len(resources), resources)
29 + }
30 +
31 c.discovery = discoveryState{
32 Resources: resources,
33 ByType: byType,
src/go/plugin/go.d/collector/azure_monitor/init.go
+26 -29
@@ -57,7 +57,7 @@ func (c *Collector) initInstruments(runtime *collectorRuntime) error {
57 }
58
59 func (c *Collector) prepareInitResult(ctx context.Context) (*initResult, error) {
60 - cfg, catalog, autoDiscover, err := c.prepareInitConfig()
60 + cfg, catalog, autoDiscover, explicitProfiles, err := c.prepareInitConfig()
61 if err != nil {
62 return nil, err
63 }
@@ -67,19 +67,23 @@ func (c *Collector) prepareInitResult(ctx context.Context) (*initResult, error)
67 return nil, err
68 }
69
70 + var profileIDs []string
71 +
72 if autoDiscover {
71 - profiles, err := resolveAutoProfiles(ctx, cfg.SubscriptionID, cfg.Timeout.Duration(), resourceGraph, catalog, cfg.Profiles)
73 + profiles, err := resolveAutoProfiles(ctx, cfg.SubscriptionID, cfg.Timeout.Duration(), resourceGraph, catalog, explicitProfiles)
74 if err != nil {
75 return nil, fmt.Errorf("auto-discover resource types: %w", err)
76 }
75 - cfg.Profiles = profiles
76 - if len(cfg.Profiles) == 0 {
77 + if len(profiles) == 0 {
78 return nil, errors.New("auto-discovery found no Azure resources matching any known profile")
79 }
79 - c.Infof("auto-discovery resolved profiles: %v", cfg.Profiles)
80 + c.Infof("auto-discovery resolved profiles: %v", profiles)
81 + profileIDs = profiles
82 + } else {
83 + profileIDs = explicitProfiles
84 }
85
82 - runtime, err := buildCollectorRuntimeFromConfig(cfg, catalog)
86 + runtime, err := buildCollectorRuntimeFromConfig(profileIDs, catalog)
87 if err != nil {
88 return nil, fmt.Errorf("build collector runtime: %w", err)
89 }
@@ -92,27 +96,33 @@ func (c *Collector) prepareInitResult(ctx context.Context) (*initResult, error)
96 }, nil
97 }
98
95 -func (c *Collector) prepareInitConfig() (Config, azureprofiles.Catalog, bool, error) {
99 +func (c *Collector) prepareInitConfig() (Config, azureprofiles.Catalog, bool, []string, error) {
100 cfg := c.Config
101 cfg.applyDefaults()
102
103 catalog, err := c.loadProfileCatalog()
104 if err != nil {
101 - return Config{}, azureprofiles.Catalog{}, false, fmt.Errorf("load profiles catalog: %w", err)
105 + return Config{}, azureprofiles.Catalog{}, false, nil, fmt.Errorf("load profiles catalog: %w", err)
106 }
107
104 - autoDiscover, explicitProfiles := extractAutoKeyword(cfg.Profiles)
105 - cfg.Profiles = explicitProfiles
106 -
107 - if !autoDiscover && len(cfg.Profiles) == 0 {
108 - return Config{}, azureprofiles.Catalog{}, false, errors.New("no profiles configured; use 'auto' for auto-discovery or specify profile ids")
108 + if err := cfg.validate(); err != nil {
109 + return Config{}, azureprofiles.Catalog{}, false, nil, fmt.Errorf("config validation: %w", err)
110 }
111
111 - if err := cfg.validate(); err != nil {
112 - return Config{}, azureprofiles.Catalog{}, false, fmt.Errorf("config validation: %w", err)
112 + var autoDiscover bool
113 + var explicitProfiles []string
114 +
115 + switch stringsLowerTrim(cfg.ProfileSelectionMode) {
116 + case profileSelectionModeAuto:
117 + autoDiscover = true
118 + case profileSelectionModeExact:
119 + explicitProfiles = cfg.ProfileSelectionModeExact.Profiles
120 + case profileSelectionModeCombined:
121 + autoDiscover = true
122 + explicitProfiles = cfg.ProfileSelectionModeCombined.Profiles
123 }
124
115 - return cfg, catalog, autoDiscover, nil
125 + return cfg, catalog, autoDiscover, explicitProfiles, nil
126 }
127
128 func (c *Collector) prepareInitClients(cfg Config) (resourceGraphClient, *queryExecutor, error) {
@@ -153,19 +163,6 @@ func createCredential(auth cloudauth.AzureADAuthConfig, cloudCfg azcloud.Configu
163 })
164 }
165
156 -func extractAutoKeyword(profiles []string) (bool, []string) {
157 - auto := false
158 - filtered := make([]string, 0, len(profiles))
159 - for _, p := range profiles {
160 - if stringsLowerTrim(p) == profileAutoKeyword {
161 - auto = true
162 - continue
163 - }
164 - filtered = append(filtered, p)
165 - }
166 - return auto, filtered
167 -}
168 -
166 func mergeProfileIDs(explicit, discovered []string) []string {
167 seen := make(map[string]struct{}, len(explicit)+len(discovered))
168 merged := make([]string, 0, len(explicit)+len(discovered))
src/go/plugin/go.d/collector/azure_monitor/metadata.yaml
+14 -4
@@ -41,7 +41,7 @@ modules:
41 default_behavior:
42 auto_detection:
43 description: |
44 - When `profiles` includes `auto` (the default), the collector queries Azure Resource Graph
44 + When `profile_selection_mode` is `auto` (the default), the collector queries Azure Resource Graph
45 to discover which resource types exist in the subscription and enables matching built-in profiles automatically.
46 limits:
47 description: |
@@ -142,9 +142,19 @@ modules:
142 default_value: 20
143 required: false
144 group: Limits
145 - - name: profiles
146 - description: "Profile ids to enable. Use `auto` to discover resource types via Azure Resource Graph and enable matching profiles. Combine with explicit ids: `[auto, custom_profile]`."
147 - default_value: "[auto]"
145 + - name: profile_selection_mode
146 + description: "Profile selection mode: `auto` discovers matching profiles via Azure Resource Graph, `exact` uses only listed profile ids, `combined` merges listed ids with auto-discovered profiles."
147 + default_value: "auto"
148 + required: false
149 + group: Profiles
150 + - name: profile_selection_mode_exact.profiles
151 + description: Profile ids to enable (used when `profile_selection_mode` is `exact`).
152 + default_value: "[]"
153 + required: false
154 + group: Profiles
155 + - name: profile_selection_mode_combined.profiles
156 + description: Profile ids to merge with auto-discovered profiles (used when `profile_selection_mode` is `combined`).
157 + default_value: "[]"
158 required: false
159 group: Profiles
160 - name: resource_groups
src/go/plugin/go.d/collector/azure_monitor/plan.go
+2 -2
@@ -12,8 +12,8 @@ import (
12 "gopkg.in/yaml.v3"
13 )
14
15 -func buildCollectorRuntimeFromConfig(cfg Config, catalog azureprofiles.Catalog) (*collectorRuntime, error) {
16 - profiles, err := catalog.Resolve(cfg.Profiles)
15 +func buildCollectorRuntimeFromConfig(profileIDs []string, catalog azureprofiles.Catalog) (*collectorRuntime, error) {
16 + profiles, err := catalog.Resolve(profileIDs)
17 if err != nil {
18 return nil, err
19 }
src/go/plugin/go.d/collector/azure_monitor/testdata/config.json
+6 -3
@@ -9,9 +9,12 @@
9 "max_concurrency": 4,
10 "max_batch_resources": 50,
11 "max_metrics_per_query": 20,
12 - "profiles": [
13 - "sql_managed_instance"
14 - ],
12 + "profile_selection_mode": "exact",
13 + "profile_selection_mode_exact": {
14 + "profiles": [
15 + "sql_managed_instance"
16 + ]
17 + },
18 "resource_groups": [
19 "rg-a"
20 ],
src/go/plugin/go.d/collector/azure_monitor/testdata/config.yaml
+4 -2
@@ -7,8 +7,10 @@ timeout: 30
7 max_concurrency: 4
8 max_batch_resources: 50
9 max_metrics_per_query: 20
10 -profiles:
11 - - sql_managed_instance
10 +profile_selection_mode: exact
11 +profile_selection_mode_exact:
12 + profiles:
13 + - sql_managed_instance
14 resource_groups:
15 - rg-a
16 auth:
src/go/plugin/go.d/config/go.d/azure_monitor.conf
+36 -4
@@ -8,14 +8,46 @@
8 ## Filenames are packaging only; matching filename == profile id is recommended.
9 ## The default profile catalog is loaded once per go.d process and cached after the first successful load.
10 ## Changes to profile files under those default dirs require a go.d process restart to take effect.
11 -## `auto` resolves matching profiles once during collector initialization.
11 +##
12 +## Profile selection modes:
13 +## auto - discovers resource types via Azure Resource Graph and enables matching profiles (default).
14 +## exact - uses only the explicitly listed profile ids.
15 +## combined - merges explicitly listed profile ids with auto-discovered profiles.
16
17 #jobs:
14 -# - name: example
18 +# - name: example_auto
19 +# subscription_id: "<subscription-id>"
20 +# timeout: 30
21 +# profile_selection_mode: auto
22 +# auth:
23 +# mode: service_principal
24 +# mode_service_principal:
25 +# tenant_id: "<tenant-id>"
26 +# client_id: "<client-id>"
27 +# client_secret: "<client-secret>"
28 +#
29 +# - name: example_exact
30 +# subscription_id: "<subscription-id>"
31 +# timeout: 30
32 +# profile_selection_mode: exact
33 +# profile_selection_mode_exact:
34 +# profiles:
35 +# - postgres_flexible
36 +# - cosmos_db
37 +# auth:
38 +# mode: service_principal
39 +# mode_service_principal:
40 +# tenant_id: "<tenant-id>"
41 +# client_id: "<client-id>"
42 +# client_secret: "<client-secret>"
43 +#
44 +# - name: example_combined
45 # subscription_id: "<subscription-id>"
46 # timeout: 30
17 -# profiles:
18 -# - auto
47 +# profile_selection_mode: combined
48 +# profile_selection_mode_combined:
49 +# profiles:
50 +# - custom_profile
51 # auth:
52 # mode: service_principal
53 # mode_service_principal: