Skip to content

Commit 75ae2b0

Browse files
author
Ben Ye
authored
Support modules configuration (prometheus-community#146)
* support modules configuration Signed-off-by: Ben Ye <ben.ye@bytedance.com> * fallback default module if the param is missing Signed-off-by: Ben Ye <ben.ye@bytedance.com> * update readme and example config file Signed-off-by: Ben Ye <ben.ye@bytedance.com> * fix lint Signed-off-by: Ben Ye <ben.ye@bytedance.com>
1 parent d43d3ed commit 75ae2b0

File tree

8 files changed

+207
-124
lines changed

8 files changed

+207
-124
lines changed

README.md

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -39,35 +39,37 @@ $ cat examples/data.json
3939

4040
$ cat examples/config.yml
4141
---
42-
metrics:
43-
- name: example_global_value
44-
path: "{ .counter }"
45-
help: Example of a top-level global value scrape in the json
46-
labels:
47-
environment: beta # static label
48-
location: "planet-{.location}" # dynamic label
49-
50-
- name: example_value
51-
type: object
52-
help: Example of sub-level value scrapes from a json
53-
path: '{.values[?(@.state == "ACTIVE")]}'
54-
labels:
55-
environment: beta # static label
56-
id: '{.id}' # dynamic label
57-
values:
58-
active: 1 # static value
59-
count: '{.count}' # dynamic value
60-
boolean: '{.some_boolean}'
61-
62-
headers:
63-
X-Dummy: my-test-header
42+
modules:
43+
default:
44+
metrics:
45+
- name: example_global_value
46+
path: "{ .counter }"
47+
help: Example of a top-level global value scrape in the json
48+
labels:
49+
environment: beta # static label
50+
location: "planet-{.location}" # dynamic label
51+
52+
- name: example_value
53+
type: object
54+
help: Example of sub-level value scrapes from a json
55+
path: '{.values[?(@.state == "ACTIVE")]}'
56+
labels:
57+
environment: beta # static label
58+
id: '{.id}' # dynamic label
59+
values:
60+
active: 1 # static value
61+
count: '{.count}' # dynamic value
62+
boolean: '{.some_boolean}'
63+
64+
headers:
65+
X-Dummy: my-test-header
6466

6567
$ python -m SimpleHTTPServer 8000 &
6668
Serving HTTP on 0.0.0.0 port 8000 ...
6769

6870
$ ./json_exporter --config.file examples/config.yml &
6971

70-
$ curl "http://localhost:7979/probe?target=http://localhost:8000/examples/data.json" | grep ^example
72+
$ curl "http://localhost:7979/probe?module=default&target=http://localhost:8000/examples/data.json" | grep ^example
7173
example_global_value{environment="beta",location="planet-mars"} 1234
7274
example_value_active{environment="beta",id="id-A"} 1
7375
example_value_active{environment="beta",id="id-C"} 1

cmd/main.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package cmd
1616
import (
1717
"context"
1818
"encoding/json"
19+
"fmt"
1920
"net/http"
2021
"os"
2122

@@ -86,9 +87,19 @@ func probeHandler(w http.ResponseWriter, r *http.Request, logger log.Logger, con
8687
defer cancel()
8788
r = r.WithContext(ctx)
8889

90+
module := r.URL.Query().Get("module")
91+
if module == "" {
92+
module = "default"
93+
}
94+
if _, ok := config.Modules[module]; !ok {
95+
http.Error(w, fmt.Sprintf("Unknown module %q", module), http.StatusBadRequest)
96+
level.Debug(logger).Log("msg", "Unknown module", "module", module)
97+
return
98+
}
99+
89100
registry := prometheus.NewPedanticRegistry()
90101

91-
metrics, err := exporter.CreateMetricsList(config)
102+
metrics, err := exporter.CreateMetricsList(config.Modules[module])
92103
if err != nil {
93104
level.Error(logger).Log("msg", "Failed to create metrics list from config", "err", err)
94105
}
@@ -102,7 +113,7 @@ func probeHandler(w http.ResponseWriter, r *http.Request, logger log.Logger, con
102113
return
103114
}
104115

105-
fetcher := exporter.NewJSONFetcher(ctx, logger, config, r.URL.Query())
116+
fetcher := exporter.NewJSONFetcher(ctx, logger, config.Modules[module], r.URL.Query())
106117
data, err := fetcher.FetchJSON(target)
107118
if err != nil {
108119
http.Error(w, "Failed to fetch JSON response. TARGET: "+target+", ERROR: "+err.Error(), http.StatusServiceUnavailable)

cmd/main_test.go

Lines changed: 83 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ func TestFailIfSelfSignedCA(t *testing.T) {
3232
}))
3333
defer target.Close()
3434

35-
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL, nil)
35+
req := httptest.NewRequest("GET", "http://example.com/foo"+"?module=default&target="+target.URL, nil)
3636
recorder := httptest.NewRecorder()
37-
probeHandler(recorder, req, log.NewNopLogger(), config.Config{})
37+
probeHandler(recorder, req, log.NewNopLogger(), config.Config{Modules: map[string]config.Module{"default": {}}})
3838

3939
resp := recorder.Result()
4040
body, _ := ioutil.ReadAll(resp.Body)
@@ -45,13 +45,21 @@ func TestFailIfSelfSignedCA(t *testing.T) {
4545
}
4646

4747
func TestSucceedIfSelfSignedCA(t *testing.T) {
48-
c := config.Config{}
49-
c.HTTPClientConfig.TLSConfig.InsecureSkipVerify = true
48+
c := config.Config{
49+
Modules: map[string]config.Module{
50+
"default": {
51+
HTTPClientConfig: pconfig.HTTPClientConfig{
52+
TLSConfig: pconfig.TLSConfig{
53+
InsecureSkipVerify: true,
54+
},
55+
},
56+
}},
57+
}
5058
target := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
5159
}))
5260
defer target.Close()
5361

54-
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL, nil)
62+
req := httptest.NewRequest("GET", "http://example.com/foo"+"?module=default&target="+target.URL, nil)
5563
recorder := httptest.NewRecorder()
5664
probeHandler(recorder, req, log.NewNopLogger(), c)
5765

@@ -63,6 +71,29 @@ func TestSucceedIfSelfSignedCA(t *testing.T) {
6371
}
6472
}
6573

74+
func TestDefaultModule(t *testing.T) {
75+
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
76+
}))
77+
defer target.Close()
78+
79+
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL, nil)
80+
recorder := httptest.NewRecorder()
81+
probeHandler(recorder, req, log.NewNopLogger(), config.Config{Modules: map[string]config.Module{"default": {}}})
82+
83+
resp := recorder.Result()
84+
if resp.StatusCode != http.StatusOK {
85+
t.Fatalf("Default module test fails unexpectedly, expected 200, got %d", resp.StatusCode)
86+
}
87+
88+
// Module doesn't exist.
89+
recorder = httptest.NewRecorder()
90+
probeHandler(recorder, req, log.NewNopLogger(), config.Config{Modules: map[string]config.Module{"foo": {}}})
91+
resp = recorder.Result()
92+
if resp.StatusCode != http.StatusBadRequest {
93+
t.Fatalf("Default module test fails unexpectedly, expected 400, got %d", resp.StatusCode)
94+
}
95+
}
96+
6697
func TestFailIfTargetMissing(t *testing.T) {
6798
req := httptest.NewRequest("GET", "http://example.com/foo", nil)
6899
recorder := httptest.NewRecorder()
@@ -86,9 +117,9 @@ func TestDefaultAcceptHeader(t *testing.T) {
86117
}))
87118
defer target.Close()
88119

89-
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL, nil)
120+
req := httptest.NewRequest("GET", "http://example.com/foo"+"?module=default&target="+target.URL, nil)
90121
recorder := httptest.NewRecorder()
91-
probeHandler(recorder, req, log.NewNopLogger(), config.Config{})
122+
probeHandler(recorder, req, log.NewNopLogger(), config.Config{Modules: map[string]config.Module{"default": {}}})
92123

93124
resp := recorder.Result()
94125
body, _ := ioutil.ReadAll(resp.Body)
@@ -118,7 +149,7 @@ func TestCorrectResponse(t *testing.T) {
118149
t.Fatalf("Failed to load config file %s", test.ConfigFile)
119150
}
120151

121-
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL+test.ServeFile, nil)
152+
req := httptest.NewRequest("GET", "http://example.com/foo"+"?module=default&target="+target.URL+test.ServeFile, nil)
122153
recorder := httptest.NewRecorder()
123154
probeHandler(recorder, req, log.NewNopLogger(), c)
124155

@@ -145,15 +176,21 @@ func TestBasicAuth(t *testing.T) {
145176
}))
146177
defer target.Close()
147178

148-
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL, nil)
179+
req := httptest.NewRequest("GET", "http://example.com/foo"+"?module=default&target="+target.URL, nil)
149180
recorder := httptest.NewRecorder()
150-
c := config.Config{}
151-
auth := &pconfig.BasicAuth{
152-
Username: username,
153-
Password: pconfig.Secret(password),
181+
c := config.Config{
182+
Modules: map[string]config.Module{
183+
"default": {
184+
HTTPClientConfig: pconfig.HTTPClientConfig{
185+
BasicAuth: &pconfig.BasicAuth{
186+
Username: username,
187+
Password: pconfig.Secret(password),
188+
},
189+
},
190+
},
191+
},
154192
}
155193

156-
c.HTTPClientConfig.BasicAuth = auth
157194
probeHandler(recorder, req, log.NewNopLogger(), c)
158195

159196
resp := recorder.Result()
@@ -175,11 +212,16 @@ func TestBearerToken(t *testing.T) {
175212
}))
176213
defer target.Close()
177214

178-
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL, nil)
215+
req := httptest.NewRequest("GET", "http://example.com/foo"+"?module=default&target="+target.URL, nil)
179216
recorder := httptest.NewRecorder()
180-
c := config.Config{}
217+
c := config.Config{
218+
Modules: map[string]config.Module{"default": {
219+
HTTPClientConfig: pconfig.HTTPClientConfig{
220+
BearerToken: pconfig.Secret(token),
221+
},
222+
}},
223+
}
181224

182-
c.HTTPClientConfig.BearerToken = pconfig.Secret(token)
183225
probeHandler(recorder, req, log.NewNopLogger(), c)
184226

185227
resp := recorder.Result()
@@ -206,10 +248,15 @@ func TestHTTPHeaders(t *testing.T) {
206248
}))
207249
defer target.Close()
208250

209-
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL, nil)
251+
req := httptest.NewRequest("GET", "http://example.com/foo"+"?module=default&target="+target.URL, nil)
210252
recorder := httptest.NewRecorder()
211-
c := config.Config{}
212-
c.Headers = headers
253+
c := config.Config{
254+
Modules: map[string]config.Module{
255+
"default": {
256+
Headers: headers,
257+
},
258+
},
259+
}
213260

214261
probeHandler(recorder, req, log.NewNopLogger(), c)
215262

@@ -264,9 +311,15 @@ func TestBodyPostTemplate(t *testing.T) {
264311
w.WriteHeader(http.StatusOK)
265312
}))
266313

267-
req := httptest.NewRequest("POST", "http://example.com/foo"+"?target="+target.URL, strings.NewReader(test.Body.Content))
314+
req := httptest.NewRequest("POST", "http://example.com/foo"+"?module=default&target="+target.URL, strings.NewReader(test.Body.Content))
268315
recorder := httptest.NewRecorder()
269-
c := config.Config{Body: test.Body}
316+
c := config.Config{
317+
Modules: map[string]config.Module{
318+
"default": {
319+
Body: test.Body,
320+
},
321+
},
322+
}
270323

271324
probeHandler(recorder, req, log.NewNopLogger(), c)
272325

@@ -351,15 +404,21 @@ func TestBodyPostQuery(t *testing.T) {
351404
w.WriteHeader(http.StatusOK)
352405
}))
353406

354-
req := httptest.NewRequest("POST", "http://example.com/foo"+"?target="+target.URL, strings.NewReader(test.Body.Content))
407+
req := httptest.NewRequest("POST", "http://example.com/foo"+"?module=default&target="+target.URL, strings.NewReader(test.Body.Content))
355408
q := req.URL.Query()
356409
for k, v := range test.QueryParams {
357410
q.Add(k, v)
358411
}
359412
req.URL.RawQuery = q.Encode()
360413

361414
recorder := httptest.NewRecorder()
362-
c := config.Config{Body: test.Body}
415+
c := config.Config{
416+
Modules: map[string]config.Module{
417+
"default": {
418+
Body: test.Body,
419+
},
420+
},
421+
}
363422

364423
probeHandler(recorder, req, log.NewNopLogger(), c)
365424

config/config.go

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,13 @@ const (
4646
ValueTypeUntyped ValueType = "untyped"
4747
)
4848

49-
// Config contains metrics and headers defining a configuration
49+
// Config contains multiple modules.
5050
type Config struct {
51+
Modules map[string]Module `yaml:"modules"`
52+
}
53+
54+
// Module contains metrics and headers defining a configuration
55+
type Module struct {
5156
Headers map[string]string `yaml:"headers,omitempty"`
5257
Metrics []Metric `yaml:"metrics"`
5358
HTTPClientConfig pconfig.HTTPClientConfig `yaml:"http_client_config,omitempty"`
@@ -71,15 +76,17 @@ func LoadConfig(configPath string) (Config, error) {
7176
}
7277

7378
// Complete Defaults
74-
for i := 0; i < len(config.Metrics); i++ {
75-
if config.Metrics[i].Type == "" {
76-
config.Metrics[i].Type = ValueScrape
77-
}
78-
if config.Metrics[i].Help == "" {
79-
config.Metrics[i].Help = config.Metrics[i].Name
80-
}
81-
if config.Metrics[i].ValueType == "" {
82-
config.Metrics[i].ValueType = ValueTypeUntyped
79+
for _, module := range config.Modules {
80+
for i := 0; i < len(module.Metrics); i++ {
81+
if module.Metrics[i].Type == "" {
82+
module.Metrics[i].Type = ValueScrape
83+
}
84+
if module.Metrics[i].Help == "" {
85+
module.Metrics[i].Help = module.Metrics[i].Name
86+
}
87+
if module.Metrics[i].ValueType == "" {
88+
module.Metrics[i].ValueType = ValueTypeUntyped
89+
}
8390
}
8491
}
8592

0 commit comments

Comments
 (0)