Skip to content

Commit 7d58b6b

Browse files
committed
Merge feature/backup-validation: Fix MongoDB backup validation
2 parents f49d473 + 3930641 commit 7d58b6b

1 file changed

Lines changed: 54 additions & 88 deletions

File tree

backend/internal/features/backups/backups/usecases/mongodb/validate_backup_uc.go

Lines changed: 54 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"log/slog"
99
"os"
1010
"os/exec"
11-
"path/filepath"
1211
"time"
1312

1413
"postgresus-backend/internal/config"
@@ -19,10 +18,7 @@ import (
1918
encryption_secrets "postgresus-backend/internal/features/encryption/secrets"
2019
"postgresus-backend/internal/features/storages"
2120
util_encryption "postgresus-backend/internal/util/encryption"
22-
files_utils "postgresus-backend/internal/util/files"
2321
"postgresus-backend/internal/util/tools"
24-
25-
"github.com/google/uuid"
2622
)
2723

2824
type ValidateMongodbBackupUsecase struct {
@@ -58,15 +54,33 @@ func (uc *ValidateMongodbBackupUsecase) Execute(
5854
}, nil
5955
}
6056

61-
// Download backup to temporary file
62-
tempFile, cleanupFunc, err := uc.downloadBackupToTempFile(ctx, backup, storage)
57+
// Get backup data from storage
58+
fieldEncryptor := util_encryption.GetFieldEncryptor()
59+
rawReader, err := storage.GetFile(fieldEncryptor, backup.ID)
6360
if err != nil {
6461
return &ValidationResult{
6562
IsValid: false,
66-
Error: stringPtr(fmt.Sprintf("failed to download backup: %v", err)),
63+
Error: stringPtr(fmt.Sprintf("failed to get backup file from storage: %v", err)),
6764
}, nil
6865
}
69-
defer cleanupFunc()
66+
defer func() {
67+
if err := rawReader.Close(); err != nil {
68+
uc.logger.Error("Failed to close backup reader", "error", err)
69+
}
70+
}()
71+
72+
// Create a reader that handles decryption if needed
73+
var backupReader io.Reader = rawReader
74+
if backup.Encryption == backups_config.BackupEncryptionEncrypted {
75+
decryptReader, err := uc.setupDecryption(rawReader, backup)
76+
if err != nil {
77+
return &ValidationResult{
78+
IsValid: false,
79+
Error: stringPtr(fmt.Sprintf("failed to setup decryption: %v", err)),
80+
}, nil
81+
}
82+
backupReader = decryptReader
83+
}
7084

7185
// Use mongorestore --dryRun to validate archive
7286
mongorestoreBin := tools.GetMongodbExecutable(
@@ -75,21 +89,48 @@ func (uc *ValidateMongodbBackupUsecase) Execute(
7589
config.GetEnv().MongodbInstallDir,
7690
)
7791

78-
// Run mongorestore --dryRun to validate the archive
92+
// Run mongorestore --dryRun with stdin input (like restore does)
7993
cmd := exec.CommandContext(
8094
ctx,
8195
mongorestoreBin,
82-
"--archive="+tempFile,
96+
"--archive",
8397
"--gzip",
8498
"--dryRun",
8599
"--quiet",
86100
)
87101

88-
output, err := cmd.CombinedOutput()
102+
cmd.Stdin = backupReader
103+
cmd.Env = os.Environ()
104+
cmd.Env = append(cmd.Env, "LC_ALL=C.UTF-8", "LANG=C.UTF-8")
105+
106+
stderrPipe, err := cmd.StderrPipe()
89107
if err != nil {
90-
errorMsg := string(output)
108+
return &ValidationResult{
109+
IsValid: false,
110+
Error: stringPtr(fmt.Sprintf("failed to create stderr pipe: %v", err)),
111+
}, nil
112+
}
113+
114+
stderrCh := make(chan []byte, 1)
115+
go func() {
116+
output, _ := io.ReadAll(stderrPipe)
117+
stderrCh <- output
118+
}()
119+
120+
if err = cmd.Start(); err != nil {
121+
return &ValidationResult{
122+
IsValid: false,
123+
Error: stringPtr(fmt.Sprintf("failed to start mongorestore: %v", err)),
124+
}, nil
125+
}
126+
127+
waitErr := cmd.Wait()
128+
stderrOutput := <-stderrCh
129+
130+
if waitErr != nil {
131+
errorMsg := string(stderrOutput)
91132
if errorMsg == "" {
92-
errorMsg = err.Error()
133+
errorMsg = waitErr.Error()
93134
}
94135
return &ValidationResult{
95136
IsValid: false,
@@ -105,81 +146,6 @@ func (uc *ValidateMongodbBackupUsecase) Execute(
105146
}, nil
106147
}
107148

108-
// downloadBackupToTempFile downloads backup data from storage to a temporary file
109-
func (uc *ValidateMongodbBackupUsecase) downloadBackupToTempFile(
110-
ctx context.Context,
111-
backup *usecases_common.BackupInfo,
112-
storage *storages.Storage,
113-
) (string, func(), error) {
114-
err := files_utils.EnsureDirectories([]string{
115-
config.GetEnv().TempFolder,
116-
})
117-
if err != nil {
118-
return "", nil, fmt.Errorf("failed to ensure directories: %w", err)
119-
}
120-
121-
tempDir, err := os.MkdirTemp(config.GetEnv().TempFolder, "validate_"+uuid.New().String())
122-
if err != nil {
123-
return "", nil, fmt.Errorf("failed to create temporary directory: %w", err)
124-
}
125-
126-
cleanupFunc := func() {
127-
_ = os.RemoveAll(tempDir)
128-
}
129-
130-
tempBackupFile := filepath.Join(tempDir, "backup.archive.gz")
131-
132-
uc.logger.Info(
133-
"Downloading backup file from storage to temporary file",
134-
"backupId", backup.ID,
135-
"tempFile", tempBackupFile,
136-
"encrypted", backup.Encryption == backups_config.BackupEncryptionEncrypted,
137-
)
138-
139-
fieldEncryptor := util_encryption.GetFieldEncryptor()
140-
rawReader, err := storage.GetFile(fieldEncryptor, backup.ID)
141-
if err != nil {
142-
cleanupFunc()
143-
return "", nil, fmt.Errorf("failed to get backup file from storage: %w", err)
144-
}
145-
defer func() {
146-
if err := rawReader.Close(); err != nil {
147-
uc.logger.Error("Failed to close backup reader", "error", err)
148-
}
149-
}()
150-
151-
// Create a reader that handles decryption if needed
152-
var backupReader io.Reader = rawReader
153-
if backup.Encryption == backups_config.BackupEncryptionEncrypted {
154-
decryptReader, err := uc.setupDecryption(rawReader, backup)
155-
if err != nil {
156-
cleanupFunc()
157-
return "", nil, fmt.Errorf("failed to setup decryption: %w", err)
158-
}
159-
backupReader = decryptReader
160-
}
161-
162-
tempFile, err := os.Create(tempBackupFile)
163-
if err != nil {
164-
cleanupFunc()
165-
return "", nil, fmt.Errorf("failed to create temporary backup file: %w", err)
166-
}
167-
defer func() {
168-
if err := tempFile.Close(); err != nil {
169-
uc.logger.Error("Failed to close temporary file", "error", err)
170-
}
171-
}()
172-
173-
_, err = io.Copy(tempFile, backupReader)
174-
if err != nil {
175-
cleanupFunc()
176-
return "", nil, fmt.Errorf("failed to write backup to temporary file: %w", err)
177-
}
178-
179-
uc.logger.Info("Backup file written to temporary location", "tempFile", tempBackupFile)
180-
return tempBackupFile, cleanupFunc, nil
181-
}
182-
183149
func (uc *ValidateMongodbBackupUsecase) setupDecryption(
184150
reader io.Reader,
185151
backup *usecases_common.BackupInfo,

0 commit comments

Comments
 (0)