All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			See #499
		
			
				
	
	
		
			145 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			145 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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
 | |
| }
 |