cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.81.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/promapi/config.go

154lines · modecode

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