179 lines
4.5 KiB
Go
179 lines
4.5 KiB
Go
package container
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os/exec"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type TIMOptions struct {
|
|
Name string
|
|
Image string
|
|
Command []string
|
|
WorkDir string
|
|
Env map[string]string
|
|
DataDir string
|
|
Runtime ContainerRuntime
|
|
Detect func() ContainerRuntime
|
|
Exec func(context.Context, string, ...string) error
|
|
Now func() time.Time
|
|
Resources TIMResources
|
|
}
|
|
|
|
type TIMResources struct {
|
|
CPUCores int `json:"cpu_cores,omitempty"`
|
|
MemoryMB int `json:"memory_mb,omitempty"`
|
|
GPU string `json:"gpu,omitempty"`
|
|
}
|
|
|
|
type TIMState struct {
|
|
Name string `json:"name"`
|
|
Image string `json:"image"`
|
|
Runtime ContainerRuntime `json:"runtime"`
|
|
Status string `json:"status"`
|
|
StartedAt time.Time `json:"started_at,omitempty"`
|
|
Command []string `json:"command,omitempty"`
|
|
DataDir string `json:"data_dir,omitempty"`
|
|
Resources TIMResources `json:"resources,omitempty"`
|
|
}
|
|
|
|
type TIMManager struct {
|
|
options TIMOptions
|
|
mu sync.Mutex
|
|
state TIMState
|
|
}
|
|
|
|
func NewTIMManager(options TIMOptions) *TIMManager {
|
|
if strings.TrimSpace(options.Name) == "" {
|
|
options.Name = "coregui-tim"
|
|
}
|
|
if strings.TrimSpace(options.Image) == "" {
|
|
options.Image = "ghcr.io/lthn/core/tim:latest"
|
|
}
|
|
if options.Detect == nil {
|
|
options.Detect = Detect
|
|
}
|
|
if options.Exec == nil {
|
|
options.Exec = func(ctx context.Context, name string, args ...string) error {
|
|
cmd := exec.CommandContext(ctx, name, args...)
|
|
return cmd.Run()
|
|
}
|
|
}
|
|
if options.Now == nil {
|
|
options.Now = time.Now
|
|
}
|
|
return &TIMManager{
|
|
options: options,
|
|
state: TIMState{
|
|
Name: options.Name,
|
|
Image: options.Image,
|
|
Runtime: coalesceRuntime(options.Runtime, options.Detect()),
|
|
Status: "stopped",
|
|
Command: append([]string(nil), options.Command...),
|
|
DataDir: options.DataDir,
|
|
Resources: options.Resources,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (m *TIMManager) State() TIMState {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return m.state
|
|
}
|
|
|
|
func (m *TIMManager) Start(ctx context.Context) (TIMState, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
runtime := coalesceRuntime(m.options.Runtime, m.options.Detect())
|
|
m.state.Runtime = runtime
|
|
if runtime == RuntimeNone {
|
|
return m.state, fmt.Errorf("no supported container runtime detected")
|
|
}
|
|
|
|
command, args := m.runtimeCommand(runtime, "run")
|
|
if err := m.options.Exec(ctx, command, args...); err != nil {
|
|
m.state.Status = "error"
|
|
return m.state, err
|
|
}
|
|
|
|
m.state.Status = "running"
|
|
m.state.StartedAt = m.options.Now()
|
|
return m.state, nil
|
|
}
|
|
|
|
func (m *TIMManager) Stop(ctx context.Context) (TIMState, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if m.state.Runtime == RuntimeNone {
|
|
m.state.Status = "stopped"
|
|
return m.state, nil
|
|
}
|
|
command, args := m.runtimeCommand(m.state.Runtime, "stop")
|
|
if err := m.options.Exec(ctx, command, args...); err != nil {
|
|
m.state.Status = "error"
|
|
return m.state, err
|
|
}
|
|
m.state.Status = "stopped"
|
|
return m.state, nil
|
|
}
|
|
|
|
func (m *TIMManager) runtimeCommand(runtime ContainerRuntime, verb string) (string, []string) {
|
|
name := m.options.Name
|
|
image := m.options.Image
|
|
switch runtime {
|
|
case RuntimeApple:
|
|
if verb == "run" {
|
|
args := []string{"run", "--name", name, image}
|
|
args = append(args, m.options.Command...)
|
|
return "container", args
|
|
}
|
|
return "container", []string{"stop", name}
|
|
case RuntimePodman:
|
|
if verb == "run" {
|
|
args := []string{"run", "-d", "--replace", "--name", name}
|
|
args = append(args, resourceArgs(m.options.Resources)...)
|
|
args = append(args, image)
|
|
args = append(args, m.options.Command...)
|
|
return "podman", args
|
|
}
|
|
return "podman", []string{"stop", name}
|
|
default:
|
|
if verb == "run" {
|
|
args := []string{"run", "-d", "--rm", "--name", name}
|
|
args = append(args, resourceArgs(m.options.Resources)...)
|
|
args = append(args, image)
|
|
args = append(args, m.options.Command...)
|
|
return "docker", args
|
|
}
|
|
return "docker", []string{"stop", name}
|
|
}
|
|
}
|
|
|
|
func resourceArgs(resources TIMResources) []string {
|
|
var args []string
|
|
if resources.CPUCores > 0 {
|
|
args = append(args, "--cpus", fmt.Sprintf("%d", resources.CPUCores))
|
|
}
|
|
if resources.MemoryMB > 0 {
|
|
args = append(args, "--memory", fmt.Sprintf("%dm", resources.MemoryMB))
|
|
}
|
|
if strings.TrimSpace(resources.GPU) != "" {
|
|
args = append(args, "--gpus", resources.GPU)
|
|
}
|
|
return args
|
|
}
|
|
|
|
func coalesceRuntime(values ...ContainerRuntime) ContainerRuntime {
|
|
for _, value := range values {
|
|
if value != RuntimeNone {
|
|
return value
|
|
}
|
|
}
|
|
return RuntimeNone
|
|
}
|