grafana-backuper/pkg/grafana/client.go

143 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
}