134 lines
3.9 KiB
Go
134 lines
3.9 KiB
Go
package docker
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os/exec"
|
|
"strings"
|
|
)
|
|
|
|
// ComposeClient — pembungkus CLI compose (strategi hybrid PRD §3: operasi
|
|
// compose dipercayakan ke CLI, bukan reimplementasi API).
|
|
type ComposeClient struct {
|
|
cmd []string // mis. ["docker","compose"] atau ["docker-compose"]
|
|
}
|
|
|
|
// NewCompose — spec "auto" memilih perintah sesuai engine & ketersediaan CLI;
|
|
// spec eksplisit (mis. "docker compose") selalu menang.
|
|
func NewCompose(spec, engine string) *ComposeClient {
|
|
return &ComposeClient{cmd: pickCompose(spec, engine, exec.LookPath)}
|
|
}
|
|
|
|
// pickCompose — dipisah agar lookPath dapat di-stub di unit test.
|
|
func pickCompose(spec, engine string, lookPath func(string) (string, error)) []string {
|
|
if spec != "" && spec != "auto" {
|
|
if f := strings.Fields(spec); len(f) > 0 {
|
|
return f
|
|
}
|
|
}
|
|
orders := [][]string{
|
|
{"docker", "compose"},
|
|
{"docker-compose"},
|
|
{"podman", "compose"},
|
|
{"podman-compose"},
|
|
}
|
|
if engine == "podman" {
|
|
orders = [][]string{
|
|
{"podman", "compose"},
|
|
{"podman-compose"},
|
|
{"docker", "compose"},
|
|
{"docker-compose"},
|
|
}
|
|
}
|
|
for _, o := range orders {
|
|
if _, err := lookPath(o[0]); err == nil {
|
|
return o
|
|
}
|
|
}
|
|
return []string{"docker", "compose"} // tidak ada → error jelas saat dijalankan
|
|
}
|
|
|
|
// Command — perintah compose yang benar-benar dipakai (untuk doctor/log).
|
|
func (c *ComposeClient) Command() string { return strings.Join(c.cmd, " ") }
|
|
|
|
// ComposeProject — entri `docker compose ls --all --format json`
|
|
// (format terverifikasi: {"Name","Status":"running(N)","ConfigFiles":"a,b"}).
|
|
type ComposeProject struct {
|
|
Name string `json:"Name"`
|
|
Status string `json:"Status"`
|
|
ConfigFiles string `json:"ConfigFiles"`
|
|
}
|
|
|
|
// ConfigFileList — ConfigFiles dipecah per koma.
|
|
func (p ComposeProject) ConfigFileList() []string {
|
|
var out []string
|
|
for _, f := range strings.Split(p.ConfigFiles, ",") {
|
|
if f = strings.TrimSpace(f); f != "" {
|
|
out = append(out, f)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// List — daftar semua project compose (termasuk yang berhenti).
|
|
func (c *ComposeClient) List(ctx context.Context) ([]ComposeProject, error) {
|
|
out, err := c.Run(ctx, "", "ls", "--all", "--format", "json")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var projects []ComposeProject
|
|
if err := json.Unmarshal([]byte(strings.TrimSpace(out)), &projects); err != nil {
|
|
return nil, fmt.Errorf("compose ls: parse json: %w", err)
|
|
}
|
|
return projects, nil
|
|
}
|
|
|
|
// Version — versi compose ringkas (mis. "5.6.0").
|
|
func (c *ComposeClient) Version(ctx context.Context) (string, error) {
|
|
out, err := c.Run(ctx, "", "version", "--short")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return strings.TrimSpace(out), nil
|
|
}
|
|
|
|
// --- operasi stack (dipakai manager & CLI companion) ---
|
|
|
|
func (c *ComposeClient) Up(ctx context.Context, dir string) (string, error) {
|
|
return c.Run(ctx, dir, "up", "-d", "--remove-orphans")
|
|
}
|
|
func (c *ComposeClient) Down(ctx context.Context, dir string) (string, error) {
|
|
return c.Run(ctx, dir, "down")
|
|
}
|
|
func (c *ComposeClient) Stop(ctx context.Context, dir string) (string, error) {
|
|
return c.Run(ctx, dir, "stop")
|
|
}
|
|
func (c *ComposeClient) Restart(ctx context.Context, dir string) (string, error) {
|
|
return c.Run(ctx, dir, "restart")
|
|
}
|
|
func (c *ComposeClient) Pull(ctx context.Context, dir string) (string, error) {
|
|
return c.Run(ctx, dir, "pull")
|
|
}
|
|
|
|
// Run — jalankan subperintah compose; dir = folder stack (working dir project).
|
|
func (c *ComposeClient) Run(ctx context.Context, dir string, args ...string) (string, error) {
|
|
full := append(append([]string{}, c.cmd[1:]...), args...)
|
|
cmd := exec.CommandContext(ctx, c.cmd[0], full...)
|
|
if dir != "" {
|
|
cmd.Dir = dir
|
|
}
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
verb := "?"
|
|
if len(args) > 0 {
|
|
verb = args[0]
|
|
}
|
|
if ee, ok := err.(*exec.ExitError); ok {
|
|
return string(out), fmt.Errorf("compose %s: exit %d", verb, ee.ExitCode())
|
|
}
|
|
return string(out), err
|
|
}
|
|
return string(out), nil
|
|
}
|