Browse Source

Doc Grafana client

Brendan Abolivier 6 years ago
parent
commit
bd07b371ce
Signed by: Brendan Abolivier <contact@brendanabolivier.com> GPG key ID: 8EF1500759F70623
2 changed files with 73 additions and 4 deletions
  1. 34
    0
      src/grafana/client.go
  2. 39
    4
      src/grafana/dashboards.go

+ 34
- 0
src/grafana/client.go View File

8
 	"strings"
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
 type Client struct {
13
 type Client struct {
12
 	BaseURL    string
14
 	BaseURL    string
13
 	APIKey     string
15
 	APIKey     string
14
 	httpClient *http.Client
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
 func NewClient(baseURL string, apiKey string) (c *Client) {
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
 	if strings.HasSuffix(baseURL, "/") {
23
 	if strings.HasSuffix(baseURL, "/") {
19
 		baseURL = baseURL[:len(baseURL)-1]
24
 		baseURL = baseURL[:len(baseURL)-1]
20
 	}
25
 	}
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
 func (c *Client) request(method string, endpoint string, body []byte) ([]byte, error) {
44
 func (c *Client) request(method string, endpoint string, body []byte) ([]byte, error) {
30
 	url := c.BaseURL + "/api/" + endpoint
45
 	url := c.BaseURL + "/api/" + endpoint
31
 
46
 
47
+	// Create the request
32
 	req, err := http.NewRequest(method, url, bytes.NewBuffer(body))
48
 	req, err := http.NewRequest(method, url, bytes.NewBuffer(body))
33
 	if err != nil {
49
 	if err != nil {
34
 		return nil, err
50
 		return nil, err
35
 	}
51
 	}
36
 
52
 
53
+	// Add the API key to the request as an Authorization HTTP header
37
 	authHeader := fmt.Sprintf("Bearer %s", c.APIKey)
54
 	authHeader := fmt.Sprintf("Bearer %s", c.APIKey)
38
 	req.Header.Add("Authorization", authHeader)
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
 	if method != "GET" {
59
 	if method != "GET" {
41
 		req.Header.Add("Content-Type", "application/json")
60
 		req.Header.Add("Content-Type", "application/json")
42
 	}
61
 	}
43
 
62
 
63
+	// Perform the request
44
 	resp, err := c.httpClient.Do(req)
64
 	resp, err := c.httpClient.Do(req)
45
 	if err != nil {
65
 	if err != nil {
46
 		return nil, err
66
 		return nil, err
47
 	}
67
 	}
48
 
68
 
69
+	// Read the response body
49
 	respBody, err := ioutil.ReadAll(resp.Body)
70
 	respBody, err := ioutil.ReadAll(resp.Body)
50
 	if err != nil {
71
 	if err != nil {
51
 		return nil, err
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
 	if resp.StatusCode != http.StatusOK {
78
 	if resp.StatusCode != http.StatusOK {
55
 		if resp.StatusCode == http.StatusNotFound {
79
 		if resp.StatusCode == http.StatusNotFound {
56
 			err = fmt.Errorf("%s not found (404)", url)
80
 			err = fmt.Errorf("%s not found (404)", url)
57
 		} else {
81
 		} else {
82
+			// Return an httpUnkownError error if the status code is neither 200
83
+			// nor 404
58
 			err = newHttpUnknownError(resp.StatusCode)
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
 	return respBody, err
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
 type httpUnkownError struct {
96
 type httpUnkownError struct {
66
 	StatusCode int
97
 	StatusCode int
67
 }
98
 }
68
 
99
 
100
+// newHttpUnknownError creates and returns a new httpUnkownError error using
101
+// the provided status code.
69
 func newHttpUnknownError(statusCode int) *httpUnkownError {
102
 func newHttpUnknownError(statusCode int) *httpUnkownError {
70
 	return &httpUnkownError{
103
 	return &httpUnkownError{
71
 		StatusCode: statusCode,
104
 		StatusCode: statusCode,
72
 	}
105
 	}
73
 }
106
 }
74
 
107
 
108
+// Error implements error.Error().
75
 func (e *httpUnkownError) Error() string {
109
 func (e *httpUnkownError) Error() string {
76
 	return fmt.Sprintf("Unknown HTTP error: %d", e.StatusCode)
110
 	return fmt.Sprintf("Unknown HTTP error: %d", e.StatusCode)
77
 }
111
 }

+ 39
- 4
src/grafana/dashboards.go View File

5
 	"fmt"
5
 	"fmt"
6
 )
6
 )
7
 
7
 
8
+// dbSearchResponse represents an element of the response to a dashboard search
9
+// query
8
 type dbSearchResponse struct {
10
 type dbSearchResponse struct {
9
 	ID      int      `json:"id"`
11
 	ID      int      `json:"id"`
10
 	Title   string   `json:"title"`
12
 	Title   string   `json:"title"`
14
 	Starred bool     `json:"isStarred"`
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
 	Dashboard rawJSON `json:"dashboard"`
22
 	Dashboard rawJSON `json:"dashboard"`
19
 	Overwrite bool    `json:"overwrite"`
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
 	Status  string `json:"success"`
31
 	Status  string `json:"success"`
24
 	Version int    `json:"version,omitempty"`
32
 	Version int    `json:"version,omitempty"`
25
 	Message string `json:"message,omitempty"`
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
 type Dashboard struct {
38
 type Dashboard struct {
29
 	RawJSON []byte
39
 	RawJSON []byte
30
 	Slug    string
40
 	Slug    string
31
 	Version int
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
 func (d *Dashboard) UnmarshalJSON(b []byte) (err error) {
47
 func (d *Dashboard) UnmarshalJSON(b []byte) (err error) {
48
+	// Define the structure of what we want to parse
35
 	var body struct {
49
 	var body struct {
36
 		Dashboard rawJSON `json:"dashboard"`
50
 		Dashboard rawJSON `json:"dashboard"`
37
 		Meta      struct {
51
 		Meta      struct {
40
 		} `json:"meta"`
54
 		} `json:"meta"`
41
 	}
55
 	}
42
 
56
 
57
+	// Unmarshal the JSON into the newly defined structure
43
 	if err = json.Unmarshal(b, &body); err != nil {
58
 	if err = json.Unmarshal(b, &body); err != nil {
44
 		return
59
 		return
45
 	}
60
 	}
61
+	// Define all fields with their corresponding value.
46
 	d.Slug = body.Meta.Slug
62
 	d.Slug = body.Meta.Slug
47
 	d.Version = body.Meta.Version
63
 	d.Version = body.Meta.Version
48
 	d.RawJSON = body.Dashboard
64
 	d.RawJSON = body.Dashboard
50
 	return
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
 func (c *Client) GetDashboardsURIs() (URIs []string, err error) {
73
 func (c *Client) GetDashboardsURIs() (URIs []string, err error) {
54
 	resp, err := c.request("GET", "search", nil)
74
 	resp, err := c.request("GET", "search", nil)
55
 
75
 
66
 	return
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
 func (c *Client) GetDashboard(URI string) (db *Dashboard, err error) {
94
 func (c *Client) GetDashboard(URI string) (db *Dashboard, err error) {
70
 	body, err := c.request("GET", "dashboards/"+URI, nil)
95
 	body, err := c.request("GET", "dashboards/"+URI, nil)
71
 	if err != nil {
96
 	if err != nil {
77
 	return
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
 func (c *Client) CreateOrUpdateDashboard(slug string, contentJSON []byte) (err error) {
115
 func (c *Client) CreateOrUpdateDashboard(slug string, contentJSON []byte) (err error) {
81
-	reqBody := dbUpdateRequest{
116
+	reqBody := dbCreateOrUpdateRequest{
82
 		Dashboard: rawJSON(contentJSON),
117
 		Dashboard: rawJSON(contentJSON),
83
 		Overwrite: true,
118
 		Overwrite: true,
84
 	}
119
 	}
99
 		}
134
 		}
100
 	}
135
 	}
101
 
136
 
102
-	var respBody dbUpdateResponse
137
+	var respBody dbCreateOrUpdateResponse
103
 	if err = json.Unmarshal(respBodyJSON, &respBody); err != nil {
138
 	if err = json.Unmarshal(respBodyJSON, &respBody); err != nil {
104
 		return
139
 		return
105
 	}
140
 	}