167 lines
4.3 KiB
Go
167 lines
4.3 KiB
Go
package docker
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
)
|
|
|
|
type Manager struct {
|
|
ClientDir string
|
|
}
|
|
|
|
func NewManager(clientDir string) *Manager {
|
|
return &Manager{
|
|
ClientDir: clientDir,
|
|
}
|
|
}
|
|
|
|
func getComposeCommand() ([]string, error) {
|
|
// Try docker compose (v2) first
|
|
cmd := exec.Command("docker", "compose", "version")
|
|
if err := cmd.Run(); err == nil {
|
|
return []string{"docker", "compose"}, nil
|
|
}
|
|
|
|
// Fall back to docker-compose (v1)
|
|
cmd = exec.Command("docker-compose", "--version")
|
|
if err := cmd.Run(); err == nil {
|
|
return []string{"docker-compose"}, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("neither 'docker compose' nor 'docker-compose' is available. Please install Docker Compose")
|
|
}
|
|
|
|
func (m *Manager) Build() error {
|
|
dockerfilePath := filepath.Join(m.ClientDir, "Dockerfile")
|
|
if _, err := os.Stat(dockerfilePath); os.IsNotExist(err) {
|
|
return fmt.Errorf("Dockerfile not found at %s", dockerfilePath)
|
|
}
|
|
|
|
// Use docker-compose build to ensure it respects docker-compose.yml configuration
|
|
// and rebuilds when Dockerfile changes
|
|
composeCmd, err := getComposeCommand()
|
|
if err != nil {
|
|
// Fallback to docker build if compose is not available
|
|
cmd := exec.Command("docker", "build", "--no-cache", "-t", "sslh-lab-client", m.ClientDir)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to build Docker image: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Use docker-compose build which will rebuild if Dockerfile changed
|
|
args := append(composeCmd, "-f", filepath.Join(m.ClientDir, "docker-compose.yml"), "build", "--no-cache")
|
|
cmd := exec.Command(args[0], args[1:]...)
|
|
cmd.Dir = m.ClientDir
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to build Docker image: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) Run() error {
|
|
composeFile := filepath.Join(m.ClientDir, "docker-compose.yml")
|
|
if _, err := os.Stat(composeFile); os.IsNotExist(err) {
|
|
return fmt.Errorf("docker-compose.yml not found at %s", composeFile)
|
|
}
|
|
|
|
composeCmd, err := getComposeCommand()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
args := append(composeCmd, "-f", composeFile, "up", "-d")
|
|
cmd := exec.Command(args[0], args[1:]...)
|
|
cmd.Dir = m.ClientDir
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to run Docker container: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) Stop() error {
|
|
composeFile := filepath.Join(m.ClientDir, "docker-compose.yml")
|
|
if _, err := os.Stat(composeFile); os.IsNotExist(err) {
|
|
return fmt.Errorf("docker-compose.yml not found at %s", composeFile)
|
|
}
|
|
|
|
composeCmd, err := getComposeCommand()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
args := append(composeCmd, "-f", composeFile, "down")
|
|
cmd := exec.Command(args[0], args[1:]...)
|
|
cmd.Dir = m.ClientDir
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to stop Docker container: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) Status() (string, error) {
|
|
cmd := exec.Command("docker", "ps", "--filter", "name=sslh-lab-client", "--format", "{{.Status}}")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to check container status: %w", err)
|
|
}
|
|
|
|
return string(output), nil
|
|
}
|
|
|
|
func (m *Manager) IsRunning() bool {
|
|
cmd := exec.Command("docker", "ps", "--filter", "name=sslh-lab-client", "--format", "{{.Names}}")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return len(output) > 0 && string(output) == "sslh-lab-client\n"
|
|
}
|
|
|
|
func (m *Manager) Connect(shell string) error {
|
|
if !m.IsRunning() {
|
|
return fmt.Errorf("container is not running. Start it first with 'sslh-lab client'")
|
|
}
|
|
|
|
cmd := exec.Command("docker", "exec", "-it", "sslh-lab-client", shell)
|
|
cmd.Stdin = os.Stdin
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
err := cmd.Run()
|
|
|
|
// Exit code 255 is common when user exits normally (Ctrl+D or 'exit' command)
|
|
// Exit code 130 is common when user presses Ctrl+C
|
|
// These are not errors, just normal ways to disconnect
|
|
if exitError, ok := err.(*exec.ExitError); ok {
|
|
exitCode := exitError.ExitCode()
|
|
if exitCode == 255 || exitCode == 130 || exitCode == 0 {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// If err is nil (exit code 0), return nil
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
// For other errors, return them
|
|
return err
|
|
}
|