Brendan Abolivier пре 7 година
родитељ
комит
bd07b371ce
Signed by: Brendan Abolivier <contact@brendanabolivier.com> GPG key ID: 8EF1500759F70623
2 измењених фајлова са 73 додато и 4 уклоњено
  1. 34
    0
      src/grafana/client.go
  2. 39
    4
      src/grafana/dashboards.go

+ 34
- 0
src/grafana/client.go Прегледај датотеку

@@ -8,13 +8,18 @@ import (
8 8
 	"strings"
9 9
 )
10 10
 
11
+// Client implements a Grafana API client, and contains the instance's base URL
12
+// and API key, along with an HTTP client used to request the API.
11 13
 type Client struct {
12 14
 	BaseURL    string
13 15
 	APIKey     string
14 16
 	httpClient *http.Client
15 17
 }
16 18
 
19
+// NewClient returns a new Grafana API client from a given base URL and API key.
17 20
 func NewClient(baseURL string, apiKey string) (c *Client) {
21
+	// Grafana doesn't support double slashes in the API routes, so we strip the
22
+	// last slash if there's one, because request() will append one anyway.
18 23
 	if strings.HasSuffix(baseURL, "/") {
19 24
 		baseURL = baseURL[:len(baseURL)-1]
20 25
 	}
@@ -26,52 +31,81 @@ func NewClient(baseURL string, apiKey string) (c *Client) {
26 31
 	}
27 32
 }
28 33
 
34
+// request preforms an HTTP request on a given endpoint, with a given method and
35
+// body. The endpoint is the Grafana API route to request, without the "/api/"
36
+// part. If the request doesn't require a body, the function has to be called
37
+// with "nil" as the "body" parameter.
38
+// Returns the response body (as a []byte containing JSON data).
39
+// Returns an error if there was an issue initialising the request, performing
40
+// it or reading the response body. Also returns an error on non-200 response
41
+// status codes. If the status code is 404, a standard error is returned, if the
42
+// status code is neither 200 nor 404 an error of type httpUnkownError is
43
+// returned.
29 44
 func (c *Client) request(method string, endpoint string, body []byte) ([]byte, error) {
30 45
 	url := c.BaseURL + "/api/" + endpoint
31 46
 
47
+	// Create the request
32 48
 	req, err := http.NewRequest(method, url, bytes.NewBuffer(body))
33 49
 	if err != nil {
34 50
 		return nil, err
35 51
 	}
36 52
 
53
+	// Add the API key to the request as an Authorization HTTP header
37 54
 	authHeader := fmt.Sprintf("Bearer %s", c.APIKey)
38 55
 	req.Header.Add("Authorization", authHeader)
39 56
 
57
+	// If the request isn't a GET, the body will be sent as JSON, so we need to
58
+	// append the appropriate header
40 59
 	if method != "GET" {
41 60
 		req.Header.Add("Content-Type", "application/json")
42 61
 	}
43 62
 
63
+	// Perform the request
44 64
 	resp, err := c.httpClient.Do(req)
45 65
 	if err != nil {
46 66
 		return nil, err
47 67
 	}
48 68
 
69
+	// Read the response body
49 70
 	respBody, err := ioutil.ReadAll(resp.Body)
50 71
 	if err != nil {
51 72
 		return nil, err
52 73
 	}
53 74
 
75
+	// Return an error if the Grafana API responded with a non-200 status code.
76
+	// We perform this here because http.Client.Do() doesn't return with an
77
+	// error on non-200 status codes.
54 78
 	if resp.StatusCode != http.StatusOK {
55 79
 		if resp.StatusCode == http.StatusNotFound {
56 80
 			err = fmt.Errorf("%s not found (404)", url)
57 81
 		} else {
82
+			// Return an httpUnkownError error if the status code is neither 200
83
+			// nor 404
58 84
 			err = newHttpUnknownError(resp.StatusCode)
59 85
 		}
60 86
 	}
61 87
 
88
+	// Return the response body along with the error. This allows callers to
89
+	// process httpUnkownError errors by displaying an error message located in
90
+	// the response body along with the data contained in the error.
62 91
 	return respBody, err
63 92
 }
64 93
 
94
+// httpUnkownError represents an HTTP error, created from an HTTP response where
95
+// the status code is neither 200 nor 404.
65 96
 type httpUnkownError struct {
66 97
 	StatusCode int
67 98
 }
68 99
 
100
+// newHttpUnknownError creates and returns a new httpUnkownError error using
101
+// the provided status code.
69 102
 func newHttpUnknownError(statusCode int) *httpUnkownError {
70 103
 	return &httpUnkownError{
71 104
 		StatusCode: statusCode,
72 105
 	}
73 106
 }
74 107
 
108
+// Error implements error.Error().
75 109
 func (e *httpUnkownError) Error() string {
76 110
 	return fmt.Sprintf("Unknown HTTP error: %d", e.StatusCode)
77 111
 }

+ 39
- 4
src/grafana/dashboards.go Прегледај датотеку

@@ -5,6 +5,8 @@ import (
5 5
 	"fmt"
6 6
 )
7 7
 
8
+// dbSearchResponse represents an element of the response to a dashboard search
9
+// query
8 10
 type dbSearchResponse struct {
9 11
 	ID      int      `json:"id"`
10 12
 	Title   string   `json:"title"`
@@ -14,24 +16,36 @@ type dbSearchResponse struct {
14 16
 	Starred bool     `json:"isStarred"`
15 17
 }
16 18
 
17
-type dbUpdateRequest struct {
19
+// dbCreateOrUpdateRequest represents the request sent to create or update a
20
+// dashboard
21
+type dbCreateOrUpdateRequest struct {
18 22
 	Dashboard rawJSON `json:"dashboard"`
19 23
 	Overwrite bool    `json:"overwrite"`
20 24
 }
21 25
 
22
-type dbUpdateResponse struct {
26
+// dbCreateOrUpdateResponse represents the response sent by the Grafana API to
27
+// a dashboard creation or update. All fields described from the Grafana
28
+// documentation aren't located in this structure because there are some we
29
+// don't need.
30
+type dbCreateOrUpdateResponse struct {
23 31
 	Status  string `json:"success"`
24 32
 	Version int    `json:"version,omitempty"`
25 33
 	Message string `json:"message,omitempty"`
26 34
 }
27 35
 
36
+// Dashboard represents a Grafana dashboard, with its JSON definition, slug and
37
+// current version.
28 38
 type Dashboard struct {
29 39
 	RawJSON []byte
30 40
 	Slug    string
31 41
 	Version int
32 42
 }
33 43
 
44
+// UnmarshalJSON tells the JSON parser how to unmarshal JSON data into an
45
+// instance of the Dashboard structure.
46
+// Returns an error if there was an issue unmarshalling the JSON.
34 47
 func (d *Dashboard) UnmarshalJSON(b []byte) (err error) {
48
+	// Define the structure of what we want to parse
35 49
 	var body struct {
36 50
 		Dashboard rawJSON `json:"dashboard"`
37 51
 		Meta      struct {
@@ -40,9 +54,11 @@ func (d *Dashboard) UnmarshalJSON(b []byte) (err error) {
40 54
 		} `json:"meta"`
41 55
 	}
42 56
 
57
+	// Unmarshal the JSON into the newly defined structure
43 58
 	if err = json.Unmarshal(b, &body); err != nil {
44 59
 		return
45 60
 	}
61
+	// Define all fields with their corresponding value.
46 62
 	d.Slug = body.Meta.Slug
47 63
 	d.Version = body.Meta.Version
48 64
 	d.RawJSON = body.Dashboard
@@ -50,6 +66,10 @@ func (d *Dashboard) UnmarshalJSON(b []byte) (err error) {
50 66
 	return
51 67
 }
52 68
 
69
+// GetDashboardsURIs requests the Grafana API for the list of all dashboards,
70
+// then returns the dashboards' URIs. An URI will look like "db/[dashboard slug]".
71
+// Returns an error if there was an issue requesting the URIs or parsing the
72
+// response body.
53 73
 func (c *Client) GetDashboardsURIs() (URIs []string, err error) {
54 74
 	resp, err := c.request("GET", "search", nil)
55 75
 
@@ -66,6 +86,11 @@ func (c *Client) GetDashboardsURIs() (URIs []string, err error) {
66 86
 	return
67 87
 }
68 88
 
89
+// GetDashboard requests the Grafana API for a dashboard identified by a given
90
+// URI (using the same format as GetDashboardsURIs).
91
+// Returns the dashboard as an instance of the Dashboard structure.
92
+// Returns an error if there was an issue requesting the dashboard or parsing
93
+// the response body.
69 94
 func (c *Client) GetDashboard(URI string) (db *Dashboard, err error) {
70 95
 	body, err := c.request("GET", "dashboards/"+URI, nil)
71 96
 	if err != nil {
@@ -77,8 +102,18 @@ func (c *Client) GetDashboard(URI string) (db *Dashboard, err error) {
77 102
 	return
78 103
 }
79 104
 
105
+// CreateOrUpdateDashboard takes a given JSON content (as []byte) and create the
106
+// dashboard if it doesn't exist on the Grafana instance, else updates the
107
+// existing one. The Grafana API decides whether to create or update based on the
108
+// "id" attribute in the dashboard's JSON: If it's unkown or null, it's a
109
+// creation, else it's an update.
110
+// The slug is only used for error reporting, and can differ from the
111
+// dashboard's actual slug in its JSON content. However, it's recommended to use
112
+// the same in both places.
113
+// Returns an error if there was an issue generating the request body, performing
114
+// the request or decoding the response's body.
80 115
 func (c *Client) CreateOrUpdateDashboard(slug string, contentJSON []byte) (err error) {
81
-	reqBody := dbUpdateRequest{
116
+	reqBody := dbCreateOrUpdateRequest{
82 117
 		Dashboard: rawJSON(contentJSON),
83 118
 		Overwrite: true,
84 119
 	}
@@ -99,7 +134,7 @@ func (c *Client) CreateOrUpdateDashboard(slug string, contentJSON []byte) (err e
99 134
 		}
100 135
 	}
101 136
 
102
-	var respBody dbUpdateResponse
137
+	var respBody dbCreateOrUpdateResponse
103 138
 	if err = json.Unmarshal(respBodyJSON, &respBody); err != nil {
104 139
 		return
105 140
 	}