mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
vmalert: allow configuring custom headers per group (#2901)
See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2860 Signed-off-by: hagen1778 <roman@victoriametrics.com>
This commit is contained in:
parent
70a822f3a0
commit
88edb3f6cf
20 changed files with 492 additions and 322 deletions
|
@ -125,6 +125,16 @@ name: <string>
|
||||||
params:
|
params:
|
||||||
[ <string>: [<string>, ...]]
|
[ <string>: [<string>, ...]]
|
||||||
|
|
||||||
|
# Optional list of HTTP headers in form `header-name: value`
|
||||||
|
# applied for all rules requests within a group
|
||||||
|
# For example:
|
||||||
|
# headers:
|
||||||
|
# - "CustomHeader: foo"
|
||||||
|
# - "CustomHeader2: bar"
|
||||||
|
# Headers set via this param have priority over headers set via `-datasource.headers` flag.
|
||||||
|
headers:
|
||||||
|
[ <string>, ...]
|
||||||
|
|
||||||
# Optional list of labels added to every rule within a group.
|
# Optional list of labels added to every rule within a group.
|
||||||
# It has priority over the external labels.
|
# It has priority over the external labels.
|
||||||
# Labels are commonly used for adding environment
|
# Labels are commonly used for adding environment
|
||||||
|
|
|
@ -75,6 +75,7 @@ func newAlertingRule(qb datasource.QuerierBuilder, group *Group, cfg config.Rule
|
||||||
DataSourceType: &group.Type,
|
DataSourceType: &group.Type,
|
||||||
EvaluationInterval: group.Interval,
|
EvaluationInterval: group.Interval,
|
||||||
QueryParams: group.Params,
|
QueryParams: group.Params,
|
||||||
|
Headers: group.Headers,
|
||||||
}),
|
}),
|
||||||
alerts: make(map[uint64]*notifier.Alert),
|
alerts: make(map[uint64]*notifier.Alert),
|
||||||
metrics: &alertingRuleMetrics{},
|
metrics: &alertingRuleMetrics{},
|
||||||
|
|
|
@ -38,6 +38,8 @@ type Group struct {
|
||||||
Checksum string
|
Checksum string
|
||||||
// Optional HTTP URL parameters added to each rule request
|
// Optional HTTP URL parameters added to each rule request
|
||||||
Params url.Values `yaml:"params"`
|
Params url.Values `yaml:"params"`
|
||||||
|
// Headers contains optional HTTP headers added to each rule request
|
||||||
|
Headers []datasource.Header `yaml:"headers,omitempty"`
|
||||||
|
|
||||||
// Catches all undefined fields and must be empty after parsing.
|
// Catches all undefined fields and must be empty after parsing.
|
||||||
XXX map[string]interface{} `yaml:",inline"`
|
XXX map[string]interface{} `yaml:",inline"`
|
||||||
|
|
|
@ -60,6 +60,10 @@ func TestParseBad(t *testing.T) {
|
||||||
[]string{"testdata/rules/rules1-bad.rules"},
|
[]string{"testdata/rules/rules1-bad.rules"},
|
||||||
"bad graphite expr",
|
"bad graphite expr",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
[]string{"testdata/dir/rules6-bad.rules"},
|
||||||
|
"missing ':' in header",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
_, err := Parse(tc.path, true, true)
|
_, err := Parse(tc.path, true, true)
|
||||||
|
@ -505,6 +509,24 @@ rules:
|
||||||
`, `
|
`, `
|
||||||
name: TestGroup
|
name: TestGroup
|
||||||
limit: 10
|
limit: 10
|
||||||
|
rules:
|
||||||
|
- alert: foo
|
||||||
|
expr: sum by(job) (up == 1)
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("`headers` change", func(t *testing.T) {
|
||||||
|
f(t, `
|
||||||
|
name: TestGroup
|
||||||
|
headers:
|
||||||
|
- "TenantID: foo"
|
||||||
|
rules:
|
||||||
|
- alert: foo
|
||||||
|
expr: sum by(job) (up == 1)
|
||||||
|
`, `
|
||||||
|
name: TestGroup
|
||||||
|
headers:
|
||||||
|
- "TenantID: bar"
|
||||||
rules:
|
rules:
|
||||||
- alert: foo
|
- alert: foo
|
||||||
expr: sum by(job) (up == 1)
|
expr: sum by(job) (up == 1)
|
||||||
|
|
7
app/vmalert/config/testdata/dir/rules6-bad.rules
vendored
Normal file
7
app/vmalert/config/testdata/dir/rules6-bad.rules
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
groups:
|
||||||
|
- name: group
|
||||||
|
headers:
|
||||||
|
- 'foobar'
|
||||||
|
rules:
|
||||||
|
- alert: rows
|
||||||
|
expr: vm_rows > 0
|
|
@ -3,9 +3,10 @@ groups:
|
||||||
interval: 2s
|
interval: 2s
|
||||||
concurrency: 2
|
concurrency: 2
|
||||||
limit: 1000
|
limit: 1000
|
||||||
|
headers:
|
||||||
|
- "MyHeader: foo"
|
||||||
params:
|
params:
|
||||||
denyPartialResponse: ["true"]
|
denyPartialResponse: ["true"]
|
||||||
extra_label: ["env=dev"]
|
|
||||||
rules:
|
rules:
|
||||||
- alert: Conns
|
- alert: Conns
|
||||||
expr: sum(vm_tcplistener_conns) by(instance) > 1
|
expr: sum(vm_tcplistener_conns) by(instance) > 1
|
||||||
|
|
|
@ -22,6 +22,7 @@ type QuerierParams struct {
|
||||||
DataSourceType *Type
|
DataSourceType *Type
|
||||||
EvaluationInterval time.Duration
|
EvaluationInterval time.Duration
|
||||||
QueryParams url.Values
|
QueryParams url.Values
|
||||||
|
Headers []Header
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metric is the basic entity which should be return by datasource
|
// Metric is the basic entity which should be return by datasource
|
||||||
|
|
|
@ -15,7 +15,7 @@ var (
|
||||||
"E.g. http://127.0.0.1:8428 . See also -remoteRead.disablePathAppend")
|
"E.g. http://127.0.0.1:8428 . See also -remoteRead.disablePathAppend")
|
||||||
appendTypePrefix = flag.Bool("datasource.appendTypePrefix", false, "Whether to add type prefix to -datasource.url based on the query type. Set to true if sending different query types to the vmselect URL.")
|
appendTypePrefix = flag.Bool("datasource.appendTypePrefix", false, "Whether to add type prefix to -datasource.url based on the query type. Set to true if sending different query types to the vmselect URL.")
|
||||||
|
|
||||||
headers = flag.String("datasource.headers", "", "Optional HTTP headers to send with each request to the corresponding -datasource.url. "+
|
headers = flag.String("datasource.headers", "", "Optional HTTP extraHeaders to send with each request to the corresponding -datasource.url. "+
|
||||||
"For example, -datasource.headers='My-Auth:foobar' would send 'My-Auth: foobar' HTTP header with every request to the corresponding -datasource.url. "+
|
"For example, -datasource.headers='My-Auth:foobar' would send 'My-Auth: foobar' HTTP header with every request to the corresponding -datasource.url. "+
|
||||||
"Multiple headers must be delimited by '^^': -datasource.headers='header1:value1^^header2:value2'")
|
"Multiple headers must be delimited by '^^': -datasource.headers='header1:value1^^header2:value2'")
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package datasource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/graphiteql"
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/graphiteql"
|
||||||
"github.com/VictoriaMetrics/metricsql"
|
"github.com/VictoriaMetrics/metricsql"
|
||||||
|
@ -89,3 +90,27 @@ func (t *Type) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
func (t Type) MarshalYAML() (interface{}, error) {
|
func (t Type) MarshalYAML() (interface{}, error) {
|
||||||
return t.name, nil
|
return t.name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Header is a Key - Value struct for holding an HTTP header.
|
||||||
|
type Header struct {
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||||
|
func (h *Header) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
|
var s string
|
||||||
|
if err := unmarshal(&s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
n := strings.IndexByte(s, ':')
|
||||||
|
if n < 0 {
|
||||||
|
return fmt.Errorf(`missing ':' in header %q; expecting "key: value" format`, s)
|
||||||
|
}
|
||||||
|
h.Key = strings.TrimSpace(s[:n])
|
||||||
|
h.Value = strings.TrimSpace(s[n+1:])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ type VMStorage struct {
|
||||||
dataSourceType Type
|
dataSourceType Type
|
||||||
evaluationInterval time.Duration
|
evaluationInterval time.Duration
|
||||||
extraParams url.Values
|
extraParams url.Values
|
||||||
|
extraHeaders []Header
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone makes clone of VMStorage, shares http client.
|
// Clone makes clone of VMStorage, shares http client.
|
||||||
|
@ -46,6 +47,7 @@ func (s *VMStorage) ApplyParams(params QuerierParams) *VMStorage {
|
||||||
}
|
}
|
||||||
s.evaluationInterval = params.EvaluationInterval
|
s.evaluationInterval = params.EvaluationInterval
|
||||||
s.extraParams = params.QueryParams
|
s.extraParams = params.QueryParams
|
||||||
|
s.extraHeaders = params.Headers
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,5 +150,8 @@ func (s *VMStorage) newRequestPOST() (*http.Request, error) {
|
||||||
if s.authCfg != nil {
|
if s.authCfg != nil {
|
||||||
s.authCfg.SetHeaders(req, true)
|
s.authCfg.SetHeaders(req, true)
|
||||||
}
|
}
|
||||||
|
for _, h := range s.extraHeaders {
|
||||||
|
req.Header.Set(h.Key, h.Value)
|
||||||
|
}
|
||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -487,7 +487,7 @@ func TestRequestParams(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthConfig(t *testing.T) {
|
func TestHeaders(t *testing.T) {
|
||||||
var testCases = []struct {
|
var testCases = []struct {
|
||||||
name string
|
name string
|
||||||
vmFn func() *VMStorage
|
vmFn func() *VMStorage
|
||||||
|
@ -527,6 +527,40 @@ func TestAuthConfig(t *testing.T) {
|
||||||
checkEqualString(t, "foo", token)
|
checkEqualString(t, "foo", token)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "custom extraHeaders",
|
||||||
|
vmFn: func() *VMStorage {
|
||||||
|
return &VMStorage{extraHeaders: []Header{
|
||||||
|
{Key: "Foo", Value: "bar"},
|
||||||
|
{Key: "Baz", Value: "qux"},
|
||||||
|
}}
|
||||||
|
},
|
||||||
|
checkFn: func(t *testing.T, r *http.Request) {
|
||||||
|
h1 := r.Header.Get("Foo")
|
||||||
|
checkEqualString(t, "bar", h1)
|
||||||
|
h2 := r.Header.Get("Baz")
|
||||||
|
checkEqualString(t, "qux", h2)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom header overrides basic auth",
|
||||||
|
vmFn: func() *VMStorage {
|
||||||
|
cfg, err := utils.AuthConfig(utils.WithBasicAuth("foo", "bar", ""))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error get auth config: %s", err)
|
||||||
|
}
|
||||||
|
return &VMStorage{
|
||||||
|
authCfg: cfg,
|
||||||
|
extraHeaders: []Header{
|
||||||
|
{Key: "Authorization", Value: "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="},
|
||||||
|
}}
|
||||||
|
},
|
||||||
|
checkFn: func(t *testing.T, r *http.Request) {
|
||||||
|
u, p, _ := r.BasicAuth()
|
||||||
|
checkEqualString(t, "Aladdin", u)
|
||||||
|
checkEqualString(t, "open sesame", p)
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range testCases {
|
for _, tt := range testCases {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
|
@ -35,8 +35,9 @@ type Group struct {
|
||||||
Checksum string
|
Checksum string
|
||||||
LastEvaluation time.Time
|
LastEvaluation time.Time
|
||||||
|
|
||||||
Labels map[string]string
|
Labels map[string]string
|
||||||
Params url.Values
|
Params url.Values
|
||||||
|
Headers []datasource.Header
|
||||||
|
|
||||||
doneCh chan struct{}
|
doneCh chan struct{}
|
||||||
finishedCh chan struct{}
|
finishedCh chan struct{}
|
||||||
|
@ -96,6 +97,7 @@ func newGroup(cfg config.Group, qb datasource.QuerierBuilder, defaultInterval ti
|
||||||
Concurrency: cfg.Concurrency,
|
Concurrency: cfg.Concurrency,
|
||||||
Checksum: cfg.Checksum,
|
Checksum: cfg.Checksum,
|
||||||
Params: cfg.Params,
|
Params: cfg.Params,
|
||||||
|
Headers: cfg.Headers,
|
||||||
Labels: cfg.Labels,
|
Labels: cfg.Labels,
|
||||||
|
|
||||||
doneCh: make(chan struct{}),
|
doneCh: make(chan struct{}),
|
||||||
|
@ -217,6 +219,7 @@ func (g *Group) updateWith(newGroup *Group) error {
|
||||||
g.Type = newGroup.Type
|
g.Type = newGroup.Type
|
||||||
g.Concurrency = newGroup.Concurrency
|
g.Concurrency = newGroup.Concurrency
|
||||||
g.Params = newGroup.Params
|
g.Params = newGroup.Params
|
||||||
|
g.Headers = newGroup.Headers
|
||||||
g.Labels = newGroup.Labels
|
g.Labels = newGroup.Labels
|
||||||
g.Limit = newGroup.Limit
|
g.Limit = newGroup.Limit
|
||||||
g.Checksum = newGroup.Checksum
|
g.Checksum = newGroup.Checksum
|
||||||
|
|
|
@ -170,6 +170,7 @@ func (g *Group) toAPI() APIGroup {
|
||||||
LastEvaluation: g.LastEvaluation,
|
LastEvaluation: g.LastEvaluation,
|
||||||
Concurrency: g.Concurrency,
|
Concurrency: g.Concurrency,
|
||||||
Params: urlValuesToStrings(g.Params),
|
Params: urlValuesToStrings(g.Params),
|
||||||
|
Headers: headersToStrings(g.Headers),
|
||||||
Labels: g.Labels,
|
Labels: g.Labels,
|
||||||
}
|
}
|
||||||
for _, r := range g.Rules {
|
for _, r := range g.Rules {
|
||||||
|
@ -198,3 +199,14 @@ func urlValuesToStrings(values url.Values) []string {
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func headersToStrings(headers []datasource.Header) []string {
|
||||||
|
if len(headers) < 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var res []string
|
||||||
|
for _, h := range headers {
|
||||||
|
res = append(res, fmt.Sprintf("%s: %s", h.Key, h.Value))
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
|
@ -73,6 +73,7 @@ func newRecordingRule(qb datasource.QuerierBuilder, group *Group, cfg config.Rul
|
||||||
DataSourceType: &group.Type,
|
DataSourceType: &group.Type,
|
||||||
EvaluationInterval: group.Interval,
|
EvaluationInterval: group.Interval,
|
||||||
QueryParams: group.Params,
|
QueryParams: group.Params,
|
||||||
|
Headers: group.Headers,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,13 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if len(g.Headers) > 0 %}
|
||||||
|
<div class="fs-6 fw-lighter">Extra headers
|
||||||
|
{% for _, header := range g.Headers %}
|
||||||
|
<span class="float-left badge bg-primary">{%s header %}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse" id="rules-{%s g.ID %}">
|
<div class="collapse" id="rules-{%s g.ID %}">
|
||||||
<table class="table table-striped table-hover table-sm">
|
<table class="table table-striped table-hover table-sm">
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -70,6 +70,8 @@ type APIGroup struct {
|
||||||
Concurrency int `json:"concurrency"`
|
Concurrency int `json:"concurrency"`
|
||||||
// Params contains HTTP URL parameters added to each Rule's request
|
// Params contains HTTP URL parameters added to each Rule's request
|
||||||
Params []string `json:"params,omitempty"`
|
Params []string `json:"params,omitempty"`
|
||||||
|
// Headers contains HTTP headers added to each Rule's request
|
||||||
|
Headers []string `json:"headers,omitempty"`
|
||||||
// Labels is a set of label value pairs, that will be added to every rule.
|
// Labels is a set of label value pairs, that will be added to every rule.
|
||||||
Labels map[string]string `json:"labels,omitempty"`
|
Labels map[string]string `json:"labels,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ The following tip changes can be tested by building VictoriaMetrics components f
|
||||||
|
|
||||||
## tip
|
## tip
|
||||||
|
|
||||||
* FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert.html): allow configuring additional headers for `datasource.url`, `remoteWrite.url` and `remoteRead.url` URLs. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2860) for details.
|
* FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert.html): allow configuring additional headers for `datasource.url`, `remoteWrite.url` and `remoteRead.url` URLs. Headers also can be set on group level via `headers` param. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2860) for details.
|
||||||
* FEATURE: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): execute left and right sides of certain operations in parallel. For example, `q1 or q2`, `aggr_func(q1) <op> q2`, `q1 <op> aggr_func(q1)`. This may improve query performance if VictoriaMetrics has enough free resources for parallel processing of both sides of the operation. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2886).
|
* FEATURE: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): execute left and right sides of certain operations in parallel. For example, `q1 or q2`, `aggr_func(q1) <op> q2`, `q1 <op> aggr_func(q1)`. This may improve query performance if VictoriaMetrics has enough free resources for parallel processing of both sides of the operation. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2886).
|
||||||
* FEATURE: [vmauth](https://docs.victoriametrics.com/vmagent.html): allow duplicate username records with different passwords at configuration file. It should allow password rotation without username change.
|
* FEATURE: [vmauth](https://docs.victoriametrics.com/vmagent.html): allow duplicate username records with different passwords at configuration file. It should allow password rotation without username change.
|
||||||
|
|
||||||
|
|
|
@ -129,6 +129,16 @@ name: <string>
|
||||||
params:
|
params:
|
||||||
[ <string>: [<string>, ...]]
|
[ <string>: [<string>, ...]]
|
||||||
|
|
||||||
|
# Optional list of HTTP headers in form `header-name: value`
|
||||||
|
# applied for all rules requests within a group
|
||||||
|
# For example:
|
||||||
|
# headers:
|
||||||
|
# - "CustomHeader: foo"
|
||||||
|
# - "CustomHeader2: bar"
|
||||||
|
# Headers set via this param have priority over headers set via `-datasource.headers` flag.
|
||||||
|
headers:
|
||||||
|
[ <string>, ...]
|
||||||
|
|
||||||
# Optional list of labels added to every rule within a group.
|
# Optional list of labels added to every rule within a group.
|
||||||
# It has priority over the external labels.
|
# It has priority over the external labels.
|
||||||
# Labels are commonly used for adding environment
|
# Labels are commonly used for adding environment
|
||||||
|
|
|
@ -308,7 +308,7 @@ func (ac *Config) HeadersNoAuthString() string {
|
||||||
return strings.Join(a, "")
|
return strings.Join(a, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHeaders sets the configuted ac headers to req.
|
// SetHeaders sets the configured ac headers to req.
|
||||||
func (ac *Config) SetHeaders(req *http.Request, setAuthHeader bool) {
|
func (ac *Config) SetHeaders(req *http.Request, setAuthHeader bool) {
|
||||||
reqHeaders := req.Header
|
reqHeaders := req.Header
|
||||||
for _, h := range ac.headers {
|
for _, h := range ac.headers {
|
||||||
|
|
Loading…
Reference in a new issue