mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
app/{vminsert,vmagent}: hide passwords and auth tokens by default at /config
page
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1764
This commit is contained in:
parent
bc72b83102
commit
847004fa77
16 changed files with 211 additions and 109 deletions
|
@ -165,7 +165,7 @@ func getAuthConfig(argIdx int) (*promauth.Config, error) {
|
||||||
if username != "" || password != "" || passwordFile != "" {
|
if username != "" || password != "" || passwordFile != "" {
|
||||||
basicAuthCfg = &promauth.BasicAuthConfig{
|
basicAuthCfg = &promauth.BasicAuthConfig{
|
||||||
Username: username,
|
Username: username,
|
||||||
Password: password,
|
Password: promauth.NewSecret(password),
|
||||||
PasswordFile: passwordFile,
|
PasswordFile: passwordFile,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,7 +179,7 @@ func getAuthConfig(argIdx int) (*promauth.Config, error) {
|
||||||
if clientSecretFile != "" || clientSecret != "" {
|
if clientSecretFile != "" || clientSecret != "" {
|
||||||
oauth2Cfg = &promauth.OAuth2Config{
|
oauth2Cfg = &promauth.OAuth2Config{
|
||||||
ClientID: oauth2ClientID.GetOptionalArg(argIdx),
|
ClientID: oauth2ClientID.GetOptionalArg(argIdx),
|
||||||
ClientSecret: clientSecret,
|
ClientSecret: promauth.NewSecret(clientSecret),
|
||||||
ClientSecretFile: clientSecretFile,
|
ClientSecretFile: clientSecretFile,
|
||||||
TokenURL: oauth2TokenURL.GetOptionalArg(argIdx),
|
TokenURL: oauth2TokenURL.GetOptionalArg(argIdx),
|
||||||
Scopes: strings.Split(oauth2Scopes.GetOptionalArg(argIdx), ";"),
|
Scopes: strings.Split(oauth2Scopes.GetOptionalArg(argIdx), ";"),
|
||||||
|
|
|
@ -20,7 +20,7 @@ var (
|
||||||
basicAuthPass = "bar"
|
basicAuthPass = "bar"
|
||||||
baCfg = &promauth.BasicAuthConfig{
|
baCfg = &promauth.BasicAuthConfig{
|
||||||
Username: basicAuthName,
|
Username: basicAuthName,
|
||||||
Password: basicAuthPass,
|
Password: promauth.NewSecret(basicAuthPass),
|
||||||
}
|
}
|
||||||
query = "vm_rows"
|
query = "vm_rows"
|
||||||
queryRender = "constantLine(10)"
|
queryRender = "constantLine(10)"
|
||||||
|
|
|
@ -10,7 +10,7 @@ func AuthConfig(baUser, baPass, baFile, bearerToken, bearerTokenFile string) (*p
|
||||||
if baUser != "" || baPass != "" || baFile != "" {
|
if baUser != "" || baPass != "" || baFile != "" {
|
||||||
baCfg = &promauth.BasicAuthConfig{
|
baCfg = &promauth.BasicAuthConfig{
|
||||||
Username: baUser,
|
Username: baUser,
|
||||||
Password: baPass,
|
Password: promauth.NewSecret(baPass),
|
||||||
PasswordFile: baFile,
|
PasswordFile: baFile,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,8 @@ sort: 15
|
||||||
* FEATURE: vmalert: allow groups with empty rules list like Prometheus does. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/1742).
|
* FEATURE: vmalert: allow groups with empty rules list like Prometheus does. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/1742).
|
||||||
* FEATURE: vmagent: add `collapse` and `expand` buttons per each group of targets with the same `job_name` at `http://vmagent:8429/targets` page.
|
* FEATURE: vmagent: add `collapse` and `expand` buttons per each group of targets with the same `job_name` at `http://vmagent:8429/targets` page.
|
||||||
* FEATURE: automatically detect timestamp precision (ns, us, ms or s) for the data ingested into VictoriaMetrics via [InfluxDB line protocol](https://docs.victoriametrics.com/#how-to-send-data-from-influxdb-compatible-agents-such-as-telegraf).
|
* FEATURE: automatically detect timestamp precision (ns, us, ms or s) for the data ingested into VictoriaMetrics via [InfluxDB line protocol](https://docs.victoriametrics.com/#how-to-send-data-from-influxdb-compatible-agents-such-as-telegraf).
|
||||||
* FEATURE: vmagent: add ability to protect `/config` page with auth key via `-configAuthKey` command-line flag. This page may contain sensitive information such as passwords, so it may be good to restrict access to this page. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1764).
|
* FEATURE: vmagent: add ability to protect `/config` page with auth key via `-configAuthKey` command-line flag. This page may contain sensitive config information, so it may be good to restrict access to this page. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1764).
|
||||||
|
* FEATURE: vmagent: hide passwords and auth tokens at `/config` page like Prometheus does. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1764).
|
||||||
* FEATURE: vmagent: add `-promscrape.maxResponseHeadersSize` command-line flag for tuning the maximum HTTP response headers size for Prometheus scrape targets.
|
* FEATURE: vmagent: add `-promscrape.maxResponseHeadersSize` command-line flag for tuning the maximum HTTP response headers size for Prometheus scrape targets.
|
||||||
* FEATURE: vmagent: send data to multiple configured remote storage systems in parallel (e.g. when multiple `-remoteWrite.url` flag values are specified). This should improve data ingestion speed.
|
* FEATURE: vmagent: send data to multiple configured remote storage systems in parallel (e.g. when multiple `-remoteWrite.url` flag values are specified). This should improve data ingestion speed.
|
||||||
* FEATURE: vmagent: add `-remoteWrite.maxRowsPerBlock` command-line flag for tuning the number of samples to send to remote storage per each block. Bigger values may improve data ingestion performance at the cost of higher memory usage.
|
* FEATURE: vmagent: add `-remoteWrite.maxRowsPerBlock` command-line flag for tuning the number of samples to send to remote storage per each block. Bigger values may improve data ingestion performance at the cost of higher memory usage.
|
||||||
|
|
|
@ -17,6 +17,51 @@ import (
|
||||||
"golang.org/x/oauth2/clientcredentials"
|
"golang.org/x/oauth2/clientcredentials"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Secret represents a string secret such as password or auth token.
|
||||||
|
//
|
||||||
|
// It is marshaled to "<secret>" string in yaml.
|
||||||
|
//
|
||||||
|
// This is needed for hiding secret strings in /config page output.
|
||||||
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1764
|
||||||
|
type Secret struct {
|
||||||
|
s string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSecret returns new secret for s.
|
||||||
|
func NewSecret(s string) *Secret {
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Secret{
|
||||||
|
s: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalYAML implements yaml.Marshaler interface.
|
||||||
|
//
|
||||||
|
// It substitutes the secret with "<secret>" string.
|
||||||
|
func (s *Secret) MarshalYAML() (interface{}, error) {
|
||||||
|
return "<secret>", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML implements yaml.Unmarshaler interface.
|
||||||
|
func (s *Secret) UnmarshalYAML(f func(interface{}) error) error {
|
||||||
|
var secret string
|
||||||
|
if err := f(&secret); err != nil {
|
||||||
|
return fmt.Errorf("cannot parse secret: %w", err)
|
||||||
|
}
|
||||||
|
s.s = secret
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the secret in plaintext.
|
||||||
|
func (s *Secret) String() string {
|
||||||
|
if s == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return s.s
|
||||||
|
}
|
||||||
|
|
||||||
// TLSConfig represents TLS config.
|
// TLSConfig represents TLS config.
|
||||||
//
|
//
|
||||||
// See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#tls_config
|
// See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#tls_config
|
||||||
|
@ -32,23 +77,23 @@ type TLSConfig struct {
|
||||||
//
|
//
|
||||||
// See https://prometheus.io/docs/prometheus/latest/configuration/configuration/
|
// See https://prometheus.io/docs/prometheus/latest/configuration/configuration/
|
||||||
type Authorization struct {
|
type Authorization struct {
|
||||||
Type string `yaml:"type,omitempty"`
|
Type string `yaml:"type,omitempty"`
|
||||||
Credentials string `yaml:"credentials,omitempty"`
|
Credentials *Secret `yaml:"credentials,omitempty"`
|
||||||
CredentialsFile string `yaml:"credentials_file,omitempty"`
|
CredentialsFile string `yaml:"credentials_file,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BasicAuthConfig represents basic auth config.
|
// BasicAuthConfig represents basic auth config.
|
||||||
type BasicAuthConfig struct {
|
type BasicAuthConfig struct {
|
||||||
Username string `yaml:"username"`
|
Username string `yaml:"username"`
|
||||||
Password string `yaml:"password,omitempty"`
|
Password *Secret `yaml:"password,omitempty"`
|
||||||
PasswordFile string `yaml:"password_file,omitempty"`
|
PasswordFile string `yaml:"password_file,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPClientConfig represents http client config.
|
// HTTPClientConfig represents http client config.
|
||||||
type HTTPClientConfig struct {
|
type HTTPClientConfig struct {
|
||||||
Authorization *Authorization `yaml:"authorization,omitempty"`
|
Authorization *Authorization `yaml:"authorization,omitempty"`
|
||||||
BasicAuth *BasicAuthConfig `yaml:"basic_auth,omitempty"`
|
BasicAuth *BasicAuthConfig `yaml:"basic_auth,omitempty"`
|
||||||
BearerToken string `yaml:"bearer_token,omitempty"`
|
BearerToken *Secret `yaml:"bearer_token,omitempty"`
|
||||||
BearerTokenFile string `yaml:"bearer_token_file,omitempty"`
|
BearerTokenFile string `yaml:"bearer_token_file,omitempty"`
|
||||||
OAuth2 *OAuth2Config `yaml:"oauth2,omitempty"`
|
OAuth2 *OAuth2Config `yaml:"oauth2,omitempty"`
|
||||||
TLSConfig *TLSConfig `yaml:"tls_config,omitempty"`
|
TLSConfig *TLSConfig `yaml:"tls_config,omitempty"`
|
||||||
|
@ -58,7 +103,7 @@ type HTTPClientConfig struct {
|
||||||
type ProxyClientConfig struct {
|
type ProxyClientConfig struct {
|
||||||
Authorization *Authorization `yaml:"proxy_authorization,omitempty"`
|
Authorization *Authorization `yaml:"proxy_authorization,omitempty"`
|
||||||
BasicAuth *BasicAuthConfig `yaml:"proxy_basic_auth,omitempty"`
|
BasicAuth *BasicAuthConfig `yaml:"proxy_basic_auth,omitempty"`
|
||||||
BearerToken string `yaml:"proxy_bearer_token,omitempty"`
|
BearerToken *Secret `yaml:"proxy_bearer_token,omitempty"`
|
||||||
BearerTokenFile string `yaml:"proxy_bearer_token_file,omitempty"`
|
BearerTokenFile string `yaml:"proxy_bearer_token_file,omitempty"`
|
||||||
TLSConfig *TLSConfig `yaml:"proxy_tls_config,omitempty"`
|
TLSConfig *TLSConfig `yaml:"proxy_tls_config,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -66,7 +111,7 @@ type ProxyClientConfig struct {
|
||||||
// OAuth2Config represent OAuth2 configuration
|
// OAuth2Config represent OAuth2 configuration
|
||||||
type OAuth2Config struct {
|
type OAuth2Config struct {
|
||||||
ClientID string `yaml:"client_id"`
|
ClientID string `yaml:"client_id"`
|
||||||
ClientSecret string `yaml:"client_secret,omitempty"`
|
ClientSecret *Secret `yaml:"client_secret,omitempty"`
|
||||||
ClientSecretFile string `yaml:"client_secret_file,omitempty"`
|
ClientSecretFile string `yaml:"client_secret_file,omitempty"`
|
||||||
Scopes []string `yaml:"scopes,omitempty"`
|
Scopes []string `yaml:"scopes,omitempty"`
|
||||||
TokenURL string `yaml:"token_url"`
|
TokenURL string `yaml:"token_url"`
|
||||||
|
@ -83,10 +128,10 @@ func (o *OAuth2Config) validate() error {
|
||||||
if o.ClientID == "" {
|
if o.ClientID == "" {
|
||||||
return fmt.Errorf("client_id cannot be empty")
|
return fmt.Errorf("client_id cannot be empty")
|
||||||
}
|
}
|
||||||
if o.ClientSecret == "" && o.ClientSecretFile == "" {
|
if o.ClientSecret == nil && o.ClientSecretFile == "" {
|
||||||
return fmt.Errorf("ClientSecret or ClientSecretFile must be set")
|
return fmt.Errorf("ClientSecret or ClientSecretFile must be set")
|
||||||
}
|
}
|
||||||
if o.ClientSecret != "" && o.ClientSecretFile != "" {
|
if o.ClientSecret != nil && o.ClientSecretFile != "" {
|
||||||
return fmt.Errorf("ClientSecret and ClientSecretFile cannot be set simultaneously")
|
return fmt.Errorf("ClientSecret and ClientSecretFile cannot be set simultaneously")
|
||||||
}
|
}
|
||||||
if o.TokenURL == "" {
|
if o.TokenURL == "" {
|
||||||
|
@ -109,7 +154,7 @@ func newOAuth2ConfigInternal(baseDir string, o *OAuth2Config) (*oauth2ConfigInte
|
||||||
oi := &oauth2ConfigInternal{
|
oi := &oauth2ConfigInternal{
|
||||||
cfg: &clientcredentials.Config{
|
cfg: &clientcredentials.Config{
|
||||||
ClientID: o.ClientID,
|
ClientID: o.ClientID,
|
||||||
ClientSecret: o.ClientSecret,
|
ClientSecret: o.ClientSecret.String(),
|
||||||
TokenURL: o.TokenURL,
|
TokenURL: o.TokenURL,
|
||||||
Scopes: o.Scopes,
|
Scopes: o.Scopes,
|
||||||
EndpointParams: urlValuesFromMap(o.EndpointParams),
|
EndpointParams: urlValuesFromMap(o.EndpointParams),
|
||||||
|
@ -238,12 +283,12 @@ func (ac *Config) NewTLSConfig() *tls.Config {
|
||||||
|
|
||||||
// NewConfig creates auth config for the given hcc.
|
// NewConfig creates auth config for the given hcc.
|
||||||
func (hcc *HTTPClientConfig) NewConfig(baseDir string) (*Config, error) {
|
func (hcc *HTTPClientConfig) NewConfig(baseDir string) (*Config, error) {
|
||||||
return NewConfig(baseDir, hcc.Authorization, hcc.BasicAuth, hcc.BearerToken, hcc.BearerTokenFile, hcc.OAuth2, hcc.TLSConfig)
|
return NewConfig(baseDir, hcc.Authorization, hcc.BasicAuth, hcc.BearerToken.String(), hcc.BearerTokenFile, hcc.OAuth2, hcc.TLSConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfig creates auth config for the given pcc.
|
// NewConfig creates auth config for the given pcc.
|
||||||
func (pcc *ProxyClientConfig) NewConfig(baseDir string) (*Config, error) {
|
func (pcc *ProxyClientConfig) NewConfig(baseDir string) (*Config, error) {
|
||||||
return NewConfig(baseDir, pcc.Authorization, pcc.BasicAuth, pcc.BearerToken, pcc.BearerTokenFile, nil, pcc.TLSConfig)
|
return NewConfig(baseDir, pcc.Authorization, pcc.BasicAuth, pcc.BearerToken.String(), pcc.BearerTokenFile, nil, pcc.TLSConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfig creates auth config from the given args.
|
// NewConfig creates auth config from the given args.
|
||||||
|
@ -256,7 +301,7 @@ func NewConfig(baseDir string, az *Authorization, basicAuth *BasicAuthConfig, be
|
||||||
azType = az.Type
|
azType = az.Type
|
||||||
}
|
}
|
||||||
if az.CredentialsFile != "" {
|
if az.CredentialsFile != "" {
|
||||||
if az.Credentials != "" {
|
if az.Credentials != nil {
|
||||||
return nil, fmt.Errorf("both `credentials`=%q and `credentials_file`=%q are set", az.Credentials, az.CredentialsFile)
|
return nil, fmt.Errorf("both `credentials`=%q and `credentials_file`=%q are set", az.Credentials, az.CredentialsFile)
|
||||||
}
|
}
|
||||||
filePath := getFilepath(baseDir, az.CredentialsFile)
|
filePath := getFilepath(baseDir, az.CredentialsFile)
|
||||||
|
@ -271,7 +316,7 @@ func NewConfig(baseDir string, az *Authorization, basicAuth *BasicAuthConfig, be
|
||||||
authDigest = fmt.Sprintf("custom(type=%q, credsFile=%q)", az.Type, filePath)
|
authDigest = fmt.Sprintf("custom(type=%q, credsFile=%q)", az.Type, filePath)
|
||||||
} else {
|
} else {
|
||||||
getAuthHeader = func() string {
|
getAuthHeader = func() string {
|
||||||
return azType + " " + az.Credentials
|
return azType + " " + az.Credentials.String()
|
||||||
}
|
}
|
||||||
authDigest = fmt.Sprintf("custom(type=%q, creds=%q)", az.Type, az.Credentials)
|
authDigest = fmt.Sprintf("custom(type=%q, creds=%q)", az.Type, az.Credentials)
|
||||||
}
|
}
|
||||||
|
@ -284,7 +329,7 @@ func NewConfig(baseDir string, az *Authorization, basicAuth *BasicAuthConfig, be
|
||||||
return nil, fmt.Errorf("missing `username` in `basic_auth` section")
|
return nil, fmt.Errorf("missing `username` in `basic_auth` section")
|
||||||
}
|
}
|
||||||
if basicAuth.PasswordFile != "" {
|
if basicAuth.PasswordFile != "" {
|
||||||
if basicAuth.Password != "" {
|
if basicAuth.Password != nil {
|
||||||
return nil, fmt.Errorf("both `password`=%q and `password_file`=%q are set in `basic_auth` section", basicAuth.Password, basicAuth.PasswordFile)
|
return nil, fmt.Errorf("both `password`=%q and `password_file`=%q are set in `basic_auth` section", basicAuth.Password, basicAuth.PasswordFile)
|
||||||
}
|
}
|
||||||
filePath := getFilepath(baseDir, basicAuth.PasswordFile)
|
filePath := getFilepath(baseDir, basicAuth.PasswordFile)
|
||||||
|
@ -303,7 +348,7 @@ func NewConfig(baseDir string, az *Authorization, basicAuth *BasicAuthConfig, be
|
||||||
} else {
|
} else {
|
||||||
getAuthHeader = func() string {
|
getAuthHeader = func() string {
|
||||||
// See https://en.wikipedia.org/wiki/Basic_access_authentication
|
// See https://en.wikipedia.org/wiki/Basic_access_authentication
|
||||||
token := basicAuth.Username + ":" + basicAuth.Password
|
token := basicAuth.Username + ":" + basicAuth.Password.String()
|
||||||
token64 := base64.StdEncoding.EncodeToString([]byte(token))
|
token64 := base64.StdEncoding.EncodeToString([]byte(token))
|
||||||
return "Basic " + token64
|
return "Basic " + token64
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ func TestNewConfig(t *testing.T) {
|
||||||
args: args{
|
args: args{
|
||||||
oauth: &OAuth2Config{
|
oauth: &OAuth2Config{
|
||||||
ClientID: "some-id",
|
ClientID: "some-id",
|
||||||
ClientSecret: "some-secret",
|
ClientSecret: NewSecret("some-secret"),
|
||||||
TokenURL: "http://localhost:8511",
|
TokenURL: "http://localhost:8511",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -49,7 +49,7 @@ func TestNewConfig(t *testing.T) {
|
||||||
args: args{
|
args: args{
|
||||||
oauth: &OAuth2Config{
|
oauth: &OAuth2Config{
|
||||||
ClientID: "some-id",
|
ClientID: "some-id",
|
||||||
ClientSecret: "some-secret",
|
ClientSecret: NewSecret("some-secret"),
|
||||||
ClientSecretFile: "testdata/test_secretfile.txt",
|
ClientSecretFile: "testdata/test_secretfile.txt",
|
||||||
TokenURL: "http://localhost:8511",
|
TokenURL: "http://localhost:8511",
|
||||||
},
|
},
|
||||||
|
@ -61,7 +61,7 @@ func TestNewConfig(t *testing.T) {
|
||||||
args: args{
|
args: args{
|
||||||
basicAuth: &BasicAuthConfig{
|
basicAuth: &BasicAuthConfig{
|
||||||
Username: "user",
|
Username: "user",
|
||||||
Password: "password",
|
Password: NewSecret("password"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectHeader: "Basic dXNlcjpwYXNzd29yZA==",
|
expectHeader: "Basic dXNlcjpwYXNzd29yZA==",
|
||||||
|
@ -81,7 +81,7 @@ func TestNewConfig(t *testing.T) {
|
||||||
args: args{
|
args: args{
|
||||||
az: &Authorization{
|
az: &Authorization{
|
||||||
Type: "Bearer",
|
Type: "Bearer",
|
||||||
Credentials: "Value",
|
Credentials: NewSecret("Value"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectHeader: "Bearer Value",
|
expectHeader: "Bearer Value",
|
||||||
|
|
|
@ -240,27 +240,29 @@ func loadStaticConfigs(path string) ([]StaticConfig, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadConfig loads Prometheus config from the given path.
|
// loadConfig loads Prometheus config from the given path.
|
||||||
func loadConfig(path string) (*Config, error) {
|
func loadConfig(path string) (*Config, []byte, error) {
|
||||||
data, err := ioutil.ReadFile(path)
|
data, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot read Prometheus config from %q: %w", path, err)
|
return nil, nil, fmt.Errorf("cannot read Prometheus config from %q: %w", path, err)
|
||||||
}
|
}
|
||||||
var c Config
|
var c Config
|
||||||
if err := c.parseData(data, path); err != nil {
|
dataNew, err := c.parseData(data, path)
|
||||||
return nil, fmt.Errorf("cannot parse Prometheus config from %q: %w", path, err)
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("cannot parse Prometheus config from %q: %w", path, err)
|
||||||
}
|
}
|
||||||
return &c, nil
|
return &c, dataNew, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadScrapeConfigFiles(baseDir string, scrapeConfigFiles []string) ([]ScrapeConfig, error) {
|
func loadScrapeConfigFiles(baseDir string, scrapeConfigFiles []string) ([]ScrapeConfig, []byte, error) {
|
||||||
var scrapeConfigs []ScrapeConfig
|
var scrapeConfigs []ScrapeConfig
|
||||||
|
var scsData []byte
|
||||||
for _, filePath := range scrapeConfigFiles {
|
for _, filePath := range scrapeConfigFiles {
|
||||||
filePath := getFilepath(baseDir, filePath)
|
filePath := getFilepath(baseDir, filePath)
|
||||||
paths := []string{filePath}
|
paths := []string{filePath}
|
||||||
if strings.Contains(filePath, "*") {
|
if strings.Contains(filePath, "*") {
|
||||||
ps, err := filepath.Glob(filePath)
|
ps, err := filepath.Glob(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid pattern %q in `scrape_config_files`: %w", filePath, err)
|
return nil, nil, fmt.Errorf("invalid pattern %q in `scrape_config_files`: %w", filePath, err)
|
||||||
}
|
}
|
||||||
sort.Strings(ps)
|
sort.Strings(ps)
|
||||||
paths = ps
|
paths = ps
|
||||||
|
@ -268,17 +270,19 @@ func loadScrapeConfigFiles(baseDir string, scrapeConfigFiles []string) ([]Scrape
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
data, err := ioutil.ReadFile(path)
|
data, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot load %q from `scrape_config_files`: %w", filePath, err)
|
return nil, nil, fmt.Errorf("cannot load %q from `scrape_config_files`: %w", filePath, err)
|
||||||
}
|
}
|
||||||
data = envtemplate.Replace(data)
|
data = envtemplate.Replace(data)
|
||||||
var scs []ScrapeConfig
|
var scs []ScrapeConfig
|
||||||
if err = yaml.UnmarshalStrict(data, &scs); err != nil {
|
if err = yaml.UnmarshalStrict(data, &scs); err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse %q from `scrape_config_files`: %w", filePath, err)
|
return nil, nil, fmt.Errorf("cannot parse %q from `scrape_config_files`: %w", filePath, err)
|
||||||
}
|
}
|
||||||
scrapeConfigs = append(scrapeConfigs, scs...)
|
scrapeConfigs = append(scrapeConfigs, scs...)
|
||||||
|
scsData = append(scsData, '\n')
|
||||||
|
scsData = append(scsData, data...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return scrapeConfigs, nil
|
return scrapeConfigs, scsData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDryRun returns true if -promscrape.config.dryRun command-line flag is set
|
// IsDryRun returns true if -promscrape.config.dryRun command-line flag is set
|
||||||
|
@ -286,30 +290,31 @@ func IsDryRun() bool {
|
||||||
return *dryRun
|
return *dryRun
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Config) parseData(data []byte, path string) error {
|
func (cfg *Config) parseData(data []byte, path string) ([]byte, error) {
|
||||||
if err := unmarshalMaybeStrict(data, cfg); err != nil {
|
if err := unmarshalMaybeStrict(data, cfg); err != nil {
|
||||||
return fmt.Errorf("cannot unmarshal data: %w", err)
|
return nil, fmt.Errorf("cannot unmarshal data: %w", err)
|
||||||
}
|
}
|
||||||
absPath, err := filepath.Abs(path)
|
absPath, err := filepath.Abs(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot obtain abs path for %q: %w", path, err)
|
return nil, fmt.Errorf("cannot obtain abs path for %q: %w", path, err)
|
||||||
}
|
}
|
||||||
cfg.baseDir = filepath.Dir(absPath)
|
cfg.baseDir = filepath.Dir(absPath)
|
||||||
|
|
||||||
// Load cfg.ScrapeConfigFiles into c.ScrapeConfigs
|
// Load cfg.ScrapeConfigFiles into c.ScrapeConfigs
|
||||||
scs, err := loadScrapeConfigFiles(cfg.baseDir, cfg.ScrapeConfigFiles)
|
scs, scsData, err := loadScrapeConfigFiles(cfg.baseDir, cfg.ScrapeConfigFiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot load `scrape_config_files` from %q: %w", path, err)
|
return nil, fmt.Errorf("cannot load `scrape_config_files` from %q: %w", path, err)
|
||||||
}
|
}
|
||||||
cfg.ScrapeConfigFiles = nil
|
cfg.ScrapeConfigFiles = nil
|
||||||
cfg.ScrapeConfigs = append(cfg.ScrapeConfigs, scs...)
|
cfg.ScrapeConfigs = append(cfg.ScrapeConfigs, scs...)
|
||||||
|
dataNew := append(data, scsData...)
|
||||||
|
|
||||||
// Check that all the scrape configs have unique JobName
|
// Check that all the scrape configs have unique JobName
|
||||||
m := make(map[string]struct{}, len(cfg.ScrapeConfigs))
|
m := make(map[string]struct{}, len(cfg.ScrapeConfigs))
|
||||||
for i := range cfg.ScrapeConfigs {
|
for i := range cfg.ScrapeConfigs {
|
||||||
jobName := cfg.ScrapeConfigs[i].JobName
|
jobName := cfg.ScrapeConfigs[i].JobName
|
||||||
if _, ok := m[jobName]; ok {
|
if _, ok := m[jobName]; ok {
|
||||||
return fmt.Errorf("duplicate `job_name` in `scrape_configs` loaded from %q: %q", path, jobName)
|
return nil, fmt.Errorf("duplicate `job_name` in `scrape_configs` loaded from %q: %q", path, jobName)
|
||||||
}
|
}
|
||||||
m[jobName] = struct{}{}
|
m[jobName] = struct{}{}
|
||||||
}
|
}
|
||||||
|
@ -319,11 +324,11 @@ func (cfg *Config) parseData(data []byte, path string) error {
|
||||||
sc := &cfg.ScrapeConfigs[i]
|
sc := &cfg.ScrapeConfigs[i]
|
||||||
swc, err := getScrapeWorkConfig(sc, cfg.baseDir, &cfg.Global)
|
swc, err := getScrapeWorkConfig(sc, cfg.baseDir, &cfg.Global)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot parse `scrape_config` #%d: %w", i+1, err)
|
return nil, fmt.Errorf("cannot parse `scrape_config` #%d: %w", i+1, err)
|
||||||
}
|
}
|
||||||
sc.swc = swc
|
sc.swc = swc
|
||||||
}
|
}
|
||||||
return nil
|
return dataNew, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmarshalMaybeStrict(data []byte, dst interface{}) error {
|
func unmarshalMaybeStrict(data []byte, dst interface{}) error {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package promscrape
|
package promscrape
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -67,39 +68,51 @@ func TestLoadStaticConfigs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadConfig(t *testing.T) {
|
func TestLoadConfig(t *testing.T) {
|
||||||
cfg, err := loadConfig("testdata/prometheus.yml")
|
cfg, data, err := loadConfig("testdata/prometheus.yml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %s", err)
|
t.Fatalf("unexpected error: %s", err)
|
||||||
}
|
}
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
t.Fatalf("expecting non-nil config")
|
t.Fatalf("expecting non-nil config")
|
||||||
}
|
}
|
||||||
|
if data == nil {
|
||||||
|
t.Fatalf("expecting non-nil data")
|
||||||
|
}
|
||||||
|
|
||||||
cfg, err = loadConfig("testdata/prometheus-with-scrape-config-files.yml")
|
cfg, data, err = loadConfig("testdata/prometheus-with-scrape-config-files.yml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %s", err)
|
t.Fatalf("unexpected error: %s", err)
|
||||||
}
|
}
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
t.Fatalf("expecting non-nil config")
|
t.Fatalf("expecting non-nil config")
|
||||||
}
|
}
|
||||||
|
if data == nil {
|
||||||
|
t.Fatalf("expecting non-nil data")
|
||||||
|
}
|
||||||
|
|
||||||
// Try loading non-existing file
|
// Try loading non-existing file
|
||||||
cfg, err = loadConfig("testdata/non-existing-file")
|
cfg, data, err = loadConfig("testdata/non-existing-file")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("expecting non-nil error")
|
t.Fatalf("expecting non-nil error")
|
||||||
}
|
}
|
||||||
if cfg != nil {
|
if cfg != nil {
|
||||||
t.Fatalf("unexpected non-nil config: %#v", cfg)
|
t.Fatalf("unexpected non-nil config: %#v", cfg)
|
||||||
}
|
}
|
||||||
|
if data != nil {
|
||||||
|
t.Fatalf("unexpected data wit length=%d: %q", len(data), data)
|
||||||
|
}
|
||||||
|
|
||||||
// Try loading invalid file
|
// Try loading invalid file
|
||||||
cfg, err = loadConfig("testdata/file_sd_1.yml")
|
cfg, data, err = loadConfig("testdata/file_sd_1.yml")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("expecting non-nil error")
|
t.Fatalf("expecting non-nil error")
|
||||||
}
|
}
|
||||||
if cfg != nil {
|
if cfg != nil {
|
||||||
t.Fatalf("unexpected non-nil config: %#v", cfg)
|
t.Fatalf("unexpected non-nil config: %#v", cfg)
|
||||||
}
|
}
|
||||||
|
if data != nil {
|
||||||
|
t.Fatalf("unexpected data wit length=%d: %q", len(data), data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBlackboxExporter(t *testing.T) {
|
func TestBlackboxExporter(t *testing.T) {
|
||||||
|
@ -122,9 +135,13 @@ scrape_configs:
|
||||||
replacement: black:9115 # The blackbox exporter's real hostname:port.%
|
replacement: black:9115 # The blackbox exporter's real hostname:port.%
|
||||||
`
|
`
|
||||||
var cfg Config
|
var cfg Config
|
||||||
if err := cfg.parseData([]byte(data), "sss"); err != nil {
|
allData, err := cfg.parseData([]byte(data), "sss")
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("cannot parase data: %s", err)
|
t.Fatalf("cannot parase data: %s", err)
|
||||||
}
|
}
|
||||||
|
if string(allData) != data {
|
||||||
|
t.Fatalf("invalid data returned from parseData;\ngot\n%s\nwant\n%s", allData, data)
|
||||||
|
}
|
||||||
sws := cfg.getStaticScrapeWork()
|
sws := cfg.getStaticScrapeWork()
|
||||||
resetNonEssentialFields(sws)
|
resetNonEssentialFields(sws)
|
||||||
swsExpected := []*ScrapeWork{{
|
swsExpected := []*ScrapeWork{{
|
||||||
|
@ -187,9 +204,13 @@ scrape_configs:
|
||||||
- files: [testdata/file_sd.json]
|
- files: [testdata/file_sd.json]
|
||||||
`
|
`
|
||||||
var cfg Config
|
var cfg Config
|
||||||
if err := cfg.parseData([]byte(data), "sss"); err != nil {
|
allData, err := cfg.parseData([]byte(data), "sss")
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("cannot parase data: %s", err)
|
t.Fatalf("cannot parase data: %s", err)
|
||||||
}
|
}
|
||||||
|
if string(allData) != data {
|
||||||
|
t.Fatalf("invalid data returned from parseData;\ngot\n%s\nwant\n%s", allData, data)
|
||||||
|
}
|
||||||
sws := cfg.getFileSDScrapeWork(nil)
|
sws := cfg.getFileSDScrapeWork(nil)
|
||||||
if !equalStaticConfigForScrapeWorks(sws, sws) {
|
if !equalStaticConfigForScrapeWorks(sws, sws) {
|
||||||
t.Fatalf("unexpected non-equal static configs;\nsws:\n%#v", sws)
|
t.Fatalf("unexpected non-equal static configs;\nsws:\n%#v", sws)
|
||||||
|
@ -203,9 +224,13 @@ scrape_configs:
|
||||||
- files: [testdata/file_sd_1.yml]
|
- files: [testdata/file_sd_1.yml]
|
||||||
`
|
`
|
||||||
var cfgNew Config
|
var cfgNew Config
|
||||||
if err := cfgNew.parseData([]byte(dataNew), "sss"); err != nil {
|
allData, err = cfgNew.parseData([]byte(dataNew), "sss")
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("cannot parse data: %s", err)
|
t.Fatalf("cannot parse data: %s", err)
|
||||||
}
|
}
|
||||||
|
if string(allData) != dataNew {
|
||||||
|
t.Fatalf("invalid data returned from parseData;\ngot\n%s\nwant\n%s", allData, dataNew)
|
||||||
|
}
|
||||||
swsNew := cfgNew.getFileSDScrapeWork(sws)
|
swsNew := cfgNew.getFileSDScrapeWork(sws)
|
||||||
if equalStaticConfigForScrapeWorks(swsNew, sws) {
|
if equalStaticConfigForScrapeWorks(swsNew, sws) {
|
||||||
t.Fatalf("unexpected equal static configs;\nswsNew:\n%#v\nsws:\n%#v", swsNew, sws)
|
t.Fatalf("unexpected equal static configs;\nswsNew:\n%#v\nsws:\n%#v", swsNew, sws)
|
||||||
|
@ -218,9 +243,13 @@ scrape_configs:
|
||||||
file_sd_configs:
|
file_sd_configs:
|
||||||
- files: [testdata/prometheus.yml]
|
- files: [testdata/prometheus.yml]
|
||||||
`
|
`
|
||||||
if err := cfg.parseData([]byte(data), "sss"); err != nil {
|
allData, err = cfg.parseData([]byte(data), "sss")
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("cannot parse data: %s", err)
|
t.Fatalf("cannot parse data: %s", err)
|
||||||
}
|
}
|
||||||
|
if string(allData) != data {
|
||||||
|
t.Fatalf("invalid data returned from parseData;\ngot\n%s\nwant\n%s", allData, data)
|
||||||
|
}
|
||||||
sws = cfg.getFileSDScrapeWork(swsNew)
|
sws = cfg.getFileSDScrapeWork(swsNew)
|
||||||
if len(sws) != 0 {
|
if len(sws) != 0 {
|
||||||
t.Fatalf("unexpected non-empty sws:\n%#v", sws)
|
t.Fatalf("unexpected non-empty sws:\n%#v", sws)
|
||||||
|
@ -233,9 +262,13 @@ scrape_configs:
|
||||||
file_sd_configs:
|
file_sd_configs:
|
||||||
- files: [testdata/empty_target_file_sd.yml]
|
- files: [testdata/empty_target_file_sd.yml]
|
||||||
`
|
`
|
||||||
if err := cfg.parseData([]byte(data), "sss"); err != nil {
|
allData, err = cfg.parseData([]byte(data), "sss")
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("cannot parse data: %s", err)
|
t.Fatalf("cannot parse data: %s", err)
|
||||||
}
|
}
|
||||||
|
if string(allData) != data {
|
||||||
|
t.Fatalf("invalid data returned from parseData;\ngot\n%s\nwant\n%s", allData, data)
|
||||||
|
}
|
||||||
sws = cfg.getFileSDScrapeWork(swsNew)
|
sws = cfg.getFileSDScrapeWork(swsNew)
|
||||||
if len(sws) != 0 {
|
if len(sws) != 0 {
|
||||||
t.Fatalf("unexpected non-empty sws:\n%#v", sws)
|
t.Fatalf("unexpected non-empty sws:\n%#v", sws)
|
||||||
|
@ -244,17 +277,25 @@ scrape_configs:
|
||||||
|
|
||||||
func getFileSDScrapeWork(data []byte, path string) ([]*ScrapeWork, error) {
|
func getFileSDScrapeWork(data []byte, path string) ([]*ScrapeWork, error) {
|
||||||
var cfg Config
|
var cfg Config
|
||||||
if err := cfg.parseData(data, path); err != nil {
|
allData, err := cfg.parseData(data, path)
|
||||||
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse data: %w", err)
|
return nil, fmt.Errorf("cannot parse data: %w", err)
|
||||||
}
|
}
|
||||||
|
if !bytes.Equal(allData, data) {
|
||||||
|
return nil, fmt.Errorf("invalid data returned from parseData;\ngot\n%s\nwant\n%s", allData, data)
|
||||||
|
}
|
||||||
return cfg.getFileSDScrapeWork(nil), nil
|
return cfg.getFileSDScrapeWork(nil), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStaticScrapeWork(data []byte, path string) ([]*ScrapeWork, error) {
|
func getStaticScrapeWork(data []byte, path string) ([]*ScrapeWork, error) {
|
||||||
var cfg Config
|
var cfg Config
|
||||||
if err := cfg.parseData(data, path); err != nil {
|
allData, err := cfg.parseData(data, path)
|
||||||
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse data: %w", err)
|
return nil, fmt.Errorf("cannot parse data: %w", err)
|
||||||
}
|
}
|
||||||
|
if !bytes.Equal(allData, data) {
|
||||||
|
return nil, fmt.Errorf("invalid data returned from parseData;\ngot\n%s\nwant\n%s", allData, data)
|
||||||
|
}
|
||||||
return cfg.getStaticScrapeWork(), nil
|
return cfg.getStaticScrapeWork(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,10 +44,10 @@ func newAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if token != "" {
|
if token != "" {
|
||||||
if hcc.BearerToken != "" {
|
if hcc.BearerToken != nil {
|
||||||
return nil, fmt.Errorf("cannot set both token and bearer_token configs")
|
return nil, fmt.Errorf("cannot set both token and bearer_token configs")
|
||||||
}
|
}
|
||||||
hcc.BearerToken = token
|
hcc.BearerToken = promauth.NewSecret(token)
|
||||||
}
|
}
|
||||||
if len(sdc.Username) > 0 {
|
if len(sdc.Username) > 0 {
|
||||||
if hcc.BasicAuth != nil {
|
if hcc.BasicAuth != nil {
|
||||||
|
@ -104,9 +104,9 @@ func newAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getToken(token *string) (string, error) {
|
func getToken(token *promauth.Secret) (string, error) {
|
||||||
if token != nil {
|
if token != nil {
|
||||||
return *token, nil
|
return token.String(), nil
|
||||||
}
|
}
|
||||||
if tokenFile := os.Getenv("CONSUL_HTTP_TOKEN_FILE"); tokenFile != "" {
|
if tokenFile := os.Getenv("CONSUL_HTTP_TOKEN_FILE"); tokenFile != "" {
|
||||||
data, err := ioutil.ReadFile(tokenFile)
|
data, err := ioutil.ReadFile(tokenFile)
|
||||||
|
|
|
@ -11,15 +11,15 @@ import (
|
||||||
//
|
//
|
||||||
// See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#consul_sd_config
|
// See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#consul_sd_config
|
||||||
type SDConfig struct {
|
type SDConfig struct {
|
||||||
Server string `yaml:"server,omitempty"`
|
Server string `yaml:"server,omitempty"`
|
||||||
Token *string `yaml:"token"`
|
Token *promauth.Secret `yaml:"token"`
|
||||||
Datacenter string `yaml:"datacenter"`
|
Datacenter string `yaml:"datacenter"`
|
||||||
// Namespace only supported at enterprise consul.
|
// Namespace only supported at enterprise consul.
|
||||||
// https://www.consul.io/docs/enterprise/namespaces
|
// https://www.consul.io/docs/enterprise/namespaces
|
||||||
Namespace string `yaml:"namespace,omitempty"`
|
Namespace string `yaml:"namespace,omitempty"`
|
||||||
Scheme string `yaml:"scheme,omitempty"`
|
Scheme string `yaml:"scheme,omitempty"`
|
||||||
Username string `yaml:"username"`
|
Username string `yaml:"username"`
|
||||||
Password string `yaml:"password"`
|
Password *promauth.Secret `yaml:"password"`
|
||||||
HTTPClientConfig promauth.HTTPClientConfig `yaml:",inline"`
|
HTTPClientConfig promauth.HTTPClientConfig `yaml:",inline"`
|
||||||
ProxyURL *proxy.URL `yaml:"proxy_url,omitempty"`
|
ProxyURL *proxy.URL `yaml:"proxy_url,omitempty"`
|
||||||
ProxyClientConfig promauth.ProxyClientConfig `yaml:",inline"`
|
ProxyClientConfig promauth.ProxyClientConfig `yaml:",inline"`
|
||||||
|
|
|
@ -93,8 +93,8 @@ func newAPIConfig(sdc *SDConfig) (*apiConfig, error) {
|
||||||
if len(sdc.AccessKey) > 0 {
|
if len(sdc.AccessKey) > 0 {
|
||||||
cfg.defaultAccessKey = sdc.AccessKey
|
cfg.defaultAccessKey = sdc.AccessKey
|
||||||
}
|
}
|
||||||
if len(sdc.SecretKey) > 0 {
|
if sdc.SecretKey != nil {
|
||||||
cfg.defaultSecretKey = sdc.SecretKey
|
cfg.defaultSecretKey = sdc.SecretKey.String()
|
||||||
}
|
}
|
||||||
cfg.creds = &apiCredentials{
|
cfg.creds = &apiCredentials{
|
||||||
AccessKeyID: cfg.defaultAccessKey,
|
AccessKeyID: cfg.defaultAccessKey,
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SDCheckInterval defines interval for targets refresh.
|
// SDCheckInterval defines interval for targets refresh.
|
||||||
|
@ -15,10 +17,10 @@ var SDCheckInterval = flag.Duration("promscrape.ec2SDCheckInterval", time.Minute
|
||||||
//
|
//
|
||||||
// See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#ec2_sd_config
|
// See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#ec2_sd_config
|
||||||
type SDConfig struct {
|
type SDConfig struct {
|
||||||
Region string `yaml:"region,omitempty"`
|
Region string `yaml:"region,omitempty"`
|
||||||
Endpoint string `yaml:"endpoint,omitempty"`
|
Endpoint string `yaml:"endpoint,omitempty"`
|
||||||
AccessKey string `yaml:"access_key,omitempty"`
|
AccessKey string `yaml:"access_key,omitempty"`
|
||||||
SecretKey string `yaml:"secret_key,omitempty"`
|
SecretKey *promauth.Secret `yaml:"secret_key,omitempty"`
|
||||||
// TODO add support for Profile, not working atm
|
// TODO add support for Profile, not working atm
|
||||||
Profile string `yaml:"profile,omitempty"`
|
Profile string `yaml:"profile,omitempty"`
|
||||||
RoleARN string `yaml:"role_arn,omitempty"`
|
RoleARN string `yaml:"role_arn,omitempty"`
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// authResponse represents identity api response
|
// authResponse represents identity api response
|
||||||
|
@ -53,7 +55,7 @@ func getComputeEndpointURL(catalog []catalogItem, availability, region string) (
|
||||||
|
|
||||||
// buildAuthRequestBody builds request for authentication
|
// buildAuthRequestBody builds request for authentication
|
||||||
func buildAuthRequestBody(sdc *SDConfig) ([]byte, error) {
|
func buildAuthRequestBody(sdc *SDConfig) ([]byte, error) {
|
||||||
if len(sdc.Password) == 0 && len(sdc.ApplicationCredentialID) == 0 && len(sdc.ApplicationCredentialName) == 0 {
|
if sdc.Password == nil && len(sdc.ApplicationCredentialID) == 0 && len(sdc.ApplicationCredentialName) == 0 {
|
||||||
return nil, fmt.Errorf("password and application credentials are missing")
|
return nil, fmt.Errorf("password and application credentials are missing")
|
||||||
}
|
}
|
||||||
type domainReq struct {
|
type domainReq struct {
|
||||||
|
@ -97,24 +99,25 @@ func buildAuthRequestBody(sdc *SDConfig) ([]byte, error) {
|
||||||
// if insufficient or incompatible information is present.
|
// if insufficient or incompatible information is present.
|
||||||
var req request
|
var req request
|
||||||
|
|
||||||
if len(sdc.Password) == 0 {
|
if sdc.Password == nil {
|
||||||
// There are three kinds of possible application_credential requests
|
// There are three kinds of possible application_credential requests
|
||||||
// 1. application_credential id + secret
|
// 1. application_credential id + secret
|
||||||
// 2. application_credential name + secret + user_id
|
// 2. application_credential name + secret + user_id
|
||||||
// 3. application_credential name + secret + username + domain_id / domain_name
|
// 3. application_credential name + secret + username + domain_id / domain_name
|
||||||
if len(sdc.ApplicationCredentialID) > 0 {
|
if len(sdc.ApplicationCredentialID) > 0 {
|
||||||
if len(sdc.ApplicationCredentialSecret) == 0 {
|
if sdc.ApplicationCredentialSecret == nil {
|
||||||
return nil, fmt.Errorf("ApplicationCredentialSecret is empty")
|
return nil, fmt.Errorf("ApplicationCredentialSecret is empty")
|
||||||
}
|
}
|
||||||
req.Auth.Identity.Methods = []string{"application_credential"}
|
req.Auth.Identity.Methods = []string{"application_credential"}
|
||||||
|
secret := sdc.ApplicationCredentialSecret.String()
|
||||||
req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{
|
req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{
|
||||||
ID: &sdc.ApplicationCredentialID,
|
ID: &sdc.ApplicationCredentialID,
|
||||||
Secret: &sdc.ApplicationCredentialSecret,
|
Secret: &secret,
|
||||||
}
|
}
|
||||||
return json.Marshal(req)
|
return json.Marshal(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sdc.ApplicationCredentialSecret) == 0 {
|
if sdc.ApplicationCredentialSecret == nil {
|
||||||
return nil, fmt.Errorf("missing application_credential_secret when application_credential_name is set")
|
return nil, fmt.Errorf("missing application_credential_secret when application_credential_name is set")
|
||||||
}
|
}
|
||||||
var userRequest *userReq
|
var userRequest *userReq
|
||||||
|
@ -143,10 +146,11 @@ func buildAuthRequestBody(sdc *SDConfig) ([]byte, error) {
|
||||||
return nil, fmt.Errorf("domain_id and domain_name cannot be empty for application_credential_name auth")
|
return nil, fmt.Errorf("domain_id and domain_name cannot be empty for application_credential_name auth")
|
||||||
}
|
}
|
||||||
req.Auth.Identity.Methods = []string{"application_credential"}
|
req.Auth.Identity.Methods = []string{"application_credential"}
|
||||||
|
secret := sdc.ApplicationCredentialSecret.String()
|
||||||
req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{
|
req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{
|
||||||
Name: &sdc.ApplicationCredentialName,
|
Name: &sdc.ApplicationCredentialName,
|
||||||
User: userRequest,
|
User: userRequest,
|
||||||
Secret: &sdc.ApplicationCredentialSecret,
|
Secret: &secret,
|
||||||
}
|
}
|
||||||
return json.Marshal(req)
|
return json.Marshal(req)
|
||||||
}
|
}
|
||||||
|
@ -168,11 +172,12 @@ func buildAuthRequestBody(sdc *SDConfig) ([]byte, error) {
|
||||||
return nil, fmt.Errorf("both domain_id and domain_name is present")
|
return nil, fmt.Errorf("both domain_id and domain_name is present")
|
||||||
}
|
}
|
||||||
// Configure the request for Username and Password authentication with a DomainID.
|
// Configure the request for Username and Password authentication with a DomainID.
|
||||||
if len(sdc.Password) > 0 {
|
if sdc.Password != nil {
|
||||||
|
password := sdc.Password.String()
|
||||||
req.Auth.Identity.Password = &passwordReq{
|
req.Auth.Identity.Password = &passwordReq{
|
||||||
User: userReq{
|
User: userReq{
|
||||||
Name: &sdc.Username,
|
Name: &sdc.Username,
|
||||||
Password: &sdc.Password,
|
Password: &password,
|
||||||
Domain: &domainReq{ID: &sdc.DomainID},
|
Domain: &domainReq{ID: &sdc.DomainID},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -180,11 +185,12 @@ func buildAuthRequestBody(sdc *SDConfig) ([]byte, error) {
|
||||||
}
|
}
|
||||||
if len(sdc.DomainName) > 0 {
|
if len(sdc.DomainName) > 0 {
|
||||||
// Configure the request for Username and Password authentication with a DomainName.
|
// Configure the request for Username and Password authentication with a DomainName.
|
||||||
if len(sdc.Password) > 0 {
|
if sdc.Password != nil {
|
||||||
|
password := sdc.Password.String()
|
||||||
req.Auth.Identity.Password = &passwordReq{
|
req.Auth.Identity.Password = &passwordReq{
|
||||||
User: userReq{
|
User: userReq{
|
||||||
Name: &sdc.Username,
|
Name: &sdc.Username,
|
||||||
Password: &sdc.Password,
|
Password: &password,
|
||||||
Domain: &domainReq{Name: &sdc.DomainName},
|
Domain: &domainReq{Name: &sdc.DomainName},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -199,11 +205,12 @@ func buildAuthRequestBody(sdc *SDConfig) ([]byte, error) {
|
||||||
return nil, fmt.Errorf("both user_id and domain_name is present")
|
return nil, fmt.Errorf("both user_id and domain_name is present")
|
||||||
}
|
}
|
||||||
// Configure the request for UserID and Password authentication.
|
// Configure the request for UserID and Password authentication.
|
||||||
if len(sdc.Password) > 0 {
|
if sdc.Password != nil {
|
||||||
|
password := sdc.Password.String()
|
||||||
req.Auth.Identity.Password = &passwordReq{
|
req.Auth.Identity.Password = &passwordReq{
|
||||||
User: userReq{
|
User: userReq{
|
||||||
ID: &sdc.UserID,
|
ID: &sdc.UserID,
|
||||||
Password: &sdc.Password,
|
Password: &password,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -300,13 +307,13 @@ func readCredentialsFromEnv() SDConfig {
|
||||||
IdentityEndpoint: authURL,
|
IdentityEndpoint: authURL,
|
||||||
Username: username,
|
Username: username,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Password: password,
|
Password: promauth.NewSecret(password),
|
||||||
ProjectName: tenantName,
|
ProjectName: tenantName,
|
||||||
ProjectID: tenantID,
|
ProjectID: tenantID,
|
||||||
DomainName: domainName,
|
DomainName: domainName,
|
||||||
DomainID: domainID,
|
DomainID: domainID,
|
||||||
ApplicationCredentialName: applicationCredentialName,
|
ApplicationCredentialName: applicationCredentialName,
|
||||||
ApplicationCredentialID: applicationCredentialID,
|
ApplicationCredentialID: applicationCredentialID,
|
||||||
ApplicationCredentialSecret: applicationCredentialSecret,
|
ApplicationCredentialSecret: promauth.NewSecret(applicationCredentialSecret),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package openstack
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_buildAuthRequestBody1(t *testing.T) {
|
func Test_buildAuthRequestBody1(t *testing.T) {
|
||||||
|
@ -27,7 +29,7 @@ func Test_buildAuthRequestBody1(t *testing.T) {
|
||||||
args: args{
|
args: args{
|
||||||
sdc: &SDConfig{
|
sdc: &SDConfig{
|
||||||
Username: "some-user",
|
Username: "some-user",
|
||||||
Password: "some-password",
|
Password: promauth.NewSecret("some-password"),
|
||||||
DomainName: "some-domain",
|
DomainName: "some-domain",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -38,7 +40,7 @@ func Test_buildAuthRequestBody1(t *testing.T) {
|
||||||
args: args{
|
args: args{
|
||||||
sdc: &SDConfig{
|
sdc: &SDConfig{
|
||||||
ApplicationCredentialID: "some-id",
|
ApplicationCredentialID: "some-id",
|
||||||
ApplicationCredentialSecret: "some-secret",
|
ApplicationCredentialSecret: promauth.NewSecret("some-secret"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: []byte(`{"auth":{"identity":{"methods":["application_credential"],"application_credential":{"id":"some-id","secret":"some-secret"}}}}`),
|
want: []byte(`{"auth":{"identity":{"methods":["application_credential"],"application_credential":{"id":"some-id","secret":"some-secret"}}}}`),
|
||||||
|
|
|
@ -17,19 +17,19 @@ var SDCheckInterval = flag.Duration("promscrape.openstackSDCheckInterval", 30*ti
|
||||||
//
|
//
|
||||||
// See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#openstack_sd_config
|
// See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#openstack_sd_config
|
||||||
type SDConfig struct {
|
type SDConfig struct {
|
||||||
IdentityEndpoint string `yaml:"identity_endpoint,omitempty"`
|
IdentityEndpoint string `yaml:"identity_endpoint,omitempty"`
|
||||||
Username string `yaml:"username,omitempty"`
|
Username string `yaml:"username,omitempty"`
|
||||||
UserID string `yaml:"userid,omitempty"`
|
UserID string `yaml:"userid,omitempty"`
|
||||||
Password string `yaml:"password,omitempty"`
|
Password *promauth.Secret `yaml:"password,omitempty"`
|
||||||
ProjectName string `yaml:"project_name,omitempty"`
|
ProjectName string `yaml:"project_name,omitempty"`
|
||||||
ProjectID string `yaml:"project_id,omitempty"`
|
ProjectID string `yaml:"project_id,omitempty"`
|
||||||
DomainName string `yaml:"domain_name,omitempty"`
|
DomainName string `yaml:"domain_name,omitempty"`
|
||||||
DomainID string `yaml:"domain_id,omitempty"`
|
DomainID string `yaml:"domain_id,omitempty"`
|
||||||
ApplicationCredentialName string `yaml:"application_credential_name,omitempty"`
|
ApplicationCredentialName string `yaml:"application_credential_name,omitempty"`
|
||||||
ApplicationCredentialID string `yaml:"application_credential_id,omitempty"`
|
ApplicationCredentialID string `yaml:"application_credential_id,omitempty"`
|
||||||
ApplicationCredentialSecret string `yaml:"application_credential_secret,omitempty"`
|
ApplicationCredentialSecret *promauth.Secret `yaml:"application_credential_secret,omitempty"`
|
||||||
Role string `yaml:"role"`
|
Role string `yaml:"role"`
|
||||||
Region string `yaml:"region"`
|
Region string `yaml:"region"`
|
||||||
// RefreshInterval time.Duration `yaml:"refresh_interval"`
|
// RefreshInterval time.Duration `yaml:"refresh_interval"`
|
||||||
// refresh_interval is obtained from `-promscrape.openstackSDCheckInterval` command-line option.
|
// refresh_interval is obtained from `-promscrape.openstackSDCheckInterval` command-line option.
|
||||||
Port int `yaml:"port,omitempty"`
|
Port int `yaml:"port,omitempty"`
|
||||||
|
|
|
@ -43,7 +43,7 @@ func CheckConfig() error {
|
||||||
if *promscrapeConfigFile == "" {
|
if *promscrapeConfigFile == "" {
|
||||||
return fmt.Errorf("missing -promscrape.config option")
|
return fmt.Errorf("missing -promscrape.config option")
|
||||||
}
|
}
|
||||||
_, err := loadConfig(*promscrapeConfigFile)
|
_, _, err := loadConfig(*promscrapeConfigFile)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,12 +99,12 @@ func runScraper(configFile string, pushData func(wr *prompbmarshal.WriteRequest)
|
||||||
sighupCh := procutil.NewSighupChan()
|
sighupCh := procutil.NewSighupChan()
|
||||||
|
|
||||||
logger.Infof("reading Prometheus configs from %q", configFile)
|
logger.Infof("reading Prometheus configs from %q", configFile)
|
||||||
cfg, err := loadConfig(configFile)
|
cfg, data, err := loadConfig(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatalf("cannot read %q: %s", configFile, err)
|
logger.Fatalf("cannot read %q: %s", configFile, err)
|
||||||
}
|
}
|
||||||
data := cfg.marshal()
|
marshaledData := cfg.marshal()
|
||||||
configData.Store(&data)
|
configData.Store(&marshaledData)
|
||||||
cfg.mustStart()
|
cfg.mustStart()
|
||||||
|
|
||||||
scs := newScrapeConfigs(pushData)
|
scs := newScrapeConfigs(pushData)
|
||||||
|
@ -134,12 +134,11 @@ func runScraper(configFile string, pushData func(wr *prompbmarshal.WriteRequest)
|
||||||
select {
|
select {
|
||||||
case <-sighupCh:
|
case <-sighupCh:
|
||||||
logger.Infof("SIGHUP received; reloading Prometheus configs from %q", configFile)
|
logger.Infof("SIGHUP received; reloading Prometheus configs from %q", configFile)
|
||||||
cfgNew, err := loadConfig(configFile)
|
cfgNew, dataNew, err := loadConfig(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("cannot read %q on SIGHUP: %s; continuing with the previous config", configFile, err)
|
logger.Errorf("cannot read %q on SIGHUP: %s; continuing with the previous config", configFile, err)
|
||||||
goto waitForChans
|
goto waitForChans
|
||||||
}
|
}
|
||||||
dataNew := cfgNew.marshal()
|
|
||||||
if bytes.Equal(data, dataNew) {
|
if bytes.Equal(data, dataNew) {
|
||||||
logger.Infof("nothing changed in %q", configFile)
|
logger.Infof("nothing changed in %q", configFile)
|
||||||
goto waitForChans
|
goto waitForChans
|
||||||
|
@ -148,14 +147,14 @@ func runScraper(configFile string, pushData func(wr *prompbmarshal.WriteRequest)
|
||||||
cfgNew.mustStart()
|
cfgNew.mustStart()
|
||||||
cfg = cfgNew
|
cfg = cfgNew
|
||||||
data = dataNew
|
data = dataNew
|
||||||
configData.Store(&data)
|
marshaledData = cfgNew.marshal()
|
||||||
|
configData.Store(&marshaledData)
|
||||||
case <-tickerCh:
|
case <-tickerCh:
|
||||||
cfgNew, err := loadConfig(configFile)
|
cfgNew, dataNew, err := loadConfig(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("cannot read %q: %s; continuing with the previous config", configFile, err)
|
logger.Errorf("cannot read %q: %s; continuing with the previous config", configFile, err)
|
||||||
goto waitForChans
|
goto waitForChans
|
||||||
}
|
}
|
||||||
dataNew := cfgNew.marshal()
|
|
||||||
if bytes.Equal(data, dataNew) {
|
if bytes.Equal(data, dataNew) {
|
||||||
// Nothing changed since the previous loadConfig
|
// Nothing changed since the previous loadConfig
|
||||||
goto waitForChans
|
goto waitForChans
|
||||||
|
@ -164,7 +163,7 @@ func runScraper(configFile string, pushData func(wr *prompbmarshal.WriteRequest)
|
||||||
cfgNew.mustStart()
|
cfgNew.mustStart()
|
||||||
cfg = cfgNew
|
cfg = cfgNew
|
||||||
data = dataNew
|
data = dataNew
|
||||||
configData.Store(&data)
|
configData.Store(&marshaledData)
|
||||||
case <-globalStopCh:
|
case <-globalStopCh:
|
||||||
cfg.mustStop()
|
cfg.mustStop()
|
||||||
logger.Infof("stopping Prometheus scrapers")
|
logger.Infof("stopping Prometheus scrapers")
|
||||||
|
|
Loading…
Reference in a new issue