neo4j_docker.go raw
1 package main
2
3 import (
4 "context"
5 "fmt"
6 "os"
7 "os/exec"
8 "path/filepath"
9 "time"
10 )
11
12 // Neo4jDocker manages a Neo4j instance via Docker Compose
13 type Neo4jDocker struct {
14 composeFile string
15 projectName string
16 }
17
18 // NewNeo4jDocker creates a new Neo4j Docker manager
19 func NewNeo4jDocker() (*Neo4jDocker, error) {
20 // Look for docker-compose-neo4j.yml in current directory or cmd/benchmark
21 composeFile := "docker-compose-neo4j.yml"
22 if _, err := os.Stat(composeFile); os.IsNotExist(err) {
23 // Try in cmd/benchmark directory
24 composeFile = filepath.Join("cmd", "benchmark", "docker-compose-neo4j.yml")
25 }
26
27 return &Neo4jDocker{
28 composeFile: composeFile,
29 projectName: "orly-benchmark-neo4j",
30 }, nil
31 }
32
33 // Start starts the Neo4j Docker container
34 func (d *Neo4jDocker) Start() error {
35 fmt.Println("Starting Neo4j Docker container...")
36
37 // Pull image first
38 pullCmd := exec.Command("docker-compose",
39 "-f", d.composeFile,
40 "-p", d.projectName,
41 "pull",
42 )
43 pullCmd.Stdout = os.Stdout
44 pullCmd.Stderr = os.Stderr
45 if err := pullCmd.Run(); err != nil {
46 return fmt.Errorf("failed to pull Neo4j image: %w", err)
47 }
48
49 // Start containers
50 upCmd := exec.Command("docker-compose",
51 "-f", d.composeFile,
52 "-p", d.projectName,
53 "up", "-d",
54 )
55 upCmd.Stdout = os.Stdout
56 upCmd.Stderr = os.Stderr
57 if err := upCmd.Run(); err != nil {
58 return fmt.Errorf("failed to start Neo4j container: %w", err)
59 }
60
61 fmt.Println("Waiting for Neo4j to be healthy...")
62 if err := d.waitForHealthy(); err != nil {
63 return err
64 }
65
66 fmt.Println("Neo4j is ready!")
67 return nil
68 }
69
70 // waitForHealthy waits for Neo4j to become healthy
71 func (d *Neo4jDocker) waitForHealthy() error {
72 timeout := 120 * time.Second
73 deadline := time.Now().Add(timeout)
74
75 containerName := "orly-benchmark-neo4j"
76
77 for time.Now().Before(deadline) {
78 // Check container health status
79 checkCmd := exec.Command("docker", "inspect",
80 "--format={{.State.Health.Status}}",
81 containerName,
82 )
83 output, err := checkCmd.Output()
84 if err == nil && string(output) == "healthy\n" {
85 return nil
86 }
87
88 time.Sleep(2 * time.Second)
89 }
90
91 return fmt.Errorf("Neo4j failed to become healthy within %v", timeout)
92 }
93
94 // Stop stops and removes the Neo4j Docker container
95 func (d *Neo4jDocker) Stop() error {
96 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
97 defer cancel()
98
99 // Get logs before stopping (useful for debugging)
100 logsCmd := exec.CommandContext(ctx, "docker-compose",
101 "-f", d.composeFile,
102 "-p", d.projectName,
103 "logs", "--tail=50",
104 )
105 logsCmd.Stdout = os.Stdout
106 logsCmd.Stderr = os.Stderr
107 _ = logsCmd.Run() // Ignore errors
108
109 fmt.Println("Stopping Neo4j Docker container...")
110
111 // Stop and remove containers
112 downCmd := exec.Command("docker-compose",
113 "-f", d.composeFile,
114 "-p", d.projectName,
115 "down", "-v",
116 )
117 downCmd.Stdout = os.Stdout
118 downCmd.Stderr = os.Stderr
119 if err := downCmd.Run(); err != nil {
120 return fmt.Errorf("failed to stop Neo4j container: %w", err)
121 }
122
123 return nil
124 }
125
126 // GetBoltEndpoint returns the Neo4j Bolt endpoint
127 func (d *Neo4jDocker) GetBoltEndpoint() string {
128 return "bolt://localhost:7687"
129 }
130
131 // IsRunning returns whether Neo4j is running
132 func (d *Neo4jDocker) IsRunning() bool {
133 checkCmd := exec.Command("docker", "ps", "--filter", "name=orly-benchmark-neo4j", "--format", "{{.Names}}")
134 output, err := checkCmd.Output()
135 return err == nil && len(output) > 0
136 }
137
138 // Logs returns the logs from Neo4j container
139 func (d *Neo4jDocker) Logs(tail int) (string, error) {
140 logsCmd := exec.Command("docker-compose",
141 "-f", d.composeFile,
142 "-p", d.projectName,
143 "logs", "--tail", fmt.Sprintf("%d", tail),
144 )
145 output, err := logsCmd.CombinedOutput()
146 return string(output), err
147 }
148