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

283 lines
9.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"flag"
"log"
"os"
)
func main() {
// Load environment variables from .env file before parsing flags
// This allows .env file to provide defaults that can be overridden by flags
loadEnvFileWithFallback()
config := parseFlags()
// Initialize state manager
stateManager := NewStateManager(config.StateFile)
if err := stateManager.Load(); err != nil {
log.Printf("Warning: Failed to load state: %v", err)
}
// Show last run info
if lastRun := stateManager.GetLastRun(); !lastRun.IsZero() {
log.Printf("Last successful run: %s", lastRun.Format("2006-01-02 15:04:05"))
clientCount, domainCount, sidCount := stateManager.GetStats()
log.Printf("Previous state: %d clients, %d domains, %d SIDs", clientCount, domainCount, sidCount)
}
// Update templates from Git
var templateCommit string
if !config.SkipGitPull {
commit, err := updateTemplates(config)
if err != nil {
log.Printf("Warning: Failed to update templates from Git: %v", err)
log.Printf("Continuing with existing templates...")
}
templateCommit = commit
} else {
// Get current commit without pulling
templateCommit = getCurrentCommit(config.TemplatesPath)
}
// Load dashboard template
template, err := loadTemplate(config.TemplatesPath)
if err != nil {
log.Fatalf("Failed to load template: %v", err)
}
log.Printf("Loaded template version: %s", template.Version)
// Connect to database
db, err := connectDB(config)
if err != nil {
log.Fatalf("Failed to connect to database: %v", err)
}
defer db.Close()
// Fetch clients data
clients, err := fetchClientsData(db, config.UseManualInfo)
if err != nil {
log.Fatalf("Failed to fetch clients data: %v", err)
}
log.Printf("Found %d clients", len(clients))
// Check if there are any clients
if len(clients) == 0 {
log.Printf("No clients found in database. Nothing to generate.")
log.Printf("This is not an error - database is empty or no data matches criteria.")
os.Exit(0)
}
// Filter out clients without domains
validClients := make(map[string]ClientData)
skippedCount := 0
for clientTitle, clientData := range clients {
if len(clientData.Domains) == 0 {
log.Printf("Warning: Client %s has no domains, skipping", clientTitle)
skippedCount++
continue
}
validClients[clientTitle] = clientData
}
if len(validClients) == 0 {
log.Printf("No valid clients with domains found. Nothing to generate.")
os.Exit(0)
}
// Check for changes
hasChanges, changeReason := stateManager.CheckChanges(templateCommit, template.Version, clients)
if !hasChanges && !config.ForceRun {
log.Printf("=== No Changes Detected ===")
log.Printf("No changes in templates or database since last run.")
log.Printf("Skipping dashboard generation.")
log.Printf("")
log.Printf("Use -force flag to regenerate anyway.")
os.Exit(0)
}
if hasChanges {
log.Printf("=== Changes Detected ===")
log.Printf("%s", changeReason)
log.Printf("")
}
if config.ForceRun {
log.Printf("Force run enabled - regenerating dashboard")
}
// Generate single dashboard with all clients
log.Printf("Generating single dashboard: %s", config.DashboardTitle)
log.Printf("Including %d clients with %d total domains", len(validClients), countTotalDomains(validClients))
dashboard := generateSingleDashboard(validClients, template, config)
if config.DryRun {
logDryRun(config.DashboardTitle, dashboard)
log.Printf("=== Summary ===")
log.Printf("Clients: %d", len(validClients))
log.Printf("Skipped: %d", skippedCount)
log.Printf("DRY RUN: Dashboard generated but not sent to Grafana")
os.Exit(0)
}
// Send to Grafana
if err := sendToGrafana(dashboard, config); err != nil {
log.Printf("Error sending dashboard: %v", err)
log.Printf("=== Summary ===")
log.Printf("Clients: %d", len(validClients))
log.Printf("Skipped: %d", skippedCount)
log.Printf("Status: FAILED")
os.Exit(1)
}
log.Printf("Successfully created/updated dashboard: %s", config.DashboardTitle)
// Summary
log.Printf("=== Summary ===")
log.Printf("Dashboard: %s", config.DashboardTitle)
log.Printf("Clients: %d", len(validClients))
if skippedCount > 0 {
log.Printf("Skipped: %d", skippedCount)
}
log.Printf("Total domains: %d", countTotalDomains(validClients))
log.Printf("Status: SUCCESS")
// Update state
stateManager.Update(templateCommit, template.Version, clients, changeReason)
if err := stateManager.Save(); err != nil {
log.Printf("Warning: Failed to save state: %v", err)
} else {
log.Printf("State saved to %s", config.StateFile)
}
}
func countTotalDomains(clients map[string]ClientData) int {
count := 0
for _, client := range clients {
count += len(client.Domains)
}
return count
}
func parseFlags() Config {
config := Config{}
// Database flags - resolve from env vars with fallback to defaults
dbUser := getEnvOrDefault("DB_USER", DefaultDBUser)
dbPassword := getEnvOrDefault("DB_PASSWORD", DefaultDBPassword)
// Grafana settings - resolve from env vars with fallback to defaults
grafanaFolder := getEnvOrDefault("GRAFANA_FOLDER", DefaultGrafanaFolder)
dashboardTitle := getEnvOrDefault("DASHBOARD_TITLE", DefaultDashboardTitle)
flag.StringVar(&config.DBHost, "db-host", DefaultPrimaryDBHost, "PostgreSQL host")
flag.IntVar(&config.DBPort, "db-port", DefaultDBPort, "PostgreSQL port")
flag.StringVar(&config.DBUser, "db-user", dbUser, "PostgreSQL user")
flag.StringVar(&config.DBPassword, "db-password", dbPassword, "PostgreSQL password")
flag.StringVar(&config.DBName, "db-name", DefaultDBName, "PostgreSQL database name")
flag.BoolVar(&config.UseManualInfo, "use-manual", false, "Use manual_info table instead of sp_info")
// Grafana flags
var grafanaURLFlag, grafanaAPIKeyFlag string
flag.StringVar(&grafanaURLFlag, "grafana-url", "", "Grafana URL")
flag.StringVar(&grafanaAPIKeyFlag, "grafana-api-key", "", "Grafana API key")
flag.StringVar(&config.GrafanaFolder, "grafana-folder", grafanaFolder, "Grafana folder for dashboards")
flag.StringVar(&config.DashboardTitle, "dashboard-title", dashboardTitle, "Dashboard title")
// Git flags
var gitTokenFlag string
flag.StringVar(&gitTokenFlag, "git-token", "", "Gitea token (overrides env var and hardcoded value)")
flag.StringVar(&config.TemplatesRepo, "templates-repo", DefaultTemplatesRepo, "Git repository URL for templates")
flag.StringVar(&config.TemplatesBranch, "templates-branch", DefaultTemplatesBranch, "Git branch for templates")
flag.StringVar(&config.TemplatesPath, "templates-path", DefaultTemplatesPath, "Local path for templates")
flag.BoolVar(&config.SkipGitPull, "skip-git-pull", false, "Skip updating templates from Git")
// Other flags
flag.BoolVar(&config.DryRun, "dry-run", false, "Generate JSON but don't send to Grafana")
flag.StringVar(&config.StateFile, "state-file", DefaultStateFile, "Path to state file")
flag.BoolVar(&config.ForceRun, "force", false, "Force regeneration even if no changes detected")
flag.Parse()
// Resolve tokens with priority
config.GitToken = resolveToken("Git", gitTokenFlag, "GIT_TOKEN", DefaultGitToken)
config.GrafanaAPIKey = resolveToken("Grafana API", grafanaAPIKeyFlag, "GRAFANA_API_KEY", DefaultGrafanaAPIKey)
config.GrafanaURL = resolveValue("Grafana URL", grafanaURLFlag, "GRAFANA_URL", DefaultGrafanaURL)
// Validate required parameters
if config.GrafanaURL == "" {
log.Fatal("Error: grafana-url is required (use -grafana-url flag, GRAFANA_URL env var, .env file, or set DefaultGrafanaURL in config.go)")
}
if config.GrafanaAPIKey == "" {
log.Fatal("Error: grafana-api-key is required (use -grafana-api-key flag, GRAFANA_API_KEY env var, .env file, or set DefaultGrafanaAPIKey in config.go)")
}
if config.DBUser == "" {
log.Fatal("Error: db-user is required (use -db-user flag, DB_USER env var, or .env file)")
}
if config.DBPassword == "" {
log.Fatal("Error: db-password is required (use -db-password flag, DB_PASSWORD env var, or .env file)")
}
return config
}
// resolveToken определяет токен с приоритетом:
// 1. Флаг командной строки
// 2. Переменная окружения
// 3. Hardcoded константа
func resolveToken(name, flagValue, envVar, defaultValue string) string {
// Приоритет 1: флаг командной строки
if flagValue != "" {
log.Printf("Using %s token from command line flag", name)
return flagValue
}
// Приоритет 2: переменная окружения
if envValue := os.Getenv(envVar); envValue != "" {
log.Printf("Using %s token from %s environment variable", name, envVar)
return envValue
}
// Приоритет 3: hardcoded в config.go
if defaultValue != "" {
log.Printf("Using %s token from config.go (hardcoded)", name)
return defaultValue
}
// Токен не задан
return ""
}
// resolveValue определяет значение с приоритетом (для non-secret параметров)
func resolveValue(name, flagValue, envVar, defaultValue string) string {
// Приоритет 1: флаг командной строки
if flagValue != "" {
return flagValue
}
// Приоритет 2: переменная окружения
if envValue := os.Getenv(envVar); envValue != "" {
log.Printf("Using %s from %s environment variable", name, envVar)
return envValue
}
// Приоритет 3: hardcoded в config.go
if defaultValue != "" {
log.Printf("Using %s from config.go (hardcoded)", name)
return defaultValue
}
return ""
}
// getEnvOrDefault возвращает переменную окружения или default значение
func getEnvOrDefault(envVar, defaultValue string) string {
if value := os.Getenv(envVar); value != "" {
return value
}
return defaultValue
}