Skip to content

Commit a80e23e

Browse files
committed
feat(massive performance & context fix)
1 parent 5b746f7 commit a80e23e

16 files changed

Lines changed: 510 additions & 133 deletions

File tree

internal/dao/compose/compose.go

Lines changed: 79 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,88 @@ import (
77
"os/exec"
88
"path/filepath"
99
"strings"
10+
"sync"
1011

1112
"github.com/docker/docker/api/types/container"
1213
"github.com/docker/docker/api/types/filters"
1314
"github.com/docker/docker/client"
1415
"github.com/gdamore/tcell/v2"
1516
"github.com/jr-k/d4s/internal/dao/common"
17+
"github.com/jr-k/d4s/internal/sshutil"
1618
"github.com/jr-k/d4s/internal/ui/styles"
1719
"golang.org/x/net/context"
1820
)
1921

2022
type Manager struct {
2123
cli *client.Client
2224
ctx context.Context
25+
26+
// Where docker compose CLI commands must run. For SSH contexts the
27+
// compose files only exist on the remote host, so commands are
28+
// executed there through ssh.
29+
targetMu sync.RWMutex
30+
contextName string
31+
remoteHost string // empty = run locally
2332
}
2433

2534
func NewManager(cli *client.Client, ctx context.Context) *Manager {
2635
return &Manager{cli: cli, ctx: ctx}
2736
}
2837

38+
// SetExecTarget configures where compose CLI commands run.
39+
// remoteHost is empty for local contexts, "user@ip" for SSH contexts.
40+
func (m *Manager) SetExecTarget(contextName, remoteHost string) {
41+
m.targetMu.Lock()
42+
m.contextName = contextName
43+
m.remoteHost = remoteHost
44+
m.targetMu.Unlock()
45+
}
46+
47+
func (m *Manager) execTarget() (string, string) {
48+
m.targetMu.RLock()
49+
defer m.targetMu.RUnlock()
50+
return m.contextName, m.remoteHost
51+
}
52+
53+
// dockerCmd builds a docker CLI invocation that runs on the host where
54+
// the compose files live: locally (pinned to the right docker context)
55+
// or on the remote SSH host.
56+
func (m *Manager) dockerCmd(args []string, workDir string) *exec.Cmd {
57+
contextName, remoteHost := m.execTarget()
58+
59+
if remoteHost != "" {
60+
quoted := make([]string, 0, len(args)+1)
61+
quoted = append(quoted, "docker")
62+
for _, a := range args {
63+
quoted = append(quoted, sshutil.ShellQuote(a))
64+
}
65+
remoteCmd := strings.Join(quoted, " ")
66+
if workDir != "" {
67+
remoteCmd = fmt.Sprintf("cd %s && %s", sshutil.ShellQuote(workDir), remoteCmd)
68+
}
69+
return sshutil.SSHCommand(contextName, remoteHost, remoteCmd)
70+
}
71+
72+
cmd := exec.Command("docker", args...)
73+
if workDir != "" {
74+
cmd.Dir = workDir
75+
}
76+
if contextName != "" && contextName != "default" && contextName != "env" {
77+
cmd.Env = append(os.Environ(), "DOCKER_CONTEXT="+contextName)
78+
}
79+
return cmd
80+
}
81+
82+
// readConfigFile reads a compose file from where it actually lives.
83+
func (m *Manager) readConfigFile(path string) ([]byte, error) {
84+
contextName, remoteHost := m.execTarget()
85+
if remoteHost == "" {
86+
return os.ReadFile(path)
87+
}
88+
cmd := sshutil.SSHCommand(contextName, remoteHost, "cat "+sshutil.ShellQuote(path))
89+
return cmd.Output()
90+
}
91+
2992
// ComposeProject Model
3093
type ComposeProject struct {
3194
Name string
@@ -209,7 +272,7 @@ func (m *Manager) GetConfig(projectName string) (string, error) {
209272
path := strings.TrimSpace(f)
210273
if path == "" { continue }
211274

212-
content, err := os.ReadFile(path)
275+
content, err := m.readConfigFile(path)
213276
if err != nil {
214277
sb.WriteString(fmt.Sprintf("# Error reading %s: %v\n", path, err))
215278
continue
@@ -223,28 +286,6 @@ func (m *Manager) GetConfig(projectName string) (string, error) {
223286
return sb.String(), nil
224287
}
225288

226-
func (m *Manager) countServices(projectName string, configPaths []string) int {
227-
args := []string{"compose", "-p", projectName}
228-
for _, path := range configPaths {
229-
args = append(args, "-f", path)
230-
}
231-
args = append(args, "config", "--services")
232-
233-
cmd := exec.Command("docker", args...)
234-
output, err := cmd.Output()
235-
if err != nil {
236-
return 0
237-
}
238-
239-
count := 0
240-
for _, line := range strings.Split(strings.TrimSpace(string(output)), "\n") {
241-
if line != "" {
242-
count++
243-
}
244-
}
245-
return count
246-
}
247-
248289
func (m *Manager) getConfigPaths(projectName string) ([]string, error) {
249290
args := filters.NewArgs()
250291
args.Add("label", fmt.Sprintf("com.docker.compose.project=%s", projectName))
@@ -285,7 +326,7 @@ func (m *Manager) Logs(projectName string, since string, tail string, timestamps
285326
args = append(args, "--since", since)
286327
}
287328

288-
cmd := exec.Command("docker", args...)
329+
cmd := m.dockerCmd(args, "")
289330

290331
stdout, err := cmd.StdoutPipe()
291332
if err != nil {
@@ -303,10 +344,7 @@ func (m *Manager) Logs(projectName string, since string, tail string, timestamps
303344
}
304345

305346
func (m *Manager) Down(projectName string) error {
306-
var args []string
307-
args = append(args, "compose", "-p", projectName, "down")
308-
309-
cmd := exec.Command("docker", args...)
347+
cmd := m.dockerCmd([]string{"compose", "-p", projectName, "down"}, "")
310348
output, err := cmd.CombinedOutput()
311349
if err != nil {
312350
return fmt.Errorf("error running docker compose down: %v, output: %s", err, string(output))
@@ -324,59 +362,41 @@ func (m *Manager) Redeploy(projectName string) error {
324362
return err
325363
}
326364

327-
args := []string{"compose", "-p", projectName}
328-
for _, path := range paths {
329-
args = append(args, "-f", path)
330-
}
331-
args = append(args, "up", "-d", "--force-recreate")
332-
333-
cmd := exec.Command("docker", args...)
334-
if len(paths) > 0 {
335-
cmd.Dir = filepath.Dir(paths[0])
336-
}
337-
output, err := cmd.CombinedOutput()
338-
if err != nil {
339-
return fmt.Errorf("error running docker compose up: %v\nOutput: %s", err, string(output))
340-
}
341-
return nil
365+
return m.up(projectName, paths, "--force-recreate")
342366
}
343367

344368
func (m *Manager) Up(projectName string) error {
345369
paths, err := m.getConfigPaths(projectName)
346370
if err != nil {
347371
return fmt.Errorf("failed to up project: %v", err)
348372
}
349-
350-
args := []string{"compose", "-p", projectName}
351-
for _, path := range paths {
352-
args = append(args, "-f", path)
353-
}
354-
args = append(args, "up", "-d", "--force-recreate")
355-
356-
cmd := exec.Command("docker", args...)
357-
output, err := cmd.CombinedOutput()
358-
if err != nil {
359-
return fmt.Errorf("error running docker compose up: %v\nOutput: %s", err, string(output))
360-
}
361-
return nil
373+
return m.up(projectName, paths, "--force-recreate")
362374
}
363375

364376
func (m *Manager) Build(projectName string) error {
365377
paths, err := m.getConfigPaths(projectName)
366378
if err != nil {
367379
return fmt.Errorf("failed to build project: %v", err)
368380
}
381+
return m.up(projectName, paths, "--build")
382+
}
369383

384+
func (m *Manager) up(projectName string, paths []string, extraFlag string) error {
370385
args := []string{"compose", "-p", projectName}
371386
for _, path := range paths {
372387
args = append(args, "-f", path)
373388
}
374-
args = append(args, "up", "-d", "--build")
389+
args = append(args, "up", "-d", extraFlag)
375390

376-
cmd := exec.Command("docker", args...)
391+
workDir := ""
392+
if len(paths) > 0 {
393+
workDir = filepath.Dir(paths[0])
394+
}
395+
396+
cmd := m.dockerCmd(args, workDir)
377397
output, err := cmd.CombinedOutput()
378398
if err != nil {
379-
return fmt.Errorf("error running docker compose up --build: %v\nOutput: %s", err, string(output))
399+
return fmt.Errorf("error running docker compose up: %v\nOutput: %s", err, string(output))
380400
}
381401
return nil
382402
}

0 commit comments

Comments
 (0)