cloudflare/pint

Public

mirrored from https://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.50.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

internal/promapi/config.go

158lines · modecode

1package promapi
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "io"
8 "log/slog"
9 "net/http"
10 "net/url"
11 "time"
12
13 v1 "github.com/prometheus/client_golang/api/prometheus/v1"
14 "github.com/prymitive/current"
15 "gopkg.in/yaml.v3"
16)
17
18type ConfigSectionGlobal struct {
19 ScrapeInterval time.Duration `yaml:"scrape_interval"`
20 ScrapeTimeout time.Duration `yaml:"scrape_timeout"`
21 EvaluationInterval time.Duration `yaml:"evaluation_interval"`
22 ExternalLabels map[string]string `yaml:"external_labels"`
23}
24
25type PrometheusConfig struct {
26 Global ConfigSectionGlobal `yaml:"global"`
27}
28
29type ConfigResult struct {
30 URI string
31 PublicURI string
32 Config PrometheusConfig
33}
34
35type configQuery struct {
36 prom *Prometheus
37 ctx context.Context
38 timestamp time.Time
39}
40
41func (q configQuery) Run() queryResult {
42 slog.Debug("Getting prometheus configuration", slog.String("uri", q.prom.safeURI))
43
44 ctx, cancel := q.prom.requestContext(q.ctx)
45 defer cancel()
46
47 var qr queryResult
48
49 args := url.Values{}
50 resp, err := q.prom.doRequest(ctx, http.MethodGet, q.Endpoint(), args)
51 if err != nil {
52 qr.err = fmt.Errorf("failed to query Prometheus config: %w", err)
53 return qr
54 }
55 defer resp.Body.Close()
56
57 if resp.StatusCode/100 != 2 {
58 qr.err = tryDecodingAPIError(resp)
59 return qr
60 }
61
62 qr.value, err = streamConfig(resp.Body)
63 if err != nil {
64 prometheusQueryErrorsTotal.WithLabelValues(q.prom.name, "/api/v1/status/config", errReason(err)).Inc()
65 qr.err = fmt.Errorf("failed to decode config data in %s response: %w", q.prom.safeURI, err)
66 }
67 return qr
68}
69
70func (q configQuery) Endpoint() string {
71 return "/api/v1/status/config"
72}
73
74func (q configQuery) String() string {
75 return "/api/v1/status/config"
76}
77
78func (q configQuery) CacheKey() uint64 {
79 return hash(q.prom.unsafeURI, q.Endpoint())
80}
81
82func (q configQuery) CacheTTL() time.Duration {
83 return time.Minute * 10
84}
85
86func (p *Prometheus) Config(ctx context.Context) (*ConfigResult, error) {
87 slog.Debug("Scheduling Prometheus configuration query", slog.String("uri", p.safeURI))
88
89 key := "/api/v1/status/config"
90 p.locker.lock(key)
91 defer p.locker.unlock(key)
92
93 resultChan := make(chan queryResult)
94 p.queries <- queryRequest{
95 query: configQuery{prom: p, ctx: ctx, timestamp: time.Now()},
96 result: resultChan,
97 }
98
99 result := <-resultChan
100 if result.err != nil {
101 return nil, QueryError{err: result.err, msg: decodeError(result.err)}
102 }
103
104 r := ConfigResult{
105 URI: p.safeURI,
106 PublicURI: p.publicURI,
107 Config: result.value.(PrometheusConfig),
108 }
109
110 return &r, nil
111}
112
113func streamConfig(r io.Reader) (cfg PrometheusConfig, err error) {
114 defer dummyReadAll(r)
115
116 var yamlBody, status, errType, errText string
117 decoder := current.Object(
118 current.Key("status", current.Value(func(s string, isNil bool) {
119 status = s
120 })),
121 current.Key("error", current.Value(func(s string, isNil bool) {
122 errText = s
123 })),
124 current.Key("errorType", current.Value(func(s string, isNil bool) {
125 errType = s
126 })),
127 current.Key("data", current.Object(
128 current.Key("yaml", current.Value(func(s string, isNil bool) {
129 yamlBody = s
130 })),
131 )),
132 )
133
134 dec := json.NewDecoder(r)
135 if err = decoder.Stream(dec); err != nil {
136 return cfg, APIError{Status: status, ErrorType: v1.ErrBadResponse, Err: fmt.Sprintf("JSON parse error: %s", err)}
137 }
138
139 if status != "success" {
140 return cfg, APIError{Status: status, ErrorType: decodeErrorType(errType), Err: errText}
141 }
142
143 if err = yaml.Unmarshal([]byte(yamlBody), &cfg); err != nil {
144 return cfg, err
145 }
146
147 if cfg.Global.ScrapeInterval == 0 {
148 cfg.Global.ScrapeInterval = time.Minute
149 }
150 if cfg.Global.ScrapeTimeout == 0 {
151 cfg.Global.ScrapeTimeout = time.Second * 10
152 }
153 if cfg.Global.EvaluationInterval == 0 {
154 cfg.Global.EvaluationInterval = time.Minute
155 }
156
157 return cfg, nil
158}