mirror of
https://github.com/prometheus-community/json_exporter
synced 2025-02-01 20:31:39 +00:00
Support modules configuration (#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>
This commit is contained in:
parent
d43d3ed28e
commit
75ae2b065e
44
README.md
44
README.md
@ -39,35 +39,37 @@ $ cat examples/data.json
|
||||
|
||||
$ cat examples/config.yml
|
||||
---
|
||||
metrics:
|
||||
- name: example_global_value
|
||||
path: "{ .counter }"
|
||||
help: Example of a top-level global value scrape in the json
|
||||
labels:
|
||||
environment: beta # static label
|
||||
location: "planet-{.location}" # dynamic label
|
||||
modules:
|
||||
default:
|
||||
metrics:
|
||||
- name: example_global_value
|
||||
path: "{ .counter }"
|
||||
help: Example of a top-level global value scrape in the json
|
||||
labels:
|
||||
environment: beta # static label
|
||||
location: "planet-{.location}" # dynamic label
|
||||
|
||||
- name: example_value
|
||||
type: object
|
||||
help: Example of sub-level value scrapes from a json
|
||||
path: '{.values[?(@.state == "ACTIVE")]}'
|
||||
labels:
|
||||
environment: beta # static label
|
||||
id: '{.id}' # dynamic label
|
||||
values:
|
||||
active: 1 # static value
|
||||
count: '{.count}' # dynamic value
|
||||
boolean: '{.some_boolean}'
|
||||
- name: example_value
|
||||
type: object
|
||||
help: Example of sub-level value scrapes from a json
|
||||
path: '{.values[?(@.state == "ACTIVE")]}'
|
||||
labels:
|
||||
environment: beta # static label
|
||||
id: '{.id}' # dynamic label
|
||||
values:
|
||||
active: 1 # static value
|
||||
count: '{.count}' # dynamic value
|
||||
boolean: '{.some_boolean}'
|
||||
|
||||
headers:
|
||||
X-Dummy: my-test-header
|
||||
headers:
|
||||
X-Dummy: my-test-header
|
||||
|
||||
$ python -m SimpleHTTPServer 8000 &
|
||||
Serving HTTP on 0.0.0.0 port 8000 ...
|
||||
|
||||
$ ./json_exporter --config.file examples/config.yml &
|
||||
|
||||
$ curl "http://localhost:7979/probe?target=http://localhost:8000/examples/data.json" | grep ^example
|
||||
$ curl "http://localhost:7979/probe?module=default&target=http://localhost:8000/examples/data.json" | grep ^example
|
||||
example_global_value{environment="beta",location="planet-mars"} 1234
|
||||
example_value_active{environment="beta",id="id-A"} 1
|
||||
example_value_active{environment="beta",id="id-C"} 1
|
||||
|
15
cmd/main.go
15
cmd/main.go
@ -16,6 +16,7 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
@ -86,9 +87,19 @@ func probeHandler(w http.ResponseWriter, r *http.Request, logger log.Logger, con
|
||||
defer cancel()
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
module := r.URL.Query().Get("module")
|
||||
if module == "" {
|
||||
module = "default"
|
||||
}
|
||||
if _, ok := config.Modules[module]; !ok {
|
||||
http.Error(w, fmt.Sprintf("Unknown module %q", module), http.StatusBadRequest)
|
||||
level.Debug(logger).Log("msg", "Unknown module", "module", module)
|
||||
return
|
||||
}
|
||||
|
||||
registry := prometheus.NewPedanticRegistry()
|
||||
|
||||
metrics, err := exporter.CreateMetricsList(config)
|
||||
metrics, err := exporter.CreateMetricsList(config.Modules[module])
|
||||
if err != nil {
|
||||
level.Error(logger).Log("msg", "Failed to create metrics list from config", "err", err)
|
||||
}
|
||||
@ -102,7 +113,7 @@ func probeHandler(w http.ResponseWriter, r *http.Request, logger log.Logger, con
|
||||
return
|
||||
}
|
||||
|
||||
fetcher := exporter.NewJSONFetcher(ctx, logger, config, r.URL.Query())
|
||||
fetcher := exporter.NewJSONFetcher(ctx, logger, config.Modules[module], r.URL.Query())
|
||||
data, err := fetcher.FetchJSON(target)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to fetch JSON response. TARGET: "+target+", ERROR: "+err.Error(), http.StatusServiceUnavailable)
|
||||
|
107
cmd/main_test.go
107
cmd/main_test.go
@ -32,9 +32,9 @@ func TestFailIfSelfSignedCA(t *testing.T) {
|
||||
}))
|
||||
defer target.Close()
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL, nil)
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?module=default&target="+target.URL, nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
probeHandler(recorder, req, log.NewNopLogger(), config.Config{})
|
||||
probeHandler(recorder, req, log.NewNopLogger(), config.Config{Modules: map[string]config.Module{"default": {}}})
|
||||
|
||||
resp := recorder.Result()
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
@ -45,13 +45,21 @@ func TestFailIfSelfSignedCA(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSucceedIfSelfSignedCA(t *testing.T) {
|
||||
c := config.Config{}
|
||||
c.HTTPClientConfig.TLSConfig.InsecureSkipVerify = true
|
||||
c := config.Config{
|
||||
Modules: map[string]config.Module{
|
||||
"default": {
|
||||
HTTPClientConfig: pconfig.HTTPClientConfig{
|
||||
TLSConfig: pconfig.TLSConfig{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
target := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
}))
|
||||
defer target.Close()
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL, nil)
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?module=default&target="+target.URL, nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
probeHandler(recorder, req, log.NewNopLogger(), c)
|
||||
|
||||
@ -63,6 +71,29 @@ func TestSucceedIfSelfSignedCA(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultModule(t *testing.T) {
|
||||
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
}))
|
||||
defer target.Close()
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL, nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
probeHandler(recorder, req, log.NewNopLogger(), config.Config{Modules: map[string]config.Module{"default": {}}})
|
||||
|
||||
resp := recorder.Result()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("Default module test fails unexpectedly, expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Module doesn't exist.
|
||||
recorder = httptest.NewRecorder()
|
||||
probeHandler(recorder, req, log.NewNopLogger(), config.Config{Modules: map[string]config.Module{"foo": {}}})
|
||||
resp = recorder.Result()
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Fatalf("Default module test fails unexpectedly, expected 400, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailIfTargetMissing(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo", nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
@ -86,9 +117,9 @@ func TestDefaultAcceptHeader(t *testing.T) {
|
||||
}))
|
||||
defer target.Close()
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL, nil)
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?module=default&target="+target.URL, nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
probeHandler(recorder, req, log.NewNopLogger(), config.Config{})
|
||||
probeHandler(recorder, req, log.NewNopLogger(), config.Config{Modules: map[string]config.Module{"default": {}}})
|
||||
|
||||
resp := recorder.Result()
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
@ -118,7 +149,7 @@ func TestCorrectResponse(t *testing.T) {
|
||||
t.Fatalf("Failed to load config file %s", test.ConfigFile)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL+test.ServeFile, nil)
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?module=default&target="+target.URL+test.ServeFile, nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
probeHandler(recorder, req, log.NewNopLogger(), c)
|
||||
|
||||
@ -145,15 +176,21 @@ func TestBasicAuth(t *testing.T) {
|
||||
}))
|
||||
defer target.Close()
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL, nil)
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?module=default&target="+target.URL, nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
c := config.Config{}
|
||||
auth := &pconfig.BasicAuth{
|
||||
Username: username,
|
||||
Password: pconfig.Secret(password),
|
||||
c := config.Config{
|
||||
Modules: map[string]config.Module{
|
||||
"default": {
|
||||
HTTPClientConfig: pconfig.HTTPClientConfig{
|
||||
BasicAuth: &pconfig.BasicAuth{
|
||||
Username: username,
|
||||
Password: pconfig.Secret(password),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
c.HTTPClientConfig.BasicAuth = auth
|
||||
probeHandler(recorder, req, log.NewNopLogger(), c)
|
||||
|
||||
resp := recorder.Result()
|
||||
@ -175,11 +212,16 @@ func TestBearerToken(t *testing.T) {
|
||||
}))
|
||||
defer target.Close()
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL, nil)
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?module=default&target="+target.URL, nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
c := config.Config{}
|
||||
c := config.Config{
|
||||
Modules: map[string]config.Module{"default": {
|
||||
HTTPClientConfig: pconfig.HTTPClientConfig{
|
||||
BearerToken: pconfig.Secret(token),
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
c.HTTPClientConfig.BearerToken = pconfig.Secret(token)
|
||||
probeHandler(recorder, req, log.NewNopLogger(), c)
|
||||
|
||||
resp := recorder.Result()
|
||||
@ -206,10 +248,15 @@ func TestHTTPHeaders(t *testing.T) {
|
||||
}))
|
||||
defer target.Close()
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL, nil)
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?module=default&target="+target.URL, nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
c := config.Config{}
|
||||
c.Headers = headers
|
||||
c := config.Config{
|
||||
Modules: map[string]config.Module{
|
||||
"default": {
|
||||
Headers: headers,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
probeHandler(recorder, req, log.NewNopLogger(), c)
|
||||
|
||||
@ -264,9 +311,15 @@ func TestBodyPostTemplate(t *testing.T) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest("POST", "http://example.com/foo"+"?target="+target.URL, strings.NewReader(test.Body.Content))
|
||||
req := httptest.NewRequest("POST", "http://example.com/foo"+"?module=default&target="+target.URL, strings.NewReader(test.Body.Content))
|
||||
recorder := httptest.NewRecorder()
|
||||
c := config.Config{Body: test.Body}
|
||||
c := config.Config{
|
||||
Modules: map[string]config.Module{
|
||||
"default": {
|
||||
Body: test.Body,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
probeHandler(recorder, req, log.NewNopLogger(), c)
|
||||
|
||||
@ -351,7 +404,7 @@ func TestBodyPostQuery(t *testing.T) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest("POST", "http://example.com/foo"+"?target="+target.URL, strings.NewReader(test.Body.Content))
|
||||
req := httptest.NewRequest("POST", "http://example.com/foo"+"?module=default&target="+target.URL, strings.NewReader(test.Body.Content))
|
||||
q := req.URL.Query()
|
||||
for k, v := range test.QueryParams {
|
||||
q.Add(k, v)
|
||||
@ -359,7 +412,13 @@ func TestBodyPostQuery(t *testing.T) {
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
c := config.Config{Body: test.Body}
|
||||
c := config.Config{
|
||||
Modules: map[string]config.Module{
|
||||
"default": {
|
||||
Body: test.Body,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
probeHandler(recorder, req, log.NewNopLogger(), c)
|
||||
|
||||
|
@ -46,8 +46,13 @@ const (
|
||||
ValueTypeUntyped ValueType = "untyped"
|
||||
)
|
||||
|
||||
// Config contains metrics and headers defining a configuration
|
||||
// Config contains multiple modules.
|
||||
type Config struct {
|
||||
Modules map[string]Module `yaml:"modules"`
|
||||
}
|
||||
|
||||
// Module contains metrics and headers defining a configuration
|
||||
type Module struct {
|
||||
Headers map[string]string `yaml:"headers,omitempty"`
|
||||
Metrics []Metric `yaml:"metrics"`
|
||||
HTTPClientConfig pconfig.HTTPClientConfig `yaml:"http_client_config,omitempty"`
|
||||
@ -71,15 +76,17 @@ func LoadConfig(configPath string) (Config, error) {
|
||||
}
|
||||
|
||||
// Complete Defaults
|
||||
for i := 0; i < len(config.Metrics); i++ {
|
||||
if config.Metrics[i].Type == "" {
|
||||
config.Metrics[i].Type = ValueScrape
|
||||
}
|
||||
if config.Metrics[i].Help == "" {
|
||||
config.Metrics[i].Help = config.Metrics[i].Name
|
||||
}
|
||||
if config.Metrics[i].ValueType == "" {
|
||||
config.Metrics[i].ValueType = ValueTypeUntyped
|
||||
for _, module := range config.Modules {
|
||||
for i := 0; i < len(module.Metrics); i++ {
|
||||
if module.Metrics[i].Type == "" {
|
||||
module.Metrics[i].Type = ValueScrape
|
||||
}
|
||||
if module.Metrics[i].Help == "" {
|
||||
module.Metrics[i].Help = module.Metrics[i].Name
|
||||
}
|
||||
if module.Metrics[i].ValueType == "" {
|
||||
module.Metrics[i].ValueType = ValueTypeUntyped
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,44 +1,45 @@
|
||||
---
|
||||
metrics:
|
||||
- name: example_global_value
|
||||
path: "{ .counter }"
|
||||
help: Example of a top-level global value scrape in the json
|
||||
labels:
|
||||
environment: beta # static label
|
||||
location: "planet-{.location}" # dynamic label
|
||||
modules:
|
||||
default:
|
||||
metrics:
|
||||
- name: example_global_value
|
||||
path: "{ .counter }"
|
||||
help: Example of a top-level global value scrape in the json
|
||||
labels:
|
||||
environment: beta # static label
|
||||
location: "planet-{.location}" # dynamic label
|
||||
|
||||
- name: example_value
|
||||
type: object
|
||||
help: Example of sub-level value scrapes from a json
|
||||
path: '{.values[?(@.state == "ACTIVE")]}'
|
||||
labels:
|
||||
environment: beta # static label
|
||||
id: '{.id}' # dynamic label
|
||||
values:
|
||||
active: 1 # static value
|
||||
count: '{.count}' # dynamic value
|
||||
boolean: '{.some_boolean}'
|
||||
- name: example_value
|
||||
type: object
|
||||
help: Example of sub-level value scrapes from a json
|
||||
path: '{.values[?(@.state == "ACTIVE")]}'
|
||||
labels:
|
||||
environment: beta # static label
|
||||
id: '{.id}' # dynamic label
|
||||
values:
|
||||
active: 1 # static value
|
||||
count: '{.count}' # dynamic value
|
||||
boolean: '{.some_boolean}'
|
||||
headers:
|
||||
X-Dummy: my-test-header
|
||||
|
||||
headers:
|
||||
X-Dummy: my-test-header
|
||||
# If 'body' is set, it will be sent by the exporter as the body content in the scrape request. The HTTP method will also be set as 'POST' in this case.
|
||||
# body:
|
||||
# content: |
|
||||
# {"time_diff": "1m25s", "anotherVar": "some value"}
|
||||
|
||||
# If 'body' is set, it will be sent by the exporter as the body content in the scrape request. The HTTP method will also be set as 'POST' in this case.
|
||||
# body:
|
||||
# content: |
|
||||
# {"time_diff": "1m25s", "anotherVar": "some value"}
|
||||
# The body content can also be a Go Template (https://golang.org/pkg/text/template), with all the functions from the Sprig library (https://masterminds.github.io/sprig/) available. All the query parameters sent by prometheus in the scrape query to the exporter, are available in the template.
|
||||
# body:
|
||||
# content: |
|
||||
# {"time_diff": "{{ duration `95` }}","anotherVar": "{{ .myVal | first }}"}
|
||||
# templatize: true
|
||||
|
||||
# The body content can also be a Go Template (https://golang.org/pkg/text/template), with all the functions from the Sprig library (https://masterminds.github.io/sprig/) available. All the query parameters sent by prometheus in the scrape query to the exporter, are available in the template.
|
||||
# body:
|
||||
# content: |
|
||||
# {"time_diff": "{{ duration `95` }}","anotherVar": "{{ .myVal | first }}"}
|
||||
# templatize: true
|
||||
|
||||
# For full http client config parameters, ref: https://pkg.go.dev/github.com/prometheus/common/config?tab=doc#HTTPClientConfig
|
||||
#
|
||||
# http_client_config:
|
||||
# tls_config:
|
||||
# insecure_skip_verify: true
|
||||
# basic_auth:
|
||||
# username: myuser
|
||||
# #password: veryverysecret
|
||||
# password_file: /tmp/mysecret.txt
|
||||
# For full http client config parameters, ref: https://pkg.go.dev/github.com/prometheus/common/config?tab=doc#HTTPClientConfig
|
||||
#
|
||||
# http_client_config:
|
||||
# tls_config:
|
||||
# insecure_skip_verify: true
|
||||
# basic_auth:
|
||||
# username: myuser
|
||||
# #password: veryverysecret
|
||||
# password_file: /tmp/mysecret.txt
|
||||
|
@ -15,6 +15,8 @@ scrape_configs:
|
||||
## gather the metrics from third party json sources, via the json exporter
|
||||
- job_name: json
|
||||
metrics_path: /probe
|
||||
params:
|
||||
module: [default]
|
||||
static_configs:
|
||||
- targets:
|
||||
- http://host-1.foobar.com/dummy/data.json
|
||||
|
@ -62,7 +62,7 @@ func SanitizeValue(s string) (float64, error) {
|
||||
return value, fmt.Errorf(resultErr)
|
||||
}
|
||||
|
||||
func CreateMetricsList(c config.Config) ([]JSONMetric, error) {
|
||||
func CreateMetricsList(c config.Module) ([]JSONMetric, error) {
|
||||
var (
|
||||
metrics []JSONMetric
|
||||
valueType prometheus.ValueType
|
||||
@ -127,17 +127,17 @@ func CreateMetricsList(c config.Config) ([]JSONMetric, error) {
|
||||
}
|
||||
|
||||
type JSONFetcher struct {
|
||||
config config.Config
|
||||
module config.Module
|
||||
ctx context.Context
|
||||
logger log.Logger
|
||||
method string
|
||||
body io.Reader
|
||||
}
|
||||
|
||||
func NewJSONFetcher(ctx context.Context, logger log.Logger, c config.Config, tplValues url.Values) *JSONFetcher {
|
||||
method, body := renderBody(logger, c.Body, tplValues)
|
||||
func NewJSONFetcher(ctx context.Context, logger log.Logger, m config.Module, tplValues url.Values) *JSONFetcher {
|
||||
method, body := renderBody(logger, m.Body, tplValues)
|
||||
return &JSONFetcher{
|
||||
config: c,
|
||||
module: m,
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
method: method,
|
||||
@ -146,7 +146,7 @@ func NewJSONFetcher(ctx context.Context, logger log.Logger, c config.Config, tpl
|
||||
}
|
||||
|
||||
func (f *JSONFetcher) FetchJSON(endpoint string) ([]byte, error) {
|
||||
httpClientConfig := f.config.HTTPClientConfig
|
||||
httpClientConfig := f.module.HTTPClientConfig
|
||||
client, err := pconfig.NewClientFromConfig(httpClientConfig, "fetch_json", pconfig.WithKeepAlivesDisabled(), pconfig.WithHTTP2Disabled())
|
||||
if err != nil {
|
||||
level.Error(f.logger).Log("msg", "Error generating HTTP client", "err", err)
|
||||
@ -161,7 +161,7 @@ func (f *JSONFetcher) FetchJSON(endpoint string) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for key, value := range f.config.Headers {
|
||||
for key, value := range f.module.Headers {
|
||||
req.Header.Add(key, value)
|
||||
}
|
||||
if req.Header.Get("Accept") == "" {
|
||||
|
@ -1,21 +1,22 @@
|
||||
---
|
||||
metrics:
|
||||
- name: example_global_value
|
||||
path: "{ .counter }"
|
||||
help: Example of a top-level global value scrape in the json
|
||||
labels:
|
||||
environment: beta # static label
|
||||
location: "planet-{.location}" # dynamic label
|
||||
|
||||
- name: example_value
|
||||
type: object
|
||||
help: Example of sub-level value scrapes from a json
|
||||
path: '{.values[?(@.state == "ACTIVE")]}'
|
||||
labels:
|
||||
environment: beta # static label
|
||||
id: '{.id}' # dynamic label
|
||||
values:
|
||||
active: 1 # static value
|
||||
count: '{.count}' # dynamic value
|
||||
boolean: '{.some_boolean}'
|
||||
modules:
|
||||
default:
|
||||
metrics:
|
||||
- name: example_global_value
|
||||
path: "{ .counter }"
|
||||
help: Example of a top-level global value scrape in the json
|
||||
labels:
|
||||
environment: beta # static label
|
||||
location: "planet-{.location}" # dynamic label
|
||||
|
||||
- name: example_value
|
||||
type: object
|
||||
help: Example of sub-level value scrapes from a json
|
||||
path: '{.values[?(@.state == "ACTIVE")]}'
|
||||
labels:
|
||||
environment: beta # static label
|
||||
id: '{.id}' # dynamic label
|
||||
values:
|
||||
active: 1 # static value
|
||||
count: '{.count}' # dynamic value
|
||||
boolean: '{.some_boolean}'
|
||||
|
Loading…
Reference in New Issue
Block a user