package internal import ( "bufio" "context" "fmt" "io/ioutil" "os/exec" "strings" appPkg "coopcloud.tech/abra/pkg/app" containerPkg "coopcloud.tech/abra/pkg/container" "coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/upstream/container" "github.com/docker/cli/cli/command" containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" dockerClient "github.com/docker/docker/client" "github.com/docker/docker/pkg/archive" ) // RunCmdRemote executes an abra.sh command in the target service func RunCmdRemote( cl *dockerClient.Client, app appPkg.App, disableTTY bool, abraSh, serviceName, cmdName, cmdArgs, remoteUser string) error { filters := filters.NewArgs() filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(), serviceName)) targetContainer, err := containerPkg.GetContainer(context.Background(), cl, filters, false) if err != nil { return err } log.Debugf("retrieved %s as target container on %s", formatter.ShortenID(targetContainer.ID), app.Server) toTarOpts := &archive.TarOptions{NoOverwriteDirNonDir: true, Compression: archive.Gzip} content, err := archive.TarWithOptions(abraSh, toTarOpts) if err != nil { return err } copyOpts := containertypes.CopyToContainerOptions{AllowOverwriteDirWithFile: false, CopyUIDGID: false} if err := cl.CopyToContainer(context.Background(), targetContainer.ID, "/tmp", content, copyOpts); err != nil { return err } // FIXME: avoid instantiating a new CLI dcli, err := command.NewDockerCli() if err != nil { return err } shell := "/bin/bash" findShell := []string{"test", "-e", shell} execCreateOpts := containertypes.ExecOptions{ AttachStderr: true, AttachStdin: true, AttachStdout: true, Cmd: findShell, Detach: false, Tty: false, } if _, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts); err != nil { log.Infof("%s does not exist for %s, use /bin/sh as fallback", shell, app.Name) shell = "/bin/sh" } var cmd []string if cmdArgs != "" { cmd = []string{shell, "-c", fmt.Sprintf("TARGET=%s; APP_NAME=%s; STACK_NAME=%s; . /tmp/abra.sh; %s %s", serviceName, app.Name, app.StackName(), cmdName, cmdArgs)} } else { cmd = []string{shell, "-c", fmt.Sprintf("TARGET=%s; APP_NAME=%s; STACK_NAME=%s; . /tmp/abra.sh; %s", serviceName, app.Name, app.StackName(), cmdName)} } log.Debugf("running command: %s", strings.Join(cmd, " ")) if remoteUser != "" { log.Debugf("running command with user %s", remoteUser) execCreateOpts.User = remoteUser } execCreateOpts.Cmd = cmd execCreateOpts.Tty = true if disableTTY { execCreateOpts.Tty = false log.Debugf("not requesting a remote TTY") } if _, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts); err != nil { return err } return nil } func EnsureCommand(abraSh, recipeName, execCmd string) error { bytes, err := ioutil.ReadFile(abraSh) if err != nil { return err } if !strings.Contains(string(bytes), execCmd) { return fmt.Errorf("%s doesn't have a %s function", recipeName, execCmd) } return nil } // RunCmd runs a shell command and streams stdout/stderr in real-time. func RunCmd(cmd *exec.Cmd) error { r, err := cmd.StdoutPipe() if err != nil { return err } cmd.Stderr = cmd.Stdout done := make(chan struct{}) scanner := bufio.NewScanner(r) go func() { for scanner.Scan() { line := scanner.Text() fmt.Println(line) } done <- struct{}{} }() if err := cmd.Start(); err != nil { return err } <-done if err := cmd.Wait(); err != nil { return err } return nil }