package swarm import ( "bytes" "fmt" "log" "os" "os/exec" "path/filepath" "strings" "text/template" "git.nixc.us/a250/ss-atlas/internal/config" ) type Client struct { cfg *config.Config } func New(cfg *config.Config) *Client { return &Client{cfg: cfg} } func (c *Client) DeployStack(stackName, username, domain string) error { tmplPath := filepath.Join(c.cfg.TemplatePath, "stack-template.yml") tmplBytes, err := os.ReadFile(tmplPath) if err != nil { return fmt.Errorf("read stack template: %w", err) } t, err := template.New("stack").Parse(string(tmplBytes)) if err != nil { return fmt.Errorf("parse stack template: %w", err) } data := map[string]string{ "ID": username, "Subdomain": username, "Domain": domain, "TraefikNetwork": c.cfg.TraefikNetwork, } var rendered bytes.Buffer if err := t.Execute(&rendered, data); err != nil { return fmt.Errorf("render stack template: %w", err) } tmpFile := filepath.Join(os.TempDir(), stackName+".yml") if err := os.WriteFile(tmpFile, rendered.Bytes(), 0600); err != nil { return fmt.Errorf("write temp stack file: %w", err) } defer os.Remove(tmpFile) cmd := exec.Command("docker", "stack", "deploy", "-c", tmpFile, stackName, ) cmd.Env = append(os.Environ(), "DOCKER_HOST="+c.cfg.DockerHost) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("docker stack deploy: %s: %w", strings.TrimSpace(string(output)), err) } log.Printf("deployed stack %s: %s", stackName, strings.TrimSpace(string(output))) return nil } func (c *Client) RemoveStack(stackName string) error { cmd := exec.Command("docker", "stack", "rm", stackName) cmd.Env = append(os.Environ(), "DOCKER_HOST="+c.cfg.DockerHost) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("docker stack rm: %s: %w", strings.TrimSpace(string(output)), err) } log.Printf("removed stack %s: %s", stackName, strings.TrimSpace(string(output))) return nil } func (c *Client) StackExists(stackName string) (bool, error) { cmd := exec.Command("docker", "stack", "ls", "--format", "{{.Name}}") cmd.Env = append(os.Environ(), "DOCKER_HOST="+c.cfg.DockerHost) output, err := cmd.CombinedOutput() if err != nil { return false, fmt.Errorf("docker stack ls: %w", err) } for _, line := range strings.Split(string(output), "\n") { if strings.TrimSpace(line) == stackName { return true, nil } } return false, nil }