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 }