github.com/cloudfoundry-attic/ltc@v0.0.0-20151123212628-098adc7919fc/ssh/command_factory/command_factory.go (about)

     1  package command_factory
     2  
     3  import (
     4  	"fmt"
     5  	"runtime"
     6  	"strings"
     7  
     8  	"github.com/cloudfoundry-incubator/ltc/app_examiner"
     9  	config_package "github.com/cloudfoundry-incubator/ltc/config"
    10  	"github.com/cloudfoundry-incubator/ltc/exit_handler"
    11  	"github.com/cloudfoundry-incubator/ltc/exit_handler/exit_codes"
    12  	"github.com/cloudfoundry-incubator/ltc/terminal"
    13  	"github.com/codegangsta/cli"
    14  )
    15  
    16  type SSHCommandFactory struct {
    17  	config      *config_package.Config
    18  	ui          terminal.UI
    19  	exitHandler exit_handler.ExitHandler
    20  	appExaminer app_examiner.AppExaminer
    21  
    22  	secureShell SSH
    23  }
    24  
    25  //go:generate counterfeiter -o mocks/fake_ssh.go . SSH
    26  type SSH interface {
    27  	Connect(appName string, instanceIndex int, config *config_package.Config) error
    28  	Forward(localAddress, remoteAddress string) error
    29  	Shell(command string, ptyDespired bool) error
    30  }
    31  
    32  func NewSSHCommandFactory(config *config_package.Config, ui terminal.UI, exitHandler exit_handler.ExitHandler, appExaminer app_examiner.AppExaminer, secureShell SSH) *SSHCommandFactory {
    33  	return &SSHCommandFactory{config, ui, exitHandler, appExaminer, secureShell}
    34  }
    35  
    36  func (f *SSHCommandFactory) MakeSSHCommand() cli.Command {
    37  	helpText := "ltc ssh <app-name> [[--] optional command with args]\n\n   If a command is specified, no interactive shell will be provided.\n   A \"--\" token should be provided to avoid parsing of command flags.\n"
    38  	if runtime.GOOS == "windows" {
    39  		helpText = helpText + "\n   ltc ssh requires a pseudo-terminal and will not function properly from the default Windows command shells (Cmd, Powershell). We recommend using ltc ssh non-interactively: ltc ssh -- ps auxw\n"
    40  	}
    41  
    42  	return cli.Command{
    43  		Name:        "ssh",
    44  		Usage:       "Connects to a running app",
    45  		Description: helpText,
    46  		Action:      f.ssh,
    47  		Flags: []cli.Flag{
    48  			cli.IntFlag{
    49  				Name:  "index, i",
    50  				Usage: "Connects to specified instance index",
    51  				Value: 0,
    52  			},
    53  			cli.StringFlag{
    54  				Name:  "L",
    55  				Usage: "Listens on specified local address/port and forwards connections to specified remote address/port\n     \te.g. ltc ssh <app-name> -L [localhost:]<local-port>:<remote-host>:<remote-port>",
    56  			},
    57  			cli.BoolFlag{
    58  				Name:  "N",
    59  				Usage: "Disables the interactive shell when forwarding connections with -L",
    60  			},
    61  			cli.BoolFlag{
    62  				Name:  "T",
    63  				Usage: "Disables pseudo-tty allocation",
    64  			},
    65  			cli.BoolFlag{
    66  				Name:  "t",
    67  				Usage: "Enables pseudo-tty allocation",
    68  			},
    69  		},
    70  	}
    71  }
    72  
    73  func (f *SSHCommandFactory) ssh(context *cli.Context) {
    74  	instanceIndex := context.Int("index")
    75  	localForward := context.String("L")
    76  	noShell := context.Bool("N")
    77  	forceNoPTY := context.Bool("T")
    78  
    79  	appName := context.Args().First()
    80  
    81  	if appName == "" {
    82  		f.ui.SayIncorrectUsage("Please input a valid <app-name>")
    83  		f.exitHandler.Exit(exit_codes.InvalidSyntax)
    84  		return
    85  	}
    86  
    87  	appInfo, err := f.appExaminer.AppStatus(appName)
    88  	if err != nil {
    89  		f.ui.SayLine("App " + appName + " not found.")
    90  		f.exitHandler.Exit(exit_codes.CommandFailed)
    91  		return
    92  	}
    93  	if instanceIndex < 0 || instanceIndex >= appInfo.ActualRunningInstances {
    94  		f.ui.SayLine("Instance %s/%d does not exist.", appName, instanceIndex)
    95  		f.exitHandler.Exit(exit_codes.CommandFailed)
    96  		return
    97  	}
    98  
    99  	if err := f.secureShell.Connect(appName, instanceIndex, f.config); err != nil {
   100  		f.ui.SayLine("Error connecting to %s/%d: %s", appName, instanceIndex, err)
   101  		f.exitHandler.Exit(exit_codes.CommandFailed)
   102  		return
   103  	}
   104  
   105  	command := ""
   106  	if len(context.Args()) > 1 {
   107  		start := 1
   108  		if context.Args().Get(1) == "--" {
   109  			start = 2
   110  		}
   111  		command = strings.Join(context.Args()[start:len(context.Args())], " ")
   112  	}
   113  
   114  	if localForward != "" && noShell {
   115  		f.forward(localForward, appName, instanceIndex)
   116  	} else if localForward != "" {
   117  		go f.forward(localForward, appName, instanceIndex)
   118  		f.shell(command, !forceNoPTY, appName, instanceIndex)
   119  	} else {
   120  		f.shell(command, !forceNoPTY, appName, instanceIndex)
   121  	}
   122  }
   123  
   124  func (f *SSHCommandFactory) forward(localForward, appName string, instanceIndex int) {
   125  	var localHost, localPort, remoteHost, remotePort string
   126  
   127  	parts := strings.Split(localForward, ":")
   128  
   129  	switch len(parts) {
   130  	case 3:
   131  		localHost, localPort, remoteHost, remotePort = "localhost", parts[0], parts[1], parts[2]
   132  	case 4:
   133  		localHost, localPort, remoteHost, remotePort = parts[0], parts[1], parts[2], parts[3]
   134  	default:
   135  		f.ui.SayIncorrectUsage("-L expects [localhost:]<local-port>:<remote-host>:<remote-port>")
   136  		f.exitHandler.Exit(exit_codes.InvalidSyntax)
   137  		return
   138  	}
   139  
   140  	if localPort == "0" {
   141  		f.ui.SayIncorrectUsage("-L expects [localhost:]<local-port>:<remote-host>:<remote-port>")
   142  		f.exitHandler.Exit(exit_codes.InvalidSyntax)
   143  		return
   144  	}
   145  
   146  	localAddr := fmt.Sprintf("%s:%s", localHost, localPort)
   147  	remoteAddr := fmt.Sprintf("%s:%s", remoteHost, remotePort)
   148  
   149  	f.ui.SayLine("Forwarding %s to %s via %s/%d at %s", localAddr, remoteAddr, appName, instanceIndex, f.config.Target())
   150  
   151  	if err := f.secureShell.Forward(localAddr, remoteAddr); err != nil {
   152  		f.ui.SayLine("Error connecting to %s/%d: %s", appName, instanceIndex, err)
   153  		f.exitHandler.Exit(exit_codes.CommandFailed)
   154  		return
   155  	}
   156  }
   157  
   158  func (f *SSHCommandFactory) shell(command string, ptyDesired bool, appName string, instanceIndex int) {
   159  	if command == "" {
   160  		f.ui.SayLine("Connecting to %s/%d at %s", appName, instanceIndex, f.config.Target())
   161  	}
   162  
   163  	if err := f.secureShell.Shell(command, ptyDesired); err != nil {
   164  		f.ui.SayLine("Error connecting to %s/%d: %s", appName, instanceIndex, err)
   165  		f.exitHandler.Exit(exit_codes.CommandFailed)
   166  		return
   167  	}
   168  }