Files
managed-portal/internal/managed/docker_runtime.go
2026-04-27 10:04:36 +08:00

99 lines
2.4 KiB
Go

package managed
import (
"context"
"errors"
"fmt"
"os/exec"
"strings"
"time"
)
type DockerRuntime interface {
GetContainerStatus(containerName string) (string, error)
RestartContainer(containerName string) error
}
type CommandRunner func(ctx context.Context, name string, args ...string) ([]byte, error)
type DockerController struct {
runner CommandRunner
timeout time.Duration
}
func NewDockerController() *DockerController {
return &DockerController{
runner: func(ctx context.Context, name string, args ...string) ([]byte, error) {
return exec.CommandContext(ctx, name, args...).CombinedOutput()
},
timeout: 5 * time.Second,
}
}
func NewDockerControllerWithRunner(runner CommandRunner) *DockerController {
return &DockerController{
runner: runner,
timeout: 5 * time.Second,
}
}
func NormalizeContainerStatus(raw string) string {
switch strings.TrimSpace(strings.ToLower(raw)) {
case "running":
return "running"
case "created", "exited", "paused":
return "stopped"
case "dead", "removing", "restarting":
return "failed"
default:
return "unknown"
}
}
func (c *DockerController) GetContainerStatus(containerName string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
defer cancel()
output, err := c.runner(ctx, "docker", "inspect", "--format", "{{.State.Status}}", containerName)
status := NormalizeContainerStatus(string(output))
if status != "unknown" {
return status, nil
}
if err != nil {
return status, fmt.Errorf("docker inspect %s: %w", containerName, err)
}
return status, nil
}
func (c *DockerController) RestartContainer(containerName string) error {
ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
defer cancel()
output, err := c.runner(ctx, "docker", "restart", containerName)
if err != nil {
trimmed := strings.TrimSpace(string(output))
if trimmed != "" {
return fmt.Errorf("docker restart %s: %w: %s", containerName, err, trimmed)
}
return fmt.Errorf("docker restart %s: %w", containerName, err)
}
return nil
}
func IsDockerUnavailable(err error) bool {
if err == nil {
return false
}
var execErr *exec.Error
if errors.As(err, &execErr) {
return execErr.Err == exec.ErrNotFound
}
message := err.Error()
return errors.Is(err, exec.ErrNotFound) ||
strings.Contains(message, `exec: "docker": executable file not found`) ||
strings.Contains(message, "failed to connect to the docker API") ||
strings.Contains(message, "docker.sock")
}