Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Fix the issue of backup failure due to complex passwords in the pg #7596

Merged
merged 1 commit into from
Dec 30, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions backend/utils/postgresql/client/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ func (r *Remote) Backup(info BackupInfo) error {
}
fileNameItem := info.TargetDir + "/" + strings.TrimSuffix(info.FileName, ".gz")
backupCommand := exec.Command("bash", "-c",
fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'PGPASSWORD=%s pg_dump -h %s -p %d --no-owner -Fc -U %s %s' > %s",
fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'PGPASSWORD=\"%s\" pg_dump -h %s -p %d --no-owner -Fc -U %s %s' > %s",
imageTag, r.Password, r.Address, r.Port, r.User, info.Name, fileNameItem))
_ = backupCommand.Run()
b := make([]byte, 5)
Expand Down Expand Up @@ -178,7 +178,7 @@ func (r *Remote) Recover(info RecoverInfo) error {
}()
}
recoverCommand := exec.Command("bash", "-c",
fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'PGPASSWORD=%s pg_restore -h %s -p %d --verbose --clean --no-privileges --no-owner -Fc -U %s -d %s --role=%s' < %s",
fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'PGPASSWORD=\"%s\" pg_restore -h %s -p %d --verbose --clean --no-privileges --no-owner -Fc -U %s -d %s --role=%s' < %s",
imageTag, r.Password, r.Address, r.Port, r.User, info.Name, info.Username, fileName))
pipe, _ := recoverCommand.StdoutPipe()
stderrPipe, _ := recoverCommand.StderrPipe()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code provided contains a few issues that I would suggest addressing:

Issues and Suggestions:

  1. Unescaped Special Characters:

    • In both Backup and Recover functions, the environment variables include special characters (%) which need to be escaped. This is particularly true for %q format specifier in Go.
  2. Variable Naming Consistency:

    • The variable names used within the commands (like imageTag, r.Password, etc.) should follow consistent naming conventions to improve readability.
  3. File Path Formatting:

    • Ensure that the file paths specified in the commands are correctly formatted without any trailing slashes or spaces.

Here is an updated version of the code with these changes applied:

func (r *Remote) Backup(info BackupInfo) error {
	//
	fileNameItem := info.TargetDir + "/" + strings.TrimSuffix(info.FileName, ".gz")
	backupCommand := exec.Command(
		"bash", "-c",
		fmt.Sprintf(
			"docker run --rm --net=host -i %s /bin/bash -c 'PGPASSWORD='%[1]q' pg_dump -h %[2]s -p %[3]d --no-owner -Fc -U %[4]s %[5]' > %s",
			r.User,
			info.Name,
			r.Address,
			strconv.Itoa(r.Port),
			info.SourceDBName,
			fileNameItem)),
	})
	var stderr bytes.Buffer
	backupCommand.Stderr = &stderr
	err := backupCommand.Run()
	if err != nil && len(stderr.Bytes()) > 0 {
		return fmt.Errorf("backup failed: %w\nError output:\n%v", err, stderr.String())
	}
	return nil
}

func (r *Remote) Recover(info RecoverInfo) error {
	//
	recoverCommand := exec.Command(
		"bash", "-c",
		fmt.Sprintf(
			"docker run --rm --net=host -i %s /bin/bash -c 'PGPASSWORD='%[1]q' pg_restore -h %[2]s -p %[3]d --verbose --clean --no-privileges --no-owner -Fc -U %[4]s -d %[5]' < %s",
			r.User,
			info.Username,
			r.Address,
			strconv.Itoa(r.Port),
			info.DatabaseName,
			fileName)),
	)
	reader, writer := io.Pipe()
	defer reader.Close()

	go func() {
		writtenBytes, e := ioutil.ReadAll(writer)
		if e != nil {
			log.Printf("%s", string(writtenBytes))
		} else if e == io.EOF {
			e = errors.New(fmt.Sprintf("pg_restore exited unexpectedly after writing %d bytes.", writtenBytes.Len()))
		}
		writer.CloseWithError(e)
	}()

	stderrPipe, _ := recoverCommand.StderrPipe()
	readerErr := io.ReadAll(stderrPipe)

	recoverCommand.Stdin = reader

	cmdStart := time.Now()

	err = recoverCommand.Start()
	if err != nil {
		log.Println("\ncmd:", recoverCommand.Args)
		cmdStart.Sub(cmdStart)) // Print execution time for debugging
		log.Fatalf("error running pg_restore start: %v\nStdout/Erro:\n%s/%s"
			, err.Error(), pipe.OutReader().String(), pipe.ErrReader().String())

	}

	_, readErr := io.Copy(os.Stdout, pipe)

	for { // Wait for either recovery completion or command termination
		waitExitCode, waitErr := recoverCommand.Wait()

		if !errors.Is(waitErr, io.EOF) { // EOF means that the process was terminated by user/integrity issue
			log.Printf("%s", "Process was terminated by signal.")
			log.Printf("Waited Exit Code:%v | waited Error:[%v]", waitExitCode, waitErr)
			return errors.New("recovery interrupted due to unexpected exit.")
		}
		break
	}
	cmdEnd := time.Now()
	cmdDuration := cmdEnd.Sub(cmdStart) // Return the duration it took to execute the script
	log.Println("Recovered database successfully, Execution Duration:", cmdDuration)
}

Notes:

  • Env Var Expansion: Used %[1]q for environment variable expansion to handle quotes gracefully.
  • Error Handling: Added comprehensive error handling by checking the command's return code and reading all possible outputs.
  • Logging Improvements: Enhanced logging to capture and display both successful and failed operations.

Expand Down
Loading