Skip to content

Commit e117e60

Browse files
committed
Add sqlc-test-setup command for database test environment setup
New cmd/sqlc-test-setup package with two subcommands: - `install`: Installs PostgreSQL and MySQL 9 via apt, including apt proxy configuration for Claude Code remote environments. - `start`: Starts both database services, configures authentication (passwords, pg_hba.conf), and verifies connectivity. Both commands log all actions verbosely for easy debugging. https://claude.ai/code/session_01CsyRwSkRxBcQoaQFVkMQsJ
1 parent 744558d commit e117e60

File tree

1 file changed

+249
-0
lines changed

1 file changed

+249
-0
lines changed

cmd/sqlc-test-setup/main.go

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"os"
7+
"os/exec"
8+
"strings"
9+
)
10+
11+
func main() {
12+
log.SetFlags(log.Ltime)
13+
log.SetPrefix("[sqlc-test-setup] ")
14+
15+
if len(os.Args) < 2 {
16+
fmt.Fprintln(os.Stderr, "usage: sqlc-test-setup <install|start>")
17+
os.Exit(1)
18+
}
19+
20+
switch os.Args[1] {
21+
case "install":
22+
if err := runInstall(); err != nil {
23+
log.Fatalf("install failed: %s", err)
24+
}
25+
case "start":
26+
if err := runStart(); err != nil {
27+
log.Fatalf("start failed: %s", err)
28+
}
29+
default:
30+
fmt.Fprintf(os.Stderr, "unknown command: %s\nusage: sqlc-test-setup <install|start>\n", os.Args[1])
31+
os.Exit(1)
32+
}
33+
}
34+
35+
// run executes a command with verbose logging, streaming output to stderr.
36+
func run(name string, args ...string) error {
37+
log.Printf("exec: %s %s", name, strings.Join(args, " "))
38+
cmd := exec.Command(name, args...)
39+
cmd.Stdout = os.Stderr
40+
cmd.Stderr = os.Stderr
41+
cmd.Stdin = os.Stdin
42+
return cmd.Run()
43+
}
44+
45+
// runOutput executes a command and returns its combined output.
46+
func runOutput(name string, args ...string) (string, error) {
47+
log.Printf("exec: %s %s", name, strings.Join(args, " "))
48+
cmd := exec.Command(name, args...)
49+
out, err := cmd.CombinedOutput()
50+
return string(out), err
51+
}
52+
53+
func runInstall() error {
54+
log.Println("=== Installing PostgreSQL and MySQL for test setup ===")
55+
56+
if err := installAptProxy(); err != nil {
57+
return fmt.Errorf("configuring apt proxy: %w", err)
58+
}
59+
60+
if err := installPostgreSQL(); err != nil {
61+
return fmt.Errorf("installing postgresql: %w", err)
62+
}
63+
64+
if err := installMySQL(); err != nil {
65+
return fmt.Errorf("installing mysql: %w", err)
66+
}
67+
68+
log.Println("=== Install complete ===")
69+
return nil
70+
}
71+
72+
func installAptProxy() error {
73+
proxy := os.Getenv("http_proxy")
74+
if proxy == "" {
75+
log.Println("http_proxy is not set, skipping apt proxy configuration")
76+
return nil
77+
}
78+
79+
log.Printf("configuring apt proxy to use %s", proxy)
80+
proxyConf := fmt.Sprintf("Acquire::http::Proxy \"%s\";", proxy)
81+
cmd := fmt.Sprintf("echo '%s' | sudo tee /etc/apt/apt.conf.d/99proxy", proxyConf)
82+
return run("bash", "-c", cmd)
83+
}
84+
85+
func installPostgreSQL() error {
86+
log.Println("--- Installing PostgreSQL ---")
87+
88+
log.Println("updating apt package lists")
89+
if err := run("sudo", "apt-get", "update", "-qq"); err != nil {
90+
return fmt.Errorf("apt-get update: %w", err)
91+
}
92+
93+
log.Println("installing postgresql package")
94+
if err := run("sudo", "apt-get", "install", "-y", "-qq", "postgresql"); err != nil {
95+
return fmt.Errorf("apt-get install postgresql: %w", err)
96+
}
97+
98+
log.Println("postgresql installed successfully")
99+
return nil
100+
}
101+
102+
func installMySQL() error {
103+
log.Println("--- Installing MySQL 9 ---")
104+
105+
bundleURL := "https://dev.mysql.com/get/Downloads/MySQL-9.1/mysql-server_9.1.0-1ubuntu24.04_amd64.deb-bundle.tar"
106+
bundleTar := "/tmp/mysql-server-bundle.tar"
107+
extractDir := "/tmp/mysql9"
108+
109+
log.Printf("downloading MySQL 9 bundle from %s", bundleURL)
110+
if err := run("curl", "-L", "-o", bundleTar, bundleURL); err != nil {
111+
return fmt.Errorf("downloading mysql bundle: %w", err)
112+
}
113+
114+
log.Printf("extracting bundle to %s", extractDir)
115+
if err := os.MkdirAll(extractDir, 0o755); err != nil {
116+
return fmt.Errorf("creating extract dir: %w", err)
117+
}
118+
if err := run("tar", "-xf", bundleTar, "-C", extractDir); err != nil {
119+
return fmt.Errorf("extracting mysql bundle: %w", err)
120+
}
121+
122+
// Install packages in dependency order
123+
packages := []string{
124+
"mysql-common_*.deb",
125+
"mysql-community-client-plugins_*.deb",
126+
"mysql-community-client-core_*.deb",
127+
"mysql-community-client_*.deb",
128+
"mysql-client_*.deb",
129+
"mysql-community-server-core_*.deb",
130+
"mysql-community-server_*.deb",
131+
"mysql-server_*.deb",
132+
}
133+
134+
for _, pkg := range packages {
135+
log.Printf("installing %s", pkg)
136+
// Use shell glob expansion via bash -c
137+
cmd := fmt.Sprintf("sudo dpkg -i %s/%s", extractDir, pkg)
138+
if err := run("bash", "-c", cmd); err != nil {
139+
return fmt.Errorf("installing %s: %w", pkg, err)
140+
}
141+
}
142+
143+
log.Println("making mysql init script executable")
144+
if err := run("sudo", "chmod", "+x", "/etc/init.d/mysql"); err != nil {
145+
return fmt.Errorf("chmod mysql init script: %w", err)
146+
}
147+
148+
log.Println("mysql 9 installed successfully")
149+
return nil
150+
}
151+
152+
func runStart() error {
153+
log.Println("=== Starting PostgreSQL and MySQL ===")
154+
155+
if err := startPostgreSQL(); err != nil {
156+
return fmt.Errorf("starting postgresql: %w", err)
157+
}
158+
159+
if err := startMySQL(); err != nil {
160+
return fmt.Errorf("starting mysql: %w", err)
161+
}
162+
163+
log.Println("=== Both databases are running and configured ===")
164+
log.Println("PostgreSQL: postgres://postgres:postgres@127.0.0.1:5432/postgres?sslmode=disable")
165+
log.Println("MySQL: root:mysecretpassword@tcp(127.0.0.1:3306)/mysql")
166+
return nil
167+
}
168+
169+
func startPostgreSQL() error {
170+
log.Println("--- Starting PostgreSQL ---")
171+
172+
log.Println("starting postgresql service")
173+
if err := run("sudo", "service", "postgresql", "start"); err != nil {
174+
return fmt.Errorf("service postgresql start: %w", err)
175+
}
176+
177+
log.Println("setting password for postgres user")
178+
if err := run("sudo", "-u", "postgres", "psql", "-c", "ALTER USER postgres PASSWORD 'postgres';"); err != nil {
179+
return fmt.Errorf("setting postgres password: %w", err)
180+
}
181+
182+
log.Println("detecting postgresql config directory")
183+
hbaPath, err := detectPgHBAPath()
184+
if err != nil {
185+
return fmt.Errorf("detecting pg_hba.conf path: %w", err)
186+
}
187+
188+
log.Printf("enabling md5 authentication in %s", hbaPath)
189+
hbaLine := "host all all 127.0.0.1/32 md5"
190+
cmd := fmt.Sprintf("echo '%s' | sudo tee -a %s", hbaLine, hbaPath)
191+
if err := run("bash", "-c", cmd); err != nil {
192+
return fmt.Errorf("configuring pg_hba.conf: %w", err)
193+
}
194+
195+
log.Println("reloading postgresql configuration")
196+
if err := run("sudo", "service", "postgresql", "reload"); err != nil {
197+
return fmt.Errorf("reloading postgresql: %w", err)
198+
}
199+
200+
log.Println("verifying postgresql connection")
201+
if err := run("bash", "-c", "PGPASSWORD=postgres psql -h 127.0.0.1 -U postgres -c 'SELECT 1;'"); err != nil {
202+
return fmt.Errorf("postgresql connection test failed: %w", err)
203+
}
204+
205+
log.Println("postgresql is running and configured")
206+
return nil
207+
}
208+
209+
// detectPgHBAPath finds the pg_hba.conf file across different PostgreSQL versions.
210+
func detectPgHBAPath() (string, error) {
211+
out, err := runOutput("bash", "-c", "sudo -u postgres psql -t -c 'SHOW hba_file;'")
212+
if err != nil {
213+
return "", fmt.Errorf("querying hba_file: %w (output: %s)", err, out)
214+
}
215+
path := strings.TrimSpace(out)
216+
if path == "" {
217+
return "", fmt.Errorf("pg_hba.conf path is empty")
218+
}
219+
log.Printf("found pg_hba.conf at %s", path)
220+
return path, nil
221+
}
222+
223+
func startMySQL() error {
224+
log.Println("--- Starting MySQL ---")
225+
226+
log.Println("initializing mysql data directory")
227+
if err := run("sudo", "mysqld", "--initialize-insecure", "--user=mysql"); err != nil {
228+
return fmt.Errorf("mysqld --initialize-insecure: %w", err)
229+
}
230+
231+
log.Println("starting mysql service")
232+
if err := run("sudo", "/etc/init.d/mysql", "start"); err != nil {
233+
return fmt.Errorf("starting mysql: %w", err)
234+
}
235+
236+
log.Println("setting mysql root password")
237+
if err := run("mysql", "-u", "root", "-e",
238+
"ALTER USER 'root'@'localhost' IDENTIFIED BY 'mysecretpassword'; FLUSH PRIVILEGES;"); err != nil {
239+
return fmt.Errorf("setting mysql root password: %w", err)
240+
}
241+
242+
log.Println("verifying mysql connection")
243+
if err := run("mysql", "-h", "127.0.0.1", "-u", "root", "-pmysecretpassword", "-e", "SELECT VERSION();"); err != nil {
244+
return fmt.Errorf("mysql connection test failed: %w", err)
245+
}
246+
247+
log.Println("mysql is running and configured")
248+
return nil
249+
}

0 commit comments

Comments
 (0)