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 }