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
3
go.mod
3
go.mod
|
@ -7,6 +7,7 @@ require (
|
|||
github.com/alecthomas/kong v0.9.0
|
||||
github.com/go-git/go-billy/v5 v5.5.0
|
||||
github.com/go-git/go-git/v5 v5.12.0
|
||||
golang.org/x/net v0.22.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -25,8 +26,8 @@ require (
|
|||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/mod v0.15.0 // indirect
|
||||
golang.org/x/net v0.22.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.18.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
|
34
go.sum
34
go.sum
|
@ -5,14 +5,12 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
|
|||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/alecthomas/assert/v2 v2.1.0 h1:tbredtNcQnoSd3QBhQWI7QZ3XHOVkw1Moklp2ojoH/0=
|
||||
github.com/alecthomas/assert/v2 v2.1.0/go.mod h1:b/+1DI2Q6NckYi+3mXyH3wFb8qG37K/DuK80n7WefXA=
|
||||
github.com/alecthomas/kong v0.8.1 h1:acZdn3m4lLRobeh3Zi2S2EpnXTd1mOL6U7xVml+vfkY=
|
||||
github.com/alecthomas/kong v0.8.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U=
|
||||
github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
|
||||
github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA=
|
||||
github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os=
|
||||
github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
|
||||
github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
|
@ -30,16 +28,14 @@ github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcej
|
|||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
|
||||
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
||||
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
|
||||
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4=
|
||||
github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
|
||||
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
|
||||
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
|
@ -69,20 +65,16 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ=
|
||||
github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
||||
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
|
||||
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
|
@ -91,8 +83,6 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
|||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
|
@ -106,8 +96,6 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
|||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -128,8 +116,6 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
@ -137,8 +123,8 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX
|
|||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
|
|
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…
Reference in a new issue