Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/mimir/client/client.go
4096 views
1
package client
2
3
import (
4
"bufio"
5
"bytes"
6
"context"
7
"errors"
8
"fmt"
9
"io"
10
"net/http"
11
"net/url"
12
"strings"
13
14
log "github.com/go-kit/log"
15
"github.com/prometheus/client_golang/prometheus"
16
"github.com/prometheus/common/config"
17
"github.com/prometheus/prometheus/model/rulefmt"
18
weaveworksClient "github.com/weaveworks/common/http/client"
19
"github.com/weaveworks/common/instrument"
20
"github.com/weaveworks/common/user"
21
)
22
23
const (
24
rulerAPIPath = "/prometheus/config/v1/rules"
25
legacyAPIPath = "/api/v1/rules"
26
)
27
28
var (
29
ErrNoConfig = errors.New("No config exists for this user")
30
ErrResourceNotFound = errors.New("requested resource not found")
31
)
32
33
// Config is used to configure a MimirClient.
34
type Config struct {
35
ID string
36
Address string
37
UseLegacyRoutes bool
38
HTTPClientConfig config.HTTPClientConfig
39
}
40
41
type Interface interface {
42
CreateRuleGroup(ctx context.Context, namespace string, rg rulefmt.RuleGroup) error
43
DeleteRuleGroup(ctx context.Context, namespace, groupName string) error
44
ListRules(ctx context.Context, namespace string) (map[string][]rulefmt.RuleGroup, error)
45
}
46
47
// MimirClient is a client to the Mimir API.
48
type MimirClient struct {
49
id string
50
51
endpoint *url.URL
52
client weaveworksClient.Requester
53
apiPath string
54
logger log.Logger
55
}
56
57
// New returns a new MimirClient.
58
func New(logger log.Logger, cfg Config, timingHistogram *prometheus.HistogramVec) (*MimirClient, error) {
59
endpoint, err := url.Parse(cfg.Address)
60
if err != nil {
61
return nil, err
62
}
63
client, err := config.NewClientFromConfig(cfg.HTTPClientConfig, "GrafanaAgent", config.WithHTTP2Disabled())
64
if err != nil {
65
return nil, err
66
}
67
68
path := rulerAPIPath
69
if cfg.UseLegacyRoutes {
70
path = legacyAPIPath
71
}
72
73
collector := instrument.NewHistogramCollector(timingHistogram)
74
timedClient := weaveworksClient.NewTimedClient(client, collector)
75
76
return &MimirClient{
77
id: cfg.ID,
78
endpoint: endpoint,
79
client: timedClient,
80
apiPath: path,
81
logger: logger,
82
}, nil
83
}
84
85
func (r *MimirClient) doRequest(operation, path, method string, payload []byte) (*http.Response, error) {
86
req, err := buildRequest(operation, path, method, *r.endpoint, payload)
87
if err != nil {
88
return nil, err
89
}
90
91
if r.id != "" {
92
req.Header.Add(user.OrgIDHeaderName, r.id)
93
}
94
95
resp, err := r.client.Do(req)
96
if err != nil {
97
return nil, err
98
}
99
100
if err := checkResponse(resp); err != nil {
101
_ = resp.Body.Close()
102
return nil, fmt.Errorf("error %s %s: %w", method, path, err)
103
}
104
105
return resp, nil
106
}
107
108
// checkResponse checks an API response for errors.
109
func checkResponse(r *http.Response) error {
110
if 200 <= r.StatusCode && r.StatusCode <= 299 {
111
return nil
112
}
113
114
var msg, errMsg string
115
scanner := bufio.NewScanner(io.LimitReader(r.Body, 512))
116
if scanner.Scan() {
117
msg = scanner.Text()
118
}
119
120
if msg == "" {
121
errMsg = fmt.Sprintf("server returned HTTP status %s", r.Status)
122
} else {
123
errMsg = fmt.Sprintf("server returned HTTP status %s: %s", r.Status, msg)
124
}
125
126
if r.StatusCode == http.StatusNotFound {
127
return ErrResourceNotFound
128
}
129
130
return errors.New(errMsg)
131
}
132
133
func joinPath(baseURLPath, targetPath string) string {
134
// trim exactly one slash at the end of the base URL, this expects target
135
// path to always start with a slash
136
return strings.TrimSuffix(baseURLPath, "/") + targetPath
137
}
138
139
func buildRequest(op, p, m string, endpoint url.URL, payload []byte) (*http.Request, error) {
140
// parse path parameter again (as it already contains escaped path information
141
pURL, err := url.Parse(p)
142
if err != nil {
143
return nil, err
144
}
145
146
// if path or endpoint contains escaping that requires RawPath to be populated, also join rawPath
147
if pURL.RawPath != "" || endpoint.RawPath != "" {
148
endpoint.RawPath = joinPath(endpoint.EscapedPath(), pURL.EscapedPath())
149
}
150
endpoint.Path = joinPath(endpoint.Path, pURL.Path)
151
r, err := http.NewRequest(m, endpoint.String(), bytes.NewBuffer(payload))
152
if err != nil {
153
return nil, err
154
}
155
r = r.WithContext(context.WithValue(r.Context(), weaveworksClient.OperationNameContextKey, op))
156
157
return r, nil
158
}
159
160