Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(prom/exp/snmp): add support to pass multiple SNMP config files to prometheus.exporter.snmp #967

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 9 additions & 6 deletions docs/sources/reference/components/prometheus.exporter.snmp.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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_files` | `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.

Each file listed in in the `config_files` argument must conform to the `config_file` requirements.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Each file listed in in the `config_files` argument must conform to the `config_file` requirements.
Each file listed in in the `config_files` argument must conform to the `config_file` requirements.
Only one of `config_file` or `config_files` can be provided.


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,

Expand Down Expand Up @@ -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"
Expand Down
10 changes: 6 additions & 4 deletions internal/component/prometheus/exporter/snmp/snmp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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,
Comment on lines +145 to +146
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be simpler if we return an error if both config_files and config_file are specified. In this function we could create a config_files out of a config_file, and only pass config_files to snmp_exporter.Config. The static mode code doesn't need to support config_file because it's not used in Alloy.

SnmpTargets: a.Targets.Convert(),
WalkParams: a.WalkParams.Convert(),
SnmpConfig: a.ConfigStruct,
}
}
8 changes: 5 additions & 3 deletions internal/component/prometheus/exporter/snmp/snmp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
35 changes: 22 additions & 13 deletions internal/static/integrations/snmp_exporter/snmp_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"fmt"
"net/http"
"net/url"
"slices"
"strings"

"github.com/go-kit/log"
"github.com/grafana/alloy/internal/static/integrations"
Expand All @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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
}
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
19 changes: 11 additions & 8 deletions internal/static/integrations/v2/snmp_exporter/snmp_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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_files,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
}
Expand All @@ -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
}
Expand Down