refactor(grafana): rework grafana package and make it more modular

This commit is contained in:
Tom Neuber 2024-06-23 02:37:22 +02:00
parent cfcf3c6c2b
commit 7ce90dacc4
Signed by: tom
GPG key ID: F17EFE4272D89FF6
12 changed files with 710 additions and 282 deletions

3
go.mod
View file

@ -7,6 +7,7 @@ require (
github.com/alecthomas/kong v0.9.0 github.com/alecthomas/kong v0.9.0
github.com/go-git/go-billy/v5 v5.5.0 github.com/go-git/go-billy/v5 v5.5.0
github.com/go-git/go-git/v5 v5.12.0 github.com/go-git/go-git/v5 v5.12.0
golang.org/x/net v0.22.0
) )
require ( require (
@ -25,8 +26,8 @@ require (
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.21.0 // indirect golang.org/x/crypto v0.21.0 // indirect
golang.org/x/mod v0.15.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/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.18.0 // indirect golang.org/x/tools v0.18.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
) )

34
go.sum
View file

@ -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/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 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= 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.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
github.com/alecthomas/assert/v2 v2.1.0/go.mod h1:b/+1DI2Q6NckYi+3mXyH3wFb8qG37K/DuK80n7WefXA= github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
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/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA= github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA=
github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os= 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.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= 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 h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= 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= 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/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 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 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.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= 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 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= 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 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= 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 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-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 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= 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= 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/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 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 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 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= 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/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 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= 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/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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= 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= 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.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.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.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 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 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= 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.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.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.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 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 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= 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.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.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.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 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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= 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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 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.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

143
pkg/grafana/client.go Normal file
View 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
}

View file

@ -4,142 +4,141 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net/url"
"time" "time"
"git.ar21.de/yolokube/grafana-backuper/pkg/grafana/schema"
) )
type BoardProperties struct { type DashboardMeta struct {
IsStarred bool `json:"isStarred,omitempty"` IsStarred bool
IsHome bool `json:"isHome,omitempty"` Type string
IsSnapshot bool `json:"isSnapshot,omitempty"` CanSave bool
Type string `json:"type,omitempty"` CanEdit bool
CanSave bool `json:"canSave"` CanAdmin bool
CanEdit bool `json:"canEdit"` CanStar bool
CanStar bool `json:"canStar"` CanDelete bool
Slug string `json:"slug"` Slug string
Expires time.Time `json:"expires"` URL string
Created time.Time `json:"created"` Expires time.Time
Updated time.Time `json:"updated"` Created time.Time
UpdatedBy string `json:"updatedBy"` Updated time.Time
CreatedBy string `json:"createdBy"` UpdatedBy string
Version int `json:"version"` CreatedBy string
FolderID int `json:"folderId"` Version uint
FolderTitle string `json:"folderTitle"` HasACL bool
FolderURL string `json:"folderUrl"` IsFolder bool
FolderID uint
FolderUID string
FolderTitle string
FolderURL string
Provisioned bool
ProvisionedExternalID string
AnnotationsPermissions AnnotationsPermissions
Dashboard any
} }
type DashboardVersion struct { type AnnotationsPermissions struct {
ID uint `json:"id"` Dashboard AnnotationPermissions
DashboardID uint `json:"dashboardId"` Organization AnnotationPermissions
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"`
} }
func (c *Client) getRawDashboardByUID(ctx context.Context, path string) ([]byte, BoardProperties, error) { type AnnotationPermissions struct {
raw, code, err := c.get(ctx, fmt.Sprintf("api/dashboards/%s", path), nil) 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 { if err != nil {
return nil, BoardProperties{}, err return nil, nil, err
}
if code != 200 {
return raw, BoardProperties{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
} }
var result struct { var body schema.DashboardMeta
Meta BoardProperties `json:"meta"`
}
dec := json.NewDecoder(bytes.NewReader(raw)) resp, err := c.client.Do(req, &body)
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)
if err != nil { if err != nil {
return nil, versionInfo, err return nil, resp, err
}
if code != 200 {
return raw, versionInfo, fmt.Errorf("HTTP error %d: returns %s", code, raw)
} }
dec := json.NewDecoder(bytes.NewReader(raw)) return DashboardMetaFromSchema(body), resp, nil
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
} }
func queryParams(params ...QueryParam) url.Values { type DashboardCreateOpts struct {
u := url.URL{} Dashboard any
q := u.Query() FolderID uint
FolderUID string
for _, p := range params { Message string
p(&q) Overwrite bool
}
return q
} }
func (c *Client) GetDashboardVersionsByDashboardUID(ctx context.Context, uid string, params ...QueryParam) ([]DashboardVersion, error) { func (o DashboardCreateOpts) Validate() error {
var ( if o.FolderUID == "" && o.FolderID == 0 {
raw []byte return errors.New("folder ID or UID missing")
code int }
err error 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 return nil, err
} }
if code != 200 {
return nil, fmt.Errorf("HTTP error %d: returns %s", code, raw)
}
var versions []DashboardVersion return c.client.Do(req, nil)
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
} }

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

View file

@ -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
View 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,
}
}

View 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"`
}

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

View file

@ -0,0 +1,6 @@
package schema
type ErrorResponse struct {
Message string `json:"message"`
TraceID uint `json:"traceid"`
}

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

View file

@ -2,68 +2,30 @@ package grafana
import ( import (
"context" "context"
"encoding/json"
"fmt"
"net/url" "net/url"
"strconv" "strconv"
"git.ar21.de/yolokube/grafana-backuper/pkg/grafana/schema"
)
type (
SearchParam func(*url.Values)
SearchType string
) )
const ( const (
SearchTypeFolder SearchParamType = "dash-folder" SearchTypeFolder SearchType = "dash-folder"
SearchTypeDashboard SearchParamType = "dash-db" SearchTypeDashboard SearchType = "dash-db"
) )
type FoundBoard struct { func WithType(searchType SearchType) SearchParam {
ID uint `json:"id"` return func(v *url.Values) {
UID string `json:"uid"` v.Set("type", string(searchType))
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"`
} }
type ( func WithQuery(query string) SearchParam {
// 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 {
return func(v *url.Values) { return func(v *url.Values) {
if query != "" { if query != "" {
v.Set("query", 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) { 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) { return func(v *url.Values) {
if tag != "" { if tag != "" {
v.Add("tag", tag) v.Add("tag", tag)
@ -85,8 +47,59 @@ func SearchTag(tag string) SearchParam {
} }
} }
func SearchType(searchType SearchParamType) SearchParam { type SearchResult struct {
return func(v *url.Values) { ID uint
v.Set("type", string(searchType)) 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
} }