package git import ( "fmt" "os" "path/filepath" "strings" "time" "git.ar21.de/yolokube/grafana-backuper/pkg/grafana" "github.com/ProtonMail/go-crypto/openpgp" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/go-git/go-git/v5/storage/memory" ) var ( fs billy.Filesystem storer *memory.Storage ) type Payload struct { Author *object.Signature Committer *object.Signature Content []byte Dashboard grafana.FoundBoard DashboardInfo grafana.BoardProperties Directory string KeyFile string Repository *git.Repository Version grafana.DashboardVersion } func NewPayload(keyFile string) *Payload { fs = memfs.New() storer = memory.NewStorage() return &Payload{ Author: nil, Committer: nil, Content: []byte{}, Dashboard: grafana.FoundBoard{}, DashboardInfo: grafana.BoardProperties{}, Directory: "", KeyFile: keyFile, Repository: nil, Version: grafana.DashboardVersion{}, } } func (p *Payload) AddAuthor(name, email string) { p.Author = &object.Signature{ Name: name, Email: email, When: time.Now(), } } func (p *Payload) UpdateAuthor(timestamp time.Time) { p.Author.When = timestamp } func (p *Payload) AddCommitter(name, email string) { p.Committer = &object.Signature{ Name: name, Email: email, When: time.Now(), } } func (p *Payload) UpdateCommitter() { p.Committer.When = time.Now() } func (p *Payload) UpdateContent(content []byte) { p.Content = content } func (p *Payload) UpdateDashboard(dashboard grafana.FoundBoard) { p.Dashboard = dashboard } func (p *Payload) UpdateDashboardInfo(dashboardInfo grafana.BoardProperties) { p.DashboardInfo = dashboardInfo } func (p *Payload) UpdateVersion(version grafana.DashboardVersion) { p.Version = version } func (p *Payload) GetRepo(repoURL, user, password string) (err error) { p.Repository, err = git.Clone( storer, fs, &git.CloneOptions{ Auth: genAuth(user, password), URL: repoURL, Progress: os.Stdout, }, ) p.Directory = filepath.Base(repoURL) return } func (p *Payload) IsVersionCommitted(branch string) bool { refName := plumbing.NewBranchReferenceName(branch) ref, err := p.Repository.Reference(refName, false) if err != nil { return false } commitIter, err := p.Repository.Log(&git.LogOptions{From: ref.Hash()}) if err != nil { return false } err = commitIter.ForEach(func(commit *object.Commit) error { if strings.Contains(commit.Message, fmt.Sprintf("Update %s", p.Version.DashboardUID)) && strings.Contains(commit.Message, fmt.Sprintf("version %d", p.Version.ID)) { return fmt.Errorf("version already committed") } return nil }) return err != nil } func genAuth(user, password string) *http.BasicAuth { return &http.BasicAuth{ Username: user, Password: password, } } func commitDashboard(repo *git.Repository, content []byte, commitMsg, dashboardTitle, folderTitle, gitRepoDirectory, keyFile string, author, committer *object.Signature) (err error) { var ( file billy.File signer *openpgp.Entity worktree *git.Worktree ) if strings.TrimSpace(keyFile) != "" { signer, err = getSigner(keyFile) if err != nil { return } } worktree, err = repo.Worktree() if err != nil { return } if err = fs.MkdirAll(folderTitle, 0755); err != nil { return } filePath := filepath.Join(folderTitle, fmt.Sprintf("%s.json", dashboardTitle)) if file, err = fs.Create(filePath); err != nil { return } if _, err = file.Write(content); err != nil { return } if err = file.Close(); err != nil { return } if _, err = worktree.Add(filePath); err != nil { return } _, err = worktree.Commit( commitMsg, &git.CommitOptions{ Author: author, Committer: committer, SignKey: signer, }, ) return } func (p *Payload) CreateCommit() error { var commitmsg string if p.Version.Message != "" { commitmsg = fmt.Sprintf( "%s: Update %s to version %d => %s", p.Dashboard.Title, p.Version.DashboardUID, p.Version.ID, p.Version.Message, ) } else { commitmsg = fmt.Sprintf( "%s: Update %s to version %d", p.Dashboard.Title, p.Version.DashboardUID, p.Version.ID, ) } p.UpdateAuthor(p.Version.Created) p.UpdateCommitter() return commitDashboard( p.Repository, p.Content, commitmsg, p.Dashboard.Title, p.DashboardInfo.FolderTitle, p.Directory, p.KeyFile, p.Author, p.Committer, ) } func (p *Payload) PushToRemote(user, password string) error { origin, err := p.Repository.Remote("origin") if err != nil { return err } return origin.Push( &git.PushOptions{ Auth: genAuth(user, password), Progress: os.Stdout, }, ) }