fix: Typo

This commit is contained in:
2025-11-12 17:10:18 +00:00
parent 472c7955fe
commit b2d3fdf105

View File

@@ -162,7 +162,7 @@ func (e *Engine) restorePostgreSQLDump(ctx context.Context, archivePath, targetD
NoPrivileges: true, NoPrivileges: true,
SingleTransaction: true, SingleTransaction: true,
} }
cmd := e.db.BuildRestoreCommand(targetDB, archivePath, opts) cmd := e.db.BuildRestoreCommand(targetDB, archivePath, opts)
if compressed { if compressed {
@@ -178,18 +178,18 @@ func (e *Engine) restorePostgreSQLDumpWithOwnership(ctx context.Context, archive
// Build restore command with ownership control // Build restore command with ownership control
opts := database.RestoreOptions{ opts := database.RestoreOptions{
Parallel: 1, Parallel: 1,
Clean: false, // We already dropped the database Clean: false, // We already dropped the database
NoOwner: !preserveOwnership, // Preserve ownership if we're superuser NoOwner: !preserveOwnership, // Preserve ownership if we're superuser
NoPrivileges: !preserveOwnership, // Preserve privileges if we're superuser NoPrivileges: !preserveOwnership, // Preserve privileges if we're superuser
SingleTransaction: true, SingleTransaction: true,
} }
e.log.Info("Restoring database", e.log.Info("Restoring database",
"database", targetDB, "database", targetDB,
"preserveOwnership", preserveOwnership, "preserveOwnership", preserveOwnership,
"noOwner", opts.NoOwner, "noOwner", opts.NoOwner,
"noPrivileges", opts.NoPrivileges) "noPrivileges", opts.NoPrivileges)
cmd := e.db.BuildRestoreCommand(targetDB, archivePath, opts) cmd := e.db.BuildRestoreCommand(targetDB, archivePath, opts)
if compressed { if compressed {
@@ -204,13 +204,13 @@ func (e *Engine) restorePostgreSQLDumpWithOwnership(ctx context.Context, archive
func (e *Engine) restorePostgreSQLSQL(ctx context.Context, archivePath, targetDB string, compressed bool) error { func (e *Engine) restorePostgreSQLSQL(ctx context.Context, archivePath, targetDB string, compressed bool) error {
// Use psql for SQL scripts // Use psql for SQL scripts
var cmd []string var cmd []string
// For localhost, omit -h to use Unix socket (avoids Ident auth issues) // For localhost, omit -h to use Unix socket (avoids Ident auth issues)
hostArg := "" hostArg := ""
if e.cfg.Host != "localhost" && e.cfg.Host != "" { if e.cfg.Host != "localhost" && e.cfg.Host != "" {
hostArg = fmt.Sprintf("-h %s -p %d", e.cfg.Host, e.cfg.Port) hostArg = fmt.Sprintf("-h %s -p %d", e.cfg.Host, e.cfg.Port)
} }
if compressed { if compressed {
psqlCmd := fmt.Sprintf("psql -U %s -d %s", e.cfg.User, targetDB) psqlCmd := fmt.Sprintf("psql -U %s -d %s", e.cfg.User, targetDB)
if hostArg != "" { if hostArg != "" {
@@ -447,7 +447,7 @@ func (e *Engine) RestoreCluster(ctx context.Context, archivePath string) error {
e.log.Warn("Could not verify superuser status", "error", err) e.log.Warn("Could not verify superuser status", "error", err)
isSuperuser = false // Assume not superuser if check fails isSuperuser = false // Assume not superuser if check fails
} }
if !isSuperuser { if !isSuperuser {
e.log.Warn("Current user is not a superuser - database ownership may not be fully restored") e.log.Warn("Current user is not a superuser - database ownership may not be fully restored")
e.progress.Update("⚠️ Warning: Non-superuser - ownership restoration limited") e.progress.Update("⚠️ Warning: Non-superuser - ownership restoration limited")
@@ -493,14 +493,14 @@ func (e *Engine) RestoreCluster(ctx context.Context, archivePath string) error {
var failedDBs []string var failedDBs []string
totalDBs := 0 totalDBs := 0
// Count total databases // Count total databases
for _, entry := range entries { for _, entry := range entries {
if !entry.IsDir() { if !entry.IsDir() {
totalDBs++ totalDBs++
} }
} }
// Create ETA estimator for database restores // Create ETA estimator for database restores
estimator := progress.NewETAEstimator("Restoring cluster", totalDBs) estimator := progress.NewETAEstimator("Restoring cluster", totalDBs)
e.progress.SetEstimator(estimator) e.progress.SetEstimator(estimator)
@@ -510,28 +510,28 @@ func (e *Engine) RestoreCluster(ctx context.Context, archivePath string) error {
if parallelism < 1 { if parallelism < 1 {
parallelism = 1 // Ensure at least sequential parallelism = 1 // Ensure at least sequential
} }
var successCount, failCount int32 var successCount, failCount int32
var failedDBsMu sync.Mutex var failedDBsMu sync.Mutex
var mu sync.Mutex // Protect shared resources (progress, logger) var mu sync.Mutex // Protect shared resources (progress, logger)
// Create semaphore to limit concurrency // Create semaphore to limit concurrency
semaphore := make(chan struct{}, parallelism) semaphore := make(chan struct{}, parallelism)
var wg sync.WaitGroup var wg sync.WaitGroup
dbIndex := 0 dbIndex := 0
for _, entry := range entries { for _, entry := range entries {
if entry.IsDir() { if entry.IsDir() {
continue continue
} }
wg.Add(1) wg.Add(1)
semaphore <- struct{}{} // Acquire semaphore <- struct{}{} // Acquire
go func(idx int, filename string) { go func(idx int, filename string) {
defer wg.Done() defer wg.Done()
defer func() { <-semaphore }() // Release defer func() { <-semaphore }() // Release
// Update estimator progress (thread-safe) // Update estimator progress (thread-safe)
mu.Lock() mu.Lock()
estimator.UpdateProgress(idx) estimator.UpdateProgress(idx)
@@ -543,7 +543,7 @@ func (e *Engine) RestoreCluster(ctx context.Context, archivePath string) error {
dbName = strings.TrimSuffix(dbName, ".sql.gz") dbName = strings.TrimSuffix(dbName, ".sql.gz")
dbProgress := 15 + int(float64(idx)/float64(totalDBs)*85.0) dbProgress := 15 + int(float64(idx)/float64(totalDBs)*85.0)
mu.Lock() mu.Lock()
statusMsg := fmt.Sprintf("Restoring database %s (%d/%d)", dbName, idx+1, totalDBs) statusMsg := fmt.Sprintf("Restoring database %s (%d/%d)", dbName, idx+1, totalDBs)
e.progress.Update(statusMsg) e.progress.Update(statusMsg)
@@ -569,7 +569,7 @@ func (e *Engine) RestoreCluster(ctx context.Context, archivePath string) error {
// STEP 3: Restore with ownership preservation if superuser // STEP 3: Restore with ownership preservation if superuser
preserveOwnership := isSuperuser preserveOwnership := isSuperuser
isCompressedSQL := strings.HasSuffix(dumpFile, ".sql.gz") isCompressedSQL := strings.HasSuffix(dumpFile, ".sql.gz")
var restoreErr error var restoreErr error
if isCompressedSQL { if isCompressedSQL {
e.log.Info("Detected compressed SQL format, using psql + gunzip", "file", dumpFile) e.log.Info("Detected compressed SQL format, using psql + gunzip", "file", dumpFile)
@@ -578,7 +578,7 @@ func (e *Engine) RestoreCluster(ctx context.Context, archivePath string) error {
e.log.Info("Detected custom dump format, using pg_restore", "file", dumpFile) e.log.Info("Detected custom dump format, using pg_restore", "file", dumpFile)
restoreErr = e.restorePostgreSQLDumpWithOwnership(ctx, dumpFile, dbName, false, preserveOwnership) restoreErr = e.restorePostgreSQLDumpWithOwnership(ctx, dumpFile, dbName, false, preserveOwnership)
} }
if restoreErr != nil { if restoreErr != nil {
e.log.Error("Failed to restore database", "name", dbName, "error", restoreErr) e.log.Error("Failed to restore database", "name", dbName, "error", restoreErr)
failedDBsMu.Lock() failedDBsMu.Lock()
@@ -590,13 +590,13 @@ func (e *Engine) RestoreCluster(ctx context.Context, archivePath string) error {
atomic.AddInt32(&successCount, 1) atomic.AddInt32(&successCount, 1)
}(dbIndex, entry.Name()) }(dbIndex, entry.Name())
dbIndex++ dbIndex++
} }
// Wait for all restores to complete // Wait for all restores to complete
wg.Wait() wg.Wait()
successCountFinal := int(atomic.LoadInt32(&successCount)) successCountFinal := int(atomic.LoadInt32(&successCount))
failCountFinal := int(atomic.LoadInt32(&failCount)) failCountFinal := int(atomic.LoadInt32(&failCount))
@@ -615,7 +615,7 @@ func (e *Engine) RestoreCluster(ctx context.Context, archivePath string) error {
// extractArchive extracts a tar.gz archive // extractArchive extracts a tar.gz archive
func (e *Engine) extractArchive(ctx context.Context, archivePath, destDir string) error { func (e *Engine) extractArchive(ctx context.Context, archivePath, destDir string) error {
cmd := exec.CommandContext(ctx, "tar", "-xzf", archivePath, "-C", destDir) cmd := exec.CommandContext(ctx, "tar", "-xzf", archivePath, "-C", destDir)
// Stream stderr to avoid memory issues - tar can produce lots of output for large archives // Stream stderr to avoid memory issues - tar can produce lots of output for large archives
stderr, err := cmd.StderrPipe() stderr, err := cmd.StderrPipe()
if err != nil { if err != nil {
@@ -649,12 +649,12 @@ func (e *Engine) restoreGlobals(ctx context.Context, globalsFile string) error {
"-d", "postgres", "-d", "postgres",
"-f", globalsFile, "-f", globalsFile,
} }
// Only add -h flag if host is not localhost (to use Unix socket for peer auth) // Only add -h flag if host is not localhost (to use Unix socket for peer auth)
if e.cfg.Host != "localhost" && e.cfg.Host != "127.0.0.1" && e.cfg.Host != "" { if e.cfg.Host != "localhost" && e.cfg.Host != "127.0.0.1" && e.cfg.Host != "" {
args = append([]string{"-h", e.cfg.Host}, args...) args = append([]string{"-h", e.cfg.Host}, args...)
} }
cmd := exec.CommandContext(ctx, "psql", args...) cmd := exec.CommandContext(ctx, "psql", args...)
cmd.Env = append(os.Environ(), fmt.Sprintf("PGPASSWORD=%s", e.cfg.Password)) cmd.Env = append(os.Environ(), fmt.Sprintf("PGPASSWORD=%s", e.cfg.Password))
@@ -701,22 +701,22 @@ func (e *Engine) checkSuperuser(ctx context.Context) (bool, error) {
"-d", "postgres", "-d", "postgres",
"-tAc", "SELECT usesuper FROM pg_user WHERE usename = current_user", "-tAc", "SELECT usesuper FROM pg_user WHERE usename = current_user",
} }
// Only add -h flag if host is not localhost (to use Unix socket for peer auth) // Only add -h flag if host is not localhost (to use Unix socket for peer auth)
if e.cfg.Host != "localhost" && e.cfg.Host != "127.0.0.1" && e.cfg.Host != "" { if e.cfg.Host != "localhost" && e.cfg.Host != "127.0.0.1" && e.cfg.Host != "" {
args = append([]string{"-h", e.cfg.Host}, args...) args = append([]string{"-h", e.cfg.Host}, args...)
} }
cmd := exec.CommandContext(ctx, "psql", args...) cmd := exec.CommandContext(ctx, "psql", args...)
// Always set PGPASSWORD (empty string is fine for peer/ident auth) // Always set PGPASSWORD (empty string is fine for peer/ident auth)
cmd.Env = append(os.Environ(), fmt.Sprintf("PGPASSWORD=%s", e.cfg.Password)) cmd.Env = append(os.Environ(), fmt.Sprintf("PGPASSWORD=%s", e.cfg.Password))
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
return false, fmt.Errorf("failed to check superuser status: %w", err) return false, fmt.Errorf("failed to check superuser status: %w", err)
} }
isSuperuser := strings.TrimSpace(string(output)) == "t" isSuperuser := strings.TrimSpace(string(output)) == "t"
return isSuperuser, nil return isSuperuser, nil
} }
@@ -729,30 +729,30 @@ func (e *Engine) terminateConnections(ctx context.Context, dbName string) error
WHERE datname = '%s' WHERE datname = '%s'
AND pid <> pg_backend_pid() AND pid <> pg_backend_pid()
`, dbName) `, dbName)
args := []string{ args := []string{
"-p", fmt.Sprintf("%d", e.cfg.Port), "-p", fmt.Sprintf("%d", e.cfg.Port),
"-U", e.cfg.User, "-U", e.cfg.User,
"-d", "postgres", "-d", "postgres",
"-tAc", query, "-tAc", query,
} }
// Only add -h flag if host is not localhost (to use Unix socket for peer auth) // Only add -h flag if host is not localhost (to use Unix socket for peer auth)
if e.cfg.Host != "localhost" && e.cfg.Host != "127.0.0.1" && e.cfg.Host != "" { if e.cfg.Host != "localhost" && e.cfg.Host != "127.0.0.1" && e.cfg.Host != "" {
args = append([]string{"-h", e.cfg.Host}, args...) args = append([]string{"-h", e.cfg.Host}, args...)
} }
cmd := exec.CommandContext(ctx, "psql", args...) cmd := exec.CommandContext(ctx, "psql", args...)
// Always set PGPASSWORD (empty string is fine for peer/ident auth) // Always set PGPASSWORD (empty string is fine for peer/ident auth)
cmd.Env = append(os.Environ(), fmt.Sprintf("PGPASSWORD=%s", e.cfg.Password)) cmd.Env = append(os.Environ(), fmt.Sprintf("PGPASSWORD=%s", e.cfg.Password))
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
e.log.Warn("Failed to terminate connections", "database", dbName, "error", err, "output", string(output)) e.log.Warn("Failed to terminate connections", "database", dbName, "error", err, "output", string(output))
// Don't fail - database might not exist or have no connections // Don't fail - database might not exist or have no connections
} }
return nil return nil
} }
@@ -762,10 +762,10 @@ func (e *Engine) dropDatabaseIfExists(ctx context.Context, dbName string) error
if err := e.terminateConnections(ctx, dbName); err != nil { if err := e.terminateConnections(ctx, dbName); err != nil {
e.log.Warn("Could not terminate connections", "database", dbName, "error", err) e.log.Warn("Could not terminate connections", "database", dbName, "error", err)
} }
// Wait a moment for connections to terminate // Wait a moment for connections to terminate
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
// Drop the database // Drop the database
args := []string{ args := []string{
"-p", fmt.Sprintf("%d", e.cfg.Port), "-p", fmt.Sprintf("%d", e.cfg.Port),
@@ -773,22 +773,22 @@ func (e *Engine) dropDatabaseIfExists(ctx context.Context, dbName string) error
"-d", "postgres", "-d", "postgres",
"-c", fmt.Sprintf("DROP DATABASE IF EXISTS \"%s\"", dbName), "-c", fmt.Sprintf("DROP DATABASE IF EXISTS \"%s\"", dbName),
} }
// Only add -h flag if host is not localhost (to use Unix socket for peer auth) // Only add -h flag if host is not localhost (to use Unix socket for peer auth)
if e.cfg.Host != "localhost" && e.cfg.Host != "127.0.0.1" && e.cfg.Host != "" { if e.cfg.Host != "localhost" && e.cfg.Host != "127.0.0.1" && e.cfg.Host != "" {
args = append([]string{"-h", e.cfg.Host}, args...) args = append([]string{"-h", e.cfg.Host}, args...)
} }
cmd := exec.CommandContext(ctx, "psql", args...) cmd := exec.CommandContext(ctx, "psql", args...)
// Always set PGPASSWORD (empty string is fine for peer/ident auth) // Always set PGPASSWORD (empty string is fine for peer/ident auth)
cmd.Env = append(os.Environ(), fmt.Sprintf("PGPASSWORD=%s", e.cfg.Password)) cmd.Env = append(os.Environ(), fmt.Sprintf("PGPASSWORD=%s", e.cfg.Password))
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
return fmt.Errorf("failed to drop database '%s': %w\nOutput: %s", dbName, err, string(output)) return fmt.Errorf("failed to drop database '%s': %w\nOutput: %s", dbName, err, string(output))
} }
e.log.Info("Dropped existing database", "name", dbName) e.log.Info("Dropped existing database", "name", dbName)
return nil return nil
} }
@@ -803,23 +803,23 @@ func (e *Engine) ensureDatabaseExists(ctx context.Context, dbName string) error
"-d", database, "-d", database,
"-tAc", query, "-tAc", query,
} }
// Only add -h flag if host is not localhost (to use Unix socket for peer auth) // Only add -h flag if host is not localhost (to use Unix socket for peer auth)
if e.cfg.Host != "localhost" && e.cfg.Host != "127.0.0.1" && e.cfg.Host != "" { if e.cfg.Host != "localhost" && e.cfg.Host != "127.0.0.1" && e.cfg.Host != "" {
args = append([]string{"-h", e.cfg.Host}, args...) args = append([]string{"-h", e.cfg.Host}, args...)
} }
cmd := exec.CommandContext(ctx, "psql", args...) cmd := exec.CommandContext(ctx, "psql", args...)
// Always set PGPASSWORD (empty string is fine for peer/ident auth) // Always set PGPASSWORD (empty string is fine for peer/ident auth)
cmd.Env = append(os.Environ(), fmt.Sprintf("PGPASSWORD=%s", e.cfg.Password)) cmd.Env = append(os.Environ(), fmt.Sprintf("PGPASSWORD=%s", e.cfg.Password))
return cmd return cmd
} }
// Check if database exists // Check if database exists
checkCmd := buildPsqlCmd(ctx, "postgres", fmt.Sprintf("SELECT 1 FROM pg_database WHERE datname = '%s'", dbName)) checkCmd := buildPsqlCmd(ctx, "postgres", fmt.Sprintf("SELECT 1 FROM pg_database WHERE datname = '%s'", dbName))
output, err := checkCmd.CombinedOutput() output, err := checkCmd.CombinedOutput()
if err != nil { if err != nil {
e.log.Warn("Database existence check failed", "name", dbName, "error", err, "output", string(output)) e.log.Warn("Database existence check failed", "name", dbName, "error", err, "output", string(output))
@@ -834,29 +834,29 @@ func (e *Engine) ensureDatabaseExists(ctx context.Context, dbName string) error
// Database doesn't exist, create it // Database doesn't exist, create it
e.log.Info("Creating database", "name", dbName) e.log.Info("Creating database", "name", dbName)
createArgs := []string{ createArgs := []string{
"-p", fmt.Sprintf("%d", e.cfg.Port), "-p", fmt.Sprintf("%d", e.cfg.Port),
"-U", e.cfg.User, "-U", e.cfg.User,
"-d", "postgres", "-d", "postgres",
"-c", fmt.Sprintf("CREATE DATABASE \"%s\"", dbName), "-c", fmt.Sprintf("CREATE DATABASE \"%s\"", dbName),
} }
// Only add -h flag if host is not localhost (to use Unix socket for peer auth) // Only add -h flag if host is not localhost (to use Unix socket for peer auth)
if e.cfg.Host != "localhost" && e.cfg.Host != "127.0.0.1" && e.cfg.Host != "" { if e.cfg.Host != "localhost" && e.cfg.Host != "127.0.0.1" && e.cfg.Host != "" {
createArgs = append([]string{"-h", e.cfg.Host}, createArgs...) createArgs = append([]string{"-h", e.cfg.Host}, createArgs...)
} }
createCmd := exec.CommandContext(ctx, "psql", createArgs...) createCmd := exec.CommandContext(ctx, "psql", createArgs...)
// Always set PGPASSWORD (empty string is fine for peer/ident auth) // Always set PGPASSWORD (empty string is fine for peer/ident auth)
createCmd.Env = append(os.Environ(), fmt.Sprintf("PGPASSWORD=%s", e.cfg.Password)) createCmd.Env = append(os.Environ(), fmt.Sprintf("PGPASSWORD=%s", e.cfg.Password))
output, err = createCmd.CombinedOutput() output, err = createCmd.CombinedOutput()
if err != nil { if err != nil {
// Log the error but don't fail - pg_restore might handle it // Log the error and include the psql output in the returned error to aid debugging
e.log.Warn("Database creation failed", "name", dbName, "error", err, "output", string(output)) e.log.Warn("Database creation failed", "name", dbName, "error", err, "output", string(output))
return fmt.Errorf("failed to create database '%s': %w", dbName, err) return fmt.Errorf("failed to create database '%s': %w (output: %s)", dbName, err, strings.TrimSpace(string(output)))
} }
e.log.Info("Successfully created database", "name", dbName) e.log.Info("Successfully created database", "name", dbName)