refactor(grafanabackuper): add grafanabackuper package with backup functionality
This commit is contained in:
parent
74cb5e0264
commit
453041b4b2
3 changed files with 263 additions and 0 deletions
159
pkg/grafanabackuper/backup.go
Normal file
159
pkg/grafanabackuper/backup.go
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
package grafanabackuper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"git.ar21.de/yolokube/grafana-backuper/internal/config"
|
||||||
|
"git.ar21.de/yolokube/grafana-backuper/pkg/git"
|
||||||
|
"git.ar21.de/yolokube/grafana-backuper/pkg/grafana"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrAlreadyCommited = errors.New("already committed")
|
||||||
|
|
||||||
|
type BackupClient struct {
|
||||||
|
client *Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BackupClient) Start(ctx context.Context, cfg *config.Config) error {
|
||||||
|
b.client.logger.Debug().Str("search-type", string(grafana.SearchTypeDashboard)).Msg("Searching dashboards by type")
|
||||||
|
dashboards, _, err := b.client.grafana.File.Search(ctx, grafana.WithType(grafana.SearchTypeDashboard))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.client.logger.Debug().
|
||||||
|
Str("search-type", string(grafana.SearchTypeDashboard)).
|
||||||
|
Int("counter", len(dashboards)).
|
||||||
|
Msg("Found dashboards")
|
||||||
|
|
||||||
|
for _, dashboardInfo := range dashboards {
|
||||||
|
b.client.logger.Debug().
|
||||||
|
Str("dashboard-uid", dashboardInfo.UID).
|
||||||
|
Str("dashboard", dashboardInfo.Title).
|
||||||
|
Msg("Fetching dashboard data")
|
||||||
|
var dashboard *grafana.Dashboard
|
||||||
|
dashboard, _, err = b.client.grafana.Dashboard.Get(ctx, dashboardInfo.UID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.client.logger.Debug().
|
||||||
|
Str("dashboard-uid", dashboardInfo.UID).
|
||||||
|
Str("dashboard", dashboardInfo.Title).
|
||||||
|
Msg("Fetching dashboard versions")
|
||||||
|
var versions []*grafana.DashboardVersion
|
||||||
|
versions, _, err = b.client.grafana.DashboardVersion.List(ctx, dashboardInfo.UID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.client.logger.Debug().
|
||||||
|
Str("dashboard-uid", dashboardInfo.UID).
|
||||||
|
Str("dashboard", dashboardInfo.Title).
|
||||||
|
Int("counter", len(versions)).
|
||||||
|
Msg("Found dashboard versions")
|
||||||
|
|
||||||
|
slices.Reverse(versions)
|
||||||
|
|
||||||
|
uncommitedVersion := cfg.ForceCommits
|
||||||
|
for _, version := range versions {
|
||||||
|
b.client.logger.Debug().
|
||||||
|
Str("dashboard-uid", dashboardInfo.UID).
|
||||||
|
Uint("version", version.Version).
|
||||||
|
Msg("Fetching version data")
|
||||||
|
var dashboardVersion *grafana.DashboardVersion
|
||||||
|
dashboardVersion, _, err = b.client.grafana.DashboardVersion.Get(ctx, dashboardInfo.UID, version.Version)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var commitmsg string
|
||||||
|
commitmsg, err = b.commitDashboardVersion(
|
||||||
|
dashboard,
|
||||||
|
dashboardVersion,
|
||||||
|
dashboardInfo,
|
||||||
|
cfg,
|
||||||
|
uncommitedVersion,
|
||||||
|
)
|
||||||
|
if errors.Is(err, ErrAlreadyCommited) {
|
||||||
|
b.client.logger.Info().Str("commit-msg", commitmsg).Msg("Already committed")
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
uncommitedVersion = true
|
||||||
|
b.client.logger.Info().Str("commit-msg", commitmsg).Msg("Commit created")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !b.client.git.HasChanges() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.client.git.Push(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BackupClient) commitDashboardVersion(
|
||||||
|
dashboard *grafana.Dashboard,
|
||||||
|
dashboardVersion *grafana.DashboardVersion,
|
||||||
|
dashboardInfo *grafana.SearchResult,
|
||||||
|
cfg *config.Config,
|
||||||
|
force bool,
|
||||||
|
) (string, error) {
|
||||||
|
commitmsg := Message{
|
||||||
|
ID: dashboardVersion.ID,
|
||||||
|
Path: dashboardInfo.Title,
|
||||||
|
Title: dashboardVersion.Message,
|
||||||
|
UID: dashboardInfo.UID,
|
||||||
|
}.String()
|
||||||
|
|
||||||
|
b.client.logger.Debug().Str("commit", commitmsg).Msg("Checking commit existence")
|
||||||
|
if !force && b.client.git.CommitExists(commitmsg) {
|
||||||
|
return commitmsg, ErrAlreadyCommited
|
||||||
|
}
|
||||||
|
|
||||||
|
b.updateDashboardInfo(dashboard, dashboardVersion)
|
||||||
|
|
||||||
|
b.client.logger.Debug().
|
||||||
|
Str("folder", dashboard.FolderTitle).
|
||||||
|
Str("dashboard-uid", dashboardVersion.DashboardUID).
|
||||||
|
Msg("Marshalling the dashboard version data")
|
||||||
|
data, err := json.MarshalIndent(grafana.SchemaFromDashboardMeta(dashboard), "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return commitmsg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
commitOpts := []git.CommitOption{
|
||||||
|
git.WithAuthor(dashboardVersion.CreatedBy, ""),
|
||||||
|
git.WithFileContent(data, dashboardInfo.Title, dashboard.FolderTitle),
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.client.signer != nil {
|
||||||
|
commitOpts = append(commitOpts, git.WithSigner(*b.client.signer))
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.GitUser != "" {
|
||||||
|
commitOpts = append(commitOpts, git.WithCommitter(cfg.GitUser, cfg.GitEmail))
|
||||||
|
}
|
||||||
|
|
||||||
|
commit := b.client.git.NewCommit(commitOpts...)
|
||||||
|
b.client.logger.Debug().Str("commit", commitmsg).Msg("Creating new commit")
|
||||||
|
return commitmsg, commit.Create(commitmsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BackupClient) updateDashboardInfo(d *grafana.Dashboard, dv *grafana.DashboardVersion) {
|
||||||
|
b.client.logger.Debug().
|
||||||
|
Str("dashboard-uid", dv.DashboardUID).
|
||||||
|
Str("folder", d.FolderTitle).
|
||||||
|
Msg("Updating dashboard information")
|
||||||
|
d.Dashboard = dv.Data
|
||||||
|
d.UpdatedBy = dv.CreatedBy
|
||||||
|
d.Updated = dv.Created
|
||||||
|
d.Version = dv.Version
|
||||||
|
b.client.logger.Debug().
|
||||||
|
Str("dashboard-uid", dv.DashboardUID).
|
||||||
|
Str("folder", d.FolderTitle).
|
||||||
|
Msg("Updated dashboard information successfully")
|
||||||
|
}
|
84
pkg/grafanabackuper/client.go
Normal file
84
pkg/grafanabackuper/client.go
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
package grafanabackuper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.ar21.de/yolokube/grafana-backuper/internal/config"
|
||||||
|
"git.ar21.de/yolokube/grafana-backuper/pkg/git"
|
||||||
|
"git.ar21.de/yolokube/grafana-backuper/pkg/grafana"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClientOption func(*Client)
|
||||||
|
|
||||||
|
func WithZerologLogger(logger zerolog.Logger) ClientOption {
|
||||||
|
return func(c *Client) {
|
||||||
|
c.logger = logger
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
git *git.Project
|
||||||
|
grafana *grafana.Client
|
||||||
|
logger zerolog.Logger
|
||||||
|
signer *git.SignKey
|
||||||
|
|
||||||
|
Backup BackupClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(options ...ClientOption) *Client {
|
||||||
|
client := &Client{}
|
||||||
|
|
||||||
|
for _, option := range options {
|
||||||
|
option(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Backup = BackupClient{client: client}
|
||||||
|
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Prepare(ctx context.Context, cfg *config.Config) error {
|
||||||
|
c.logger.Debug().Msg("Creating new Grafana Client")
|
||||||
|
c.grafana = grafana.NewClient(
|
||||||
|
cfg.GrafanaURL,
|
||||||
|
grafana.WithToken(cfg.GrafanaToken),
|
||||||
|
)
|
||||||
|
c.logger.Debug().Msg("Created new Grafana Client successfully")
|
||||||
|
|
||||||
|
c.git = git.NewProject(
|
||||||
|
cfg.GitRepo,
|
||||||
|
git.WithBasicAuth(cfg.GitUser, cfg.GitPass),
|
||||||
|
git.WithBranch(cfg.GitBranch),
|
||||||
|
git.WithOutputWriter(cfg.Output),
|
||||||
|
)
|
||||||
|
|
||||||
|
c.logger.Debug().Msg("Cloning git project")
|
||||||
|
if err := c.git.Clone(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.logger.Debug().Msg("Checking out git project")
|
||||||
|
if err := c.git.Checkout(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.logger.Debug().Msg("Pulling git project content")
|
||||||
|
if err := c.git.Pull(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.logger.Debug().Msg("Loading git logs")
|
||||||
|
return c.git.LoadLogs()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetSigner(cfg *config.Config) error {
|
||||||
|
if cfg.GPGKey == "" {
|
||||||
|
c.logger.Debug().Msg("GPG key path parameter is empty")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.signer = &git.SignKey{KeyFile: cfg.GPGKey}
|
||||||
|
c.logger.Debug().Msg("Reading GPG key file")
|
||||||
|
return c.signer.ReadKeyFile()
|
||||||
|
}
|
20
pkg/grafanabackuper/message.go
Normal file
20
pkg/grafanabackuper/message.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package grafanabackuper
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
ID uint
|
||||||
|
Path string
|
||||||
|
Title string
|
||||||
|
UID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Message) String() string {
|
||||||
|
commitMsg := fmt.Sprintf("%s: Update %s to version %d", m.Path, m.UID, m.ID)
|
||||||
|
|
||||||
|
if m.Title != "" {
|
||||||
|
commitMsg += fmt.Sprintf(" => %s", m.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
return commitMsg
|
||||||
|
}
|
Loading…
Reference in a new issue