github.com/cloudfoundry-attic/ltc@v0.0.0-20151123212628-098adc7919fc/ssh/command_factory/command_factory_test.go (about) 1 package command_factory_test 2 3 import ( 4 "errors" 5 6 . "github.com/onsi/ginkgo" 7 . "github.com/onsi/gomega" 8 "github.com/onsi/gomega/gbytes" 9 10 "github.com/cloudfoundry-incubator/ltc/app_examiner" 11 "github.com/cloudfoundry-incubator/ltc/app_examiner/fake_app_examiner" 12 config_package "github.com/cloudfoundry-incubator/ltc/config" 13 "github.com/cloudfoundry-incubator/ltc/exit_handler/exit_codes" 14 "github.com/cloudfoundry-incubator/ltc/exit_handler/fake_exit_handler" 15 "github.com/cloudfoundry-incubator/ltc/ssh/command_factory" 16 "github.com/cloudfoundry-incubator/ltc/ssh/command_factory/mocks" 17 "github.com/cloudfoundry-incubator/ltc/terminal" 18 "github.com/cloudfoundry-incubator/ltc/test_helpers" 19 "github.com/codegangsta/cli" 20 ) 21 22 var _ = Describe("SSH CommandFactory", func() { 23 var ( 24 config *config_package.Config 25 outputBuffer *gbytes.Buffer 26 terminalUI terminal.UI 27 fakeExitHandler *fake_exit_handler.FakeExitHandler 28 fakeAppExaminer *fake_app_examiner.FakeAppExaminer 29 fakeSSH *mocks.FakeSSH 30 ) 31 32 BeforeEach(func() { 33 config = config_package.New(nil) 34 config.SetTarget("lattice.xip.io") 35 36 outputBuffer = gbytes.NewBuffer() 37 terminalUI = terminal.NewUI(nil, outputBuffer, nil) 38 fakeExitHandler = &fake_exit_handler.FakeExitHandler{} 39 fakeAppExaminer = &fake_app_examiner.FakeAppExaminer{} 40 fakeSSH = &mocks.FakeSSH{} 41 }) 42 43 Describe("SSHCommand", func() { 44 var sshCommand cli.Command 45 46 BeforeEach(func() { 47 commandFactory := command_factory.NewSSHCommandFactory(config, terminalUI, fakeExitHandler, fakeAppExaminer, fakeSSH) 48 sshCommand = commandFactory.MakeSSHCommand() 49 }) 50 51 Context("when connecting fails", func() { 52 It("should print an error", func() { 53 fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{ActualRunningInstances: 1}, nil) 54 fakeSSH.ConnectReturns(errors.New("connection failed")) 55 56 test_helpers.ExecuteCommandWithArgs(sshCommand, []string{"good-name"}) 57 58 Expect(outputBuffer).To(test_helpers.SayLine("Error connecting to good-name/0: connection failed")) 59 60 Expect(fakeSSH.ConnectCallCount()).To(Equal(1)) 61 Expect(fakeSSH.ForwardCallCount()).To(Equal(0)) 62 Expect(fakeSSH.ShellCallCount()).To(Equal(0)) 63 Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.CommandFailed})) 64 }) 65 }) 66 67 Describe("port forwarding", func() { 68 It("should forward a local port to a remote host and port", func() { 69 fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{ActualRunningInstances: 1}, nil) 70 71 test_helpers.ExecuteCommandWithArgs(sshCommand, []string{"app-name", "-N", "-L", "mrlocalhost:1234:remotehost:5678"}) 72 73 Expect(outputBuffer).To(test_helpers.SayLine("Forwarding mrlocalhost:1234 to remotehost:5678 via app-name/0 at %s", config.Target())) 74 75 Expect(fakeSSH.ConnectCallCount()).To(Equal(1)) 76 appName, instanceIndex, actualConfig := fakeSSH.ConnectArgsForCall(0) 77 Expect(appName).To(Equal("app-name")) 78 Expect(instanceIndex).To(Equal(0)) 79 Expect(actualConfig).To(Equal(config)) 80 81 Expect(fakeSSH.ForwardCallCount()).To(Equal(1)) 82 localAddr, remoteAddr := fakeSSH.ForwardArgsForCall(0) 83 Expect(localAddr).To(Equal("mrlocalhost:1234")) 84 Expect(remoteAddr).To(Equal("remotehost:5678")) 85 86 Expect(fakeAppExaminer.AppStatusCallCount()).To(Equal(1)) 87 Expect(fakeAppExaminer.AppStatusArgsForCall(0)).To(Equal("app-name")) 88 }) 89 90 Context("when the local host address is not specified", func() { 91 It("should default to localhost", func() { 92 fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{ActualRunningInstances: 1}, nil) 93 94 test_helpers.ExecuteCommandWithArgs(sshCommand, []string{"app-name", "-N", "-L", "1234:remotehost:5678"}) 95 96 Expect(outputBuffer).To(test_helpers.SayLine("Forwarding localhost:1234 to remotehost:5678 via app-name/0 at %s", config.Target())) 97 98 Expect(fakeSSH.ForwardCallCount()).To(Equal(1)) 99 localAddr, _ := fakeSSH.ForwardArgsForCall(0) 100 Expect(localAddr).To(Equal("localhost:1234")) 101 }) 102 }) 103 104 Context("when forwarding fails", func() { 105 BeforeEach(func() { 106 fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{ActualRunningInstances: 1}, nil) 107 }) 108 109 It("should print an error", func() { 110 fakeSSH.ForwardReturns(errors.New("forwarding failed")) 111 112 test_helpers.ExecuteCommandWithArgs(sshCommand, []string{"good-name", "-N", "-L", "mrlocalhost:1234:remotehost:5678"}) 113 114 Expect(outputBuffer).To(test_helpers.SayLine("Forwarding mrlocalhost:1234 to remotehost:5678 via good-name/0 at %s", config.Target())) 115 Expect(outputBuffer).To(test_helpers.SayLine("Error connecting to good-name/0: forwarding failed")) 116 117 Expect(fakeSSH.ForwardCallCount()).To(Equal(1)) 118 Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.CommandFailed})) 119 }) 120 }) 121 122 Context("invalid syntax", func() { 123 BeforeEach(func() { 124 fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{ActualRunningInstances: 1}, nil) 125 }) 126 127 It("should reject malformed local forward specs", func() { 128 test_helpers.ExecuteCommandWithArgs(sshCommand, []string{"app-name", "-N", "-L", "9999:localhost:1234:remotehost:5678"}) 129 Expect(outputBuffer).To(test_helpers.SayLine("Incorrect Usage: -L expects [localhost:]<local-port>:<remote-host>:<remote-port>")) 130 131 test_helpers.ExecuteCommandWithArgs(sshCommand, []string{"app-name", "-N", "-L", "remotehost:5678"}) 132 Expect(outputBuffer).To(test_helpers.SayLine("Incorrect Usage: -L expects [localhost:]<local-port>:<remote-host>:<remote-port>")) 133 134 test_helpers.ExecuteCommandWithArgs(sshCommand, []string{"app-name", "-N", "-L", "5678"}) 135 Expect(outputBuffer).To(test_helpers.SayLine("Incorrect Usage: -L expects [localhost:]<local-port>:<remote-host>:<remote-port>")) 136 137 Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.InvalidSyntax, exit_codes.InvalidSyntax, exit_codes.InvalidSyntax})) 138 139 Expect(fakeSSH.ForwardCallCount()).To(Equal(0)) 140 }) 141 142 Context("when the local host port is set to zero", func() { 143 It("should print incorrect usage", func() { 144 test_helpers.ExecuteCommandWithArgs(sshCommand, []string{"app-name", "-N", "-L", "0:localhost:5678"}) 145 Expect(outputBuffer).To(test_helpers.SayLine("Incorrect Usage: -L expects [localhost:]<local-port>:<remote-host>:<remote-port>")) 146 147 Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.InvalidSyntax})) 148 149 Expect(fakeSSH.ForwardCallCount()).To(Equal(0)) 150 }) 151 }) 152 }) 153 }) 154 155 Describe("interactive shell", func() { 156 It("should ssh to instance 0 given an app name", func() { 157 fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{ActualRunningInstances: 1}, nil) 158 159 test_helpers.ExecuteCommandWithArgs(sshCommand, []string{"app-name"}) 160 161 Expect(outputBuffer).To(test_helpers.SayLine("Connecting to app-name/0 at %s", config.Target())) 162 163 Expect(fakeSSH.ConnectCallCount()).To(Equal(1)) 164 appName, instanceIndex, actualConfig := fakeSSH.ConnectArgsForCall(0) 165 Expect(appName).To(Equal("app-name")) 166 Expect(instanceIndex).To(Equal(0)) 167 Expect(actualConfig).To(Equal(config)) 168 169 Expect(fakeSSH.ShellCallCount()).To(Equal(1)) 170 command, ptyDesired := fakeSSH.ShellArgsForCall(0) 171 Expect(command).To(BeEmpty()) 172 Expect(ptyDesired).To(BeTrue()) 173 174 Expect(fakeAppExaminer.AppStatusCallCount()).To(Equal(1)) 175 Expect(fakeAppExaminer.AppStatusArgsForCall(0)).To(Equal("app-name")) 176 }) 177 178 It("should ssh to instance index specified", func() { 179 fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{ActualRunningInstances: 3}, nil) 180 181 test_helpers.ExecuteCommandWithArgs(sshCommand, []string{"--index", "2", "app-name"}) 182 183 Expect(outputBuffer).To(test_helpers.SayLine("Connecting to app-name/2 at %s", config.Target())) 184 185 Expect(fakeSSH.ConnectCallCount()).To(Equal(1)) 186 appName, instanceIndex, actualConfig := fakeSSH.ConnectArgsForCall(0) 187 Expect(appName).To(Equal("app-name")) 188 Expect(instanceIndex).To(Equal(2)) 189 Expect(actualConfig).To(Equal(config)) 190 191 Expect(fakeSSH.ShellCallCount()).To(Equal(1)) 192 command, _ := fakeSSH.ShellArgsForCall(0) 193 Expect(command).To(BeEmpty()) 194 }) 195 196 It("should disable pty when requested", func() { 197 fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{ActualRunningInstances: 3}, nil) 198 199 test_helpers.ExecuteCommandWithArgs(sshCommand, []string{"-T", "app-name", "/bin/ls"}) 200 201 Expect(fakeSSH.ShellCallCount()).To(Equal(1)) 202 _, ptyDesired := fakeSSH.ShellArgsForCall(0) 203 Expect(ptyDesired).To(BeFalse()) 204 }) 205 206 It("should enable pty when requested", func() { 207 fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{ActualRunningInstances: 3}, nil) 208 209 test_helpers.ExecuteCommandWithArgs(sshCommand, []string{"-t", "app-name", "/bin/ls"}) 210 211 Expect(fakeSSH.ShellCallCount()).To(Equal(1)) 212 _, ptyDesired := fakeSSH.ShellArgsForCall(0) 213 Expect(ptyDesired).To(BeTrue()) 214 }) 215 216 Context("when a command is provided", func() { 217 It("should run a command remotely instead of the login shell", func() { 218 fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{ActualRunningInstances: 1}, nil) 219 220 doneChan := test_helpers.AsyncExecuteCommandWithArgs(sshCommand, []string{"app-name", "echo", "1", "2", "3"}) 221 222 Eventually(doneChan, 3).Should(BeClosed()) 223 Expect(outputBuffer).NotTo(test_helpers.Say("Connecting to app-name")) 224 225 Expect(fakeSSH.ShellCallCount()).To(Equal(1)) 226 command, _ := fakeSSH.ShellArgsForCall(0) 227 Expect(command).To(Equal("echo 1 2 3")) 228 }) 229 230 It("should support -- delimiter for args", func() { 231 fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{ActualRunningInstances: 1}, nil) 232 233 test_helpers.ExecuteCommandWithArgs(sshCommand, []string{"app-name", "--", "/bin/ls", "-l"}) 234 235 Expect(fakeSSH.ShellCallCount()).To(Equal(1)) 236 command, _ := fakeSSH.ShellArgsForCall(0) 237 Expect(command).To(Equal("/bin/ls -l")) 238 }) 239 }) 240 241 Context("when opening a shell fails", func() { 242 It("should print an error", func() { 243 fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{ActualRunningInstances: 1}, nil) 244 fakeSSH.ShellReturns(errors.New("shell failed")) 245 246 test_helpers.ExecuteCommandWithArgs(sshCommand, []string{"good-name"}) 247 248 Expect(outputBuffer).To(test_helpers.SayLine("Connecting to good-name/0 at %s", config.Target())) 249 Expect(outputBuffer).To(test_helpers.SayLine("Error connecting to good-name/0: shell failed")) 250 251 Expect(fakeSSH.ShellCallCount()).To(Equal(1)) 252 Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.CommandFailed})) 253 }) 254 }) 255 }) 256 257 Context("port forwarding with an interactive shell", func() { 258 It("should forward a local port to a remote host and port", func() { 259 fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{ActualRunningInstances: 1}, nil) 260 261 test_helpers.ExecuteCommandWithArgs(sshCommand, []string{"app-name", "-L", "mrlocalhost:1234:remotehost:5678"}) 262 263 Eventually(fakeSSH.ForwardCallCount).Should(Equal(1)) 264 localAddr, remoteAddr := fakeSSH.ForwardArgsForCall(0) 265 Expect(localAddr).To(Equal("mrlocalhost:1234")) 266 Expect(remoteAddr).To(Equal("remotehost:5678")) 267 268 Expect(fakeAppExaminer.AppStatusCallCount()).To(Equal(1)) 269 Expect(fakeAppExaminer.AppStatusArgsForCall(0)).To(Equal("app-name")) 270 271 Expect(outputBuffer).To(test_helpers.SayLine("Connecting to app-name/0 at %s", config.Target())) 272 Expect(outputBuffer).To(test_helpers.SayLine("Forwarding mrlocalhost:1234 to remotehost:5678 via app-name/0 at %s", config.Target())) 273 274 Expect(fakeSSH.ConnectCallCount()).To(Equal(1)) 275 appName, instanceIndex, actualConfig := fakeSSH.ConnectArgsForCall(0) 276 Expect(appName).To(Equal("app-name")) 277 Expect(instanceIndex).To(Equal(0)) 278 Expect(actualConfig).To(Equal(config)) 279 280 Expect(fakeSSH.ShellCallCount()).To(Equal(1)) 281 command, _ := fakeSSH.ShellArgsForCall(0) 282 Expect(command).To(BeEmpty()) 283 }) 284 }) 285 286 Context("when not given an app name", func() { 287 It("prints an error", func() { 288 test_helpers.ExecuteCommandWithArgs(sshCommand, []string{}) 289 290 Expect(outputBuffer).To(test_helpers.SayLine("Please input a valid <app-name>")) 291 292 Expect(fakeSSH.ConnectCallCount()).To(Equal(0)) 293 Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.InvalidSyntax})) 294 }) 295 }) 296 297 Context("when given a non-existent app name", func() { 298 It("prints an error", func() { 299 fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{}, errors.New("no app")) 300 301 test_helpers.ExecuteCommandWithArgs(sshCommand, []string{"bad-app"}) 302 303 Expect(outputBuffer).To(test_helpers.SayLine("App bad-app not found.")) 304 305 Expect(fakeSSH.ConnectCallCount()).To(Equal(0)) 306 Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.CommandFailed})) 307 }) 308 }) 309 310 Context("when given an invalid instance index", func() { 311 It("prints an error", func() { 312 fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{ActualRunningInstances: 1}, nil) 313 314 test_helpers.ExecuteCommandWithArgs(sshCommand, []string{"good-app", "-i", "1"}) 315 316 Expect(outputBuffer).To(test_helpers.SayLine("Instance good-app/1 does not exist.")) 317 318 Expect(fakeSSH.ConnectCallCount()).To(Equal(0)) 319 Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.CommandFailed})) 320 }) 321 }) 322 }) 323 })