refactor(grafana): rework grafana package and make it more modular
This commit is contained in:
parent
cfcf3c6c2b
commit
7ce90dacc4
12 changed files with 710 additions and 282 deletions
143
pkg/grafana/client.go
Normal file
143
pkg/grafana/client.go
Normal file
|
@ -0,0 +1,143 @@
|
|||
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
|
||||
}
|
|
@ -4,142 +4,141 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"git.ar21.de/yolokube/grafana-backuper/pkg/grafana/schema"
|
||||
)
|
||||
|
||||
type BoardProperties struct {
|
||||
IsStarred bool `json:"isStarred,omitempty"`
|
||||
IsHome bool `json:"isHome,omitempty"`
|
||||
IsSnapshot bool `json:"isSnapshot,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
CanSave bool `json:"canSave"`
|
||||
CanEdit bool `json:"canEdit"`
|
||||
CanStar bool `json:"canStar"`
|
||||
Slug string `json:"slug"`
|
||||
Expires time.Time `json:"expires"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
UpdatedBy string `json:"updatedBy"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
Version int `json:"version"`
|
||||
FolderID int `json:"folderId"`
|
||||
FolderTitle string `json:"folderTitle"`
|
||||
FolderURL string `json:"folderUrl"`
|
||||
type DashboardMeta struct {
|
||||
IsStarred bool
|
||||
Type string
|
||||
CanSave bool
|
||||
CanEdit bool
|
||||
CanAdmin bool
|
||||
CanStar bool
|
||||
CanDelete bool
|
||||
Slug string
|
||||
URL string
|
||||
Expires time.Time
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
UpdatedBy string
|
||||
CreatedBy string
|
||||
Version uint
|
||||
HasACL bool
|
||||
IsFolder bool
|
||||
FolderID uint
|
||||
FolderUID string
|
||||
FolderTitle string
|
||||
FolderURL string
|
||||
Provisioned bool
|
||||
ProvisionedExternalID string
|
||||
AnnotationsPermissions AnnotationsPermissions
|
||||
Dashboard any
|
||||
}
|
||||
|
||||
type DashboardVersion struct {
|
||||
ID uint `json:"id"`
|
||||
DashboardID uint `json:"dashboardId"`
|
||||
DashboardUID string `json:"uid"`
|
||||
ParentVersion uint `json:"parentVersion"`
|
||||
RestoredFrom uint `json:"restoredFrom"`
|
||||
Version uint `json:"version"`
|
||||
Created time.Time `json:"created"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
Message string `json:"message"`
|
||||
type AnnotationsPermissions struct {
|
||||
Dashboard AnnotationPermissions
|
||||
Organization AnnotationPermissions
|
||||
}
|
||||
|
||||
func (c *Client) getRawDashboardByUID(ctx context.Context, path string) ([]byte, BoardProperties, error) {
|
||||
raw, code, err := c.get(ctx, fmt.Sprintf("api/dashboards/%s", path), nil)
|
||||
type AnnotationPermissions struct {
|
||||
CanAdd bool
|
||||
CanEdit bool
|
||||
CanDelete bool
|
||||
}
|
||||
|
||||
type DashboardClient struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
func (c *DashboardClient) Get(ctx context.Context, uid string) (*DashboardMeta, *Response, error) {
|
||||
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/dashboards/uid/%s", uid), nil)
|
||||
if err != nil {
|
||||
return nil, BoardProperties{}, err
|
||||
}
|
||||
if code != 200 {
|
||||
return raw, BoardProperties{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Meta BoardProperties `json:"meta"`
|
||||
}
|
||||
var body schema.DashboardMeta
|
||||
|
||||
dec := json.NewDecoder(bytes.NewReader(raw))
|
||||
dec.UseNumber()
|
||||
|
||||
if err := dec.Decode(&result); err != nil {
|
||||
return raw, BoardProperties{}, fmt.Errorf("failed unmarshalling dashboard from path %s: %v", path, err)
|
||||
}
|
||||
return raw, result.Meta, nil
|
||||
}
|
||||
|
||||
func (c *Client) getRawDashboardFromVersion(ctx context.Context, path string) ([]byte, DashboardVersion, error) {
|
||||
var versionInfo DashboardVersion
|
||||
|
||||
raw, code, err := c.get(ctx, fmt.Sprintf("api/dashboards/%s", path), nil)
|
||||
resp, err := c.client.Do(req, &body)
|
||||
if err != nil {
|
||||
return nil, versionInfo, err
|
||||
}
|
||||
if code != 200 {
|
||||
return raw, versionInfo, fmt.Errorf("HTTP error %d: returns %s", code, raw)
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
dec := json.NewDecoder(bytes.NewReader(raw))
|
||||
dec.UseNumber()
|
||||
|
||||
if err := dec.Decode(&versionInfo); err != nil {
|
||||
return raw, versionInfo, fmt.Errorf("failed unmarshalling dashboard from path %s: %v", path, err)
|
||||
}
|
||||
return raw, versionInfo, nil
|
||||
return DashboardMetaFromSchema(body), resp, nil
|
||||
}
|
||||
|
||||
func queryParams(params ...QueryParam) url.Values {
|
||||
u := url.URL{}
|
||||
q := u.Query()
|
||||
|
||||
for _, p := range params {
|
||||
p(&q)
|
||||
}
|
||||
return q
|
||||
type DashboardCreateOpts struct {
|
||||
Dashboard any
|
||||
FolderID uint
|
||||
FolderUID string
|
||||
Message string
|
||||
Overwrite bool
|
||||
}
|
||||
|
||||
func (c *Client) GetDashboardVersionsByDashboardUID(ctx context.Context, uid string, params ...QueryParam) ([]DashboardVersion, error) {
|
||||
var (
|
||||
raw []byte
|
||||
code int
|
||||
err error
|
||||
)
|
||||
func (o DashboardCreateOpts) Validate() error {
|
||||
if o.FolderUID == "" && o.FolderID == 0 {
|
||||
return errors.New("folder ID or UID missing")
|
||||
}
|
||||
if o.Dashboard == nil {
|
||||
return errors.New("dashboard is nil")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if raw, code, err = c.get(ctx, fmt.Sprintf("api/dashboards/uid/%s/versions", uid), queryParams(params...)); err != nil {
|
||||
type DashboardCreateResponse struct {
|
||||
DashboardID uint
|
||||
DashboardUID string
|
||||
URL string
|
||||
Status string
|
||||
Version uint
|
||||
Slug string
|
||||
}
|
||||
|
||||
func (c *DashboardClient) Create(
|
||||
ctx context.Context,
|
||||
opts DashboardCreateOpts,
|
||||
) (*DashboardCreateResponse, *Response, error) {
|
||||
if err := opts.Validate(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var reqBody schema.DashboardCreateRequest
|
||||
reqBody.Dashboard = opts.Dashboard
|
||||
reqBody.Overwrite = opts.Overwrite
|
||||
reqBody.Message = opts.Message
|
||||
if opts.FolderUID != "" {
|
||||
reqBody.FolderUID = opts.FolderUID
|
||||
} else {
|
||||
reqBody.FolderID = opts.FolderID
|
||||
}
|
||||
|
||||
reqBodyData, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := c.client.NewRequest(ctx, "POST", "/dashboards/db", bytes.NewReader(reqBodyData))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var respBody schema.DashboardCreateResponse
|
||||
resp, err := c.client.Do(req, &respBody)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return DashboardCreateResponseFromSchema(respBody), resp, nil
|
||||
}
|
||||
|
||||
func (c *DashboardClient) Delete(ctx context.Context, uid string) (*Response, error) {
|
||||
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/dashboards/uid/%s", uid), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if code != 200 {
|
||||
return nil, fmt.Errorf("HTTP error %d: returns %s", code, raw)
|
||||
}
|
||||
|
||||
var versions []DashboardVersion
|
||||
err = json.Unmarshal(raw, &versions)
|
||||
|
||||
return versions, err
|
||||
}
|
||||
|
||||
func (c *Client) GetRawDashboardByUID(ctx context.Context, uid string) ([]byte, BoardProperties, error) {
|
||||
return c.getRawDashboardByUID(ctx, "uid/"+uid)
|
||||
}
|
||||
|
||||
func (c *Client) GetRawDashboardByUIDAndVersion(ctx context.Context, uid string, version uint) ([]byte, DashboardVersion, error) {
|
||||
return c.getRawDashboardFromVersion(ctx, "uid/"+uid+"/versions/"+fmt.Sprint(version))
|
||||
}
|
||||
|
||||
func (c *Client) SearchDashboards(ctx context.Context, query string, starred bool, tags ...string) ([]FoundBoard, error) {
|
||||
params := []SearchParam{
|
||||
SearchType(SearchTypeDashboard),
|
||||
SearchQuery(query),
|
||||
SearchStarred(starred),
|
||||
}
|
||||
for _, tag := range tags {
|
||||
params = append(params, SearchTag(tag))
|
||||
}
|
||||
return c.Search(ctx, params...)
|
||||
}
|
||||
|
||||
func ConvertRawToIndent(raw []byte) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
err := json.Indent(&buf, raw, "", " ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error pritty-printing raw json string: %v", err)
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
return c.client.Do(req, nil)
|
||||
}
|
||||
|
|
163
pkg/grafana/dashboard_version.go
Normal file
163
pkg/grafana/dashboard_version.go
Normal file
|
@ -0,0 +1,163 @@
|
|||
package grafana
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.ar21.de/yolokube/grafana-backuper/pkg/grafana/schema"
|
||||
)
|
||||
|
||||
type DashboardVersionParam func(*url.Values)
|
||||
|
||||
func WithLimit(limit uint) DashboardVersionParam {
|
||||
return func(v *url.Values) {
|
||||
v.Set("limit", strconv.FormatUint(uint64(limit), 10))
|
||||
}
|
||||
}
|
||||
|
||||
func WithStart(start uint) DashboardVersionParam {
|
||||
return func(v *url.Values) {
|
||||
v.Set("start", strconv.FormatUint(uint64(start), 10))
|
||||
}
|
||||
}
|
||||
|
||||
type DashboardVersion struct {
|
||||
ID uint
|
||||
DashboardID uint
|
||||
DashboardUID string
|
||||
ParentVersion uint
|
||||
RestoredFrom uint
|
||||
Version uint
|
||||
Created time.Time
|
||||
CreatedBy string
|
||||
Message string
|
||||
Data any
|
||||
}
|
||||
|
||||
type DashboardVersionClient struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
func (c *DashboardVersionClient) GetByID(
|
||||
ctx context.Context,
|
||||
id, version uint,
|
||||
params ...DashboardVersionParam,
|
||||
) (*DashboardVersion, *Response, error) {
|
||||
dashboardVersionURL, err := url.Parse(fmt.Sprintf("/dashboards/id/%d/versions/%d", id, version))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
dashboards, resp, err := c.get(ctx, dashboardVersionURL, params...)
|
||||
if err != nil || len(dashboards) < 1 {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return dashboards[0], resp, nil
|
||||
}
|
||||
|
||||
func (c *DashboardVersionClient) GetByUID(
|
||||
ctx context.Context,
|
||||
uid string,
|
||||
version uint,
|
||||
params ...DashboardVersionParam,
|
||||
) (*DashboardVersion, *Response, error) {
|
||||
dashboardVersionURL, err := url.Parse(fmt.Sprintf("/dashboards/id/%s/versions/%d", uid, version))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
dashboards, resp, err := c.get(ctx, dashboardVersionURL, params...)
|
||||
if err != nil || len(dashboards) < 1 {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return dashboards[0], resp, nil
|
||||
}
|
||||
|
||||
func (c *DashboardVersionClient) Get(
|
||||
ctx context.Context,
|
||||
input string,
|
||||
version uint,
|
||||
params ...DashboardVersionParam,
|
||||
) (*DashboardVersion, *Response, error) {
|
||||
if id, err := strconv.Atoi(input); err == nil {
|
||||
return c.GetByID(ctx, uint(id), version, params...)
|
||||
}
|
||||
return c.GetByUID(ctx, input, version, params...)
|
||||
}
|
||||
|
||||
func (c *DashboardVersionClient) ListByID(
|
||||
ctx context.Context,
|
||||
id uint,
|
||||
params ...DashboardVersionParam,
|
||||
) ([]*DashboardVersion, *Response, error) {
|
||||
dashboardVersionURL, err := url.Parse(fmt.Sprintf("/dashboards/id/%d/versions", id))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return c.get(ctx, dashboardVersionURL, params...)
|
||||
}
|
||||
|
||||
func (c *DashboardVersionClient) ListByUID(
|
||||
ctx context.Context,
|
||||
uid string,
|
||||
params ...DashboardVersionParam,
|
||||
) ([]*DashboardVersion, *Response, error) {
|
||||
dashboardVersionURL, err := url.Parse(fmt.Sprintf("/dashboards/uid/%s/versions", uid))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return c.get(ctx, dashboardVersionURL, params...)
|
||||
}
|
||||
|
||||
func (c *DashboardVersionClient) List(
|
||||
ctx context.Context,
|
||||
input string,
|
||||
params ...DashboardVersionParam,
|
||||
) ([]*DashboardVersion, *Response, error) {
|
||||
if id, err := strconv.Atoi(input); err == nil {
|
||||
return c.ListByID(ctx, uint(id), params...)
|
||||
}
|
||||
return c.ListByUID(ctx, input, params...)
|
||||
}
|
||||
|
||||
func (c *DashboardVersionClient) get(
|
||||
ctx context.Context,
|
||||
dashboardVersionURL *url.URL,
|
||||
params ...DashboardVersionParam,
|
||||
) ([]*DashboardVersion, *Response, error) {
|
||||
if len(params) > 0 {
|
||||
query := dashboardVersionURL.Query()
|
||||
|
||||
for _, param := range params {
|
||||
param(&query)
|
||||
}
|
||||
|
||||
dashboardVersionURL.RawQuery = query.Encode()
|
||||
}
|
||||
|
||||
req, err := c.client.NewRequest(ctx, "GET", dashboardVersionURL.String(), nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var body schema.DashboardVersionListResponse
|
||||
|
||||
resp, err := c.client.Do(req, &body)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
dashboardVersions := make([]*DashboardVersion, 0, len(body.DashboardVersions))
|
||||
for _, dashboardVersion := range body.DashboardVersions {
|
||||
dashboardVersions = append(dashboardVersions, DashboardVersionFromSchema(dashboardVersion))
|
||||
}
|
||||
|
||||
return dashboardVersions, resp, nil
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
package grafana
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var DefaultHTTPClient = http.DefaultClient
|
||||
|
||||
type Client struct {
|
||||
baseURL string
|
||||
key string
|
||||
basicAuth bool
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func NewClient(apiURL, authString string, client *http.Client) (*Client, error) {
|
||||
baseURL, err := url.Parse(apiURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var key string
|
||||
basicAuth := strings.Contains(authString, ":")
|
||||
|
||||
if len(authString) > 0 {
|
||||
if !basicAuth {
|
||||
key = fmt.Sprintf("Bearer %s", authString)
|
||||
} else {
|
||||
parts := strings.SplitN(authString, ":", 2)
|
||||
baseURL.User = url.UserPassword(parts[0], parts[1])
|
||||
}
|
||||
}
|
||||
|
||||
return &Client{
|
||||
baseURL: baseURL.String(),
|
||||
basicAuth: basicAuth,
|
||||
key: key,
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(ctx context.Context, method, query string, params url.Values, buf io.Reader) ([]byte, int, error) {
|
||||
u, _ := url.Parse(c.baseURL)
|
||||
u.Path = path.Join(u.Path, query)
|
||||
|
||||
if params != nil {
|
||||
u.RawQuery = params.Encode()
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, u.String(), buf)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
if !c.basicAuth && len(c.key) > 0 {
|
||||
req.Header.Set("Authorization", c.key)
|
||||
}
|
||||
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", "grafana-backuper")
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
return data, resp.StatusCode, err
|
||||
}
|
||||
|
||||
func (c *Client) get(ctx context.Context, query string, params url.Values) ([]byte, int, error) {
|
||||
return c.doRequest(ctx, "GET", query, params, nil)
|
||||
}
|
98
pkg/grafana/schema.go
Normal file
98
pkg/grafana/schema.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
package grafana
|
||||
|
||||
import (
|
||||
"git.ar21.de/yolokube/grafana-backuper/pkg/grafana/schema"
|
||||
)
|
||||
|
||||
func DashboardCreateResponseFromSchema(source schema.DashboardCreateResponse) *DashboardCreateResponse {
|
||||
return &DashboardCreateResponse{
|
||||
DashboardID: source.DashboardID,
|
||||
DashboardUID: source.DashboardUID,
|
||||
URL: source.URL,
|
||||
Status: source.Status,
|
||||
Version: source.Version,
|
||||
Slug: source.Slug,
|
||||
}
|
||||
}
|
||||
|
||||
func DashboardMetaFromSchema(source schema.DashboardMeta) *DashboardMeta {
|
||||
return &DashboardMeta{
|
||||
IsStarred: source.IsStarred,
|
||||
Type: source.Type,
|
||||
CanSave: source.CanSave,
|
||||
CanEdit: source.CanEdit,
|
||||
CanAdmin: source.CanAdmin,
|
||||
CanStar: source.CanStar,
|
||||
CanDelete: source.CanDelete,
|
||||
Slug: source.Slug,
|
||||
URL: source.URL,
|
||||
Expires: source.Expires,
|
||||
Created: source.Created,
|
||||
Updated: source.Updated,
|
||||
UpdatedBy: source.UpdatedBy,
|
||||
CreatedBy: source.CreatedBy,
|
||||
Version: source.Version,
|
||||
HasACL: source.HasACL,
|
||||
IsFolder: source.IsFolder,
|
||||
FolderID: source.FolderID,
|
||||
FolderUID: source.FolderUID,
|
||||
FolderTitle: source.FolderTitle,
|
||||
FolderURL: source.FolderURL,
|
||||
Provisioned: source.Provisioned,
|
||||
ProvisionedExternalID: source.ProvisionedExternalID,
|
||||
AnnotationsPermissions: AnnotationsPermissions{
|
||||
Dashboard: AnnotationPermissions{
|
||||
CanAdd: source.AnnotationsPermissions.Dashboard.CanAdd,
|
||||
CanEdit: source.AnnotationsPermissions.Dashboard.CanEdit,
|
||||
CanDelete: source.AnnotationsPermissions.Dashboard.CanDelete,
|
||||
},
|
||||
Organization: AnnotationPermissions{
|
||||
CanAdd: source.AnnotationsPermissions.Organization.CanAdd,
|
||||
CanEdit: source.AnnotationsPermissions.Organization.CanEdit,
|
||||
CanDelete: source.AnnotationsPermissions.Organization.CanDelete,
|
||||
},
|
||||
},
|
||||
Dashboard: source.Dashboard,
|
||||
}
|
||||
}
|
||||
|
||||
func DashboardVersionFromSchema(source schema.DashboardVersion) *DashboardVersion {
|
||||
return &DashboardVersion{
|
||||
ID: source.ID,
|
||||
DashboardID: source.DashboardID,
|
||||
DashboardUID: source.UID,
|
||||
ParentVersion: source.ParentVersion,
|
||||
RestoredFrom: source.RestoredFrom,
|
||||
Version: source.Version,
|
||||
Created: source.Created,
|
||||
CreatedBy: source.CreatedBy,
|
||||
Message: source.Message,
|
||||
Data: source.Data,
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorResponseFromSchema(source schema.ErrorResponse) *ErrorResponse {
|
||||
return &ErrorResponse{
|
||||
Message: source.Message,
|
||||
TraceID: source.TraceID,
|
||||
}
|
||||
}
|
||||
|
||||
func SearchResultFromSchema(source schema.SearchResult) *SearchResult {
|
||||
return &SearchResult{
|
||||
ID: source.ID,
|
||||
UID: source.UID,
|
||||
Title: source.Title,
|
||||
URI: source.URI,
|
||||
URL: source.URL,
|
||||
Slug: source.Slug,
|
||||
Type: source.Type,
|
||||
Tags: source.Tags,
|
||||
IsStarred: source.IsStarred,
|
||||
SortMeta: source.SortMeta,
|
||||
FolderID: source.FolderID,
|
||||
FolderUID: source.FolderUID,
|
||||
FolderTitle: source.FolderTitle,
|
||||
FolderURL: source.FolderURL,
|
||||
}
|
||||
}
|
59
pkg/grafana/schema/dashboard.go
Normal file
59
pkg/grafana/schema/dashboard.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
package schema
|
||||
|
||||
import "time"
|
||||
|
||||
type DashboardCreateRequest struct {
|
||||
Dashboard any `json:"dasboard"`
|
||||
FolderID uint `json:"folderId,omitempty"`
|
||||
FolderUID string `json:"folderUid"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Overwrite bool `json:"overwrite"`
|
||||
}
|
||||
|
||||
type DashboardCreateResponse struct {
|
||||
DashboardID uint `json:"id"`
|
||||
DashboardUID string `json:"uid"`
|
||||
URL string `json:"url"`
|
||||
Status string `json:"status"`
|
||||
Version uint `json:"version"`
|
||||
Slug string `json:"slug"`
|
||||
}
|
||||
|
||||
type DashboardMeta struct {
|
||||
IsStarred bool `json:"isStarred"`
|
||||
Type string `json:"type"`
|
||||
CanSave bool `json:"canSave"`
|
||||
CanEdit bool `json:"canEdit"`
|
||||
CanAdmin bool `json:"canAdmin"`
|
||||
CanStar bool `json:"canStar"`
|
||||
CanDelete bool `json:"canDelete"`
|
||||
Slug string `json:"slug"`
|
||||
URL string `json:"url"`
|
||||
Expires time.Time `json:"expires"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
UpdatedBy string `json:"updatedBy"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
Version uint `json:"version"`
|
||||
HasACL bool `json:"hasAcl"`
|
||||
IsFolder bool `json:"isFolder"`
|
||||
FolderID uint `json:"folderId"`
|
||||
FolderUID string `json:"folderUid"`
|
||||
FolderTitle string `json:"folderTitle"`
|
||||
FolderURL string `json:"folderUrl"`
|
||||
Provisioned bool `json:"provisioned"`
|
||||
ProvisionedExternalID string `json:"provisionedExternalId"`
|
||||
AnnotationsPermissions struct {
|
||||
Dashboard struct {
|
||||
CanAdd bool `json:"canAdd"`
|
||||
CanEdit bool `json:"canEdit"`
|
||||
CanDelete bool `json:"canDelete"`
|
||||
} `json:"dashboard"`
|
||||
Organization struct {
|
||||
CanAdd bool `json:"canAdd"`
|
||||
CanEdit bool `json:"canEdit"`
|
||||
CanDelete bool `json:"canDelete"`
|
||||
} `json:"organization"`
|
||||
} `json:"annotationsPermissions"`
|
||||
Dashboard any `json:"dashboard"`
|
||||
}
|
20
pkg/grafana/schema/dashboard_version.go
Normal file
20
pkg/grafana/schema/dashboard_version.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package schema
|
||||
|
||||
import "time"
|
||||
|
||||
type DashboardVersion struct {
|
||||
ID uint `json:"id"`
|
||||
DashboardID uint `json:"dashboardId"`
|
||||
UID string `json:"uid"`
|
||||
ParentVersion uint `json:"parentVersion"`
|
||||
RestoredFrom uint `json:"restoredFrom"`
|
||||
Version uint `json:"version"`
|
||||
Created time.Time `json:"created"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
Message string `json:"message"`
|
||||
Data any `json:"data"`
|
||||
}
|
||||
|
||||
type DashboardVersionListResponse struct {
|
||||
DashboardVersions []DashboardVersion
|
||||
}
|
6
pkg/grafana/schema/error.go
Normal file
6
pkg/grafana/schema/error.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package schema
|
||||
|
||||
type ErrorResponse struct {
|
||||
Message string `json:"message"`
|
||||
TraceID uint `json:"traceid"`
|
||||
}
|
22
pkg/grafana/schema/search.go
Normal file
22
pkg/grafana/schema/search.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
package schema
|
||||
|
||||
type SearchResult struct {
|
||||
ID uint `json:"id"`
|
||||
UID string `json:"uid"`
|
||||
Title string `json:"title"`
|
||||
URI string `json:"uri"`
|
||||
URL string `json:"url"`
|
||||
Slug string `json:"slug"`
|
||||
Type string `json:"type"`
|
||||
Tags []string `json:"tags"`
|
||||
IsStarred bool `json:"isStarred"`
|
||||
SortMeta uint `json:"sortMeta"`
|
||||
FolderID int `json:"folderId"`
|
||||
FolderUID string `json:"folderUid"`
|
||||
FolderTitle string `json:"folderTitle"`
|
||||
FolderURL string `json:"folderUrl"`
|
||||
}
|
||||
|
||||
type SearchResultListResponse struct {
|
||||
SearchResults []SearchResult
|
||||
}
|
|
@ -2,68 +2,30 @@ package grafana
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"git.ar21.de/yolokube/grafana-backuper/pkg/grafana/schema"
|
||||
)
|
||||
|
||||
type (
|
||||
SearchParam func(*url.Values)
|
||||
|
||||
SearchType string
|
||||
)
|
||||
|
||||
const (
|
||||
SearchTypeFolder SearchParamType = "dash-folder"
|
||||
SearchTypeDashboard SearchParamType = "dash-db"
|
||||
SearchTypeFolder SearchType = "dash-folder"
|
||||
SearchTypeDashboard SearchType = "dash-db"
|
||||
)
|
||||
|
||||
type FoundBoard struct {
|
||||
ID uint `json:"id"`
|
||||
UID string `json:"uid"`
|
||||
Title string `json:"title"`
|
||||
URI string `json:"uri"`
|
||||
URL string `json:"url"`
|
||||
Slug string `json:"slug"`
|
||||
Type string `json:"type"`
|
||||
Tags []string `json:"tags"`
|
||||
IsStarred bool `json:"isStarred"`
|
||||
FolderID int `json:"folderId"`
|
||||
FolderUID string `json:"folderUid"`
|
||||
FolderTitle string `json:"folderTitle"`
|
||||
FolderURL string `json:"folderUrl"`
|
||||
func WithType(searchType SearchType) SearchParam {
|
||||
return func(v *url.Values) {
|
||||
v.Set("type", string(searchType))
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
// SearchParam is a type for specifying Search params.
|
||||
SearchParam func(*url.Values)
|
||||
// SearchParamType is a type accepted by SearchType func.
|
||||
SearchParamType string
|
||||
// QueryParam is a type for specifying arbitrary API parameters
|
||||
QueryParam func(*url.Values)
|
||||
)
|
||||
|
||||
func (c *Client) Search(ctx context.Context, params ...SearchParam) ([]FoundBoard, error) {
|
||||
var (
|
||||
raw []byte
|
||||
boards []FoundBoard
|
||||
code int
|
||||
err error
|
||||
)
|
||||
u := url.URL{}
|
||||
q := u.Query()
|
||||
|
||||
for _, p := range params {
|
||||
p(&q)
|
||||
}
|
||||
|
||||
if raw, code, err = c.get(ctx, "api/search", q); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if code != 200 {
|
||||
return nil, fmt.Errorf("HTTP error %d: returns %s", code, raw)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(raw, &boards)
|
||||
return boards, err
|
||||
}
|
||||
|
||||
func SearchQuery(query string) SearchParam {
|
||||
func WithQuery(query string) SearchParam {
|
||||
return func(v *url.Values) {
|
||||
if query != "" {
|
||||
v.Set("query", query)
|
||||
|
@ -71,13 +33,13 @@ func SearchQuery(query string) SearchParam {
|
|||
}
|
||||
}
|
||||
|
||||
func SearchStarred(starred bool) SearchParam {
|
||||
func WithStarred() SearchParam {
|
||||
return func(v *url.Values) {
|
||||
v.Set("starred", strconv.FormatBool(starred))
|
||||
v.Set("starred", strconv.FormatBool(true))
|
||||
}
|
||||
}
|
||||
|
||||
func SearchTag(tag string) SearchParam {
|
||||
func WithTag(tag string) SearchParam {
|
||||
return func(v *url.Values) {
|
||||
if tag != "" {
|
||||
v.Add("tag", tag)
|
||||
|
@ -85,8 +47,59 @@ func SearchTag(tag string) SearchParam {
|
|||
}
|
||||
}
|
||||
|
||||
func SearchType(searchType SearchParamType) SearchParam {
|
||||
return func(v *url.Values) {
|
||||
v.Set("type", string(searchType))
|
||||
}
|
||||
type SearchResult struct {
|
||||
ID uint
|
||||
UID string
|
||||
Title string
|
||||
URI string
|
||||
URL string
|
||||
Slug string
|
||||
Type string
|
||||
Tags []string
|
||||
IsStarred bool
|
||||
SortMeta uint
|
||||
FolderID int
|
||||
FolderUID string
|
||||
FolderTitle string
|
||||
FolderURL string
|
||||
}
|
||||
|
||||
type SearchClient struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
func (c *SearchClient) Search(ctx context.Context, params ...SearchParam) ([]*SearchResult, *Response, error) {
|
||||
searchURL, err := url.Parse("/search")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(params) > 0 {
|
||||
query := searchURL.Query()
|
||||
|
||||
for _, param := range params {
|
||||
param(&query)
|
||||
}
|
||||
|
||||
searchURL.RawQuery = query.Encode()
|
||||
}
|
||||
|
||||
req, err := c.client.NewRequest(ctx, "GET", searchURL.String(), nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var body schema.SearchResultListResponse
|
||||
|
||||
resp, err := c.client.Do(req, &body)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
searchResults := make([]*SearchResult, 0, len(body.SearchResults))
|
||||
for _, searchResult := range body.SearchResults {
|
||||
searchResults = append(searchResults, SearchResultFromSchema(searchResult))
|
||||
}
|
||||
|
||||
return searchResults, resp, nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue