// Package compose runs docker compose the way a stack was deployed — which is not always // simply "on this host". A stack deployed by an orchestrator running inside its own // container records paths in that container's filesystem, so compose has to run in there; // a stack deployed to another machine over tcp needs DOCKER_HOST pointed at it. Both the // console and the scheduled updates it writes must invoke it identically, or a redeploy // from one would not match the other's labels. package compose import ( "context" "os" "os/exec" "path/filepath" ) // Spec is everything needed to run compose for one stack. type Spec struct { Project string // -p Files []string // -f, in order Via string // a container to run compose inside; "" is this host Remote string // a DOCKER_HOST; "" is this machine's Docker Tool string // "docker" or "podman" } // Cmd builds the command. args are compose's own, e.g. "pull" or "up", "-d". func Cmd(ctx context.Context, s Spec, args ...string) *exec.Cmd { tool := s.Tool if tool == "" { tool = "docker" } base := []string{"compose", "-p", s.Project} for _, f := range s.Files { base = append(base, "-f", f) } if s.Via != "" { pre := []string{"exec"} if s.Remote != "" { pre = append(pre, "-e", "DOCKER_HOST="+s.Remote) } return exec.CommandContext(ctx, tool, append(append(append(pre, s.Via, "docker"), base...), args...)...) } cmd := exec.CommandContext(ctx, tool, append(base, args...)...) if len(s.Files) > 0 { cmd.Dir = filepath.Dir(s.Files[0]) } if s.Remote != "" { cmd.Env = append(os.Environ(), "DOCKER_HOST="+s.Remote) } return cmd }