readysite / pkg / platform / service.go
3.5 KB
service.go
package platform

import "fmt"

// Service represents a Docker container to run on a server
type Service struct {
	Name        string
	Image       string
	Command     []string
	Ports       []Port
	Volumes     []Mount
	Env         map[string]string
	Network     string
	Restart     string
	Healthcheck *Healthcheck
	Resources   *Resources
	Logging     *LogConfig
	Privileged  bool
}

// Healthcheck configures container health checking
type Healthcheck struct {
	Cmd         string // Command to run (e.g., "curl -f http://localhost/health")
	Interval    string // Time between checks (e.g., "30s")
	Timeout     string // Time to wait for check to complete (e.g., "10s")
	Retries     int    // Consecutive failures before unhealthy
	StartPeriod string // Grace period before health checks start (e.g., "5s")
}

// Resources configures CPU and memory limits
type Resources struct {
	CPUs   string // CPU limit (e.g., "0.5", "2")
	Memory string // Memory limit (e.g., "512m", "1g")
}

// LogConfig configures container logging
type LogConfig struct {
	Driver  string            // Log driver (e.g., "json-file", "syslog")
	Options map[string]string // Driver-specific options
}

// Port maps a host port to a container port
type Port struct {
	Host      int
	Container int
	Bind      string // Optional bind address (e.g., "127.0.0.1")
}

// Mount binds a host path to a container path
type Mount struct {
	Source string
	Target string
}

// Start runs a service container on the server
func (s *Server) Start(service *Service) error {
	args := []string{"docker", "run", "-d", "--name", service.Name}

	// Add restart policy
	if service.Restart != "" {
		args = append(args, "--restart", service.Restart)
	}

	// Add network
	if service.Network != "" {
		args = append(args, "--network", service.Network)
	}

	// Add ports
	for _, p := range service.Ports {
		portMap := fmt.Sprintf("%d:%d", p.Host, p.Container)
		if p.Bind != "" {
			portMap = p.Bind + ":" + portMap
		}
		args = append(args, "-p", portMap)
	}

	// Add volumes
	for _, v := range service.Volumes {
		args = append(args, "-v", v.Source+":"+v.Target)
	}

	// Add environment variables
	for k, v := range service.Env {
		args = append(args, "-e", k+"="+v)
	}

	// Add healthcheck
	if h := service.Healthcheck; h != nil {
		if h.Cmd != "" {
			// shellEscape in SSH handles quoting
			args = append(args, "--health-cmd", h.Cmd)
		}
		if h.Interval != "" {
			args = append(args, "--health-interval", h.Interval)
		}
		if h.Timeout != "" {
			args = append(args, "--health-timeout", h.Timeout)
		}
		if h.Retries > 0 {
			args = append(args, "--health-retries", fmt.Sprintf("%d", h.Retries))
		}
		if h.StartPeriod != "" {
			args = append(args, "--health-start-period", h.StartPeriod)
		}
	}

	// Add resource limits
	if r := service.Resources; r != nil {
		if r.CPUs != "" {
			args = append(args, "--cpus", r.CPUs)
		}
		if r.Memory != "" {
			args = append(args, "--memory", r.Memory)
		}
	}

	// Add logging configuration
	if l := service.Logging; l != nil {
		if l.Driver != "" {
			args = append(args, "--log-driver", l.Driver)
		}
		for k, v := range l.Options {
			args = append(args, "--log-opt", k+"="+v)
		}
	}

	// Add privileged flag
	if service.Privileged {
		args = append(args, "--privileged")
	}

	// Add image
	args = append(args, service.Image)

	// Add command
	args = append(args, service.Command...)

	_, err := s.SSH(args...)
	return err
}

// Stop kills and removes a container on the server
func (s *Server) Stop(name string) error {
	_, err := s.SSH("docker", "rm", "-f", name)
	return err
}
← Back