mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
app/vmauth: allow using regexps in url_map
paths
See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1112
This commit is contained in:
parent
5807ff57f3
commit
26cb6f8861
7 changed files with 112 additions and 26 deletions
|
@ -65,13 +65,13 @@ users:
|
|||
|
||||
|
||||
# A single user for querying and inserting data:
|
||||
# - Requests to http://vmauth:8427/api/v1/query or http://vmauth:8427/api/v1/query_range
|
||||
# are routed to http://vmselect:8481/select/42/prometheus.
|
||||
# - Requests to http://vmauth:8427/api/v1/query, http://vmauth:8427/api/v1/query_range
|
||||
# and http://vmauth:8427/api/v1/label/<label_name>/values are routed to http://vmselect:8481/select/42/prometheus.
|
||||
# For example, http://vmauth:8427/api/v1/query is routed to http://vmselect:8480/select/42/prometheus/api/v1/query
|
||||
# - Requests to http://vmauth:8427/api/v1/write are routed to http://vminsert:8480/insert/42/prometheus/api/v1/write
|
||||
- username: "foobar"
|
||||
url_map:
|
||||
- src_paths: ["/api/v1/query", "/api/v1/query_range"]
|
||||
- src_paths: ["/api/v1/query", "/api/v1/query_range", "/api/v1/label/[^/]+/values"]
|
||||
url_prefix: "http://vmselect:8481/select/42/prometheus"
|
||||
- src_paths: ["/api/v1/write"]
|
||||
url_prefix: "http://vminsert:8480/insert/42/prometheus"
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
@ -38,10 +39,49 @@ type UserInfo struct {
|
|||
|
||||
// URLMap is a mapping from source paths to target urls.
|
||||
type URLMap struct {
|
||||
SrcPaths []string `yaml:"src_paths"`
|
||||
SrcPaths []*SrcPath `yaml:"src_paths"`
|
||||
URLPrefix string `yaml:"url_prefix"`
|
||||
}
|
||||
|
||||
// SrcPath represents an src path
|
||||
type SrcPath struct {
|
||||
sOriginal string
|
||||
re *regexp.Regexp
|
||||
}
|
||||
|
||||
func (sp *SrcPath) match(s string) bool {
|
||||
prefix, ok := sp.re.LiteralPrefix()
|
||||
if ok {
|
||||
// Fast path - literal match
|
||||
return s == prefix
|
||||
}
|
||||
if !strings.HasPrefix(s, prefix) {
|
||||
return false
|
||||
}
|
||||
return sp.re.MatchString(s)
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler
|
||||
func (sp *SrcPath) UnmarshalYAML(f func(interface{}) error) error {
|
||||
var s string
|
||||
if err := f(&s); err != nil {
|
||||
return err
|
||||
}
|
||||
sAnchored := "^(?:" + s + ")$"
|
||||
re, err := regexp.Compile(sAnchored)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot build regexp from %q: %w", s, err)
|
||||
}
|
||||
sp.sOriginal = s
|
||||
sp.re = re
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalYAML implements yaml.Marshaler.
|
||||
func (sp *SrcPath) MarshalYAML() (interface{}, error) {
|
||||
return sp.sOriginal, nil
|
||||
}
|
||||
|
||||
func initAuthConfig() {
|
||||
if len(*authConfigPath) == 0 {
|
||||
logger.Fatalf("missing required `-auth.config` command-line flag")
|
||||
|
@ -127,11 +167,6 @@ func parseAuthConfig(data []byte) (map[string]*UserInfo, error) {
|
|||
if len(e.SrcPaths) == 0 {
|
||||
return nil, fmt.Errorf("missing `src_paths`")
|
||||
}
|
||||
for _, path := range e.SrcPaths {
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
return nil, fmt.Errorf("`src_path`=%q must start with `/`", path)
|
||||
}
|
||||
}
|
||||
urlPrefix, err := sanitizeURLPrefix(e.URLPrefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func TestParseAuthConfigFailure(t *testing.T) {
|
||||
|
@ -79,12 +83,12 @@ users:
|
|||
- url_prefix: http://foobar
|
||||
`)
|
||||
|
||||
// src_path not starting with `/`
|
||||
// Invalid regexp in src_path.
|
||||
f(`
|
||||
users:
|
||||
- username: a
|
||||
url_map:
|
||||
- src_paths: [foobar]
|
||||
- src_paths: ['fo[obar']
|
||||
url_prefix: http://foobar
|
||||
`)
|
||||
}
|
||||
|
@ -97,8 +101,8 @@ func TestParseAuthConfigSuccess(t *testing.T) {
|
|||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
removeMetrics(m)
|
||||
if !reflect.DeepEqual(m, expectedAuthConfig) {
|
||||
t.Fatalf("unexpected auth config\ngot\n%v\nwant\n%v", m, expectedAuthConfig)
|
||||
if err := areEqualConfigs(m, expectedAuthConfig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,7 +143,7 @@ users:
|
|||
users:
|
||||
- username: foo
|
||||
url_map:
|
||||
- src_paths: ["/api/v1/query","/api/v1/query_range"]
|
||||
- src_paths: ["/api/v1/query","/api/v1/query_range","/api/v1/label/[^./]+/.+"]
|
||||
url_prefix: http://vmselect/select/0/prometheus
|
||||
- src_paths: ["/api/v1/write"]
|
||||
url_prefix: http://vminsert/insert/0/prometheus
|
||||
|
@ -148,11 +152,11 @@ users:
|
|||
Username: "foo",
|
||||
URLMap: []URLMap{
|
||||
{
|
||||
SrcPaths: []string{"/api/v1/query", "/api/v1/query_range"},
|
||||
SrcPaths: getSrcPaths([]string{"/api/v1/query", "/api/v1/query_range", "/api/v1/label/[^./]+/.+"}),
|
||||
URLPrefix: "http://vmselect/select/0/prometheus",
|
||||
},
|
||||
{
|
||||
SrcPaths: []string{"/api/v1/write"},
|
||||
SrcPaths: getSrcPaths([]string{"/api/v1/write"}),
|
||||
URLPrefix: "http://vminsert/insert/0/prometheus",
|
||||
},
|
||||
},
|
||||
|
@ -160,8 +164,34 @@ users:
|
|||
})
|
||||
}
|
||||
|
||||
func getSrcPaths(paths []string) []*SrcPath {
|
||||
var sps []*SrcPath
|
||||
for _, path := range paths {
|
||||
sps = append(sps, &SrcPath{
|
||||
sOriginal: path,
|
||||
re: regexp.MustCompile("^(?:" + path + ")$"),
|
||||
})
|
||||
}
|
||||
return sps
|
||||
}
|
||||
|
||||
func removeMetrics(m map[string]*UserInfo) {
|
||||
for _, info := range m {
|
||||
info.requests = nil
|
||||
}
|
||||
}
|
||||
|
||||
func areEqualConfigs(a, b map[string]*UserInfo) error {
|
||||
aData, err := yaml.Marshal(a)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot marshal a: %w", err)
|
||||
}
|
||||
bData, err := yaml.Marshal(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot marshal b: %w", err)
|
||||
}
|
||||
if !bytes.Equal(aData, bData) {
|
||||
return fmt.Errorf("unexpected configs;\ngot\n%s\nwant\n%s", aData, bData)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@ func createTargetURL(ui *UserInfo, uOrig *url.URL) (string, error) {
|
|||
u.Path = "/" + u.Path
|
||||
}
|
||||
for _, e := range ui.URLMap {
|
||||
for _, path := range e.SrcPaths {
|
||||
if u.Path == path {
|
||||
for _, sp := range e.SrcPaths {
|
||||
if sp.match(u.Path) {
|
||||
return e.URLPrefix + u.RequestURI(), nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,11 +44,11 @@ func TestCreateTargetURLSuccess(t *testing.T) {
|
|||
ui := &UserInfo{
|
||||
URLMap: []URLMap{
|
||||
{
|
||||
SrcPaths: []string{"/api/v1/query"},
|
||||
SrcPaths: getSrcPaths([]string{"/api/v1/query"}),
|
||||
URLPrefix: "http://vmselect/0/prometheus",
|
||||
},
|
||||
{
|
||||
SrcPaths: []string{"/api/v1/write"},
|
||||
SrcPaths: getSrcPaths([]string{"/api/v1/write"}),
|
||||
URLPrefix: "http://vminsert/0/prometheus",
|
||||
},
|
||||
},
|
||||
|
@ -57,6 +57,26 @@ func TestCreateTargetURLSuccess(t *testing.T) {
|
|||
f(ui, "/api/v1/query?query=up", "http://vmselect/0/prometheus/api/v1/query?query=up")
|
||||
f(ui, "/api/v1/write", "http://vminsert/0/prometheus/api/v1/write")
|
||||
f(ui, "/api/v1/query_range", "http://default-server/api/v1/query_range")
|
||||
|
||||
// Complex routing regexp paths in `url_map`
|
||||
ui = &UserInfo{
|
||||
URLMap: []URLMap{
|
||||
{
|
||||
SrcPaths: getSrcPaths([]string{"/api/v1/query(_range)?", "/api/v1/label/[^/]+/values"}),
|
||||
URLPrefix: "http://vmselect/0/prometheus",
|
||||
},
|
||||
{
|
||||
SrcPaths: getSrcPaths([]string{"/api/v1/write"}),
|
||||
URLPrefix: "http://vminsert/0/prometheus",
|
||||
},
|
||||
},
|
||||
URLPrefix: "http://default-server",
|
||||
}
|
||||
f(ui, "/api/v1/query?query=up", "http://vmselect/0/prometheus/api/v1/query?query=up")
|
||||
f(ui, "/api/v1/query_range?query=up", "http://vmselect/0/prometheus/api/v1/query_range?query=up")
|
||||
f(ui, "/api/v1/label/foo/values", "http://vmselect/0/prometheus/api/v1/label/foo/values")
|
||||
f(ui, "/api/v1/write", "http://vminsert/0/prometheus/api/v1/write")
|
||||
f(ui, "/api/v1/foo/bar", "http://default-server/api/v1/foo/bar")
|
||||
}
|
||||
|
||||
func TestCreateTargetURLFailure(t *testing.T) {
|
||||
|
@ -78,7 +98,7 @@ func TestCreateTargetURLFailure(t *testing.T) {
|
|||
f(&UserInfo{
|
||||
URLMap: []URLMap{
|
||||
{
|
||||
SrcPaths: []string{"/api/v1/query"},
|
||||
SrcPaths: getSrcPaths([]string{"/api/v1/query"}),
|
||||
URLPrefix: "http://foobar/baz",
|
||||
},
|
||||
},
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
- `histogram_stdvar(buckets)` - returns standard variance for the given buckets.
|
||||
- `histogram_stddev(buckets)` - returns standard deviation for the given buckets.
|
||||
* FEATURE: vmagent: add ability to replicate scrape targets among `vmagent` instances in the cluster with `-promscrape.cluster.replicationFactor` command-line flag. See [these docs](https://victoriametrics.github.io/vmagent.html#scraping-big-number-of-targets).
|
||||
* FEATURE: vmauth: allow using regexp paths in `url_map`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1112) for details.
|
||||
|
||||
* BUGFIX: vmagent: reduce memory usage when Kubernetes service discovery is used in big number of distinct jobs by sharing the cache. See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1113
|
||||
|
||||
|
|
|
@ -65,13 +65,13 @@ users:
|
|||
|
||||
|
||||
# A single user for querying and inserting data:
|
||||
# - Requests to http://vmauth:8427/api/v1/query or http://vmauth:8427/api/v1/query_range
|
||||
# are routed to http://vmselect:8481/select/42/prometheus.
|
||||
# - Requests to http://vmauth:8427/api/v1/query, http://vmauth:8427/api/v1/query_range
|
||||
# and http://vmauth:8427/api/v1/label/<label_name>/values are routed to http://vmselect:8481/select/42/prometheus.
|
||||
# For example, http://vmauth:8427/api/v1/query is routed to http://vmselect:8480/select/42/prometheus/api/v1/query
|
||||
# - Requests to http://vmauth:8427/api/v1/write are routed to http://vminsert:8480/insert/42/prometheus/api/v1/write
|
||||
- username: "foobar"
|
||||
url_map:
|
||||
- src_paths: ["/api/v1/query", "/api/v1/query_range"]
|
||||
- src_paths: ["/api/v1/query", "/api/v1/query_range", "/api/v1/label/[^/]+/values"]
|
||||
url_prefix: "http://vmselect:8481/select/42/prometheus"
|
||||
- src_paths: ["/api/v1/write"]
|
||||
url_prefix: "http://vminsert:8480/insert/42/prometheus"
|
||||
|
|
Loading…
Reference in a new issue