99 lines
2.4 KiB
Go
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")
|
|
}
|