144 lines
3 KiB
Go
144 lines
3 KiB
Go
|
package grafana
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"context"
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"net/http"
|
||
|
|
||
|
"git.ar21.de/yolokube/grafana-backuper/pkg/grafana/schema"
|
||
|
"golang.org/x/net/http/httpguts"
|
||
|
)
|
||
|
|
||
|
const UserAgent = "grafana-backuper"
|
||
|
|
||
|
type Client struct {
|
||
|
endpoint string
|
||
|
token string
|
||
|
tokenValid bool
|
||
|
userAgent string
|
||
|
httpClient *http.Client
|
||
|
|
||
|
Dashboard DashboardClient
|
||
|
DashboardVersion DashboardVersionClient
|
||
|
File SearchClient
|
||
|
}
|
||
|
|
||
|
type ClientOption func(*Client)
|
||
|
|
||
|
func WithToken(token string) ClientOption {
|
||
|
return func(client *Client) {
|
||
|
client.token = token
|
||
|
client.tokenValid = httpguts.ValidHeaderFieldName(token)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func WithHTTPClient(httpClient *http.Client) ClientOption {
|
||
|
return func(client *Client) {
|
||
|
client.httpClient = httpClient
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func WithUserAgent(userAgent string) ClientOption {
|
||
|
return func(client *Client) {
|
||
|
client.userAgent = userAgent
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func NewClient(endpoint string, options ...ClientOption) *Client {
|
||
|
client := &Client{
|
||
|
endpoint: endpoint,
|
||
|
tokenValid: true,
|
||
|
httpClient: &http.Client{},
|
||
|
userAgent: UserAgent,
|
||
|
}
|
||
|
|
||
|
for _, option := range options {
|
||
|
option(client)
|
||
|
}
|
||
|
|
||
|
client.Dashboard = DashboardClient{client: client}
|
||
|
client.DashboardVersion = DashboardVersionClient{client: client}
|
||
|
client.File = SearchClient{client: client}
|
||
|
|
||
|
return client
|
||
|
}
|
||
|
|
||
|
func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Reader) (*http.Request, error) {
|
||
|
url := fmt.Sprintf("%s/%s", c.endpoint, path)
|
||
|
req, err := http.NewRequest(method, url, body)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
req.Header.Set("User-Agent", c.userAgent)
|
||
|
|
||
|
if !c.tokenValid {
|
||
|
return nil, errors.New("authorization token contains invalid characters")
|
||
|
}
|
||
|
|
||
|
if c.token != "" {
|
||
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token))
|
||
|
}
|
||
|
|
||
|
if body != nil {
|
||
|
req.Header.Set("Content-Type", "application/json")
|
||
|
}
|
||
|
|
||
|
return req.WithContext(ctx), nil
|
||
|
}
|
||
|
|
||
|
type Response struct {
|
||
|
*http.Response
|
||
|
ErrorResponse *ErrorResponse
|
||
|
|
||
|
body []byte
|
||
|
}
|
||
|
|
||
|
type ErrorResponse struct {
|
||
|
Message string
|
||
|
TraceID uint
|
||
|
}
|
||
|
|
||
|
func (c *Client) Do(req *http.Request, v any) (*Response, error) {
|
||
|
httpResp, err := c.httpClient.Do(req)
|
||
|
resp := &Response{Response: httpResp}
|
||
|
if err != nil {
|
||
|
return resp, err
|
||
|
}
|
||
|
defer httpResp.Body.Close()
|
||
|
|
||
|
body, err := io.ReadAll(resp.Body)
|
||
|
resp.Body.Close()
|
||
|
if err != nil {
|
||
|
return resp, err
|
||
|
}
|
||
|
resp.body = body
|
||
|
|
||
|
resp.Body = io.NopCloser(bytes.NewReader(body))
|
||
|
|
||
|
if resp.StatusCode != http.StatusOK {
|
||
|
var errorResponse schema.ErrorResponse
|
||
|
if err = json.Unmarshal(resp.body, &errorResponse); err == nil {
|
||
|
return resp, fmt.Errorf(
|
||
|
"grafana: got error with status code %d: %s",
|
||
|
resp.StatusCode,
|
||
|
ErrorResponseFromSchema(errorResponse).Message,
|
||
|
)
|
||
|
}
|
||
|
return resp, fmt.Errorf("grafana: server responded with an unexpected status code %d", resp.StatusCode)
|
||
|
}
|
||
|
|
||
|
if v != nil {
|
||
|
if writer, ok := v.(io.Writer); ok {
|
||
|
_, err = io.Copy(writer, bytes.NewReader(resp.body))
|
||
|
} else {
|
||
|
err = json.Unmarshal(resp.body, v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return resp, err
|
||
|
}
|