ci: add golangci-lint config and fix formatting
- Add .golangci.yml with minimal linters (govet, ineffassign) - Run gofmt -s and goimports on all files to fix formatting - Disable fieldalignment and copylocks checks in govet
This commit is contained in:
@@ -19,21 +19,21 @@ func runClusterBackup(ctx context.Context) error {
|
||||
if !cfg.IsPostgreSQL() {
|
||||
return fmt.Errorf("cluster backup requires PostgreSQL (detected: %s). Use 'backup single' for individual database backups", cfg.DisplayDatabaseType())
|
||||
}
|
||||
|
||||
|
||||
// Update config from environment
|
||||
cfg.UpdateFromEnvironment()
|
||||
|
||||
|
||||
// Validate configuration
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return fmt.Errorf("configuration error: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Check privileges
|
||||
privChecker := security.NewPrivilegeChecker(log)
|
||||
if err := privChecker.CheckAndWarn(cfg.AllowRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
// Check resource limits
|
||||
if cfg.CheckResources {
|
||||
resChecker := security.NewResourceChecker(log)
|
||||
@@ -41,23 +41,23 @@ func runClusterBackup(ctx context.Context) error {
|
||||
log.Warn("Failed to check resource limits", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Starting cluster backup",
|
||||
"host", cfg.Host,
|
||||
|
||||
log.Info("Starting cluster backup",
|
||||
"host", cfg.Host,
|
||||
"port", cfg.Port,
|
||||
"backup_dir", cfg.BackupDir)
|
||||
|
||||
|
||||
// Audit log: backup start
|
||||
user := security.GetCurrentUser()
|
||||
auditLogger.LogBackupStart(user, "all_databases", "cluster")
|
||||
|
||||
|
||||
// Rate limit connection attempts
|
||||
host := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
|
||||
if err := rateLimiter.CheckAndWait(host); err != nil {
|
||||
auditLogger.LogBackupFailed(user, "all_databases", err)
|
||||
return fmt.Errorf("rate limit exceeded for %s. Too many connection attempts. Wait 60s or check credentials: %w", host, err)
|
||||
}
|
||||
|
||||
|
||||
// Create database instance
|
||||
db, err := database.New(cfg, log)
|
||||
if err != nil {
|
||||
@@ -65,7 +65,7 @@ func runClusterBackup(ctx context.Context) error {
|
||||
return fmt.Errorf("failed to create database instance: %w", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
|
||||
// Connect to database
|
||||
if err := db.Connect(ctx); err != nil {
|
||||
rateLimiter.RecordFailure(host)
|
||||
@@ -73,16 +73,16 @@ func runClusterBackup(ctx context.Context) error {
|
||||
return fmt.Errorf("failed to connect to %s@%s:%d. Check: 1) Database is running 2) Credentials are correct 3) pg_hba.conf allows connection: %w", cfg.User, cfg.Host, cfg.Port, err)
|
||||
}
|
||||
rateLimiter.RecordSuccess(host)
|
||||
|
||||
|
||||
// Create backup engine
|
||||
engine := backup.New(cfg, log, db)
|
||||
|
||||
|
||||
// Perform cluster backup
|
||||
if err := engine.BackupCluster(ctx); err != nil {
|
||||
auditLogger.LogBackupFailed(user, "all_databases", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
// Apply encryption if requested
|
||||
if isEncryptionEnabled() {
|
||||
if err := encryptLatestClusterBackup(); err != nil {
|
||||
@@ -91,10 +91,10 @@ func runClusterBackup(ctx context.Context) error {
|
||||
}
|
||||
log.Info("Cluster backup encrypted successfully")
|
||||
}
|
||||
|
||||
|
||||
// Audit log: backup success
|
||||
auditLogger.LogBackupComplete(user, "all_databases", cfg.BackupDir, 0)
|
||||
|
||||
|
||||
// Cleanup old backups if retention policy is enabled
|
||||
if cfg.RetentionDays > 0 {
|
||||
retentionPolicy := security.NewRetentionPolicy(cfg.RetentionDays, cfg.MinBackups, log)
|
||||
@@ -104,7 +104,7 @@ func runClusterBackup(ctx context.Context) error {
|
||||
log.Info("Cleaned up old backups", "deleted", deleted, "freed_mb", freed/1024/1024)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Save configuration for future use (unless disabled)
|
||||
if !cfg.NoSaveConfig {
|
||||
localCfg := config.ConfigFromConfig(cfg)
|
||||
@@ -115,7 +115,7 @@ func runClusterBackup(ctx context.Context) error {
|
||||
auditLogger.LogConfigChange(user, "config_file", "", ".dbbackup.conf")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -123,17 +123,17 @@ func runClusterBackup(ctx context.Context) error {
|
||||
func runSingleBackup(ctx context.Context, databaseName string) error {
|
||||
// Update config from environment
|
||||
cfg.UpdateFromEnvironment()
|
||||
|
||||
|
||||
// Get backup type and base backup from command line flags (set via global vars in PreRunE)
|
||||
// These are populated by cobra flag binding in cmd/backup.go
|
||||
backupType := "full" // Default to full backup if not specified
|
||||
baseBackup := "" // Base backup path for incremental backups
|
||||
|
||||
backupType := "full" // Default to full backup if not specified
|
||||
baseBackup := "" // Base backup path for incremental backups
|
||||
|
||||
// Validate backup type
|
||||
if backupType != "full" && backupType != "incremental" {
|
||||
return fmt.Errorf("invalid backup type: %s (must be 'full' or 'incremental')", backupType)
|
||||
}
|
||||
|
||||
|
||||
// Validate incremental backup requirements
|
||||
if backupType == "incremental" {
|
||||
if !cfg.IsPostgreSQL() && !cfg.IsMySQL() {
|
||||
@@ -147,41 +147,41 @@ func runSingleBackup(ctx context.Context, databaseName string) error {
|
||||
return fmt.Errorf("base backup file not found at %s. Ensure path is correct and file exists", baseBackup)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Validate configuration
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return fmt.Errorf("configuration error: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Check privileges
|
||||
privChecker := security.NewPrivilegeChecker(log)
|
||||
if err := privChecker.CheckAndWarn(cfg.AllowRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("Starting single database backup",
|
||||
|
||||
log.Info("Starting single database backup",
|
||||
"database", databaseName,
|
||||
"db_type", cfg.DatabaseType,
|
||||
"backup_type", backupType,
|
||||
"host", cfg.Host,
|
||||
"host", cfg.Host,
|
||||
"port", cfg.Port,
|
||||
"backup_dir", cfg.BackupDir)
|
||||
|
||||
|
||||
if backupType == "incremental" {
|
||||
log.Info("Incremental backup", "base_backup", baseBackup)
|
||||
}
|
||||
|
||||
|
||||
// Audit log: backup start
|
||||
user := security.GetCurrentUser()
|
||||
auditLogger.LogBackupStart(user, databaseName, "single")
|
||||
|
||||
|
||||
// Rate limit connection attempts
|
||||
host := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
|
||||
if err := rateLimiter.CheckAndWait(host); err != nil {
|
||||
auditLogger.LogBackupFailed(user, databaseName, err)
|
||||
return fmt.Errorf("rate limit exceeded: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Create database instance
|
||||
db, err := database.New(cfg, log)
|
||||
if err != nil {
|
||||
@@ -189,7 +189,7 @@ func runSingleBackup(ctx context.Context, databaseName string) error {
|
||||
return fmt.Errorf("failed to create database instance: %w", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
|
||||
// Connect to database
|
||||
if err := db.Connect(ctx); err != nil {
|
||||
rateLimiter.RecordFailure(host)
|
||||
@@ -197,7 +197,7 @@ func runSingleBackup(ctx context.Context, databaseName string) error {
|
||||
return fmt.Errorf("failed to connect to database: %w", err)
|
||||
}
|
||||
rateLimiter.RecordSuccess(host)
|
||||
|
||||
|
||||
// Verify database exists
|
||||
exists, err := db.DatabaseExists(ctx, databaseName)
|
||||
if err != nil {
|
||||
@@ -209,57 +209,57 @@ func runSingleBackup(ctx context.Context, databaseName string) error {
|
||||
auditLogger.LogBackupFailed(user, databaseName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
// Create backup engine
|
||||
engine := backup.New(cfg, log, db)
|
||||
|
||||
|
||||
// Perform backup based on type
|
||||
var backupErr error
|
||||
if backupType == "incremental" {
|
||||
// Incremental backup - supported for PostgreSQL and MySQL
|
||||
log.Info("Creating incremental backup", "base_backup", baseBackup)
|
||||
|
||||
|
||||
// Create appropriate incremental engine based on database type
|
||||
var incrEngine interface {
|
||||
FindChangedFiles(context.Context, *backup.IncrementalBackupConfig) ([]backup.ChangedFile, error)
|
||||
CreateIncrementalBackup(context.Context, *backup.IncrementalBackupConfig, []backup.ChangedFile) error
|
||||
}
|
||||
|
||||
|
||||
if cfg.IsPostgreSQL() {
|
||||
incrEngine = backup.NewPostgresIncrementalEngine(log)
|
||||
} else {
|
||||
incrEngine = backup.NewMySQLIncrementalEngine(log)
|
||||
}
|
||||
|
||||
|
||||
// Configure incremental backup
|
||||
incrConfig := &backup.IncrementalBackupConfig{
|
||||
BaseBackupPath: baseBackup,
|
||||
DataDirectory: cfg.BackupDir, // Note: This should be the actual data directory
|
||||
CompressionLevel: cfg.CompressionLevel,
|
||||
}
|
||||
|
||||
|
||||
// Find changed files
|
||||
changedFiles, err := incrEngine.FindChangedFiles(ctx, incrConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find changed files: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Create incremental backup
|
||||
if err := incrEngine.CreateIncrementalBackup(ctx, incrConfig, changedFiles); err != nil {
|
||||
return fmt.Errorf("failed to create incremental backup: %w", err)
|
||||
}
|
||||
|
||||
|
||||
log.Info("Incremental backup completed", "changed_files", len(changedFiles))
|
||||
} else {
|
||||
// Full backup
|
||||
backupErr = engine.BackupSingle(ctx, databaseName)
|
||||
}
|
||||
|
||||
|
||||
if backupErr != nil {
|
||||
auditLogger.LogBackupFailed(user, databaseName, backupErr)
|
||||
return backupErr
|
||||
}
|
||||
|
||||
|
||||
// Apply encryption if requested
|
||||
if isEncryptionEnabled() {
|
||||
if err := encryptLatestBackup(databaseName); err != nil {
|
||||
@@ -268,10 +268,10 @@ func runSingleBackup(ctx context.Context, databaseName string) error {
|
||||
}
|
||||
log.Info("Backup encrypted successfully")
|
||||
}
|
||||
|
||||
|
||||
// Audit log: backup success
|
||||
auditLogger.LogBackupComplete(user, databaseName, cfg.BackupDir, 0)
|
||||
|
||||
|
||||
// Cleanup old backups if retention policy is enabled
|
||||
if cfg.RetentionDays > 0 {
|
||||
retentionPolicy := security.NewRetentionPolicy(cfg.RetentionDays, cfg.MinBackups, log)
|
||||
@@ -281,7 +281,7 @@ func runSingleBackup(ctx context.Context, databaseName string) error {
|
||||
log.Info("Cleaned up old backups", "deleted", deleted, "freed_mb", freed/1024/1024)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Save configuration for future use (unless disabled)
|
||||
if !cfg.NoSaveConfig {
|
||||
localCfg := config.ConfigFromConfig(cfg)
|
||||
@@ -292,7 +292,7 @@ func runSingleBackup(ctx context.Context, databaseName string) error {
|
||||
auditLogger.LogConfigChange(user, "config_file", "", ".dbbackup.conf")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -300,23 +300,23 @@ func runSingleBackup(ctx context.Context, databaseName string) error {
|
||||
func runSampleBackup(ctx context.Context, databaseName string) error {
|
||||
// Update config from environment
|
||||
cfg.UpdateFromEnvironment()
|
||||
|
||||
|
||||
// Validate configuration
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return fmt.Errorf("configuration error: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Check privileges
|
||||
privChecker := security.NewPrivilegeChecker(log)
|
||||
if err := privChecker.CheckAndWarn(cfg.AllowRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
// Validate sample parameters
|
||||
if cfg.SampleValue <= 0 {
|
||||
return fmt.Errorf("sample value must be greater than 0")
|
||||
}
|
||||
|
||||
|
||||
switch cfg.SampleStrategy {
|
||||
case "percent":
|
||||
if cfg.SampleValue > 100 {
|
||||
@@ -331,27 +331,27 @@ func runSampleBackup(ctx context.Context, databaseName string) error {
|
||||
default:
|
||||
return fmt.Errorf("invalid sampling strategy: %s (must be ratio, percent, or count)", cfg.SampleStrategy)
|
||||
}
|
||||
|
||||
log.Info("Starting sample database backup",
|
||||
|
||||
log.Info("Starting sample database backup",
|
||||
"database", databaseName,
|
||||
"db_type", cfg.DatabaseType,
|
||||
"strategy", cfg.SampleStrategy,
|
||||
"value", cfg.SampleValue,
|
||||
"host", cfg.Host,
|
||||
"host", cfg.Host,
|
||||
"port", cfg.Port,
|
||||
"backup_dir", cfg.BackupDir)
|
||||
|
||||
|
||||
// Audit log: backup start
|
||||
user := security.GetCurrentUser()
|
||||
auditLogger.LogBackupStart(user, databaseName, "sample")
|
||||
|
||||
|
||||
// Rate limit connection attempts
|
||||
host := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
|
||||
if err := rateLimiter.CheckAndWait(host); err != nil {
|
||||
auditLogger.LogBackupFailed(user, databaseName, err)
|
||||
return fmt.Errorf("rate limit exceeded: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Create database instance
|
||||
db, err := database.New(cfg, log)
|
||||
if err != nil {
|
||||
@@ -359,7 +359,7 @@ func runSampleBackup(ctx context.Context, databaseName string) error {
|
||||
return fmt.Errorf("failed to create database instance: %w", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
|
||||
// Connect to database
|
||||
if err := db.Connect(ctx); err != nil {
|
||||
rateLimiter.RecordFailure(host)
|
||||
@@ -367,7 +367,7 @@ func runSampleBackup(ctx context.Context, databaseName string) error {
|
||||
return fmt.Errorf("failed to connect to database: %w", err)
|
||||
}
|
||||
rateLimiter.RecordSuccess(host)
|
||||
|
||||
|
||||
// Verify database exists
|
||||
exists, err := db.DatabaseExists(ctx, databaseName)
|
||||
if err != nil {
|
||||
@@ -379,16 +379,16 @@ func runSampleBackup(ctx context.Context, databaseName string) error {
|
||||
auditLogger.LogBackupFailed(user, databaseName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
// Create backup engine
|
||||
engine := backup.New(cfg, log, db)
|
||||
|
||||
|
||||
// Perform sample backup
|
||||
if err := engine.BackupSample(ctx, databaseName); err != nil {
|
||||
auditLogger.LogBackupFailed(user, databaseName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
// Apply encryption if requested
|
||||
if isEncryptionEnabled() {
|
||||
if err := encryptLatestBackup(databaseName); err != nil {
|
||||
@@ -397,10 +397,10 @@ func runSampleBackup(ctx context.Context, databaseName string) error {
|
||||
}
|
||||
log.Info("Sample backup encrypted successfully")
|
||||
}
|
||||
|
||||
|
||||
// Audit log: backup success
|
||||
auditLogger.LogBackupComplete(user, databaseName, cfg.BackupDir, 0)
|
||||
|
||||
|
||||
// Save configuration for future use (unless disabled)
|
||||
if !cfg.NoSaveConfig {
|
||||
localCfg := config.ConfigFromConfig(cfg)
|
||||
@@ -411,9 +411,10 @@ func runSampleBackup(ctx context.Context, databaseName string) error {
|
||||
auditLogger.LogConfigChange(user, "config_file", "", ".dbbackup.conf")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// encryptLatestBackup finds and encrypts the most recent backup for a database
|
||||
func encryptLatestBackup(databaseName string) error {
|
||||
// Load encryption key
|
||||
@@ -452,86 +453,86 @@ func encryptLatestClusterBackup() error {
|
||||
|
||||
// findLatestBackup finds the most recently created backup file for a database
|
||||
func findLatestBackup(backupDir, databaseName string) (string, error) {
|
||||
entries, err := os.ReadDir(backupDir)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read backup directory: %w", err)
|
||||
}
|
||||
entries, err := os.ReadDir(backupDir)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read backup directory: %w", err)
|
||||
}
|
||||
|
||||
var latestPath string
|
||||
var latestTime time.Time
|
||||
var latestPath string
|
||||
var latestTime time.Time
|
||||
|
||||
prefix := "db_" + databaseName + "_"
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
prefix := "db_" + databaseName + "_"
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
name := entry.Name()
|
||||
// Skip metadata files and already encrypted files
|
||||
if strings.HasSuffix(name, ".meta.json") || strings.HasSuffix(name, ".encrypted") {
|
||||
continue
|
||||
}
|
||||
name := entry.Name()
|
||||
// Skip metadata files and already encrypted files
|
||||
if strings.HasSuffix(name, ".meta.json") || strings.HasSuffix(name, ".encrypted") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Match database backup files
|
||||
if strings.HasPrefix(name, prefix) && (strings.HasSuffix(name, ".dump") ||
|
||||
strings.HasSuffix(name, ".dump.gz") || strings.HasSuffix(name, ".sql.gz")) {
|
||||
info, err := entry.Info()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// Match database backup files
|
||||
if strings.HasPrefix(name, prefix) && (strings.HasSuffix(name, ".dump") ||
|
||||
strings.HasSuffix(name, ".dump.gz") || strings.HasSuffix(name, ".sql.gz")) {
|
||||
info, err := entry.Info()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if info.ModTime().After(latestTime) {
|
||||
latestTime = info.ModTime()
|
||||
latestPath = filepath.Join(backupDir, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if info.ModTime().After(latestTime) {
|
||||
latestTime = info.ModTime()
|
||||
latestPath = filepath.Join(backupDir, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if latestPath == "" {
|
||||
return "", fmt.Errorf("no backup found for database: %s", databaseName)
|
||||
}
|
||||
if latestPath == "" {
|
||||
return "", fmt.Errorf("no backup found for database: %s", databaseName)
|
||||
}
|
||||
|
||||
return latestPath, nil
|
||||
return latestPath, nil
|
||||
}
|
||||
|
||||
// findLatestClusterBackup finds the most recently created cluster backup
|
||||
func findLatestClusterBackup(backupDir string) (string, error) {
|
||||
entries, err := os.ReadDir(backupDir)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read backup directory: %w", err)
|
||||
}
|
||||
entries, err := os.ReadDir(backupDir)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read backup directory: %w", err)
|
||||
}
|
||||
|
||||
var latestPath string
|
||||
var latestTime time.Time
|
||||
var latestPath string
|
||||
var latestTime time.Time
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
name := entry.Name()
|
||||
// Skip metadata files and already encrypted files
|
||||
if strings.HasSuffix(name, ".meta.json") || strings.HasSuffix(name, ".encrypted") {
|
||||
continue
|
||||
}
|
||||
name := entry.Name()
|
||||
// Skip metadata files and already encrypted files
|
||||
if strings.HasSuffix(name, ".meta.json") || strings.HasSuffix(name, ".encrypted") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Match cluster backup files
|
||||
if strings.HasPrefix(name, "cluster_") && strings.HasSuffix(name, ".tar.gz") {
|
||||
info, err := entry.Info()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// Match cluster backup files
|
||||
if strings.HasPrefix(name, "cluster_") && strings.HasSuffix(name, ".tar.gz") {
|
||||
info, err := entry.Info()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if info.ModTime().After(latestTime) {
|
||||
latestTime = info.ModTime()
|
||||
latestPath = filepath.Join(backupDir, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if info.ModTime().After(latestTime) {
|
||||
latestTime = info.ModTime()
|
||||
latestPath = filepath.Join(backupDir, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if latestPath == "" {
|
||||
return "", fmt.Errorf("no cluster backup found")
|
||||
}
|
||||
if latestPath == "" {
|
||||
return "", fmt.Errorf("no cluster backup found")
|
||||
}
|
||||
|
||||
return latestPath, nil
|
||||
return latestPath, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user