grafana_gen/git.go
2026-02-24 12:45:35 +03:00

207 lines
5.7 KiB
Go

package main
import (
"fmt"
"log"
"os"
"os/exec"
"strings"
)
func updateTemplates(config Config) (string, error) {
log.Printf("Updating templates from Git: %s (branch: %s)", sanitizeRepoURL(config.TemplatesRepo), config.TemplatesBranch)
// Check if templates directory exists
if _, err := os.Stat(config.TemplatesPath); os.IsNotExist(err) {
// Clone repository
if err := cloneRepository(config); err != nil {
return "", err
}
return getCurrentCommit(config.TemplatesPath), nil
}
// Check if it's a valid git repository
if !isGitRepository(config.TemplatesPath) {
log.Printf("Warning: %s exists but is not a git repository", config.TemplatesPath)
log.Printf("Remove the directory or use -skip-git-pull flag")
return "", fmt.Errorf("not a git repository")
}
// Pull latest changes and detect if there were updates
if err := pullRepository(config); err != nil {
return getCurrentCommit(config.TemplatesPath), err
}
return getCurrentCommit(config.TemplatesPath), nil
}
func cloneRepository(config Config) error {
log.Printf("Cloning templates repository...")
repoURL := buildRepoURL(config.TemplatesRepo, config.GitToken)
cmd := exec.Command("git", "clone", "-b", config.TemplatesBranch, repoURL, config.TemplatesPath)
cmd.Stdout = os.Stdout
// Don't show stderr to avoid leaking token
var stderr strings.Builder
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
// Sanitize error message
errMsg := sanitizeGitError(stderr.String())
return fmt.Errorf("failed to clone repository: %w\n%s", err, errMsg)
}
log.Printf("Templates cloned successfully")
showCurrentCommit(config.TemplatesPath)
return nil
}
func pullRepository(config Config) error {
// Get current commit before pull
oldCommit := getCurrentCommit(config.TemplatesPath)
log.Printf("Pulling latest templates...")
// Update remote URL with token if provided
if config.GitToken != "" {
if err := updateRemoteURL(config); err != nil {
log.Printf("Warning: Failed to update remote URL: %v", err)
}
}
cmd := exec.Command("git", "-C", config.TemplatesPath, "pull", "origin", config.TemplatesBranch)
var stdout, stderr strings.Builder
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
errMsg := sanitizeGitError(stderr.String())
return fmt.Errorf("failed to pull repository: %w\n%s", err, errMsg)
}
// Get current commit after pull
newCommit := getCurrentCommit(config.TemplatesPath)
// Check if there were changes
if oldCommit == newCommit {
log.Printf("No new changes in templates (commit: %s)", truncateHash(oldCommit))
} else if oldCommit == "" {
log.Printf("Successfully pulled templates")
showCurrentCommit(config.TemplatesPath)
} else {
log.Printf("Templates updated: %s -> %s", truncateHash(oldCommit), truncateHash(newCommit))
showCommitDiff(config.TemplatesPath, oldCommit, newCommit)
}
return nil
}
func updateRemoteURL(config Config) error {
repoURL := buildRepoURL(config.TemplatesRepo, config.GitToken)
cmd := exec.Command("git", "-C", config.TemplatesPath, "remote", "set-url", "origin", repoURL)
return cmd.Run()
}
func buildRepoURL(repoURL, token string) string {
if token == "" {
return repoURL
}
// For Gitea, format: https://TOKEN@svc-git.cirex.ru/...
// Remove https:// prefix if present
cleanURL := strings.TrimPrefix(repoURL, "https://")
cleanURL = strings.TrimPrefix(cleanURL, "http://")
return fmt.Sprintf("https://%s@%s", token, cleanURL)
}
func sanitizeRepoURL(repoURL string) string {
// Remove token from URL for logging
if strings.Contains(repoURL, "@") {
parts := strings.SplitN(repoURL, "@", 2)
if len(parts) == 2 {
// Keep protocol and domain
protocol := "https://"
if strings.HasPrefix(repoURL, "http://") {
protocol = "http://"
}
return protocol + "***:***@" + parts[1]
}
}
return repoURL
}
func sanitizeGitError(errMsg string) string {
// Remove potential token from error messages
// Support both Gitea (TOKEN@) and GitLab (oauth2:TOKEN@) formats
// Gitea format: https://TOKEN@host
if strings.Contains(errMsg, "@") && strings.Contains(errMsg, "://") {
parts := strings.Split(errMsg, "://")
for i := 1; i < len(parts); i++ {
atIndex := strings.Index(parts[i], "@")
if atIndex > 0 {
parts[i] = "***@" + parts[i][atIndex+1:]
}
}
errMsg = strings.Join(parts, "://")
}
// GitLab format: https://oauth2:TOKEN@host (legacy support)
if strings.Contains(errMsg, "oauth2:") {
parts := strings.Split(errMsg, "oauth2:")
for i := 1; i < len(parts); i++ {
atIndex := strings.Index(parts[i], "@")
if atIndex > 0 {
parts[i] = "***" + parts[i][atIndex:]
}
}
errMsg = strings.Join(parts, "oauth2:")
}
return errMsg
}
func isGitRepository(path string) bool {
gitDir := path + "/.git"
_, err := os.Stat(gitDir)
return err == nil
}
func getCurrentCommit(templatesPath string) string {
cmd := exec.Command("git", "-C", templatesPath, "rev-parse", "HEAD")
output, err := cmd.Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(output))
}
func showCurrentCommit(templatesPath string) {
cmd := exec.Command("git", "-C", templatesPath, "log", "-1", "--oneline")
output, err := cmd.Output()
if err == nil {
log.Printf("Current template commit: %s", strings.TrimSpace(string(output)))
}
}
func showCommitDiff(templatesPath, oldCommit, newCommit string) {
// Show files changed
cmd := exec.Command("git", "-C", templatesPath, "diff", "--name-only", oldCommit, newCommit)
output, err := cmd.Output()
if err == nil && len(output) > 0 {
files := strings.Split(strings.TrimSpace(string(output)), "\n")
log.Printf("Changed files:")
for _, file := range files {
log.Printf(" - %s", file)
}
}
}
func truncateHash(hash string) string {
if len(hash) > 8 {
return hash[:8]
}
return hash
}