Skip to content

Commit c2f7f90

Browse files
authored
Merge pull request #232 from msvticket/tagselection
feat: select db instances by tags
2 parents 5317cb4 + f65dd9a commit c2f7f90

File tree

15 files changed

+268
-224
lines changed

15 files changed

+268
-224
lines changed

README.md

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -172,24 +172,25 @@ Prometheus RDS exporter</br>
172172

173173
Configuration could be defined in [prometheus-rds-exporter.yaml](https://github.com/qonto/prometheus-rds-exporter/blob/main/configs/prometheus-rds-exporter/prometheus-rds-exporter.yaml) or environment variables (format `PROMETHEUS_RDS_EXPORTER_<PARAMETER_NAME>`).
174174

175-
| Parameter | Description | Default |
176-
| --- | --- | --- |
177-
| aws-assume-role-arn | AWS IAM ARN role to assume to fetch metrics | |
178-
| aws-assume-role-session | AWS assume role session name | prometheus-rds-exporter |
179-
| collect-instance-metrics | Collect AWS instances metrics (AWS Cloudwatch API) | true |
180-
| collect-instance-tags | Collect AWS RDS tags | true |
181-
| collect-instance-types | Collect AWS instance types information (AWS EC2 API) | true |
182-
| collect-logs-size | Collect AWS instances logs size (AWS RDS API) | true |
183-
| collect-maintenances | Collect AWS instances maintenances (AWS RDS API) | true |
184-
| collect-quotas | Collect AWS RDS quotas (AWS quotas API) | true |
185-
| collect-usages | Collect AWS RDS usages (AWS Cloudwatch API) | true |
186-
| debug | Enable debug mode | |
187-
| enable-otel-traces | Enable OpenTelemetry traces. See [configuration](https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/) | false |
188-
| listen-address | Address to listen on for web interface | :9043 |
189-
| log-format | Log format (`text` or `json`) | json |
190-
| metrics-path | Path under which to expose metrics | /metrics |
191-
| tls-cert-path | Path to TLS certificate | |
192-
| tls-key-path | Path to private key for TLS |
175+
| Parameter | Description | Default |
176+
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------|
177+
| aws-assume-role-arn | AWS IAM ARN role to assume to fetch metrics | |
178+
| aws-assume-role-session | AWS assume role session name | prometheus-rds-exporter |
179+
| collect-instance-metrics | Collect AWS instances metrics (AWS Cloudwatch API) | true |
180+
| collect-instance-tags | Collect AWS RDS tags | true |
181+
| collect-instance-types | Collect AWS instance types information (AWS EC2 API) | true |
182+
| collect-logs-size | Collect AWS instances logs size (AWS RDS API) | true |
183+
| collect-maintenances | Collect AWS instances maintenances (AWS RDS API) | true |
184+
| collect-quotas | Collect AWS RDS quotas (AWS quotas API) | true |
185+
| collect-usages | Collect AWS RDS usages (AWS Cloudwatch API) | true |
186+
| tag-selections | Tags to select database instances with. See [configuration on TagFilters field](https://docs.aws.amazon.com/resourcegroupstagging/latest/APIReference/API_GetResources.html#resourcegrouptagging-GetResources-request-TagFilters) | |
187+
| debug | Enable debug mode | |
188+
| enable-otel-traces | Enable OpenTelemetry traces. See [configuration](https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/) | false |
189+
| listen-address | Address to listen on for web interface | :9043 |
190+
| log-format | Log format (`text` or `json`) | json |
191+
| metrics-path | Path under which to expose metrics | /metrics |
192+
| tls-cert-path | Path to TLS certificate | |
193+
| tls-key-path | Path to private key for TLS | |
193194

194195
Configuration parameters priorities:
195196

cmd/root.go

Lines changed: 61 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,23 @@ import (
77
"path/filepath"
88
"strings"
99

10+
"github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi"
11+
1012
"github.com/aws/aws-sdk-go-v2/service/cloudwatch"
1113
"github.com/aws/aws-sdk-go-v2/service/ec2"
1214
"github.com/aws/aws-sdk-go-v2/service/rds"
1315
"github.com/aws/aws-sdk-go-v2/service/servicequotas"
16+
"github.com/knadh/koanf/parsers/yaml"
17+
"github.com/knadh/koanf/providers/env"
18+
"github.com/knadh/koanf/providers/file"
19+
"github.com/knadh/koanf/providers/posflag"
20+
"github.com/knadh/koanf/v2"
1421
"github.com/prometheus/client_golang/prometheus"
1522
"github.com/qonto/prometheus-rds-exporter/internal/app/exporter"
1623
"github.com/qonto/prometheus-rds-exporter/internal/infra/build"
1724
"github.com/qonto/prometheus-rds-exporter/internal/infra/http"
1825
"github.com/qonto/prometheus-rds-exporter/internal/infra/logger"
1926
"github.com/spf13/cobra"
20-
"github.com/spf13/viper"
2127
)
2228

2329
const (
@@ -27,25 +33,29 @@ const (
2733
awsErrorExitCode = 4
2834
)
2935

30-
var cfgFile string
36+
var (
37+
cfgFile string
38+
k = koanf.New(".")
39+
)
3140

3241
type exporterConfig struct {
33-
Debug bool `mapstructure:"debug"`
34-
LogFormat string `mapstructure:"log-format"`
35-
TLSCertPath string `mapstructure:"tls-cert-path"`
36-
TLSKeyPath string `mapstructure:"tls-key-path"`
37-
MetricPath string `mapstructure:"metrics-path"`
38-
ListenAddress string `mapstructure:"listen-address"`
39-
AWSAssumeRoleSession string `mapstructure:"aws-assume-role-session"`
40-
AWSAssumeRoleArn string `mapstructure:"aws-assume-role-arn"`
41-
CollectInstanceMetrics bool `mapstructure:"collect-instance-metrics"`
42-
CollectInstanceTags bool `mapstructure:"collect-instance-tags"`
43-
CollectInstanceTypes bool `mapstructure:"collect-instance-types"`
44-
CollectLogsSize bool `mapstructure:"collect-logs-size"`
45-
CollectMaintenances bool `mapstructure:"collect-maintenances"`
46-
CollectQuotas bool `mapstructure:"collect-quotas"`
47-
CollectUsages bool `mapstructure:"collect-usages"`
48-
OTELTracesEnabled bool `mapstructure:"enable-otel-traces"`
42+
Debug bool `koanf:"debug"`
43+
LogFormat string `koanf:"log-format"`
44+
TLSCertPath string `koanf:"tls-cert-path"`
45+
TLSKeyPath string `koanf:"tls-key-path"`
46+
MetricPath string `koanf:"metrics-path"`
47+
ListenAddress string `koanf:"listen-address"`
48+
AWSAssumeRoleSession string `koanf:"aws-assume-role-session"`
49+
AWSAssumeRoleArn string `koanf:"aws-assume-role-arn"`
50+
CollectInstanceMetrics bool `koanf:"collect-instance-metrics"`
51+
CollectInstanceTags bool `koanf:"collect-instance-tags"`
52+
CollectInstanceTypes bool `koanf:"collect-instance-types"`
53+
CollectLogsSize bool `koanf:"collect-logs-size"`
54+
CollectMaintenances bool `koanf:"collect-maintenances"`
55+
CollectQuotas bool `koanf:"collect-quotas"`
56+
CollectUsages bool `koanf:"collect-usages"`
57+
OTELTracesEnabled bool `koanf:"enable-otel-traces"`
58+
TagSelections map[string][]string `koanf:"tag-selections"`
4959
}
5060

5161
func run(configuration exporterConfig) {
@@ -55,6 +65,8 @@ func run(configuration exporterConfig) {
5565
panic(err)
5666
}
5767

68+
logger.Debug(fmt.Sprintf("Config: %+v\n", configuration))
69+
5870
cfg, err := getAWSConfiguration(logger, configuration.AWSAssumeRoleArn, configuration.AWSAssumeRoleSession)
5971
if err != nil {
6072
logger.Error("can't initialize AWS configuration", "reason", err)
@@ -68,6 +80,13 @@ func run(configuration exporterConfig) {
6880
}
6981

7082
rdsClient := rds.NewFromConfig(cfg)
83+
84+
var tagClient *resourcegroupstaggingapi.Client
85+
86+
if configuration.TagSelections != nil {
87+
tagClient = resourcegroupstaggingapi.NewFromConfig(cfg)
88+
}
89+
7190
ec2Client := ec2.NewFromConfig(cfg)
7291
cloudWatchClient := cloudwatch.NewFromConfig(cfg)
7392
servicequotasClient := servicequotas.NewFromConfig(cfg)
@@ -80,9 +99,10 @@ func run(configuration exporterConfig) {
8099
CollectMaintenances: configuration.CollectMaintenances,
81100
CollectQuotas: configuration.CollectQuotas,
82101
CollectUsages: configuration.CollectUsages,
102+
TagSelections: configuration.TagSelections,
83103
}
84104

85-
collector := exporter.NewCollector(*logger, collectorConfiguration, awsAccountID, awsRegion, rdsClient, ec2Client, cloudWatchClient, servicequotasClient)
105+
collector := exporter.NewCollector(*logger, collectorConfiguration, awsAccountID, awsRegion, rdsClient, ec2Client, cloudWatchClient, servicequotasClient, tagClient)
86106

87107
prometheus.MustRegister(collector)
88108

@@ -111,10 +131,16 @@ func NewRootCommand() (*cobra.Command, error) {
111131
Long: `Collect AWS RDS key metrics from AWS APIs
112132
and expose them as Prometheus metrics.`,
113133
Run: func(cmd *cobra.Command, args []string) {
114-
var c exporterConfig
115-
err := viper.Unmarshal(&c)
134+
err := k.Load(posflag.Provider(cmd.Flags(), ".", k), nil)
116135
if err != nil {
117-
fmt.Println("ERROR: Unable to decode configuration, %w", err)
136+
fmt.Printf("ERROR: Unable to interpret flags, %v\n", err)
137+
138+
return
139+
}
140+
141+
var c exporterConfig
142+
if err := k.Unmarshal("", &c); err != nil {
143+
fmt.Printf("ERROR: Unable to decode configuration, %v\n", err)
118144

119145
return
120146
}
@@ -142,86 +168,6 @@ func NewRootCommand() (*cobra.Command, error) {
142168
cmd.Flags().BoolP("collect-quotas", "", true, "Collect AWS RDS quotas")
143169
cmd.Flags().BoolP("collect-usages", "", true, "Collect AWS RDS usages")
144170

145-
err := viper.BindPFlag("debug", cmd.Flags().Lookup("debug"))
146-
if err != nil {
147-
return cmd, fmt.Errorf("failed to bind 'debug' parameter: %w", err)
148-
}
149-
150-
err = viper.BindPFlag("log-format", cmd.Flags().Lookup("log-format"))
151-
if err != nil {
152-
return cmd, fmt.Errorf("failed to bind 'log-format' parameter: %w", err)
153-
}
154-
155-
err = viper.BindPFlag("enable-otel-traces", cmd.Flags().Lookup("enable-otel-traces"))
156-
if err != nil {
157-
return cmd, fmt.Errorf("failed to bind 'enable-otel-traces' parameter: %w", err)
158-
}
159-
160-
err = viper.BindPFlag("metrics-path", cmd.Flags().Lookup("metrics-path"))
161-
if err != nil {
162-
return cmd, fmt.Errorf("failed to bind 'metrics-path' parameter: %w", err)
163-
}
164-
165-
err = viper.BindPFlag("tls-cert-path", cmd.Flags().Lookup("tls-cert-path"))
166-
if err != nil {
167-
return cmd, fmt.Errorf("failed to bind 'tls-cert-path' parameter: %w", err)
168-
}
169-
170-
err = viper.BindPFlag("tls-key-path", cmd.Flags().Lookup("tls-key-path"))
171-
if err != nil {
172-
return cmd, fmt.Errorf("failed to bind 'tls-key-path' parameter: %w", err)
173-
}
174-
175-
err = viper.BindPFlag("listen-address", cmd.Flags().Lookup("listen-address"))
176-
if err != nil {
177-
return cmd, fmt.Errorf("failed to bind 'listen-address' parameter: %w", err)
178-
}
179-
180-
err = viper.BindPFlag("aws-assume-role-arn", cmd.Flags().Lookup("aws-assume-role-arn"))
181-
if err != nil {
182-
return cmd, fmt.Errorf("failed to bind 'aws-assume-role-arn' parameter: %w", err)
183-
}
184-
185-
err = viper.BindPFlag("aws-assume-role-session", cmd.Flags().Lookup("aws-assume-role-session"))
186-
if err != nil {
187-
return cmd, fmt.Errorf("failed to bind 'aws-assume-role-session' parameter: %w", err)
188-
}
189-
190-
err = viper.BindPFlag("collect-instance-metrics", cmd.Flags().Lookup("collect-instance-metrics"))
191-
if err != nil {
192-
return cmd, fmt.Errorf("failed to bind 'collect-instance-metrics' parameter: %w", err)
193-
}
194-
195-
err = viper.BindPFlag("collect-instance-tags", cmd.Flags().Lookup("collect-instance-tags"))
196-
if err != nil {
197-
return cmd, fmt.Errorf("failed to bind 'collect-instance-tags' parameter: %w", err)
198-
}
199-
200-
err = viper.BindPFlag("collect-instance-types", cmd.Flags().Lookup("collect-instance-types"))
201-
if err != nil {
202-
return cmd, fmt.Errorf("failed to bind 'collect-instance-types' parameter: %w", err)
203-
}
204-
205-
err = viper.BindPFlag("collect-quotas", cmd.Flags().Lookup("collect-quotas"))
206-
if err != nil {
207-
return cmd, fmt.Errorf("failed to bind 'collect-quotas' parameter: %w", err)
208-
}
209-
210-
err = viper.BindPFlag("collect-usages", cmd.Flags().Lookup("collect-usages"))
211-
if err != nil {
212-
return cmd, fmt.Errorf("failed to bind 'collect-usages' parameter: %w", err)
213-
}
214-
215-
err = viper.BindPFlag("collect-logs-size", cmd.Flags().Lookup("collect-logs-size"))
216-
if err != nil {
217-
return cmd, fmt.Errorf("failed to bind 'collect-logs-size' parameter: %w", err)
218-
}
219-
220-
err = viper.BindPFlag("collect-maintenances", cmd.Flags().Lookup("collect-maintenances"))
221-
if err != nil {
222-
return cmd, fmt.Errorf("failed to bind 'collect-maintenances' parameter: %w", err)
223-
}
224-
225171
return cmd, nil
226172
}
227173

@@ -243,32 +189,30 @@ func Execute() {
243189
func initConfig() {
244190
if cfgFile != "" {
245191
// Use config file from the flag.
246-
viper.SetConfigFile(cfgFile)
192+
err := k.Load(file.Provider(cfgFile), yaml.Parser())
193+
cobra.CheckErr(err)
247194
} else {
248-
// Find home directory
195+
// Find home directory.
249196
home, err := os.UserHomeDir()
250197
cobra.CheckErr(err)
251198

252-
// Search config in home directory or current directory with name "prometheus-rds-exporter.yaml"
253-
199+
// Search config in home directory or current directory with name "prometheus-rds-exporter.yaml".
254200
configurationFilename := "prometheus-rds-exporter.yaml"
255201
currentPathFilename := configurationFilename
256202
homeFilename := filepath.Join(home, configurationFilename)
257203

258-
if _, err := os.Stat(homeFilename); err == nil {
259-
viper.SetConfigFile(homeFilename)
204+
if err := k.Load(file.Provider(homeFilename), yaml.Parser()); err == nil {
205+
fmt.Printf("Using config file: %s\n", homeFilename)
260206
}
261207

262-
if _, err := os.Stat(currentPathFilename); err == nil {
263-
viper.SetConfigFile(currentPathFilename)
208+
if err := k.Load(file.Provider(currentPathFilename), yaml.Parser()); err == nil {
209+
fmt.Printf("Using config file: %s\n", currentPathFilename)
264210
}
265211
}
266212

267-
if err := viper.ReadInConfig(); err == nil {
268-
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
269-
}
270-
271-
viper.SetEnvPrefix("prometheus_rds_exporter") // will be uppercased automatically
272-
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
273-
viper.AutomaticEnv()
213+
// Set environment variables.
214+
err := k.Load(env.Provider("PROMETHEUS_RDS_EXPORTER_", ".", func(s string) string {
215+
return strings.ReplaceAll(strings.ToLower(strings.TrimPrefix(s, "PROMETHEUS_RDS_EXPORTER_")), "_", ".")
216+
}), nil)
217+
cobra.CheckErr(err)
274218
}

configs/aws/policy.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
{
22
"Version": "2012-10-17",
33
"Statement": [
4+
{
5+
"Sid": "AllowSearchDBByTags",
6+
"Effect": "Allow",
7+
"Action": [
8+
"tag:GetResources"
9+
],
10+
"Resource": "*"
11+
},
412
{
513
"Sid": "AllowInstanceAndLogDescriptions",
614
"Effect": "Allow",

configs/helm/templates/configmap.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{{- if .Values.config }}
2+
apiVersion: v1
3+
kind: ConfigMap
4+
metadata:
5+
name: {{ include "prometheus-rds-exporter.fullname" . }}
6+
labels:
7+
{{- include "prometheus-rds-exporter.labels" . | nindent 4 }}
8+
data:
9+
prometheus-rds-exporter.yaml: | {{ toYaml .Values.config | nindent 4 }}
10+
{{- end }}

configs/helm/templates/deployment.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ spec:
6363
port: http
6464
resources:
6565
{{- toYaml .Values.resources | nindent 12 }}
66+
{{- if .Values.config }}
67+
args:
68+
- --config
69+
- /config/prometheus-rds-exporter.yaml
70+
volumeMounts:
71+
- mountPath: /config
72+
name: config
73+
{{- end }}
6674
{{- with .Values.nodeSelector }}
6775
nodeSelector:
6876
{{- toYaml . | nindent 8 }}
@@ -75,3 +83,10 @@ spec:
7583
tolerations:
7684
{{- toYaml . | nindent 8 }}
7785
{{- end }}
86+
{{- if .Values.config }}
87+
volumes:
88+
- name: config
89+
configMap:
90+
name: {{ include "prometheus-rds-exporter.fullname" . }}
91+
{{- end }}
92+

configs/helm/values.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ image:
1111
# Define environment variables
1212
env: {}
1313

14+
# Application configuration. If set a configmap will be created. For example values see https://github.com/qonto/prometheus-rds-exporter/blob/main/configs/prometheus-rds-exporter/prometheus-rds-exporter.yaml
15+
config: {}
16+
1417
# Name of the secret containing AWS credentials
1518
awsCredentialsSecret: ""
1619

configs/prometheus-rds-exporter/prometheus-rds-exporter.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,8 @@
5858

5959
# Collect AWS RDS usages (AWS Cloudwatch API)
6060
# collect-usages: true
61+
62+
# Select AWS instances by tags. See https://docs.aws.amazon.com/resourcegroupstagging/latest/APIReference/API_GetResources.html#resourcegrouptagging-GetResources-request-TagFilters
63+
# tag-selections:
64+
# Environment:
65+
# - staging

docs/add-configuration-parameter.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ Steps:
1010

1111
1. Add a new field in `exporterConfig` structure in `cmd/root.go`
1212
1. Add the parameter in flag `cmd.Flags()`
13-
1. Add the parameter in viper `viper.BindPFlag()`
1413

1514
1. Add tests
1615

0 commit comments

Comments
 (0)