Skip to content

Commit 66a7bce

Browse files
committed
Return HTTP response codes as typed errors
This allows to differentiate a datasource not found from other errors, e.g. ```go _, err = client.GetDatasource(ctx, dsID) if errors.Is(err, sdk.ErrNotFound) { fmt.Fprintf(os.Stderr, "Creating new datasource %s (id=%d)\n", ds.Name, ds.ID) } ```
1 parent 9de4d14 commit 66a7bce

12 files changed

+264
-105
lines changed

README.md

+17-14
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ It was made foremost for
1717
later separated from it and moved to this new repository because the
1818
library is useful per se.
1919

20+
The library requires at least Go 1.13.
21+
2022
## Library design principles
2123

2224
1. SDK offers client functionality so it covers Grafana REST API with
@@ -102,20 +104,7 @@ datasources. State of support for misc API parts noted below.
102104
| Frontend settings | - |
103105
| Admin | partially |
104106

105-
There is no exact roadmap. The integration tests are being run against the
106-
following Grafana versions:
107-
108-
* [6.7.1](./travis.yml)
109-
* [6.6.2](/.travis.yml)
110-
* [6.5.3](/.travis.yml)
111-
* [6.4.5](/.travis.yml)
112-
113-
With the following Go versions:
114-
115-
* 1.14.x
116-
* 1.13.x
117-
* 1.12.x
118-
* 1.11.x
107+
The integration tests are being run for the Grafana and Go versions listed in [`.github/workflows/go.yml`](.github/workflows/go.yml).
119108

120109
I still have interest to this library development but not always have
121110
time for it. So I gladly accept new contributions. Drop an issue or
@@ -136,3 +125,17 @@ https://github.com/grafana-tools/sdk
136125
* [github.com/raintank/memo](https://github.com/raintank/memo) — send slack mentions to Grafana annotations.
137126
* [github.com/retzkek/grafctl](https://github.com/retzkek/grafctl) — backup/restore/track dashboards with git.
138127
* [github.com/grafana/grizzly](https://github.com/grafana/grizzly) — manage Grafana dashboards via CLI and libsonnet/jsonnet
128+
129+
## Running tests
130+
131+
* Unit tests:
132+
```command
133+
$ go test ./...
134+
```
135+
136+
* Integration tests:
137+
138+
```command
139+
$ GRAFANA_VERSION=6.7.1 docker-compose up -d
140+
$ GRAFANA_INTEGRATION=1 go test ./...
141+
```

rest-admin.go

+16-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"net/http"
78
)
89

910
// CreateUser creates a new global user.
@@ -14,13 +15,17 @@ func (r *Client) CreateUser(ctx context.Context, user User) (StatusMessage, erro
1415
raw []byte
1516
resp StatusMessage
1617
err error
18+
code int
1719
)
1820
if raw, err = json.Marshal(user); err != nil {
1921
return StatusMessage{}, err
2022
}
21-
if raw, _, err = r.post(ctx, "api/admin/users", nil, raw); err != nil {
23+
if raw, code, err = r.post(ctx, "api/admin/users", nil, raw); err != nil {
2224
return StatusMessage{}, err
2325
}
26+
if code != http.StatusOK {
27+
return StatusMessage{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
28+
}
2429
if err = json.Unmarshal(raw, &resp); err != nil {
2530
return StatusMessage{}, err
2631
}
@@ -35,13 +40,17 @@ func (r *Client) UpdateUserPermissions(ctx context.Context, permissions UserPerm
3540
raw []byte
3641
reply StatusMessage
3742
err error
43+
code int
3844
)
3945
if raw, err = json.Marshal(permissions); err != nil {
4046
return StatusMessage{}, err
4147
}
42-
if raw, _, err = r.put(ctx, fmt.Sprintf("api/admin/users/%d/permissions", uid), nil, raw); err != nil {
48+
if raw, code, err = r.put(ctx, fmt.Sprintf("api/admin/users/%d/permissions", uid), nil, raw); err != nil {
4349
return StatusMessage{}, err
4450
}
51+
if code != http.StatusOK {
52+
return StatusMessage{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
53+
}
4554
err = json.Unmarshal(raw, &reply)
4655
return reply, err
4756
}
@@ -54,11 +63,15 @@ func (r *Client) SwitchUserContext(ctx context.Context, uid uint, oid uint) (Sta
5463
raw []byte
5564
resp StatusMessage
5665
err error
66+
code int
5767
)
5868

59-
if raw, _, err = r.post(ctx, fmt.Sprintf("/api/users/%d/using/%d", uid, oid), nil, raw); err != nil {
69+
if raw, code, err = r.post(ctx, fmt.Sprintf("/api/users/%d/using/%d", uid, oid), nil, raw); err != nil {
6070
return StatusMessage{}, err
6171
}
72+
if code != http.StatusOK {
73+
return StatusMessage{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
74+
}
6275
if err = json.Unmarshal(raw, &resp); err != nil {
6376
return StatusMessage{}, err
6477
}

rest-alertnotification.go

+9-8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"encoding/json"
2222
"fmt"
23+
"net/http"
2324
)
2425

2526
// GetAllAlertNotifications gets all alert notification channels.
@@ -34,7 +35,7 @@ func (c *Client) GetAllAlertNotifications(ctx context.Context) ([]AlertNotificat
3435
if raw, code, err = c.get(ctx, "api/alert-notifications", nil); err != nil {
3536
return nil, err
3637
}
37-
if code != 200 {
38+
if code != http.StatusOK {
3839
return nil, fmt.Errorf("HTTP error %d: returns %s", code, raw)
3940
}
4041
err = json.Unmarshal(raw, &an)
@@ -53,7 +54,7 @@ func (c *Client) GetAlertNotificationUID(ctx context.Context, uid string) (Alert
5354
if raw, code, err = c.get(ctx, fmt.Sprintf("api/alert-notifications/uid/%s", uid), nil); err != nil {
5455
return an, err
5556
}
56-
if code != 200 {
57+
if code != http.StatusOK {
5758
return an, fmt.Errorf("HTTP error %d: returns %s", code, raw)
5859
}
5960
err = json.Unmarshal(raw, &an)
@@ -72,7 +73,7 @@ func (c *Client) GetAlertNotificationID(ctx context.Context, id uint) (AlertNoti
7273
if raw, code, err = c.get(ctx, fmt.Sprintf("api/alert-notifications/%d", id), nil); err != nil {
7374
return an, err
7475
}
75-
if code != 200 {
76+
if code != http.StatusOK {
7677
return an, fmt.Errorf("HTTP error %d: returns %s", code, raw)
7778
}
7879
err = json.Unmarshal(raw, &an)
@@ -93,7 +94,7 @@ func (c *Client) CreateAlertNotification(ctx context.Context, an AlertNotificati
9394
if raw, code, err = c.post(ctx, "api/alert-notifications", nil, raw); err != nil {
9495
return -1, err
9596
}
96-
if code != 200 {
97+
if code != http.StatusOK {
9798
return -1, fmt.Errorf("HTTP error %d: returns %s", code, raw)
9899
}
99100
result := struct {
@@ -117,7 +118,7 @@ func (c *Client) UpdateAlertNotificationUID(ctx context.Context, an AlertNotific
117118
if raw, code, err = c.put(ctx, fmt.Sprintf("api/alert-notifications/uid/%s", uid), nil, raw); err != nil {
118119
return err
119120
}
120-
if code != 200 {
121+
if code != http.StatusOK {
121122
return fmt.Errorf("HTTP error %d: returns %s", code, raw)
122123
}
123124
return nil
@@ -137,7 +138,7 @@ func (c *Client) UpdateAlertNotificationID(ctx context.Context, an AlertNotifica
137138
if raw, code, err = c.put(ctx, fmt.Sprintf("api/alert-notifications/%d", id), nil, raw); err != nil {
138139
return err
139140
}
140-
if code != 200 {
141+
if code != http.StatusOK {
141142
return fmt.Errorf("HTTP error %d: returns %s", code, raw)
142143
}
143144
return nil
@@ -154,7 +155,7 @@ func (c *Client) DeleteAlertNotificationUID(ctx context.Context, uid string) err
154155
if raw, code, err = c.delete(ctx, fmt.Sprintf("api/alert-notifications/uid/%s", uid)); err != nil {
155156
return err
156157
}
157-
if code != 200 {
158+
if code != http.StatusOK {
158159
return fmt.Errorf("HTTP error %d: returns %s", code, raw)
159160
}
160161
return nil
@@ -171,7 +172,7 @@ func (c *Client) DeleteAlertNotificationID(ctx context.Context, id uint) error {
171172
if raw, code, err = c.delete(ctx, fmt.Sprintf("api/alert-notifications/%d", id)); err != nil {
172173
return err
173174
}
174-
if code != 200 {
175+
if code != http.StatusOK {
175176
return fmt.Errorf("HTTP error %d: returns %s", code, raw)
176177
}
177178
return nil

rest-annotation.go

+21-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"net/http"
78
"net/url"
89
"strconv"
910
"time"
@@ -19,13 +20,17 @@ func (r *Client) CreateAnnotation(ctx context.Context, a CreateAnnotationRequest
1920
raw []byte
2021
resp StatusMessage
2122
err error
23+
code int
2224
)
2325
if raw, err = json.Marshal(a); err != nil {
2426
return StatusMessage{}, errors.Wrap(err, "marshal request")
2527
}
26-
if raw, _, err = r.post(ctx, "api/annotations", nil, raw); err != nil {
28+
if raw, code, err = r.post(ctx, "api/annotations", nil, raw); err != nil {
2729
return StatusMessage{}, errors.Wrap(err, "create annotation")
2830
}
31+
if code != http.StatusOK {
32+
return StatusMessage{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
33+
}
2934
if err = json.Unmarshal(raw, &resp); err != nil {
3035
return StatusMessage{}, errors.Wrap(err, "unmarshal response message")
3136
}
@@ -38,13 +43,17 @@ func (r *Client) PatchAnnotation(ctx context.Context, id uint, a PatchAnnotation
3843
raw []byte
3944
resp StatusMessage
4045
err error
46+
code int
4147
)
4248
if raw, err = json.Marshal(a); err != nil {
4349
return StatusMessage{}, errors.Wrap(err, "marshal request")
4450
}
45-
if raw, _, err = r.patch(ctx, fmt.Sprintf("api/annotations/%d", id), nil, raw); err != nil {
51+
if raw, code, err = r.patch(ctx, fmt.Sprintf("api/annotations/%d", id), nil, raw); err != nil {
4652
return StatusMessage{}, errors.Wrap(err, "patch annotation")
4753
}
54+
if code != http.StatusOK {
55+
return StatusMessage{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
56+
}
4857
if err = json.Unmarshal(raw, &resp); err != nil {
4958
return StatusMessage{}, errors.Wrap(err, "unmarshal response message")
5059
}
@@ -58,15 +67,19 @@ func (r *Client) GetAnnotations(ctx context.Context, params ...GetAnnotationsPar
5867
err error
5968
resp []AnnotationResponse
6069
requestParams = make(url.Values)
70+
code int
6171
)
6272

6373
for _, p := range params {
6474
p(requestParams)
6575
}
6676

67-
if raw, _, err = r.get(ctx, "api/annotations", requestParams); err != nil {
77+
if raw, code, err = r.get(ctx, "api/annotations", requestParams); err != nil {
6878
return nil, errors.Wrap(err, "get annotations")
6979
}
80+
if code != http.StatusOK {
81+
return nil, fmt.Errorf("HTTP error %d: returns %s", code, raw)
82+
}
7083
if err = json.Unmarshal(raw, &resp); err != nil {
7184
return nil, errors.Wrap(err, "unmarshal response message")
7285
}
@@ -79,11 +92,15 @@ func (r *Client) DeleteAnnotation(ctx context.Context, id uint) (StatusMessage,
7992
raw []byte
8093
err error
8194
resp StatusMessage
95+
code int
8296
)
8397

84-
if raw, _, err = r.delete(ctx, fmt.Sprintf("api/annotations/%d", id)); err != nil {
98+
if raw, code, err = r.delete(ctx, fmt.Sprintf("api/annotations/%d", id)); err != nil {
8599
return StatusMessage{}, errors.Wrap(err, "delete annotation")
86100
}
101+
if code != http.StatusOK {
102+
return StatusMessage{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
103+
}
87104
if err = json.Unmarshal(raw, &resp); err != nil {
88105
return StatusMessage{}, errors.Wrap(err, "unmarshal response message")
89106
}

rest-common.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package sdk
2+
3+
import "errors"
4+
5+
var (
6+
ErrNotFound = errors.New("not found")
7+
ErrAlreadyExists = errors.New("already exists")
8+
ErrNotAccessDenied = errors.New("access denied")
9+
ErrNotAuthorized = errors.New("not authorized")
10+
ErrCannotCreate = errors.New("cannot create; see body for details")
11+
)

0 commit comments

Comments
 (0)