cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.83.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/promapi/config.go

163lines · modecode

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