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