/
grafana.go
209 lines (183 loc) · 5.54 KB
/
grafana.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package grafana
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httputil"
"strings"
)
var (
//ErrUnauthorized error
ErrUnauthorized = newError(401, "Not authenticated")
//ErrForbidden error
ErrForbidden = newError(403, "Access refused or not allowed")
//ErrNotFound error
ErrNotFound = newError(404, "Resource not found")
//ErrConflict error
ErrConflict = newError(409, "Conflict")
//ErrTooManyRequests error
ErrTooManyRequests = newError(429, "You have exceeded the API call rate limit")
//ErrNotImplemented error
ErrNotImplemented = newError(501, "Not implemented")
//ErrBadGateway error
ErrBadGateway = newError(502, "Bad gateway")
//ErrServiceUnavailable error
ErrServiceUnavailable = newError(503, "Service unavailable")
//ErrGatewayTimeout error
ErrGatewayTimeout = newError(504, "Gateway timeout")
)
//Error struct for http errors comming from grafana
type Error struct {
StatusCode int
Message string
}
func (e *Error) Error() string {
return fmt.Sprintf("Error %d, %s", e.StatusCode, e.Message)
}
// New returns an error that formats as the given text.
func newError(statusCode int, text string) error {
return &Error{
StatusCode: statusCode,
Message: text,
}
}
//Client for http requests
type Client struct {
apiURI string
username string
password string
verbose bool
}
// New creates a new Grafana API client.
func New(apiURI, username, password string) *Client {
return &Client{
apiURI: apiURI,
username: username,
password: password,
}
}
//SetVerbose enables verbous logging of requests and responses
func (c *Client) SetVerbose(verbose bool) {
c.verbose = verbose
}
type request struct {
method string
endpoint string
data interface{}
query map[string]string
}
//func (c *Client) doRequest(method, endpoint string, data interface{}, query map[string]string) (*http.Response, error) {
func (c *Client) doRequest(req *request) (*http.Response, error) {
client := http.DefaultClient
// Encode data if we are passed an object.
b := bytes.NewBuffer(nil)
if req.data != nil {
// Create the encoder.
enc := json.NewEncoder(b)
if err := enc.Encode(req.data); err != nil {
return nil, fmt.Errorf("json encoding data for doRequest failed: %v", err)
}
}
// Create the request.
uri := fmt.Sprintf("%s/%s", c.apiURI, strings.Trim(req.endpoint, "/"))
httpReq, err := http.NewRequest(req.method, uri, b)
if err != nil {
return nil, fmt.Errorf("creating %s request to %s failed: %v", req.method, uri, err)
}
// Set the correct headers.
httpReq.Header.Set("Authorization", AuthorizationTypeBasic+basicAuth(c.username, c.password))
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("Accept", "application/json")
// Set query parameters if any
if req.query != nil {
q := httpReq.URL.Query()
for k, v := range req.query {
q.Set(k, v)
}
httpReq.URL.RawQuery = q.Encode()
}
if c.verbose {
debug, err := httputil.DumpRequestOut(httpReq, true)
if err == nil {
fmt.Printf("%s\n", string(debug))
}
}
// Do the request.
resp, err := client.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("performing %s request to %s failed: %v", req.method, uri, err)
}
if c.verbose {
debug, err := httputil.DumpResponse(resp, true)
if err == nil {
fmt.Printf("%s\n", string(debug))
}
}
// Check that the response status code was OK.
if resp.StatusCode >= 400 {
// Read the body of the request, ignore the error since we are already in the error state.
body, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close()
// Create a friendly error message based off the status code returned.
var message string
switch resp.StatusCode {
case http.StatusBadRequest: //400
return nil, decodeError(resp, body)
case http.StatusUnauthorized: // 401
return nil, ErrUnauthorized
case http.StatusForbidden: // 403
return nil, ErrForbidden
case http.StatusNotFound: // 404
return nil, ErrNotFound
case http.StatusConflict:
return nil, ErrConflict
case http.StatusTooManyRequests: // 429
return nil, ErrTooManyRequests
case http.StatusInternalServerError: // 500
return nil, decodeError(resp, body)
case http.StatusNotImplemented: // 501
return nil, ErrNotImplemented
case http.StatusBadGateway: // 502
return nil, ErrBadGateway
case http.StatusServiceUnavailable: // 503
return nil, ErrServiceUnavailable
case http.StatusGatewayTimeout: // 504
return nil, ErrServiceUnavailable
}
return nil, fmt.Errorf("%s request to %s returned status code %d: message -> %s\nbody -> %s", req.method, uri, resp.StatusCode, message, string(body))
}
// Return errors on the API errors.
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
return resp, fmt.Errorf("API error")
}
return resp, nil
}
func decodeResponse(resp *http.Response, v interface{}) error {
// Copy buffer so we have a backup.
buf := new(bytes.Buffer)
if _, err := io.Copy(buf, resp.Body); err != nil {
return err
}
defer resp.Body.Close()
return json.Unmarshal(buf.Bytes(), v)
}
func decodeError(resp *http.Response, body []byte) error {
var msg map[string]interface{}
err := json.Unmarshal(body, &msg)
if err != nil {
return fmt.Errorf("Unexpected status code %d", resp.StatusCode)
}
msgStr := msg["message"].(string)
if msgStr == "" {
return fmt.Errorf("Unexpected status code %d", resp.StatusCode)
}
return newError(resp.StatusCode, msg["message"].(string))
}
func basicAuth(username, password string) string {
auth := username + ":" + password
return base64.StdEncoding.EncodeToString([]byte(auth))
}