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  })