예제 #1
0
func (*AppDialer) Dial(appName string, instanceIndex int, config *config_package.Config) (Client, error) {
	diegoSSHUser := fmt.Sprintf("diego:%s/%d", appName, instanceIndex)
	address := fmt.Sprintf("%s:2222", config.Target())

	client, err := sshapi.New(diegoSSHUser, config.Username(), config.Password(), address)
	if err != nil {
		return nil, err
	}

	return client, nil
}
		fakeExitHandler = &fake_exit_handler.FakeExitHandler{}
		fakeSecureShell = &fake_secure_shell.FakeSecureShell{}
	})

	Describe("SSHCommand", func() {
		var sshCommand cli.Command

		BeforeEach(func() {
			commandFactory := command_factory.NewSSHCommandFactory(config, terminalUI, fakeExitHandler, fakeSecureShell)
			sshCommand = commandFactory.MakeSSHCommand()
		})

		It("should ssh to instance 0 given an app name", func() {
			test_helpers.ExecuteCommandWithArgs(sshCommand, []string{"app-name"})

			Expect(outputBuffer).To(test_helpers.SayLine("Connecting to app-name/0 at %s", config.Target()))

			Expect(fakeSecureShell.ConnectToShellCallCount()).To(Equal(1))
			appName, instanceIndex, command, actualConfig := fakeSecureShell.ConnectToShellArgsForCall(0)
			Expect(appName).To(Equal("app-name"))
			Expect(instanceIndex).To(Equal(0))
			Expect(command).To(BeEmpty())
			Expect(actualConfig).To(Equal(config))
		})

		It("should ssh to instance index specified", func() {
			test_helpers.ExecuteCommandWithArgs(sshCommand, []string{"--instance", "2", "app-name"})

			Expect(outputBuffer).To(test_helpers.SayLine("Connecting to app-name/2 at %s", config.Target()))

			Expect(fakeSecureShell.ConnectToShellCallCount()).To(Equal(1))
예제 #3
0
func cliCommands(ltcConfigRoot string, exitHandler exit_handler.ExitHandler, config *config.Config, logger lager.Logger, targetVerifier target_verifier.TargetVerifier, ui terminal.UI) []cli.Command {

	receptorClient := receptor.NewClient(config.Receptor())
	noaaConsumer := noaa.NewConsumer(LoggregatorUrl(config.Loggregator()), nil, nil)
	appRunner := app_runner.New(receptorClient, config.Target())

	clock := clock.NewClock()

	logReader := logs.NewLogReader(noaaConsumer)
	tailedLogsOutputter := console_tailed_logs_outputter.NewConsoleTailedLogsOutputter(ui, logReader)

	taskExaminer := task_examiner.New(receptorClient)
	taskExaminerCommandFactory := task_examiner_command_factory.NewTaskExaminerCommandFactory(taskExaminer, ui, exitHandler)

	taskRunner := task_runner.New(receptorClient, taskExaminer)
	taskRunnerCommandFactory := task_runner_command_factory.NewTaskRunnerCommandFactory(taskRunner, ui, exitHandler)

	appExaminer := app_examiner.New(receptorClient, app_examiner.NewNoaaConsumer(noaaConsumer))
	graphicalVisualizer := graphical.NewGraphicalVisualizer(appExaminer)
	appExaminerCommandFactory := app_examiner_command_factory.NewAppExaminerCommandFactory(appExaminer, ui, clock, exitHandler, graphicalVisualizer, taskExaminer, config.Target())

	appRunnerCommandFactoryConfig := app_runner_command_factory.AppRunnerCommandFactoryConfig{
		AppRunner:           appRunner,
		AppExaminer:         appExaminer,
		UI:                  ui,
		Domain:              config.Target(),
		Env:                 os.Environ(),
		Clock:               clock,
		Logger:              logger,
		TailedLogsOutputter: tailedLogsOutputter,
		ExitHandler:         exitHandler,
	}

	appRunnerCommandFactory := app_runner_command_factory.NewAppRunnerCommandFactory(appRunnerCommandFactoryConfig)

	dockerRunnerCommandFactoryConfig := docker_runner_command_factory.DockerRunnerCommandFactoryConfig{
		AppRunner:             appRunner,
		AppExaminer:           appExaminer,
		UI:                    ui,
		Domain:                config.Target(),
		Env:                   os.Environ(),
		Clock:                 clock,
		Logger:                logger,
		ExitHandler:           exitHandler,
		TailedLogsOutputter:   tailedLogsOutputter,
		DockerMetadataFetcher: docker_metadata_fetcher.New(docker_metadata_fetcher.NewDockerSessionFactory()),
	}
	dockerRunnerCommandFactory := docker_runner_command_factory.NewDockerRunnerCommandFactory(dockerRunnerCommandFactoryConfig)

	logsCommandFactory := logs_command_factory.NewLogsCommandFactory(appExaminer, taskExaminer, ui, tailedLogsOutputter, exitHandler)

	clusterTestRunner := cluster_test.NewClusterTestRunner(config, ltcConfigRoot)
	clusterTestCommandFactory := cluster_test_command_factory.NewClusterTestCommandFactory(clusterTestRunner)

	blobStore := blob_store.New(config)
	blobStoreVerifier := blob_store.NewVerifier(config)

	dropletRunner := droplet_runner.New(appRunner, taskRunner, config, blobStore, appExaminer)
	cfIgnore := cf_ignore.New()
	zipper := &zipper_package.DropletArtifactZipper{}
	dropletRunnerCommandFactory := droplet_runner_command_factory.NewDropletRunnerCommandFactory(*appRunnerCommandFactory, blobStoreVerifier, taskExaminer, dropletRunner, cfIgnore, zipper, config)

	configCommandFactory := config_command_factory.NewConfigCommandFactory(config, ui, targetVerifier, blobStoreVerifier, exitHandler)

	helpCommand := cli.Command{
		Name:        "help",
		Aliases:     []string{"h"},
		Usage:       "Shows a list of commands or help for one command",
		Description: "ltc help",
		Action:      defaultAction,
	}

	return []cli.Command{
		appExaminerCommandFactory.MakeCellsCommand(),
		dockerRunnerCommandFactory.MakeCreateAppCommand(),
		appRunnerCommandFactory.MakeSubmitLrpCommand(),
		logsCommandFactory.MakeDebugLogsCommand(),
		appExaminerCommandFactory.MakeListAppCommand(),
		logsCommandFactory.MakeLogsCommand(),
		appRunnerCommandFactory.MakeRemoveAppCommand(),
		appRunnerCommandFactory.MakeScaleAppCommand(),
		appExaminerCommandFactory.MakeStatusCommand(),
		taskRunnerCommandFactory.MakeSubmitTaskCommand(),
		configCommandFactory.MakeTargetCommand(),
		taskExaminerCommandFactory.MakeTaskCommand(),
		taskRunnerCommandFactory.MakeDeleteTaskCommand(),
		taskRunnerCommandFactory.MakeCancelTaskCommand(),
		clusterTestCommandFactory.MakeClusterTestCommand(),
		appRunnerCommandFactory.MakeUpdateRoutesCommand(),
		appRunnerCommandFactory.MakeUpdateCommand(),
		appExaminerCommandFactory.MakeVisualizeCommand(),
		dropletRunnerCommandFactory.MakeBuildDropletCommand(),
		dropletRunnerCommandFactory.MakeListDropletsCommand(),
		dropletRunnerCommandFactory.MakeLaunchDropletCommand(),
		dropletRunnerCommandFactory.MakeRemoveDropletCommand(),
		dropletRunnerCommandFactory.MakeImportDropletCommand(),
		dropletRunnerCommandFactory.MakeExportDropletCommand(),
		helpCommand,
	}
}
			BeforeEach(func() {
				fakeTargetVerifier.VerifyTargetReturns(true, false, nil)
				fakeBlobStoreVerifier.VerifyReturns(true, nil)
				fakePasswordReader.PromptForPasswordReturns("testpassword")
			})

			It("prompts for credentials and stores them in the config", func() {
				doneChan := test_helpers.AsyncExecuteCommandWithArgs(targetCommand, []string{"myapi.com"})

				Eventually(outputBuffer).Should(test_helpers.Say("Username: "******"testusername\n"))

				Eventually(doneChan, 3).Should(BeClosed())

				Expect(config.Target()).To(Equal("myapi.com"))
				Expect(config.Receptor()).To(Equal("http://*****:*****@receptor.myapi.com"))
				Expect(outputBuffer).To(test_helpers.SayLine("API location set."))

				Expect(fakePasswordReader.PromptForPasswordCallCount()).To(Equal(1))
				Expect(fakePasswordReader.PromptForPasswordArgsForCall(0)).To(Equal("Password"))

				Expect(fakeTargetVerifier.VerifyTargetCallCount()).To(Equal(2))
				Expect(fakeTargetVerifier.VerifyTargetArgsForCall(0)).To(Equal("http://receptor.myapi.com"))
				Expect(fakeTargetVerifier.VerifyTargetArgsForCall(1)).To(Equal("http://*****:*****@receptor.myapi.com"))
			})

			Context("when provided receptor credentials are invalid", func() {
				It("does not save the config", func() {
					fakePasswordReader.PromptForPasswordReturns("some-invalid-password")
					doneChan := test_helpers.AsyncExecuteCommandWithArgs(targetCommand, []string{"newtarget.com"})
예제 #5
0
func (ss *SecureShell) ConnectToShell(appName string, instanceIndex int, command string, config *config_package.Config) error {
	diegoSSHUser := fmt.Sprintf("diego:%s/%d", appName, instanceIndex)
	address := fmt.Sprintf("%s:2222", config.Target())

	session, err := ss.Dialer.Dial(diegoSSHUser, config.Username(), config.Password(), address)
	if err != nil {
		return err
	}
	defer session.Close()

	sessionIn, err := session.StdinPipe()
	if err != nil {
		return err
	}

	sessionOut, err := session.StdoutPipe()
	if err != nil {
		return err
	}

	sessionErr, err := session.StderrPipe()
	if err != nil {
		return err
	}

	modes := ssh.TerminalModes{
		ssh.ECHO:          1,
		ssh.TTY_OP_ISPEED: 115200,
		ssh.TTY_OP_OSPEED: 115200,
	}

	width, height := ss.Term.GetWinsize(os.Stdout.Fd())

	terminalType := os.Getenv("TERM")
	if terminalType == "" {
		terminalType = "xterm"
	}

	if err := session.RequestPty(terminalType, height, width, modes); err != nil {
		return err
	}

	if state, err := ss.Term.SetRawTerminal(os.Stdin.Fd()); err == nil {
		defer ss.Term.RestoreTerminal(os.Stdin.Fd(), state)
	}

	go copyAndClose(nil, sessionIn, os.Stdin)
	go io.Copy(os.Stdout, sessionOut)
	go io.Copy(os.Stderr, sessionErr)

	resized := make(chan os.Signal, 16)
	signal.Notify(resized, syscall.SIGWINCH)
	defer func() {
		signal.Stop(resized)
		close(resized)
	}()
	go ss.resize(resized, session, os.Stdout.Fd(), width, height)

	keepaliveStopCh := make(chan struct{})
	defer close(keepaliveStopCh)

	go ss.keepalive(session, keepaliveStopCh)

	if command == "" {
		session.Shell()
		session.Wait()
	} else {
		session.Run(command)
	}

	return nil
}
예제 #6
0
func cliCommands(ltcConfigRoot string, exitHandler exit_handler.ExitHandler, config *config.Config, logger lager.Logger, receptorClientCreator receptor_client.Creator, targetVerifier target_verifier.TargetVerifier, ui terminal.UI) []cli.Command {
	receptorClient := receptorClientCreator.CreateReceptorClient(config.Receptor())
	noaaConsumer := noaa.NewConsumer(LoggregatorUrl(config.Loggregator()), nil, nil)
	appRunner := app_runner.New(receptorClient, config.Target(), &keygen_package.KeyGenerator{RandReader: rand.Reader})

	clock := clock.NewClock()

	logReader := logs.NewLogReader(noaaConsumer)
	tailedLogsOutputter := console_tailed_logs_outputter.NewConsoleTailedLogsOutputter(ui, logReader)

	taskExaminer := task_examiner.New(receptorClient)
	taskExaminerCommandFactory := task_examiner_command_factory.NewTaskExaminerCommandFactory(taskExaminer, ui, exitHandler)

	taskRunner := task_runner.New(receptorClient, taskExaminer)
	taskRunnerCommandFactory := task_runner_command_factory.NewTaskRunnerCommandFactory(taskRunner, ui, exitHandler)

	appExaminer := app_examiner.New(receptorClient, app_examiner.NewNoaaConsumer(noaaConsumer))
	graphicalVisualizer := graphical.NewGraphicalVisualizer(appExaminer)
	dockerTerminal := &app_examiner_command_factory.DockerTerminal{}
	appExaminerCommandFactory := app_examiner_command_factory.NewAppExaminerCommandFactory(appExaminer, ui, dockerTerminal, clock, exitHandler, graphicalVisualizer, taskExaminer, config.Target())

	appRunnerCommandFactoryConfig := app_runner_command_factory.AppRunnerCommandFactoryConfig{
		AppRunner:           appRunner,
		AppExaminer:         appExaminer,
		UI:                  ui,
		Domain:              config.Target(),
		Env:                 os.Environ(),
		Clock:               clock,
		Logger:              logger,
		TailedLogsOutputter: tailedLogsOutputter,
		ExitHandler:         exitHandler,
	}

	appRunnerCommandFactory := app_runner_command_factory.NewAppRunnerCommandFactory(appRunnerCommandFactoryConfig)

	dockerRunnerCommandFactoryConfig := docker_runner_command_factory.DockerRunnerCommandFactoryConfig{
		AppRunner:             appRunner,
		AppExaminer:           appExaminer,
		UI:                    ui,
		Domain:                config.Target(),
		Env:                   os.Environ(),
		Clock:                 clock,
		Logger:                logger,
		ExitHandler:           exitHandler,
		TailedLogsOutputter:   tailedLogsOutputter,
		DockerMetadataFetcher: docker_metadata_fetcher.New(docker_metadata_fetcher.NewDockerSessionFactory()),
	}
	dockerRunnerCommandFactory := docker_runner_command_factory.NewDockerRunnerCommandFactory(dockerRunnerCommandFactoryConfig)

	logsCommandFactory := logs_command_factory.NewLogsCommandFactory(appExaminer, taskExaminer, ui, tailedLogsOutputter, exitHandler)

	clusterTestRunner := cluster_test.NewClusterTestRunner(config, ltcConfigRoot)
	clusterTestCommandFactory := cluster_test_command_factory.NewClusterTestCommandFactory(clusterTestRunner)

	blobStore := blob_store.New(config)
	blobStoreVerifier := blob_store.BlobStoreVerifier{
		DAVBlobStoreVerifier: dav_blob_store.Verifier{},
		S3BlobStoreVerifier:  s3_blob_store.Verifier{},
	}

	httpProxyConfReader := &droplet_runner.HTTPProxyConfReader{
		URL: fmt.Sprintf("http://%s:8444/proxyconf.json", config.Target()),
	}
	dropletRunner := droplet_runner.New(appRunner, taskRunner, config, blobStore, appExaminer, httpProxyConfReader)
	cfIgnore := cf_ignore.New()
	zipper := &zipper_package.DropletArtifactZipper{}
	dropletRunnerCommandFactory := droplet_runner_command_factory.NewDropletRunnerCommandFactory(*appRunnerCommandFactory, blobStoreVerifier, taskExaminer, dropletRunner, cfIgnore, zipper, config)

	configCommandFactory := config_command_factory.NewConfigCommandFactory(config, ui, targetVerifier, blobStoreVerifier, exitHandler)

	secureDialer := &secure_shell.SecureDialer{DialFunc: ssh.Dial}
	secureTerm := &secure_shell.SecureTerm{}
	keepaliveInterval := 30 * time.Second
	secureShell := &secure_shell.SecureShell{
		Dialer: secureDialer,
		Term:   secureTerm,
		Clock:  clock,
		Ticker: clock.NewTicker(keepaliveInterval),
	}

	sshCommandFactory := ssh_command_factory.NewSSHCommandFactory(config, ui, exitHandler, appExaminer, secureShell)

	helpCommand := cli.Command{
		Name:        "help",
		Aliases:     []string{"h"},
		Usage:       "Shows a list of commands or help for one command",
		Description: "ltc help",
		Action:      defaultAction,
	}

	return []cli.Command{
		appExaminerCommandFactory.MakeCellsCommand(),
		dockerRunnerCommandFactory.MakeCreateAppCommand(),
		appRunnerCommandFactory.MakeSubmitLrpCommand(),
		logsCommandFactory.MakeDebugLogsCommand(),
		appExaminerCommandFactory.MakeListAppCommand(),
		logsCommandFactory.MakeLogsCommand(),
		appRunnerCommandFactory.MakeRemoveAppCommand(),
		appRunnerCommandFactory.MakeScaleAppCommand(),
		appExaminerCommandFactory.MakeStatusCommand(),
		taskRunnerCommandFactory.MakeSubmitTaskCommand(),
		configCommandFactory.MakeTargetCommand(),
		taskExaminerCommandFactory.MakeTaskCommand(),
		taskRunnerCommandFactory.MakeDeleteTaskCommand(),
		taskRunnerCommandFactory.MakeCancelTaskCommand(),
		clusterTestCommandFactory.MakeClusterTestCommand(),
		appRunnerCommandFactory.MakeUpdateRoutesCommand(),
		appRunnerCommandFactory.MakeUpdateCommand(),
		appExaminerCommandFactory.MakeVisualizeCommand(),
		dropletRunnerCommandFactory.MakeBuildDropletCommand(),
		dropletRunnerCommandFactory.MakeListDropletsCommand(),
		dropletRunnerCommandFactory.MakeLaunchDropletCommand(),
		dropletRunnerCommandFactory.MakeRemoveDropletCommand(),
		dropletRunnerCommandFactory.MakeImportDropletCommand(),
		dropletRunnerCommandFactory.MakeExportDropletCommand(),
		sshCommandFactory.MakeSSHCommand(),
		helpCommand,
	}
}
예제 #7
0
func cliCommands(ltcConfigRoot string, exitHandler exit_handler.ExitHandler, config *config.Config, logger lager.Logger, targetVerifier target_verifier.TargetVerifier, ui terminal.UI) []cli.Command {

	receptorClient := receptor.NewClient(config.Receptor())
	noaaConsumer := noaa.NewConsumer(LoggregatorUrl(config.Loggregator()), nil, nil)
	appRunner := app_runner.New(receptorClient, config.Target())

	clock := clock.NewClock()

	logReader := logs.NewLogReader(noaaConsumer)
	tailedLogsOutputter := console_tailed_logs_outputter.NewConsoleTailedLogsOutputter(ui, logReader)

	taskExaminer := task_examiner.New(receptorClient)
	taskExaminerCommandFactory := task_examiner_command_factory.NewTaskExaminerCommandFactory(taskExaminer, ui, exitHandler)

	taskRunner := task_runner.New(receptorClient, taskExaminer)
	taskRunnerCommandFactory := task_runner_command_factory.NewTaskRunnerCommandFactory(taskRunner, ui, exitHandler)

	appExaminer := app_examiner.New(receptorClient, app_examiner.NewNoaaConsumer(noaaConsumer))
	graphicalVisualizer := graphical.NewGraphicalVisualizer(appExaminer)
	appExaminerCommandFactory := app_examiner_command_factory.NewAppExaminerCommandFactory(appExaminer, ui, clock, exitHandler, graphicalVisualizer, taskExaminer)

	appRunnerCommandFactoryConfig := app_runner_command_factory.AppRunnerCommandFactoryConfig{
		AppRunner:           appRunner,
		AppExaminer:         appExaminer,
		UI:                  ui,
		Domain:              config.Target(),
		Env:                 os.Environ(),
		Clock:               clock,
		Logger:              logger,
		TailedLogsOutputter: tailedLogsOutputter,
		ExitHandler:         exitHandler,
	}

	appRunnerCommandFactory := app_runner_command_factory.NewAppRunnerCommandFactory(appRunnerCommandFactoryConfig)

	dockerRunnerCommandFactoryConfig := docker_runner_command_factory.DockerRunnerCommandFactoryConfig{
		AppRunner:             appRunner,
		AppExaminer:           appExaminer,
		UI:                    ui,
		Domain:                config.Target(),
		Env:                   os.Environ(),
		Clock:                 clock,
		Logger:                logger,
		ExitHandler:           exitHandler,
		TailedLogsOutputter:   tailedLogsOutputter,
		DockerMetadataFetcher: docker_metadata_fetcher.New(docker_metadata_fetcher.NewDockerSessionFactory()),
	}
	dockerRunnerCommandFactory := docker_runner_command_factory.NewDockerRunnerCommandFactory(dockerRunnerCommandFactoryConfig)

	logsCommandFactory := logs_command_factory.NewLogsCommandFactory(appExaminer, ui, tailedLogsOutputter, exitHandler)

	configCommandFactory := config_command_factory.NewConfigCommandFactory(config, ui, targetVerifier, exitHandler)

	testRunner := integration_test.NewIntegrationTestRunner(config, ltcConfigRoot)
	integrationTestCommandFactory := integration_test_command_factory.NewIntegrationTestCommandFactory(testRunner)

	s3Auth := aws.Auth{
		AccessKey: config.BlobTarget().AccessKey,
		SecretKey: config.BlobTarget().SecretKey,
	}

	s3S3 := s3.New(s3Auth, awsRegion, &http.Client{
		Transport: &http.Transport{
			Proxy: config.BlobTarget().Proxy(),
			Dial: (&net.Dialer{
				Timeout:   30 * time.Second,
				KeepAlive: 30 * time.Second,
			}).Dial,
			TLSHandshakeTimeout: 10 * time.Second,
		},
	})

	blobStore := blob_store.NewBlobStore(config, s3S3)
	blobBucket := blobStore.Bucket(config.BlobTarget().BucketName)

	dropletRunner := droplet_runner.New(appRunner, taskRunner, config, blobStore, blobBucket, targetVerifier, appExaminer)
	cfIgnore := cf_ignore.New()
	dropletRunnerCommandFactory := droplet_runner_command_factory.NewDropletRunnerCommandFactory(*appRunnerCommandFactory, taskExaminer, dropletRunner, cfIgnore)

	helpCommand := cli.Command{
		Name:        "help",
		Aliases:     []string{"h"},
		Usage:       "Shows a list of commands or help for one command",
		Description: "ltc help",
		Action:      defaultAction,
	}

	return []cli.Command{
		appExaminerCommandFactory.MakeCellsCommand(),
		dockerRunnerCommandFactory.MakeCreateAppCommand(),
		appRunnerCommandFactory.MakeSubmitLrpCommand(),
		logsCommandFactory.MakeDebugLogsCommand(),
		appExaminerCommandFactory.MakeListAppCommand(),
		logsCommandFactory.MakeLogsCommand(),
		appRunnerCommandFactory.MakeRemoveAppCommand(),
		appRunnerCommandFactory.MakeScaleAppCommand(),
		appExaminerCommandFactory.MakeStatusCommand(),
		taskRunnerCommandFactory.MakeSubmitTaskCommand(),
		configCommandFactory.MakeTargetCommand(),
		configCommandFactory.MakeTargetBlobCommand(),
		taskExaminerCommandFactory.MakeTaskCommand(),
		taskRunnerCommandFactory.MakeDeleteTaskCommand(),
		taskRunnerCommandFactory.MakeCancelTaskCommand(),
		integrationTestCommandFactory.MakeIntegrationTestCommand(),
		appRunnerCommandFactory.MakeUpdateRoutesCommand(),
		appExaminerCommandFactory.MakeVisualizeCommand(),
		dropletRunnerCommandFactory.MakeBuildDropletCommand(),
		dropletRunnerCommandFactory.MakeListDropletsCommand(),
		dropletRunnerCommandFactory.MakeLaunchDropletCommand(),
		dropletRunnerCommandFactory.MakeRemoveDropletCommand(),
		helpCommand,
	}
}
예제 #8
0
var _ = Describe("Config", func() {
	var (
		testPersister *fakePersister
		testConfig    *config.Config
	)

	BeforeEach(func() {
		testPersister = &fakePersister{}
		testConfig = config.New(testPersister)
	})

	Describe("Target", func() {
		It("sets the target", func() {
			testConfig.SetTarget("mynewapi.com")

			Expect(testConfig.Target()).To(Equal("mynewapi.com"))
		})
	})

	Describe("Username", func() {
		It("sets the target", func() {
			testConfig.SetLogin("ausername", "apassword")

			Expect(testConfig.Username()).To(Equal("ausername"))
		})
	})

	Describe("Receptor", func() {
		It("returns the Receptor with a username and password", func() {
			testConfig.SetTarget("mynewapi.com")
			testConfig.SetLogin("testusername", "testpassword")
				Expect(outputBuffer).To(test_helpers.SayLine("Error connecting to good-name/0: connection failed"))

				Expect(fakeSSH.ConnectCallCount()).To(Equal(1))
				Expect(fakeSSH.ForwardCallCount()).To(Equal(0))
				Expect(fakeSSH.ShellCallCount()).To(Equal(0))
				Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.CommandFailed}))
			})
		})

		Describe("port forwarding", func() {
			It("should forward a local port to a remote host and port", func() {
				fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{ActualRunningInstances: 1}, nil)

				test_helpers.ExecuteCommandWithArgs(sshCommand, []string{"app-name", "-N", "-L", "mrlocalhost:1234:remotehost:5678"})

				Expect(outputBuffer).To(test_helpers.SayLine("Forwarding mrlocalhost:1234 to remotehost:5678 via app-name/0 at %s", config.Target()))

				Expect(fakeSSH.ConnectCallCount()).To(Equal(1))
				appName, instanceIndex, actualConfig := fakeSSH.ConnectArgsForCall(0)
				Expect(appName).To(Equal("app-name"))
				Expect(instanceIndex).To(Equal(0))
				Expect(actualConfig).To(Equal(config))

				Expect(fakeSSH.ForwardCallCount()).To(Equal(1))
				localAddr, remoteAddr := fakeSSH.ForwardArgsForCall(0)
				Expect(localAddr).To(Equal("mrlocalhost:1234"))
				Expect(remoteAddr).To(Equal("remotehost:5678"))

				Expect(fakeAppExaminer.AppStatusCallCount()).To(Equal(1))
				Expect(fakeAppExaminer.AppStatusArgsForCall(0)).To(Equal("app-name"))
			})