diff --git a/CHANGELOG.md b/CHANGELOG.md index c543ea778a..2d09c48ed5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,8 @@ Main (unreleased) - Added support for NS records to `discovery.dns`. (@djcode) +- Add support to pass multiple SNMP config files to `prometheus.exporter.snmp`. (@hainenber) + ### Bugfixes - Fixed an issue with `prometheus.scrape` in which targets that move from one diff --git a/docs/sources/reference/components/prometheus.exporter.snmp.md b/docs/sources/reference/components/prometheus.exporter.snmp.md index 99e59d410f..89662ffd3a 100644 --- a/docs/sources/reference/components/prometheus.exporter.snmp.md +++ b/docs/sources/reference/components/prometheus.exporter.snmp.md @@ -17,7 +17,7 @@ The `prometheus.exporter.snmp` component embeds ```alloy prometheus.exporter.snmp "LABEL" { - config_file = SNMP_CONFIG_FILE_PATH + config_files = [SNMP_CONFIG_FILE_PATH_1, SNMP_CONFIG_FILE_PATH_2] target "TARGET_NAME" { address = TARGET_ADDRESS @@ -30,14 +30,17 @@ prometheus.exporter.snmp "LABEL" { The following arguments can be used to configure the exporter's behavior. Omitted fields take their default values. -| Name | Type | Description | Default | Required | -| ------------- | -------------------- | ------------------------------------------------ | ------- | -------- | -| `config_file` | `string` | SNMP configuration file defining custom modules. | | no | -| `config` | `string` or `secret` | SNMP configuration as inline string. | | no | +| Name | Type | Description | Default | Required | +| ------------- | -------------------- | --------------------------------------------------------- | ------- | -------- | +| `config_file` | `string` | SNMP configuration file defining custom modules. | | no | +| `config_file` | `list(string)` | List of SNMP configuration files defining custom modules. | | no | +| `config` | `string` or `secret` | SNMP configuration as inline string. | | no | The `config_file` argument points to a YAML file defining which snmp_exporter modules to use. Refer to [snmp_exporter](https://github.com/prometheus/snmp_exporter#generating-configuration) for details on how to generate a configuration file. +The `config_files` argument consist of list of YAML files with content as described as above for `config_file`. + The `config` argument must be a YAML document as string defining which SNMP modules and auths to use. `config` is typically loaded by using the exports of another component. For example, @@ -109,7 +112,7 @@ from `prometheus.exporter.snmp`: ```alloy prometheus.exporter.snmp "example" { - config_file = "snmp_modules.yml" + config_files = ["snmp_modules.yml"] target "network_switch_1" { address = "192.168.1.2" diff --git a/internal/component/prometheus/exporter/snmp/snmp.go b/internal/component/prometheus/exporter/snmp/snmp.go index ec3581da00..aa8b581d0d 100644 --- a/internal/component/prometheus/exporter/snmp/snmp.go +++ b/internal/component/prometheus/exporter/snmp/snmp.go @@ -113,6 +113,7 @@ func (w WalkParams) Convert() map[string]snmp_config.WalkParams { type Arguments struct { ConfigFile string `alloy:"config_file,attr,optional"` + ConfigFiles []string `alloy:"config_files,attr,optional"` Config alloytypes.OptionalSecret `alloy:"config,attr,optional"` Targets TargetBlock `alloy:"target,block"` WalkParams WalkParams `alloy:"walk_param,block,optional"` @@ -141,9 +142,10 @@ func (a *Arguments) UnmarshalAlloy(f func(interface{}) error) error { // Convert converts the component's Arguments to the integration's Config. func (a *Arguments) Convert() *snmp_exporter.Config { return &snmp_exporter.Config{ - SnmpConfigFile: a.ConfigFile, - SnmpTargets: a.Targets.Convert(), - WalkParams: a.WalkParams.Convert(), - SnmpConfig: a.ConfigStruct, + SnmpConfigFile: a.ConfigFile, + SnmpConfigFiles: a.ConfigFiles, + SnmpTargets: a.Targets.Convert(), + WalkParams: a.WalkParams.Convert(), + SnmpConfig: a.ConfigStruct, } } diff --git a/internal/component/prometheus/exporter/snmp/snmp_test.go b/internal/component/prometheus/exporter/snmp/snmp_test.go index efae26ecfb..e985c62b57 100644 --- a/internal/component/prometheus/exporter/snmp/snmp_test.go +++ b/internal/component/prometheus/exporter/snmp/snmp_test.go @@ -62,13 +62,15 @@ func TestUnmarshalAlloy(t *testing.T) { func TestConvertConfig(t *testing.T) { args := Arguments{ - ConfigFile: "modules.yml", - Targets: TargetBlock{{Name: "network_switch_1", Target: "192.168.1.2", Module: "if_mib"}}, - WalkParams: WalkParams{{Name: "public", Retries: 2}}, + ConfigFile: "modules.yml", + ConfigFiles: []string{"modules_1.yml", "modules_2.yml"}, + Targets: TargetBlock{{Name: "network_switch_1", Target: "192.168.1.2", Module: "if_mib"}}, + WalkParams: WalkParams{{Name: "public", Retries: 2}}, } res := args.Convert() require.Equal(t, "modules.yml", res.SnmpConfigFile) + require.Equal(t, []string{"modules_1.yml", "modules_2.yml"}, res.SnmpConfigFiles) require.Equal(t, 1, len(res.SnmpTargets)) require.Equal(t, "network_switch_1", res.SnmpTargets[0].Name) } diff --git a/internal/static/integrations/snmp_exporter/snmp_exporter.go b/internal/static/integrations/snmp_exporter/snmp_exporter.go index 98aed8496b..e20d67ccc8 100644 --- a/internal/static/integrations/snmp_exporter/snmp_exporter.go +++ b/internal/static/integrations/snmp_exporter/snmp_exporter.go @@ -6,6 +6,8 @@ import ( "fmt" "net/http" "net/url" + "slices" + "strings" "github.com/go-kit/log" "github.com/grafana/alloy/internal/static/integrations" @@ -19,10 +21,11 @@ import ( // DefaultConfig holds the default settings for the snmp_exporter integration. var DefaultConfig = Config{ - WalkParams: make(map[string]snmp_config.WalkParams), - SnmpConfigFile: "", - SnmpTargets: make([]SNMPTarget, 0), - SnmpConfig: snmp_config.Config{}, + WalkParams: make(map[string]snmp_config.WalkParams), + SnmpConfigFile: "", + SnmpConfigFiles: []string{}, + SnmpTargets: make([]SNMPTarget, 0), + SnmpConfig: snmp_config.Config{}, } // SNMPTarget defines a target device to be used by the integration. @@ -36,10 +39,11 @@ type SNMPTarget struct { // Config configures the SNMP integration. type Config struct { - WalkParams map[string]snmp_config.WalkParams `yaml:"walk_params,omitempty"` - SnmpConfigFile string `yaml:"config_file,omitempty"` - SnmpTargets []SNMPTarget `yaml:"snmp_targets"` - SnmpConfig snmp_config.Config `yaml:"snmp_config,omitempty"` + WalkParams map[string]snmp_config.WalkParams `yaml:"walk_params,omitempty"` + SnmpConfigFile string `yaml:"config_file,omitempty"` + SnmpConfigFiles []string `yaml:"config_files,omitempty"` + SnmpTargets []SNMPTarget `yaml:"snmp_targets"` + SnmpConfig snmp_config.Config `yaml:"snmp_config,omitempty"` } // UnmarshalYAML implements yaml.Unmarshaler for Config. @@ -71,7 +75,8 @@ func init() { // New creates a new snmp_exporter integration func New(log log.Logger, c *Config) (integrations.Integration, error) { - snmpCfg, err := LoadSNMPConfig(c.SnmpConfigFile, &c.SnmpConfig) + snmpConfigFiles := c.SnmpConfigFiles + snmpCfg, err := LoadSNMPConfig(snmpConfigFiles, &c.SnmpConfig) if err != nil { return nil, err } @@ -97,12 +102,16 @@ func New(log log.Logger, c *Config) (integrations.Integration, error) { // LoadSNMPConfig loads the SNMP configuration from the given file. If the file is empty, it will // load the embedded configuration. -func LoadSNMPConfig(snmpConfigFile string, snmpCfg *snmp_config.Config) (*snmp_config.Config, error) { +func LoadSNMPConfig(snmpConfigFiles []string, snmpCfg *snmp_config.Config) (*snmp_config.Config, error) { var err error - if snmpConfigFile != "" { - snmpCfg, err = snmp_config.LoadFile([]string{snmpConfigFile}, false) + + // Remove empty string of default `snmpConfig` + validSnmpConfigFiles := slices.DeleteFunc(snmpConfigFiles, func(i string) bool { return i == "" }) + + if len(validSnmpConfigFiles) > 0 { + snmpCfg, err = snmp_config.LoadFile(validSnmpConfigFiles, false) if err != nil { - return nil, fmt.Errorf("failed to load snmp config from file %v: %w", snmpConfigFile, err) + return nil, fmt.Errorf("failed to load snmp config from files %v: %w", strings.Join(snmpConfigFiles, " "), err) } } else { if len(snmpCfg.Modules) == 0 && len(snmpCfg.Auths) == 0 { // If the user didn't specify a config, load the embedded config. diff --git a/internal/static/integrations/snmp_exporter/snmp_exporter_test.go b/internal/static/integrations/snmp_exporter/snmp_exporter_test.go index c8b9c2be7c..3826398738 100644 --- a/internal/static/integrations/snmp_exporter/snmp_exporter_test.go +++ b/internal/static/integrations/snmp_exporter/snmp_exporter_test.go @@ -35,7 +35,7 @@ func TestLoadSNMPConfig(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - cfg, err := LoadSNMPConfig(tt.cfg.SnmpConfigFile, &tt.cfg.SnmpConfig) + cfg, err := LoadSNMPConfig([]string{tt.cfg.SnmpConfigFile}, &tt.cfg.SnmpConfig) require.NoError(t, err) require.Equal(t, tt.expectedNumModules, len(cfg.Modules)) diff --git a/internal/static/integrations/v2/snmp_exporter/snmp_exporter.go b/internal/static/integrations/v2/snmp_exporter/snmp_exporter.go index 842dfc014b..196a4ce1af 100644 --- a/internal/static/integrations/v2/snmp_exporter/snmp_exporter.go +++ b/internal/static/integrations/v2/snmp_exporter/snmp_exporter.go @@ -11,17 +11,19 @@ import ( // DefaultConfig holds the default settings for the snmp_exporter integration. var DefaultConfig = Config{ - WalkParams: make(map[string]snmp_config.WalkParams), - SnmpConfigFile: "", + WalkParams: make(map[string]snmp_config.WalkParams), + SnmpConfigFile: "", + SnmpConfigFiles: []string{}, } // Config configures the SNMP integration. type Config struct { - WalkParams map[string]snmp_config.WalkParams `yaml:"walk_params,omitempty"` - SnmpConfigFile string `yaml:"config_file,omitempty"` - SnmpTargets []snmp_exporter.SNMPTarget `yaml:"snmp_targets"` - SnmpConfig snmp_config.Config `yaml:"snmp_config,omitempty"` - Common common.MetricsConfig `yaml:",inline"` + WalkParams map[string]snmp_config.WalkParams `yaml:"walk_params,omitempty"` + SnmpConfigFile string `yaml:"config_file,omitempty"` + SnmpConfigFiles []string `yaml:"config_file,omitempty"` + SnmpTargets []snmp_exporter.SNMPTarget `yaml:"snmp_targets"` + SnmpConfig snmp_config.Config `yaml:"snmp_config,omitempty"` + Common common.MetricsConfig `yaml:",inline"` globals integrations_v2.Globals } @@ -42,7 +44,8 @@ func (c *Config) Identifier(globals integrations_v2.Globals) (string, error) { // NewIntegration creates a new SNMP integration. func (c *Config) NewIntegration(log log.Logger, globals integrations_v2.Globals) (integrations_v2.Integration, error) { - snmpCfg, err := snmp_exporter.LoadSNMPConfig(c.SnmpConfigFile, &c.SnmpConfig) + snmpConfigFiles := append(c.SnmpConfigFiles, c.SnmpConfigFile) + snmpCfg, err := snmp_exporter.LoadSNMPConfig(snmpConfigFiles, &c.SnmpConfig) if err != nil { return nil, err }