func RunAndParse(
	ctx context.Context,
	client *ssh.Client,
	log logrus.FieldLogger,
	cmd string,
	env map[string]string,
	parse OutputParseFn,
) (err error) {
	log = log.WithField("cmd", cmd)
	session, err := client.NewSession()
	if err != nil {
		return trace.Wrap(err)
	}
	defer session.Close()
	err = session.RequestPty(term, termH, termW, termModes)
	if err != nil {
		return trace.Wrap(err)
	}
	envStrings := []string{}
	for k, v := range env {
		envStrings = append(envStrings, fmt.Sprintf("%s=%s", k, v))
	}
	session.Stdin = new(bytes.Buffer)
	var stdout io.Reader
	if parse != nil {
		stdout, err = session.StdoutPipe()
		if err != nil {
			return trace.Wrap(err)
		}
	}
	stderr, err := session.StderrPipe()
	if err != nil {
		return trace.Wrap(err)
	}
	sessionCommand := fmt.Sprintf("%s %s", strings.Join(envStrings, " "), cmd)
	err = session.Start(sessionCommand)
	if err != nil {
		return trace.Wrap(err)
	}
	errCh := make(chan error, 2)
	expectErrors := 1
	if parse != nil {
		expectErrors = 2
		go func() {
			err := parse(bufio.NewReader(
				&readLogger{
					log: log.WithField("stream", "stdout"),
					r:   stdout,
				}))
			err = trace.Wrap(err)
			errCh <- err
		}()
	}
	go func() {
		r := bufio.NewReader(stderr)
		stderrLog := log.WithField("stream", "stderr")
		for {
			line, err := r.ReadString('\n')
			if line != "" {
				stderrLog.Debug(line)
			}
			if err != nil {
				return
			}
		}
	}()
	go func() {
		err := trace.Wrap(session.Wait())
		errCh <- err
	}()
	for i := 0; i < expectErrors; i++ {
		select {
		case <-ctx.Done():
			_ = session.Signal(ssh.SIGTERM)
			log.WithError(ctx.Err()).Debug("Context terminated, sent SIGTERM.")
			return trace.Wrap(ctx.Err())
		case err = <-errCh:
			switch sshError := trace.Unwrap(err).(type) {
			case *ssh.ExitError:
				err = trace.Wrap(sshError)
				log.WithError(err).Debugf("Command %v failed: %v", cmd, sshError.Error())
				return err
			case *ssh.ExitMissingError:
				err = trace.Wrap(sshError)
				log.WithError(err).Debug("Session aborted unexpectedly (node destroyed?).")
				return err
			}
			if err != nil {
				err = trace.Wrap(err)
				log.WithError(err).Debug("Unexpected error.")
				return err
			}
		}
	}
	return nil
}
RunAndParse runs remote SSH command cmd with environment variables set with env.
parse if set, will be provided the reader that consumes stdout of the command.
Returns *ssh.ExitError if the command has completed with a non-0 exit code,
*ssh.ExitMissingError if the other side has terminated the session without providing
the exit code and nil for no errors
package sshutils
import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"strings"
	"github.com/gravitational/trace"
	"github.com/sirupsen/logrus"
	"golang.org/x/crypto/ssh"
)
type OutputParseFn func(r *bufio.Reader) error
func Run(ctx context.Context, client *ssh.Client, log logrus.FieldLogger, cmd string, env map[string]string) error {
	err := RunAndParse(ctx, client, log, cmd, env, ParseDiscard)
	if err != nil {
		return trace.Wrap(err, "command %q failed", cmd)
	}
	return nil
}
const (
	term  = "unknown"
	termH = 40
	termW = 80
)
var (
	termModes = ssh.TerminalModes{
		ssh.ECHO:          0,      
		ssh.TTY_OP_ISPEED: 14400,  
		ssh.TTY_OP_OSPEED: 14400,  
	}
)