cloudflare/pint

Public

mirrored fromhttps://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.75.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/promapi/flags.go

130lines · 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)
16
17const (
18 APIPathFlags = "/api/v1/status/flags"
19)
20
21type FlagsResult struct {
22 Flags v1.FlagsResult
23 URI string
24}
25
26type flagsQuery struct {
27 prom *Prometheus
28 ctx context.Context
29 timestamp time.Time
30}
31
32func (q flagsQuery) Run() queryResult {
33 slog.Debug("Getting prometheus flags", slog.String("uri", q.prom.safeURI))
34
35 ctx, cancel := q.prom.requestContext(q.ctx)
36 defer cancel()
37
38 var qr queryResult
39
40 args := url.Values{}
41 resp, err := q.prom.doRequest(ctx, http.MethodGet, q.Endpoint(), args)
42 if err != nil {
43 qr.err = fmt.Errorf("failed to query Prometheus flags: %w", err)
44 return qr
45 }
46 defer resp.Body.Close()
47
48 if resp.StatusCode/100 != 2 {
49 qr.err = tryDecodingAPIError(resp)
50 return qr
51 }
52
53 flags, err := streamFlags(resp.Body)
54 qr.value, qr.err = flags, err
55 return qr
56}
57
58func (q flagsQuery) Endpoint() string {
59 return APIPathFlags
60}
61
62func (q flagsQuery) String() string {
63 return APIPathFlags
64}
65
66func (q flagsQuery) CacheKey() uint64 {
67 return hash(q.prom.unsafeURI, q.Endpoint())
68}
69
70func (q flagsQuery) CacheTTL() time.Duration {
71 return time.Minute * 10
72}
73
74func (prom *Prometheus) Flags(ctx context.Context) (*FlagsResult, error) {
75 slog.Debug("Scheduling Prometheus flags query", slog.String("uri", prom.safeURI))
76
77 prom.locker.lock(APIPathFlags)
78 defer prom.locker.unlock(APIPathFlags)
79
80 resultChan := make(chan queryResult)
81 prom.queries <- queryRequest{
82 query: flagsQuery{prom: prom, ctx: ctx, timestamp: time.Now()},
83 result: resultChan,
84 }
85
86 result := <-resultChan
87 if result.err != nil {
88 return nil, QueryError{err: result.err, msg: decodeError(result.err)}
89 }
90
91 r := FlagsResult{
92 URI: prom.publicURI,
93 Flags: result.value.(v1.FlagsResult),
94 }
95
96 return &r, nil
97}
98
99func streamFlags(r io.Reader) (flags v1.FlagsResult, err error) {
100 defer dummyReadAll(r)
101
102 var status, errType, errText string
103 errText = "empty response object"
104 flags = v1.FlagsResult{}
105 decoder := current.Object(
106 current.Key("status", current.Value(func(s string, _ bool) {
107 status = s
108 })),
109 current.Key("error", current.Value(func(s string, _ bool) {
110 errText = s
111 })),
112 current.Key("errorType", current.Value(func(s string, _ bool) {
113 errType = s
114 })),
115 current.Key("data", current.Map(func(k, v string) {
116 flags[k] = v
117 })),
118 )
119
120 dec := json.NewDecoder(r)
121 if err = decoder.Stream(dec); err != nil {
122 return nil, APIError{Status: status, ErrorType: v1.ErrBadResponse, Err: fmt.Sprintf("JSON parse error: %s", err)}
123 }
124
125 if status != "success" {
126 return nil, APIError{Status: status, ErrorType: decodeErrorType(errType), Err: errText}
127 }
128
129 return flags, nil
130}
131