Files
buque/internal/docker/compose.go
2025-11-02 01:44:53 +01:00

257 líneas
6.4 KiB
Go

package docker
import (
"context"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"github.com/manalejandro/buque/internal/models"
)
// ComposeManager manages Docker Compose operations
type ComposeManager struct {
composeCommand string
}
// NewComposeManager creates a new Docker Compose manager
func NewComposeManager() (*ComposeManager, error) {
// Try to find docker compose command
cmd := "docker"
if err := exec.Command(cmd, "compose", "version").Run(); err == nil {
return &ComposeManager{composeCommand: cmd}, nil
}
// Fallback to docker-compose
cmd = "docker-compose"
if err := exec.Command(cmd, "version").Run(); err == nil {
return &ComposeManager{composeCommand: cmd}, nil
}
return nil, fmt.Errorf("docker compose not found. Please install Docker and Docker Compose")
}
// Up starts services in an environment
func (cm *ComposeManager) Up(ctx context.Context, env models.Environment, detach bool) error {
args := cm.buildComposeArgs(env)
args = append(args, "up")
if detach {
args = append(args, "-d")
}
cmd := cm.createCommand(ctx, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = env.Path
return cmd.Run()
}
// Down stops and removes services in an environment
func (cm *ComposeManager) Down(ctx context.Context, env models.Environment, removeVolumes bool) error {
args := cm.buildComposeArgs(env)
args = append(args, "down")
if removeVolumes {
args = append(args, "-v")
}
cmd := cm.createCommand(ctx, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = env.Path
return cmd.Run()
}
// Restart restarts services in an environment
func (cm *ComposeManager) Restart(ctx context.Context, env models.Environment) error {
args := cm.buildComposeArgs(env)
args = append(args, "restart")
cmd := cm.createCommand(ctx, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = env.Path
return cmd.Run()
}
// Pull pulls images for an environment
func (cm *ComposeManager) Pull(ctx context.Context, env models.Environment) error {
args := cm.buildComposeArgs(env)
args = append(args, "pull")
cmd := cm.createCommand(ctx, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = env.Path
return cmd.Run()
}
// PS lists services in an environment
func (cm *ComposeManager) PS(ctx context.Context, env models.Environment) (string, error) {
args := cm.buildComposeArgs(env)
args = append(args, "ps", "--format", "json")
cmd := cm.createCommand(ctx, args...)
cmd.Dir = env.Path
output, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("failed to list services: %w\n%s", err, string(output))
}
return string(output), nil
}
// Logs retrieves logs from an environment
func (cm *ComposeManager) Logs(ctx context.Context, env models.Environment, follow bool, tail string) error {
args := cm.buildComposeArgs(env)
args = append(args, "logs")
if follow {
args = append(args, "-f")
}
if tail != "" {
args = append(args, "--tail", tail)
}
cmd := cm.createCommand(ctx, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = env.Path
return cmd.Run()
}
// Update updates images and recreates services
func (cm *ComposeManager) Update(ctx context.Context, env models.Environment) error {
// Pull latest images
if err := cm.Pull(ctx, env); err != nil {
return fmt.Errorf("failed to pull images: %w", err)
}
// Recreate services with new images
args := cm.buildComposeArgs(env)
args = append(args, "up", "-d", "--force-recreate")
cmd := cm.createCommand(ctx, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = env.Path
return cmd.Run()
}
// Build builds images for an environment
func (cm *ComposeManager) Build(ctx context.Context, env models.Environment) error {
args := cm.buildComposeArgs(env)
args = append(args, "build")
cmd := cm.createCommand(ctx, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = env.Path
return cmd.Run()
}
// ValidateComposeFile checks if the compose file is valid
func (cm *ComposeManager) ValidateComposeFile(env models.Environment) error {
composeFilePath := filepath.Join(env.Path, env.ComposeFile)
if _, err := os.Stat(composeFilePath); os.IsNotExist(err) {
return fmt.Errorf("compose file not found: %s", composeFilePath)
}
args := cm.buildComposeArgs(env)
args = append(args, "config", "--quiet")
cmd := cm.createCommand(context.Background(), args...)
cmd.Dir = env.Path
if err := cmd.Run(); err != nil {
return fmt.Errorf("invalid compose file: %w", err)
}
return nil
}
// GetConfig returns the resolved compose configuration
func (cm *ComposeManager) GetConfig(ctx context.Context, env models.Environment) (string, error) {
args := cm.buildComposeArgs(env)
args = append(args, "config")
cmd := cm.createCommand(ctx, args...)
cmd.Dir = env.Path
output, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("failed to get config: %w\n%s", err, string(output))
}
return string(output), nil
}
// buildComposeArgs builds the base arguments for docker compose commands
func (cm *ComposeManager) buildComposeArgs(env models.Environment) []string {
args := []string{}
if cm.composeCommand == "docker" {
args = append(args, "compose")
}
if env.ComposeFile != "" && env.ComposeFile != "docker-compose.yml" {
args = append(args, "-f", env.ComposeFile)
}
return args
}
// createCommand creates an exec.Cmd with the given arguments
func (cm *ComposeManager) createCommand(ctx context.Context, args ...string) *exec.Cmd {
if ctx == nil {
ctx = context.Background()
}
if cm.composeCommand == "docker-compose" {
return exec.CommandContext(ctx, cm.composeCommand, args...)
}
return exec.CommandContext(ctx, cm.composeCommand, args...)
}
// ExecInService executes a command in a running service
func (cm *ComposeManager) ExecInService(ctx context.Context, env models.Environment, service string, command []string) error {
args := cm.buildComposeArgs(env)
args = append(args, "exec", service)
args = append(args, command...)
cmd := cm.createCommand(ctx, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Dir = env.Path
return cmd.Run()
}
// CopyLogs copies logs from a service to a writer
func (cm *ComposeManager) CopyLogs(ctx context.Context, env models.Environment, service string, writer io.Writer) error {
args := cm.buildComposeArgs(env)
args = append(args, "logs", service)
cmd := cm.createCommand(ctx, args...)
cmd.Stdout = writer
cmd.Stderr = writer
cmd.Dir = env.Path
return cmd.Run()
}