Skip to content

Commit 51203a3

Browse files
committed
fix(postgres): review feedback — pg_basebackup user, shell-quote DbUser, recovery test, RmdirAll, generic test creds
PR #136 review feedback, dispositions: 1. replication.go (VALIDE): pg_basebackup hardcoded "-U postgres"; now uses config.DbUser (fallback "postgres"), matching the superuser created by initdb --username=<db-user>. Needed so replication works with a custom initial superuser (otherwise the primary has no 'postgres' role). exec.Command is argv-based, so no shell-quoting needed here. 2. scripts.go (VALIDE): DbUser was interpolated raw into generated bash scripts (use/clear/replication/recovery). Added a local shellQuote helper (POSIX single-quoting) applied at all four generation sites; preserves any valid PostgreSQL role name while neutralizing shell metacharacters. No new dependency (no shlex in go.mod, no repo helper existed). 3. test coverage (VALIDE): added TestGenerateCheckRecoveryScriptDbUser covering GenerateCheckRecoveryScript with a custom DbUser; existing replication test left untouched. 4. RmdirAll (SKIPPE): common.RmdirAll exists (common/fileutil.go:639) but calls ErrCheckExitf -> os.Exit(1) on any error and has $HOME/$PWD guards that also exit. The paths I added do 'os.RemoveAll(config.Dir); return wrapped err' to surface a clean error to the caller; RmdirAll would hard-exit instead and would be inconsistent with the pre-existing os.RemoveAll calls in the same function (not in scope of this review). Per the finding's own instruction ('si ça change la sémantique, skippe'), skipped. Generic test creds: replaced project-specific 'wse' with 'testuser' in TestGenerateScriptsDbUserPropagation (DbUser value + quoted assertions) and in the new recovery test. No password value was used in these script-generation tests, so only the username was renamed. No 'wse' appears in provider code or comments (only in this commit's predecessor example, which is git history).
1 parent 3fad707 commit 51203a3

3 files changed

Lines changed: 57 additions & 20 deletions

File tree

providers/postgresql/postgresql_test.go

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -214,24 +214,24 @@ func TestGenerateScripts(t *testing.T) {
214214
}
215215

216216
// TestGenerateScriptsDbUserPropagation ensures the configured db-user is embedded
217-
// in the generated psql/initdb invocations, and that an empty DbUser falls back
218-
// to the historical "postgres" default.
217+
// (shell-quoted) in the generated psql/initdb invocations, and that an empty
218+
// DbUser falls back to the historical "postgres" default.
219219
func TestGenerateScriptsDbUserPropagation(t *testing.T) {
220-
// Explicit user: scripts must reference it and not the default.
220+
// Explicit user: scripts must reference it (shell-quoted) and not the default.
221221
custom := GenerateScripts(ScriptOptions{
222222
BinDir: "/opt/postgresql/16.13/bin",
223223
DataDir: "/tmp/pg/data",
224224
Port: 16613,
225-
DbUser: "wse",
225+
DbUser: "testuser",
226226
})
227-
if !strings.Contains(custom["use"], "-U wse") {
228-
t.Errorf("use script must embed -U wse; got: %q", custom["use"])
227+
if !strings.Contains(custom["use"], "-U 'testuser'") {
228+
t.Errorf("use script must embed -U 'testuser'; got: %q", custom["use"])
229229
}
230230
if strings.Contains(custom["use"], "-U postgres") {
231231
t.Errorf("use script must not reference -U postgres; got: %q", custom["use"])
232232
}
233-
if !strings.Contains(custom["clear"], "--username=wse") {
234-
t.Errorf("clear script must embed --username=wse; got: %q", custom["clear"])
233+
if !strings.Contains(custom["clear"], "--username='testuser'") {
234+
t.Errorf("clear script must embed --username='testuser'; got: %q", custom["clear"])
235235
}
236236

237237
// Empty user: falls back to "postgres" (backward compatible).
@@ -240,20 +240,41 @@ func TestGenerateScriptsDbUserPropagation(t *testing.T) {
240240
DataDir: "/tmp/pg/data",
241241
Port: 16613,
242242
})
243-
if !strings.Contains(def["use"], "-U postgres") {
244-
t.Errorf("default use script must embed -U postgres; got: %q", def["use"])
243+
if !strings.Contains(def["use"], "-U 'postgres'") {
244+
t.Errorf("default use script must embed -U 'postgres'; got: %q", def["use"])
245245
}
246-
if !strings.Contains(def["clear"], "--username=postgres") {
247-
t.Errorf("default clear script must embed --username=postgres; got: %q", def["clear"])
246+
if !strings.Contains(def["clear"], "--username='postgres'") {
247+
t.Errorf("default clear script must embed --username='postgres'; got: %q", def["clear"])
248248
}
249249

250250
// Replication scripts honor DbUser too.
251251
repl := GenerateCheckReplicationScript(ScriptOptions{
252252
BinDir: "/opt/postgresql/16.13/bin",
253253
Port: 16613,
254-
DbUser: "wse",
254+
DbUser: "testuser",
255255
})
256-
if !strings.Contains(repl, "-U wse") {
257-
t.Errorf("replication script must embed -U wse; got: %q", repl)
256+
if !strings.Contains(repl, "-U 'testuser'") {
257+
t.Errorf("replication script must embed -U 'testuser'; got: %q", repl)
258+
}
259+
}
260+
261+
// TestGenerateCheckRecoveryScriptDbUser ensures the recovery-check script embeds
262+
// the configured db-user (shell-quoted), complementing the existing
263+
// TestGenerateCheckRecoveryScript which only checks the query text and ports.
264+
func TestGenerateCheckRecoveryScriptDbUser(t *testing.T) {
265+
ports := []int{16614, 16615}
266+
script := GenerateCheckRecoveryScript(ScriptOptions{
267+
BinDir: "/opt/postgresql/16.13/bin",
268+
Port: 16613,
269+
DbUser: "testuser",
270+
}, ports)
271+
if !strings.Contains(script, "pg_is_in_recovery") {
272+
t.Error("missing pg_is_in_recovery query")
273+
}
274+
if !strings.Contains(script, "-U 'testuser'") {
275+
t.Errorf("recovery script must embed -U 'testuser'; got: %q", script)
276+
}
277+
if !strings.Contains(script, "16614") || !strings.Contains(script, "16615") {
278+
t.Error("recovery script missing replica ports")
258279
}
259280
}

providers/postgresql/replication.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,20 @@ func (p *PostgreSQLProvider) CreateReplica(primary providers.SandboxInfo, config
2222
dataDir := filepath.Join(config.Dir, "data")
2323
logFile := filepath.Join(config.Dir, "postgresql.log")
2424

25+
// The replica connects to the primary as the configured superuser, which
26+
// matches the role created by initdb --username=<db-user>. Fall back to
27+
// "postgres" to preserve the historical behavior when no user is set.
28+
dbUser := config.DbUser
29+
if dbUser == "" {
30+
dbUser = "postgres"
31+
}
32+
2533
// pg_basebackup from the running primary
2634
pgBasebackup := filepath.Join(binDir, "pg_basebackup")
2735
bbCmd := exec.Command(pgBasebackup,
2836
"-h", "127.0.0.1",
2937
"-p", fmt.Sprintf("%d", primary.Port),
30-
"-U", "postgres",
38+
"-U", dbUser,
3139
"-D", dataDir,
3240
"-Fp", "-Xs", "-R",
3341
)

providers/postgresql/scripts.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ export LD_LIBRARY_PATH="%s"
2323
unset PGDATA PGPORT PGHOST PGUSER PGDATABASE
2424
`
2525

26+
// shellQuote wraps s in POSIX single quotes (escaping any embedded single
27+
// quotes) so it is safe to interpolate into a generated bash script. This
28+
// preserves any valid PostgreSQL role name while neutralizing shell
29+
// metacharacters in user-supplied values.
30+
func shellQuote(s string) string {
31+
return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'"
32+
}
33+
2634
func GenerateScripts(opts ScriptOptions) map[string]string {
2735
preamble := fmt.Sprintf(envPreamble, opts.LibDir)
2836
dbUser := opts.DbUser
@@ -44,10 +52,10 @@ func GenerateScripts(opts ScriptOptions) map[string]string {
4452
preamble, opts.BinDir, opts.DataDir, opts.LogFile),
4553

4654
"use": fmt.Sprintf("%s%s/psql -h 127.0.0.1 -p %d -U %s \"$@\"\n",
47-
preamble, opts.BinDir, opts.Port, dbUser),
55+
preamble, opts.BinDir, opts.Port, shellQuote(dbUser)),
4856

4957
"clear": fmt.Sprintf("%s%s/pg_ctl -D %s stop -m fast 2>/dev/null\nrm -rf %s\n%s/initdb -D %s --auth=trust --username=%s\necho \"Sandbox cleared.\"\n",
50-
preamble, opts.BinDir, opts.DataDir, opts.DataDir, opts.BinDir, opts.DataDir, dbUser),
58+
preamble, opts.BinDir, opts.DataDir, opts.DataDir, opts.BinDir, opts.DataDir, shellQuote(dbUser)),
5159
}
5260
}
5361

@@ -59,7 +67,7 @@ func GenerateCheckReplicationScript(opts ScriptOptions) string {
5967
}
6068
return fmt.Sprintf(`%s%s/psql -h 127.0.0.1 -p %d -U %s -c \
6169
"SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn FROM pg_stat_replication;"
62-
`, preamble, opts.BinDir, opts.Port, dbUser)
70+
`, preamble, opts.BinDir, opts.Port, shellQuote(dbUser))
6371
}
6472

6573
func GenerateCheckRecoveryScript(opts ScriptOptions, replicaPorts []int) string {
@@ -72,7 +80,7 @@ func GenerateCheckRecoveryScript(opts ScriptOptions, replicaPorts []int) string
7280
b.WriteString(preamble)
7381
for _, port := range replicaPorts {
7482
b.WriteString(fmt.Sprintf("echo \"=== Replica port %d ===\"\n", port))
75-
b.WriteString(fmt.Sprintf("%s/psql -h 127.0.0.1 -p %d -U %s -c \"SELECT pg_is_in_recovery();\"\n", opts.BinDir, port, dbUser))
83+
b.WriteString(fmt.Sprintf("%s/psql -h 127.0.0.1 -p %d -U %s -c \"SELECT pg_is_in_recovery();\"\n", opts.BinDir, port, shellQuote(dbUser)))
7684
}
7785
return b.String()
7886
}

0 commit comments

Comments
 (0)