mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-25 05:34:05 -08:00
Expose current Prometheus config via /status/config
This PR adds the `/status/config` endpoint which exposes the currently loaded Prometheus config. This is the same config that is displayed on `/config` in the UI in YAML format. The response payload looks like such: ``` { "status": "success", "data": { "yaml": <CONFIG> } } ```
This commit is contained in:
parent
1ea9ab601e
commit
1c96fbb992
|
@ -540,15 +540,27 @@ func TestLoadConfig(t *testing.T) {
|
|||
if !reflect.DeepEqual(c, expectedConf) {
|
||||
t.Fatalf("%s: unexpected config result: \n\n%s\n expected\n\n%s", "testdata/conf.good.yml", bgot, bexp)
|
||||
}
|
||||
}
|
||||
|
||||
// String method must not reveal authentication credentials.
|
||||
s := c.String()
|
||||
secretRe := regexp.MustCompile("<secret>")
|
||||
matches := secretRe.FindAllStringIndex(s, -1)
|
||||
if len(matches) != 6 || strings.Contains(s, "mysecret") {
|
||||
t.Fatalf("config's String method reveals authentication credentials.")
|
||||
// YAML marshalling must not reveal authentication credentials.
|
||||
func TestElideSecrets(t *testing.T) {
|
||||
c, err := LoadFile("testdata/conf.good.yml")
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing %s: %s", "testdata/conf.good.yml", err)
|
||||
}
|
||||
|
||||
secretRe := regexp.MustCompile(`\\u003csecret\\u003e|<secret>`)
|
||||
|
||||
config, err := yaml.Marshal(c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
yamlConfig := string(config)
|
||||
|
||||
matches := secretRe.FindAllStringIndex(yamlConfig, -1)
|
||||
if len(matches) != 6 || strings.Contains(yamlConfig, "mysecret") {
|
||||
t.Fatalf("yaml marshal reveals authentication credentials.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfigRuleFilesAbsolutePath(t *testing.T) {
|
||||
|
|
|
@ -29,6 +29,7 @@ import (
|
|||
"github.com/prometheus/common/route"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
"github.com/prometheus/prometheus/retrieval"
|
||||
"github.com/prometheus/prometheus/storage/local"
|
||||
|
@ -103,17 +104,19 @@ type API struct {
|
|||
targetRetriever targetRetriever
|
||||
alertmanagerRetriever alertmanagerRetriever
|
||||
|
||||
now func() model.Time
|
||||
now func() model.Time
|
||||
config func() config.Config
|
||||
}
|
||||
|
||||
// NewAPI returns an initialized API type.
|
||||
func NewAPI(qe *promql.Engine, st local.Storage, tr targetRetriever, ar alertmanagerRetriever) *API {
|
||||
func NewAPI(qe *promql.Engine, st local.Storage, tr targetRetriever, ar alertmanagerRetriever, configFunc func() config.Config) *API {
|
||||
return &API{
|
||||
QueryEngine: qe,
|
||||
Storage: st,
|
||||
targetRetriever: tr,
|
||||
alertmanagerRetriever: ar,
|
||||
now: model.Now,
|
||||
now: model.Now,
|
||||
config: configFunc,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,6 +150,8 @@ func (api *API) Register(r *route.Router) {
|
|||
|
||||
r.Get("/targets", instr("targets", api.targets))
|
||||
r.Get("/alertmanagers", instr("alertmanagers", api.alertmanagers))
|
||||
|
||||
r.Get("/status/config", instr("config", api.serveConfig))
|
||||
}
|
||||
|
||||
type queryData struct {
|
||||
|
@ -436,6 +441,17 @@ func (api *API) alertmanagers(r *http.Request) (interface{}, *apiError) {
|
|||
return ams, nil
|
||||
}
|
||||
|
||||
type prometheusConfig struct {
|
||||
YAML string `json:"yaml"`
|
||||
}
|
||||
|
||||
func (api *API) serveConfig(r *http.Request) (interface{}, *apiError) {
|
||||
cfg := &prometheusConfig{
|
||||
YAML: api.config().String(),
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func respond(w http.ResponseWriter, data interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
|
|
@ -29,6 +29,7 @@ import (
|
|||
"github.com/prometheus/common/route"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
"github.com/prometheus/prometheus/retrieval"
|
||||
)
|
||||
|
@ -45,6 +46,15 @@ func (f alertmanagerRetrieverFunc) Alertmanagers() []*url.URL {
|
|||
return f()
|
||||
}
|
||||
|
||||
var samplePrometheusCfg = config.Config{
|
||||
GlobalConfig: config.GlobalConfig{},
|
||||
AlertingConfig: config.AlertingConfig{},
|
||||
RuleFiles: []string{},
|
||||
ScrapeConfigs: []*config.ScrapeConfig{},
|
||||
RemoteWriteConfigs: []*config.RemoteWriteConfig{},
|
||||
RemoteReadConfigs: []*config.RemoteReadConfig{},
|
||||
}
|
||||
|
||||
func TestEndpoints(t *testing.T) {
|
||||
suite, err := promql.NewTest(t, `
|
||||
load 1m
|
||||
|
@ -91,6 +101,9 @@ func TestEndpoints(t *testing.T) {
|
|||
targetRetriever: tr,
|
||||
alertmanagerRetriever: ar,
|
||||
now: func() model.Time { return now },
|
||||
config: func() config.Config {
|
||||
return samplePrometheusCfg
|
||||
},
|
||||
}
|
||||
|
||||
start := model.Time(0)
|
||||
|
@ -445,7 +458,8 @@ func TestEndpoints(t *testing.T) {
|
|||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
}, {
|
||||
},
|
||||
{
|
||||
endpoint: api.dropSeries,
|
||||
query: url.Values{
|
||||
"match[]": []string{`{__name__=~".+"}`},
|
||||
|
@ -453,7 +467,8 @@ func TestEndpoints(t *testing.T) {
|
|||
response: struct {
|
||||
NumDeleted int `json:"numDeleted"`
|
||||
}{2},
|
||||
}, {
|
||||
},
|
||||
{
|
||||
endpoint: api.targets,
|
||||
response: &TargetDiscovery{
|
||||
ActiveTargets: []*Target{
|
||||
|
@ -465,7 +480,8 @@ func TestEndpoints(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
},
|
||||
{
|
||||
endpoint: api.alertmanagers,
|
||||
response: &AlertmanagerDiscovery{
|
||||
ActiveAlertmanagers: []*AlertmanagerTarget{
|
||||
|
@ -475,6 +491,12 @@ func TestEndpoints(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
endpoint: api.serveConfig,
|
||||
response: &prometheusConfig{
|
||||
YAML: samplePrometheusCfg.String(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
|
|
@ -74,7 +74,7 @@ func (h *Handler) federation(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
sort.Sort(byName(vector))
|
||||
|
||||
externalLabels := h.externalLabels.Clone()
|
||||
externalLabels := h.config.GlobalConfig.ExternalLabels.Clone()
|
||||
if _, ok := externalLabels[model.InstanceLabel]; !ok {
|
||||
externalLabels[model.InstanceLabel] = ""
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
)
|
||||
|
||||
|
@ -178,10 +179,13 @@ func TestFederation(t *testing.T) {
|
|||
storage: suite.Storage(),
|
||||
queryEngine: suite.QueryEngine(),
|
||||
now: func() model.Time { return 101 * 60 * 1000 }, // 101min after epoch.
|
||||
config: &config.Config{
|
||||
GlobalConfig: config.GlobalConfig{},
|
||||
},
|
||||
}
|
||||
|
||||
for name, scenario := range scenarios {
|
||||
h.externalLabels = scenario.externalLabels
|
||||
h.config.GlobalConfig.ExternalLabels = scenario.externalLabels
|
||||
req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(
|
||||
"GET http://example.org/federate?" + scenario.params + " HTTP/1.0\r\n\r\n",
|
||||
)))
|
||||
|
|
51
web/web.go
51
web/web.go
|
@ -68,31 +68,29 @@ type Handler struct {
|
|||
|
||||
apiV1 *api_v1.API
|
||||
|
||||
router *route.Router
|
||||
listenErrCh chan error
|
||||
quitCh chan struct{}
|
||||
reloadCh chan chan error
|
||||
options *Options
|
||||
configString string
|
||||
versionInfo *PrometheusVersion
|
||||
birth time.Time
|
||||
cwd string
|
||||
flagsMap map[string]string
|
||||
router *route.Router
|
||||
listenErrCh chan error
|
||||
quitCh chan struct{}
|
||||
reloadCh chan chan error
|
||||
options *Options
|
||||
config *config.Config
|
||||
versionInfo *PrometheusVersion
|
||||
birth time.Time
|
||||
cwd string
|
||||
flagsMap map[string]string
|
||||
|
||||
externalLabels model.LabelSet
|
||||
mtx sync.RWMutex
|
||||
now func() model.Time
|
||||
mtx sync.RWMutex
|
||||
now func() model.Time
|
||||
|
||||
ready uint32 // ready is uint32 rather than boolean to be able to use atomic functions.
|
||||
}
|
||||
|
||||
// ApplyConfig updates the status state as the new config requires.
|
||||
// ApplyConfig updates the config field of the Handler struct
|
||||
func (h *Handler) ApplyConfig(conf *config.Config) error {
|
||||
h.mtx.Lock()
|
||||
defer h.mtx.Unlock()
|
||||
|
||||
h.externalLabels = conf.GlobalConfig.ExternalLabels
|
||||
h.configString = conf.String()
|
||||
h.config = conf
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -158,12 +156,23 @@ func New(o *Options) *Handler {
|
|||
storage: o.Storage,
|
||||
notifier: o.Notifier,
|
||||
|
||||
apiV1: api_v1.NewAPI(o.QueryEngine, o.Storage, o.TargetManager, o.Notifier),
|
||||
now: model.Now,
|
||||
now: model.Now,
|
||||
|
||||
ready: 0,
|
||||
}
|
||||
|
||||
h.apiV1 = api_v1.NewAPI(
|
||||
o.QueryEngine,
|
||||
o.Storage,
|
||||
o.TargetManager,
|
||||
o.Notifier,
|
||||
func() config.Config {
|
||||
h.mtx.RLock()
|
||||
defer h.mtx.RUnlock()
|
||||
return *h.config
|
||||
},
|
||||
)
|
||||
|
||||
if o.RoutePrefix != "/" {
|
||||
// If the prefix is missing for the root path, prepend it.
|
||||
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -184,7 +193,7 @@ func New(o *Options) *Handler {
|
|||
router.Get("/graph", readyf(instrf("graph", h.graph)))
|
||||
router.Get("/status", readyf(instrf("status", h.status)))
|
||||
router.Get("/flags", readyf(instrf("flags", h.flags)))
|
||||
router.Get("/config", readyf(instrf("config", h.config)))
|
||||
router.Get("/config", readyf(instrf("config", h.serveConfig)))
|
||||
router.Get("/rules", readyf(instrf("rules", h.rules)))
|
||||
router.Get("/targets", readyf(instrf("targets", h.targets)))
|
||||
router.Get("/version", readyf(instrf("version", h.version)))
|
||||
|
@ -404,11 +413,11 @@ func (h *Handler) flags(w http.ResponseWriter, r *http.Request) {
|
|||
h.executeTemplate(w, "flags.html", h.flagsMap)
|
||||
}
|
||||
|
||||
func (h *Handler) config(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *Handler) serveConfig(w http.ResponseWriter, r *http.Request) {
|
||||
h.mtx.RLock()
|
||||
defer h.mtx.RUnlock()
|
||||
|
||||
h.executeTemplate(w, "config.html", h.configString)
|
||||
h.executeTemplate(w, "config.html", h.config.String())
|
||||
}
|
||||
|
||||
func (h *Handler) rules(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
Loading…
Reference in a new issue