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

     1  package command_factory_test
     2  
     3  import (
     4  	"errors"
     5  	"time"
     6  
     7  	. "github.com/onsi/ginkgo"
     8  	. "github.com/onsi/gomega"
     9  	"github.com/onsi/gomega/gbytes"
    10  
    11  	"github.com/cloudfoundry-incubator/ltc/app_examiner"
    12  	"github.com/cloudfoundry-incubator/ltc/app_examiner/fake_app_examiner"
    13  	"github.com/cloudfoundry-incubator/ltc/app_runner"
    14  	"github.com/cloudfoundry-incubator/ltc/app_runner/fake_app_runner"
    15  	"github.com/cloudfoundry-incubator/ltc/docker_runner/command_factory"
    16  	"github.com/cloudfoundry-incubator/ltc/docker_runner/docker_metadata_fetcher"
    17  	"github.com/cloudfoundry-incubator/ltc/docker_runner/docker_metadata_fetcher/fake_docker_metadata_fetcher"
    18  	"github.com/cloudfoundry-incubator/ltc/exit_handler/exit_codes"
    19  	"github.com/cloudfoundry-incubator/ltc/exit_handler/fake_exit_handler"
    20  	"github.com/cloudfoundry-incubator/ltc/logs/console_tailed_logs_outputter/fake_tailed_logs_outputter"
    21  	"github.com/cloudfoundry-incubator/ltc/route_helpers"
    22  	"github.com/cloudfoundry-incubator/ltc/terminal"
    23  	"github.com/cloudfoundry-incubator/ltc/terminal/colors"
    24  	"github.com/cloudfoundry-incubator/ltc/test_helpers"
    25  	. "github.com/cloudfoundry-incubator/ltc/test_helpers/matchers"
    26  	"github.com/codegangsta/cli"
    27  	"github.com/pivotal-golang/clock/fakeclock"
    28  	"github.com/pivotal-golang/lager"
    29  
    30  	app_runner_command_factory "github.com/cloudfoundry-incubator/ltc/app_runner/command_factory"
    31  )
    32  
    33  var _ = Describe("CommandFactory", func() {
    34  	var (
    35  		fakeAppRunner                 *fake_app_runner.FakeAppRunner
    36  		fakeAppExaminer               *fake_app_examiner.FakeAppExaminer
    37  		outputBuffer                  *gbytes.Buffer
    38  		terminalUI                    terminal.UI
    39  		domain                        string = "192.168.11.11.xip.io"
    40  		fakeClock                     *fakeclock.FakeClock
    41  		fakeDockerMetadataFetcher     *fake_docker_metadata_fetcher.FakeDockerMetadataFetcher
    42  		appRunnerCommandFactoryConfig command_factory.DockerRunnerCommandFactoryConfig
    43  		logger                        lager.Logger
    44  		fakeTailedLogsOutputter       *fake_tailed_logs_outputter.FakeTailedLogsOutputter
    45  		fakeExitHandler               *fake_exit_handler.FakeExitHandler
    46  	)
    47  
    48  	BeforeEach(func() {
    49  		fakeAppRunner = &fake_app_runner.FakeAppRunner{}
    50  		fakeAppExaminer = &fake_app_examiner.FakeAppExaminer{}
    51  		outputBuffer = gbytes.NewBuffer()
    52  		terminalUI = terminal.NewUI(nil, outputBuffer, nil)
    53  		fakeDockerMetadataFetcher = &fake_docker_metadata_fetcher.FakeDockerMetadataFetcher{}
    54  		fakeClock = fakeclock.NewFakeClock(time.Now())
    55  		logger = lager.NewLogger("ltc-test")
    56  		fakeTailedLogsOutputter = fake_tailed_logs_outputter.NewFakeTailedLogsOutputter()
    57  		fakeExitHandler = &fake_exit_handler.FakeExitHandler{}
    58  	})
    59  
    60  	Describe("CreateAppCommand", func() {
    61  		var createCommand cli.Command
    62  
    63  		BeforeEach(func() {
    64  			env := []string{"SHELL=/bin/bash", "COLOR=Blue"}
    65  			appRunnerCommandFactoryConfig = command_factory.DockerRunnerCommandFactoryConfig{
    66  				AppRunner:   fakeAppRunner,
    67  				AppExaminer: fakeAppExaminer,
    68  				UI:          terminalUI,
    69  				DockerMetadataFetcher: fakeDockerMetadataFetcher,
    70  				Domain:                domain,
    71  				Env:                   env,
    72  				Clock:                 fakeClock,
    73  				TailedLogsOutputter:   fakeTailedLogsOutputter,
    74  				ExitHandler:           fakeExitHandler,
    75  			}
    76  
    77  			commandFactory := command_factory.NewDockerRunnerCommandFactory(appRunnerCommandFactoryConfig)
    78  			createCommand = commandFactory.MakeCreateAppCommand()
    79  
    80  			fakeDockerMetadataFetcher.FetchMetadataReturns(&docker_metadata_fetcher.ImageMetadata{
    81  				Env: []string{"TIMEZONE=PST", "DOCKER=ME"},
    82  			}, nil)
    83  		})
    84  
    85  		It("creates a Docker based app as specified in the command via the AppRunner", func() {
    86  			fakeAppExaminer.RunningAppInstancesInfoReturns(22, false, nil)
    87  			fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{
    88  				Routes: route_helpers.Routes{
    89  					AppRoutes: []route_helpers.AppRoute{
    90  						{
    91  							Hostnames: []string{"route-3000-yay.192.168.11.11.xip.io"},
    92  							Port:      8080,
    93  						},
    94  						{
    95  							Hostnames: []string{"route-1111-wahoo.192.168.11.11.xip.io"},
    96  							Port:      1111,
    97  						},
    98  						{
    99  							Hostnames: []string{"route-1111-me-too.192.168.11.11.xip.io"},
   100  							Port:      1111,
   101  						},
   102  					},
   103  				},
   104  			}, nil)
   105  
   106  			args := []string{
   107  				"--cpu-weight=57",
   108  				"--memory-mb=12",
   109  				"--disk-mb=12",
   110  				"--user=some-user",
   111  				"--working-dir=/applications",
   112  				"--instances=22",
   113  				"--ports=3000",
   114  				"--http-route=route-3000-yay",
   115  				"--http-route=route-1111-wahoo:1111",
   116  				"--http-route=route-1111-me-too:1111",
   117  				"--env=TIMEZONE=CST",
   118  				`--env=LANG="Chicago English"`,
   119  				`--env=JAVA_OPTS="-Djava.arg=/dev/urandom"`,
   120  				"--env=COLOR",
   121  				"--env=UNSET",
   122  				"--timeout=28s",
   123  				"cool-web-app",
   124  				"superfun/app:mycooltag",
   125  				"--",
   126  				"/start-me-please",
   127  				"AppArg0",
   128  				`--appFlavor="purple"`,
   129  			}
   130  			test_helpers.ExecuteCommandWithArgs(createCommand, args)
   131  
   132  			Expect(fakeDockerMetadataFetcher.FetchMetadataCallCount()).To(Equal(1))
   133  			Expect(fakeDockerMetadataFetcher.FetchMetadataArgsForCall(0)).To(Equal("superfun/app:mycooltag"))
   134  
   135  			Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   136  			createAppParams := fakeAppRunner.CreateAppArgsForCall(0)
   137  			Expect(createAppParams.Name).To(Equal("cool-web-app"))
   138  			Expect(createAppParams.StartCommand).To(Equal("/start-me-please"))
   139  			Expect(createAppParams.RootFS).To(Equal("docker:///superfun/app#mycooltag"))
   140  			Expect(createAppParams.AppArgs).To(Equal([]string{"AppArg0", "--appFlavor=\"purple\""}))
   141  			Expect(createAppParams.Instances).To(Equal(22))
   142  			Expect(createAppParams.EnvironmentVariables).To(Equal(map[string]string{
   143  				"DOCKER":       "ME",
   144  				"TIMEZONE":     "CST",
   145  				"LANG":         `"Chicago English"`,
   146  				"JAVA_OPTS":    `"-Djava.arg=/dev/urandom"`,
   147  				"PROCESS_GUID": "cool-web-app",
   148  				"COLOR":        "Blue",
   149  				"UNSET":        "",
   150  			}))
   151  			Expect(createAppParams.Privileged).To(BeFalse())
   152  			Expect(createAppParams.User).To(Equal("some-user"))
   153  			Expect(createAppParams.CPUWeight).To(Equal(uint(57)))
   154  			Expect(createAppParams.MemoryMB).To(Equal(12))
   155  			Expect(createAppParams.DiskMB).To(Equal(12))
   156  			Expect(createAppParams.Monitor.Method).To(Equal(app_runner.PortMonitor))
   157  			Expect(createAppParams.Timeout).To(Equal(time.Second * 28))
   158  			Expect(createAppParams.RouteOverrides).To(ContainExactly(app_runner.RouteOverrides{
   159  				{HostnamePrefix: "route-3000-yay", Port: 3000},
   160  				{HostnamePrefix: "route-1111-wahoo", Port: 1111},
   161  				{HostnamePrefix: "route-1111-me-too", Port: 1111},
   162  			}))
   163  			Expect(createAppParams.NoRoutes).To(BeFalse())
   164  			Expect(createAppParams.WorkingDir).To(Equal("/applications"))
   165  
   166  			Expect(outputBuffer).To(test_helpers.SayLine("Creating App: cool-web-app"))
   167  			Expect(outputBuffer).To(test_helpers.SayLine(colors.Green("cool-web-app is now running.")))
   168  			Expect(outputBuffer).To(test_helpers.SayLine("App is reachable at:"))
   169  			Expect(outputBuffer).To(test_helpers.SayLine(colors.Green("http://route-3000-yay.192.168.11.11.xip.io")))
   170  			Expect(outputBuffer).To(test_helpers.SayLine(colors.Green("http://route-1111-wahoo.192.168.11.11.xip.io")))
   171  			Expect(outputBuffer).To(test_helpers.SayLine(colors.Green("http://route-1111-me-too.192.168.11.11.xip.io")))
   172  		})
   173  
   174  		Context("when the PROCESS_GUID is passed in as --env", func() {
   175  			It("sets the PROCESS_GUID to the value passed in", func() {
   176  				fakeDockerMetadataFetcher.FetchMetadataReturns(&docker_metadata_fetcher.ImageMetadata{StartCommand: []string{""}}, nil)
   177  				fakeAppExaminer.RunningAppInstancesInfoReturns(1, false, nil)
   178  
   179  				args := []string{
   180  					"app-to-start",
   181  					"fun-org/app",
   182  					"--env=PROCESS_GUID=MyHappyGuid",
   183  				}
   184  				test_helpers.ExecuteCommandWithArgs(createCommand, args)
   185  
   186  				Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   187  				createAppParams := fakeAppRunner.CreateAppArgsForCall(0)
   188  				appEnvVars := createAppParams.EnvironmentVariables
   189  				processGuidEnvVar, found := appEnvVars["PROCESS_GUID"]
   190  				Expect(found).To(BeTrue())
   191  				Expect(processGuidEnvVar).To(Equal("MyHappyGuid"))
   192  			})
   193  		})
   194  
   195  		Context("when a malformed routes flag is passed", func() {
   196  			It("errors out when the port is not an int", func() {
   197  				args := []string{
   198  					"cool-web-app",
   199  					"superfun/app",
   200  					"--http-route=woo:aahh",
   201  					"--",
   202  					"/start-me-please",
   203  				}
   204  				test_helpers.ExecuteCommandWithArgs(createCommand, args)
   205  
   206  				Expect(outputBuffer).To(test_helpers.SayLine(app_runner_command_factory.InvalidPortErrorMessage))
   207  				Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(0))
   208  				Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.InvalidSyntax}))
   209  			})
   210  		})
   211  
   212  		Context("when deprecated --http-routes is passed", func() {
   213  			It("prints a deprecation warning", func() {
   214  				args := []string{"app", "docker/docker", "--http-routes=a,b,c"}
   215  				test_helpers.ExecuteCommandWithArgs(createCommand, args)
   216  
   217  				Expect(outputBuffer).To(test_helpers.SayLine("Incorrect Usage: Unable to parse routes\n  Pass multiple --http-route flags instead of comma-delimiting.  See help page for details."))
   218  				Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(0))
   219  				Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.InvalidSyntax}))
   220  			})
   221  		})
   222  
   223  		Context("when deprecated --tcp-routes is passed", func() {
   224  			It("prints a deprecation warning", func() {
   225  				args := []string{"app", "docker/docker", "--tcp-routes=a,b,c"}
   226  				test_helpers.ExecuteCommandWithArgs(createCommand, args)
   227  
   228  				Expect(outputBuffer).To(test_helpers.SayLine("Incorrect Usage: Unable to parse routes\n  Pass multiple --tcp-route flags instead of comma-delimiting.  See help page for details."))
   229  				Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(0))
   230  				Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.InvalidSyntax}))
   231  			})
   232  		})
   233  
   234  		Describe("Exposed Ports", func() {
   235  			BeforeEach(func() {
   236  				fakeAppExaminer.RunningAppInstancesInfoReturns(1, false, nil)
   237  				fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{
   238  					Routes: route_helpers.Routes{
   239  						AppRoutes: []route_helpers.AppRoute{
   240  							{
   241  								Hostnames: []string{"cool-web-app.192.168.11.11.xip.io", "cool-web-app-8080.192.168.11.11.xip.io"},
   242  								Port:      8080,
   243  							},
   244  							{
   245  								Hostnames: []string{"cool-web-app-9090.192.168.11.11.xip.io"},
   246  								Port:      9090,
   247  							},
   248  						},
   249  					},
   250  				}, nil)
   251  			})
   252  
   253  			It("exposes ports passed by --ports", func() {
   254  				args := []string{
   255  					"cool-web-app",
   256  					"superfun/app",
   257  					"--ports=8080,9090",
   258  					"--",
   259  					"/start-me-please",
   260  				}
   261  				test_helpers.ExecuteCommandWithArgs(createCommand, args)
   262  
   263  				Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   264  				createAppParams := fakeAppRunner.CreateAppArgsForCall(0)
   265  				Expect(createAppParams.ExposedPorts).To(Equal([]uint16{8080, 9090}))
   266  
   267  				Expect(outputBuffer).To(test_helpers.SayLine("App is reachable at:"))
   268  				Expect(outputBuffer).To(test_helpers.SayLine(colors.Green("http://cool-web-app.192.168.11.11.xip.io")))
   269  				Expect(outputBuffer).To(test_helpers.SayLine(colors.Green("http://cool-web-app-8080.192.168.11.11.xip.io")))
   270  				Expect(outputBuffer).To(test_helpers.SayLine(colors.Green("http://cool-web-app-9090.192.168.11.11.xip.io")))
   271  			})
   272  
   273  			It("exposes ports from image metadata", func() {
   274  				fakeDockerMetadataFetcher.FetchMetadataReturns(&docker_metadata_fetcher.ImageMetadata{
   275  					ExposedPorts: []uint16{1200, 2701, 4302},
   276  				}, nil)
   277  
   278  				fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{
   279  					Routes: route_helpers.Routes{
   280  						AppRoutes: []route_helpers.AppRoute{
   281  							{
   282  								Hostnames: []string{"cool-web-app.192.168.11.11.xip.io", "cool-web-app-8080.192.168.11.11.xip.io"},
   283  								Port:      8080,
   284  							},
   285  							{
   286  								Hostnames: []string{"cool-web-app-1200.192.168.11.11.xip.io"},
   287  								Port:      1200,
   288  							},
   289  							{
   290  								Hostnames: []string{"cool-web-app-2701.192.168.11.11.xip.io"},
   291  								Port:      2701,
   292  							},
   293  							{
   294  								Hostnames: []string{"cool-web-app-4302.192.168.11.11.xip.io"},
   295  								Port:      4302,
   296  							},
   297  						},
   298  					},
   299  				}, nil)
   300  
   301  				args := []string{
   302  					"cool-web-app",
   303  					"superfun/app",
   304  					"--",
   305  					"/start-me-please",
   306  				}
   307  				test_helpers.ExecuteCommandWithArgs(createCommand, args)
   308  
   309  				createAppParams := fakeAppRunner.CreateAppArgsForCall(0)
   310  				Expect(createAppParams.ExposedPorts).To(Equal([]uint16{1200, 2701, 4302}))
   311  
   312  				Expect(outputBuffer).To(test_helpers.SayLine("No port specified, using exposed ports from the image metadata."))
   313  				Expect(outputBuffer).To(test_helpers.SayLine("\tExposed Ports: 1200, 2701, 4302"))
   314  				Expect(outputBuffer).To(test_helpers.SayLine("App is reachable at:"))
   315  				Expect(outputBuffer).To(test_helpers.SayLine(colors.Green("http://cool-web-app.192.168.11.11.xip.io")))
   316  				Expect(outputBuffer).To(test_helpers.SayLine(colors.Green("http://cool-web-app-1200.192.168.11.11.xip.io")))
   317  				Expect(outputBuffer).To(test_helpers.SayLine(colors.Green("http://cool-web-app-2701.192.168.11.11.xip.io")))
   318  				Expect(outputBuffer).To(test_helpers.SayLine(colors.Green("http://cool-web-app-4302.192.168.11.11.xip.io")))
   319  			})
   320  
   321  			It("exposes --ports ports when both --ports and EXPOSE metadata exist", func() {
   322  				fakeDockerMetadataFetcher.FetchMetadataReturns(&docker_metadata_fetcher.ImageMetadata{
   323  					ExposedPorts: []uint16{1200, 2701, 4302},
   324  				}, nil)
   325  
   326  				args := []string{
   327  					"cool-web-app",
   328  					"superfun/app",
   329  					"--ports=8080,9090",
   330  					"--",
   331  					"/start-me-please",
   332  				}
   333  				test_helpers.ExecuteCommandWithArgs(createCommand, args)
   334  
   335  				createAppParams := fakeAppRunner.CreateAppArgsForCall(0)
   336  				Expect(createAppParams.ExposedPorts).To(Equal([]uint16{8080, 9090}))
   337  			})
   338  
   339  			Context("when the metadata does not have EXPOSE ports", func() {
   340  				It("exposes the default port 8080", func() {
   341  					args := []string{
   342  						"cool-web-app",
   343  						"superfun/app",
   344  						"--no-monitor",
   345  						"--",
   346  						"/start-me-please",
   347  					}
   348  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   349  
   350  					createAppParams := fakeAppRunner.CreateAppArgsForCall(0)
   351  					Expect(createAppParams.ExposedPorts).To(Equal([]uint16{8080}))
   352  				})
   353  			})
   354  
   355  			Context("when malformed --ports flag is passed", func() {
   356  				It("blows up when you pass bad port strings", func() {
   357  					args := []string{
   358  						"--ports=1000,98feh34",
   359  						"cool-web-app",
   360  						"superfun/app:mycooltag",
   361  						"--",
   362  						"/start-me-please",
   363  					}
   364  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   365  
   366  					Expect(outputBuffer).To(test_helpers.SayLine(app_runner_command_factory.InvalidPortErrorMessage))
   367  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(0))
   368  					Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.InvalidSyntax}))
   369  				})
   370  
   371  				It("errors out when any port is > 65535 (max Linux port number)", func() {
   372  					args := []string{
   373  						"cool-web-app",
   374  						"superfun/app",
   375  						"--ports=8080,65536",
   376  						"--monitor-port=8080",
   377  						"--",
   378  						"/start-me-please",
   379  					}
   380  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   381  
   382  					Expect(outputBuffer).To(test_helpers.SayLine(app_runner_command_factory.InvalidPortErrorMessage))
   383  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(0))
   384  					Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.InvalidSyntax}))
   385  				})
   386  			})
   387  		})
   388  
   389  		//TODO:  little wonky - this test makes sure we default stuff, but says it's dealing w/ fetcher
   390  		Describe("interactions with the docker metadata fetcher", func() {
   391  			Context("when the docker image is hosted on a docker registry", func() {
   392  				It("creates a Docker based app with sensible defaults and checks for metadata to know the image exists", func() {
   393  					fakeAppExaminer.RunningAppInstancesInfoReturns(1, false, nil)
   394  
   395  					args := []string{
   396  						"cool-web-app",
   397  						"awesome/app",
   398  						"--",
   399  						"/start-me-please",
   400  					}
   401  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   402  
   403  					Expect(outputBuffer).To(test_helpers.SayLine("No port specified, image metadata did not contain exposed ports. Defaulting to 8080."))
   404  
   405  					Expect(fakeDockerMetadataFetcher.FetchMetadataCallCount()).To(Equal(1))
   406  					Expect(fakeDockerMetadataFetcher.FetchMetadataArgsForCall(0)).To(Equal("awesome/app"))
   407  
   408  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   409  					createAppParams := fakeAppRunner.CreateAppArgsForCall(0)
   410  					Expect(createAppParams.Privileged).To(BeFalse())
   411  					Expect(createAppParams.User).To(Equal("root"))
   412  					Expect(createAppParams.MemoryMB).To(Equal(128))
   413  					Expect(createAppParams.DiskMB).To(Equal(0))
   414  					Expect(createAppParams.Monitor.Port).To(Equal(uint16(8080)))
   415  					Expect(createAppParams.ExposedPorts).To(Equal([]uint16{8080}))
   416  					Expect(createAppParams.Instances).To(Equal(1))
   417  					Expect(createAppParams.WorkingDir).To(Equal("/"))
   418  				})
   419  			})
   420  
   421  			Context("when the docker metadata fetcher returns an error", func() {
   422  				It("exposes the error from trying to fetch the Docker metadata", func() {
   423  					fakeDockerMetadataFetcher.FetchMetadataReturns(nil, errors.New("Docker Says No."))
   424  
   425  					args := []string{
   426  						"cool-web-app",
   427  						"superfun/app",
   428  						"--",
   429  						"/start-me-please",
   430  					}
   431  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   432  
   433  					Expect(outputBuffer).To(test_helpers.SayLine("Error fetching image metadata: Docker Says No."))
   434  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(0))
   435  					Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.BadDocker}))
   436  				})
   437  			})
   438  		})
   439  
   440  		Describe("User Context", func() {
   441  			BeforeEach(func() {
   442  				fakeAppExaminer.RunningAppInstancesInfoReturns(1, false, nil)
   443  			})
   444  
   445  			Context("when a privileged container has been requested", func() {
   446  				It("sets the param and warns the user", func() {
   447  					fakeDockerMetadataFetcher.FetchMetadataReturns(&docker_metadata_fetcher.ImageMetadata{}, nil)
   448  
   449  					args := []string{
   450  						"--privileged",
   451  						"cool-web-app",
   452  						"superfun/app",
   453  						"--",
   454  						"/start-me-please",
   455  					}
   456  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   457  
   458  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   459  					createAppParams := fakeAppRunner.CreateAppArgsForCall(0)
   460  					Expect(createAppParams.Privileged).To(BeTrue())
   461  					Expect(outputBuffer).To(test_helpers.SayLine("Warning: It is possible for a privileged app to break out of its container and access the host OS!"))
   462  				})
   463  			})
   464  
   465  			Context("When the user has been set", func() {
   466  				It("should use the given user, overriding image metadata", func() {
   467  					fakeDockerMetadataFetcher.FetchMetadataReturns(&docker_metadata_fetcher.ImageMetadata{User: "meta-user"}, nil)
   468  
   469  					args := []string{
   470  						"--user=some-user",
   471  						"cool-web-app",
   472  						"superfun/app",
   473  						"--",
   474  						"/start-me-please",
   475  					}
   476  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   477  
   478  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   479  					createAppParams := fakeAppRunner.CreateAppArgsForCall(0)
   480  					Expect(createAppParams.User).To(Equal("some-user"))
   481  					Expect(outputBuffer).To(test_helpers.SayLine("Setting the user to some-user from option..."))
   482  				})
   483  
   484  				It("should not print a warning message if user is set to root", func() {
   485  					fakeDockerMetadataFetcher.FetchMetadataReturns(&docker_metadata_fetcher.ImageMetadata{}, nil)
   486  
   487  					args := []string{
   488  						"--user=root",
   489  						"cool-web-app",
   490  						"superfun/app",
   491  						"--",
   492  						"/start-me-please",
   493  					}
   494  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   495  
   496  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   497  					createAppParams := fakeAppRunner.CreateAppArgsForCall(0)
   498  					Expect(createAppParams.User).To(Equal("root"))
   499  					Expect(outputBuffer).To(test_helpers.SayLine("Setting the user to root from option..."))
   500  					Expect(outputBuffer).NotTo(test_helpers.SayLine("Warning: No container user specified to run your app, your app will be run as root!"))
   501  				})
   502  
   503  				Context("when the deprecated --run-as-root flag is passed", func() {
   504  					It("should print a warning message", func() {
   505  						fakeDockerMetadataFetcher.FetchMetadataReturns(&docker_metadata_fetcher.ImageMetadata{}, nil)
   506  
   507  						args := []string{
   508  							"--run-as-root",
   509  							"cool-web-app",
   510  							"superfun/app",
   511  							"--",
   512  							"/start-me-please",
   513  						}
   514  						test_helpers.ExecuteCommandWithArgs(createCommand, args)
   515  
   516  						Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   517  						createAppParams := fakeAppRunner.CreateAppArgsForCall(0)
   518  						Expect(createAppParams.User).To(Equal("root"))
   519  						Expect(outputBuffer).To(test_helpers.SayLine("Warning: run-as-root has been deprecated, please use '--user=root' instead)"))
   520  						Expect(outputBuffer).To(test_helpers.SayLine("Setting the user to root from option..."))
   521  					})
   522  
   523  					It("overrides given --user option and image metadata", func() {
   524  						fakeDockerMetadataFetcher.FetchMetadataReturns(&docker_metadata_fetcher.ImageMetadata{User: "meta-user"}, nil)
   525  
   526  						args := []string{
   527  							"--run-as-root",
   528  							"--user=some-user",
   529  							"cool-web-app",
   530  							"superfun/app",
   531  							"--",
   532  							"/start-me-please",
   533  						}
   534  						test_helpers.ExecuteCommandWithArgs(createCommand, args)
   535  
   536  						Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   537  						createAppParams := fakeAppRunner.CreateAppArgsForCall(0)
   538  						Expect(createAppParams.User).To(Equal("root"))
   539  						Expect(outputBuffer).To(test_helpers.SayLine("Warning: run-as-root has been deprecated, please use '--user=root' instead)"))
   540  						Expect(outputBuffer).To(test_helpers.SayLine("Setting the user to root from option..."))
   541  					})
   542  				})
   543  			})
   544  
   545  			Context("When the user has not been set", func() {
   546  				It("should set user to metadata user", func() {
   547  					fakeDockerMetadataFetcher.FetchMetadataReturns(&docker_metadata_fetcher.ImageMetadata{
   548  						User: "meta-user",
   549  					}, nil)
   550  
   551  					args := []string{
   552  						"cool-web-app",
   553  						"superfun/app",
   554  						"--",
   555  						"/start-me-please",
   556  					}
   557  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   558  
   559  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   560  					createAppParams := fakeAppRunner.CreateAppArgsForCall(0)
   561  					Expect(createAppParams.User).To(Equal("meta-user"))
   562  					Expect(outputBuffer).To(test_helpers.SayLine("Setting the user to meta-user (obtained from docker image metadata)..."))
   563  
   564  				})
   565  
   566  				It("should default to 'root' when no metadata user is present", func() {
   567  					fakeDockerMetadataFetcher.FetchMetadataReturns(&docker_metadata_fetcher.ImageMetadata{}, nil)
   568  
   569  					args := []string{
   570  						"cool-web-app",
   571  						"superfun/app",
   572  						"--",
   573  						"/start-me-please",
   574  					}
   575  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   576  
   577  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   578  					createAppParams := fakeAppRunner.CreateAppArgsForCall(0)
   579  					Expect(createAppParams.User).To(Equal("root"))
   580  					Expect(outputBuffer).To(test_helpers.SayLine("Warning: No container user specified to run your app, your app will be run as root!"))
   581  				})
   582  			})
   583  		})
   584  
   585  		Describe("Monitor Config", func() {
   586  			BeforeEach(func() {
   587  				fakeAppExaminer.RunningAppInstancesInfoReturns(1, false, nil)
   588  			})
   589  
   590  			Context("when --no-monitor is passed", func() {
   591  				It("does not monitor", func() {
   592  					args := []string{
   593  						"cool-web-app",
   594  						"superfun/app",
   595  						"--no-monitor",
   596  						"--",
   597  						"/start-me-please",
   598  					}
   599  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   600  
   601  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   602  					monitorConfig := fakeAppRunner.CreateAppArgsForCall(0).Monitor
   603  					Expect(monitorConfig.Method).To(Equal(app_runner.NoMonitor))
   604  				})
   605  			})
   606  
   607  			Context("when --monitor-port is passed", func() {
   608  				It("port-monitors a specified port", func() {
   609  					args := []string{
   610  						"--ports=1000,2000",
   611  						"--monitor-port=2000",
   612  						"cool-web-app",
   613  						"superfun/app:mycooltag",
   614  						"--",
   615  						"/start-me-please",
   616  					}
   617  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   618  
   619  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   620  					monitorConfig := fakeAppRunner.CreateAppArgsForCall(0).Monitor
   621  					Expect(monitorConfig.Method).To(Equal(app_runner.PortMonitor))
   622  					Expect(monitorConfig.Port).To(Equal(uint16(2000)))
   623  				})
   624  
   625  				It("prints an error when the monitored port is not exposed", func() {
   626  					args := []string{
   627  						"--ports=1000,1200",
   628  						"--monitor-port=2000",
   629  						"cool-web-app",
   630  						"superfun/app:mycooltag",
   631  						"--",
   632  						"/start-me-please",
   633  					}
   634  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   635  
   636  					Expect(outputBuffer).To(test_helpers.SayLine(app_runner_command_factory.MonitorPortNotExposed))
   637  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(0))
   638  					Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.CommandFailed}))
   639  				})
   640  			})
   641  
   642  			Context("when --monitor-url is passed", func() {
   643  				It("url-monitors a specified url", func() {
   644  					args := []string{
   645  						"--ports=1000,2000",
   646  						"--monitor-url=1000:/sup/yeah",
   647  						"cool-web-app",
   648  						"superfun/app",
   649  						"--",
   650  						"/start-me-please",
   651  					}
   652  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   653  
   654  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   655  					monitorConfig := fakeAppRunner.CreateAppArgsForCall(0).Monitor
   656  					Expect(monitorConfig.Method).To(Equal(app_runner.URLMonitor))
   657  					Expect(monitorConfig.Port).To(Equal(uint16(1000)))
   658  				})
   659  
   660  				It("prints an error if the url can't be split", func() {
   661  					args := []string{
   662  						"--ports=1000,2000",
   663  						"--monitor-url=1000/sup/yeah",
   664  						"cool-web-app",
   665  						"superfun/app:mycooltag",
   666  						"--",
   667  						"/start-me-please",
   668  					}
   669  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   670  
   671  					Expect(outputBuffer).To(test_helpers.SayLine(app_runner_command_factory.InvalidPortErrorMessage))
   672  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(0))
   673  					Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.InvalidSyntax}))
   674  				})
   675  
   676  				It("prints an error if the port is non-numeric", func() {
   677  					args := []string{
   678  						"--ports=1000,2000",
   679  						"--monitor-url=TOTES:/sup/yeah",
   680  						"cool-web-app",
   681  						"superfun/app:mycooltag",
   682  						"--",
   683  						"/start-me-please",
   684  					}
   685  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   686  
   687  					Expect(outputBuffer).To(test_helpers.SayLine(app_runner_command_factory.InvalidPortErrorMessage))
   688  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(0))
   689  					Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.InvalidSyntax}))
   690  				})
   691  
   692  				It("prints an error when the monitored url port is not exposed", func() {
   693  					args := []string{
   694  						"--ports=1000,2000",
   695  						"--monitor-url=1200:/sup/yeah",
   696  						"cool-web-app",
   697  						"superfun/app:mycooltag",
   698  						"--",
   699  						"/start-me-please",
   700  					}
   701  
   702  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   703  
   704  					Expect(outputBuffer).To(test_helpers.SayLine(app_runner_command_factory.MonitorPortNotExposed))
   705  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(0))
   706  					Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.CommandFailed}))
   707  				})
   708  			})
   709  
   710  			Context("when --monitor-command is passed", func() {
   711  				It("healthchecks using a custom command", func() {
   712  					args := []string{
   713  						`--monitor-command="/custom/monitor 'arg1' 'arg2'"`,
   714  						"cool-web-app",
   715  						"superfun/app",
   716  						"--",
   717  						"/start-me-please",
   718  					}
   719  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   720  
   721  					Expect(outputBuffer).To(test_helpers.SayLine(`Monitoring the app with command "/custom/monitor 'arg1' 'arg2'"`))
   722  
   723  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   724  					monitorConfig := fakeAppRunner.CreateAppArgsForCall(0).Monitor
   725  					Expect(monitorConfig.Method).To(Equal(app_runner.CustomMonitor))
   726  					Expect(monitorConfig.CustomCommand).To(Equal(`"/custom/monitor 'arg1' 'arg2'"`))
   727  				})
   728  			})
   729  
   730  			Context("when no monitoring options are passed", func() {
   731  				It("port-monitors the first exposed port", func() {
   732  					args := []string{
   733  						"--ports=1000,2000",
   734  						"cool-web-app",
   735  						"superfun/app:mycooltag",
   736  						"--",
   737  						"/start-me-please",
   738  					}
   739  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   740  
   741  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   742  					monitorConfig := fakeAppRunner.CreateAppArgsForCall(0).Monitor
   743  					Expect(monitorConfig.Method).To(Equal(app_runner.PortMonitor))
   744  					Expect(monitorConfig.Port).To(Equal(uint16(1000)))
   745  				})
   746  
   747  				It("sets a timeout", func() {
   748  					args := []string{
   749  						"--monitor-timeout=5s",
   750  						"cool-web-app",
   751  						"superfun/app",
   752  						"--",
   753  						"/start-me-please",
   754  					}
   755  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   756  
   757  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   758  					monitorConfig := fakeAppRunner.CreateAppArgsForCall(0).Monitor
   759  					Expect(monitorConfig.Timeout).To(Equal(5 * time.Second))
   760  				})
   761  			})
   762  
   763  			Context("when multiple monitoring options are passed", func() {
   764  				It("no-monitor takes precedence", func() {
   765  					args := []string{
   766  						"--ports=1200",
   767  						"--monitor-url=1200:/sup/yeah",
   768  						"--no-monitor",
   769  						"cool-web-app",
   770  						"superfun/app",
   771  						"--",
   772  						"/start-me-please",
   773  					}
   774  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   775  
   776  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   777  					monitorConfig := fakeAppRunner.CreateAppArgsForCall(0).Monitor
   778  					Expect(monitorConfig.Method).To(Equal(app_runner.NoMonitor))
   779  				})
   780  
   781  				It("monitor-command takes precedence over monitor-url", func() {
   782  					args := []string{
   783  						"--ports=1200",
   784  						"--monitor-url=1200:/sup/yeah",
   785  						"--monitor-command=/custom/monitor",
   786  						"--monitor-port=1200",
   787  						"cool-web-app",
   788  						"superfun/app",
   789  						"--",
   790  						"/start-me-please",
   791  					}
   792  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   793  
   794  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   795  					monitorConfig := fakeAppRunner.CreateAppArgsForCall(0).Monitor
   796  					Expect(monitorConfig.Method).To(Equal(app_runner.CustomMonitor))
   797  					Expect(monitorConfig.CustomCommand).To(Equal("/custom/monitor"))
   798  				})
   799  
   800  				It("monitor-url takes precedence over monitor-port", func() {
   801  					args := []string{
   802  						"--ports=1200",
   803  						"--monitor-url=1200:/sup/yeah",
   804  						"--monitor-port=1200",
   805  						"cool-web-app",
   806  						"superfun/app",
   807  						"--",
   808  						"/start-me-please",
   809  					}
   810  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   811  
   812  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   813  					monitorConfig := fakeAppRunner.CreateAppArgsForCall(0).Monitor
   814  					Expect(monitorConfig.Method).To(Equal(app_runner.URLMonitor))
   815  					Expect(monitorConfig.Port).To(Equal(uint16(1200)))
   816  				})
   817  			})
   818  		})
   819  
   820  		Context("when the --no-routes flag is passed", func() {
   821  			It("calls app runner with NoRoutes equal to true", func() {
   822  				fakeAppExaminer.RunningAppInstancesInfoReturns(1, false, nil)
   823  				fakeDockerMetadataFetcher.FetchMetadataReturns(&docker_metadata_fetcher.ImageMetadata{}, nil)
   824  
   825  				args := []string{
   826  					"cool-web-app",
   827  					"superfun/app",
   828  					"--no-routes",
   829  					"--",
   830  					"/start-me-please",
   831  				}
   832  				test_helpers.ExecuteCommandWithArgs(createCommand, args)
   833  
   834  				Expect(outputBuffer).NotTo(test_helpers.SayLine("App is reachable at:"))
   835  				Expect(outputBuffer).NotTo(test_helpers.SayLine("http://cool-web-app.192.168.11.11.xip.io"))
   836  
   837  				Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   838  				createAppParams := fakeAppRunner.CreateAppArgsForCall(0)
   839  				Expect(createAppParams.NoRoutes).To(BeTrue())
   840  			})
   841  		})
   842  
   843  		Context("when no working dir is provided, but the metadata has a working dir", func() {
   844  			It("sets the working dir from the Docker metadata", func() {
   845  				fakeAppExaminer.RunningAppInstancesInfoReturns(1, false, nil)
   846  				fakeDockerMetadataFetcher.FetchMetadataReturns(&docker_metadata_fetcher.ImageMetadata{WorkingDir: "/work/it"}, nil)
   847  
   848  				args := []string{
   849  					"cool-web-app",
   850  					"superfun/app",
   851  					"--",
   852  					"/start-me-please",
   853  				}
   854  				test_helpers.ExecuteCommandWithArgs(createCommand, args)
   855  
   856  				createAppParams := fakeAppRunner.CreateAppArgsForCall(0)
   857  				Expect(createAppParams.WorkingDir).To(Equal("/work/it"))
   858  			})
   859  		})
   860  
   861  		Context("when no start command is provided", func() {
   862  			var args = []string{
   863  				"cool-web-app",
   864  				"fun-org/app",
   865  			}
   866  
   867  			BeforeEach(func() {
   868  				fakeAppExaminer.RunningAppInstancesInfoReturns(1, false, nil)
   869  			})
   870  
   871  			It("creates a Docker app with the create command retrieved from the docker image metadata", func() {
   872  				fakeDockerMetadataFetcher.FetchMetadataReturns(&docker_metadata_fetcher.ImageMetadata{WorkingDir: "/this/directory/right/here", StartCommand: []string{"/fetch-start", "arg1", "arg2"}}, nil)
   873  
   874  				test_helpers.ExecuteCommandWithArgs(createCommand, args)
   875  
   876  				Expect(outputBuffer).To(test_helpers.SayLine("No working directory specified, using working directory from the image metadata..."))
   877  				Expect(outputBuffer).To(test_helpers.SayLine("Working directory is:"))
   878  				Expect(outputBuffer).To(test_helpers.SayLine("/this/directory/right/here"))
   879  
   880  				Expect(outputBuffer).To(test_helpers.SayLine("No start command specified, using start command from the image metadata..."))
   881  				Expect(outputBuffer).To(test_helpers.SayLine("Start command is:"))
   882  				Expect(outputBuffer).To(test_helpers.SayLine("/fetch-start arg1 arg2"))
   883  
   884  				Expect(fakeDockerMetadataFetcher.FetchMetadataCallCount()).To(Equal(1))
   885  				Expect(fakeDockerMetadataFetcher.FetchMetadataArgsForCall(0)).To(Equal("fun-org/app"))
   886  
   887  				Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   888  				createAppParams := fakeAppRunner.CreateAppArgsForCall(0)
   889  				Expect(createAppParams.StartCommand).To(Equal("/fetch-start"))
   890  				Expect(createAppParams.AppArgs).To(Equal([]string{"arg1", "arg2"}))
   891  				Expect(createAppParams.RootFS).To(Equal("docker:///fun-org/app#latest"))
   892  				Expect(createAppParams.WorkingDir).To(Equal("/this/directory/right/here"))
   893  			})
   894  
   895  			It("does not output the working directory if it is not set", func() {
   896  				fakeDockerMetadataFetcher.FetchMetadataReturns(&docker_metadata_fetcher.ImageMetadata{StartCommand: []string{"/fetch-start"}}, nil)
   897  
   898  				test_helpers.ExecuteCommandWithArgs(createCommand, args)
   899  
   900  				Expect(outputBuffer).NotTo(test_helpers.Say("Working directory is:"))
   901  				Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   902  			})
   903  
   904  			Context("when the metadata also has no start command", func() {
   905  				It("outputs an error message and exits", func() {
   906  					fakeDockerMetadataFetcher.FetchMetadataReturns(&docker_metadata_fetcher.ImageMetadata{}, nil)
   907  
   908  					test_helpers.ExecuteCommandWithArgs(createCommand, args)
   909  
   910  					Expect(outputBuffer).To(test_helpers.SayLine("Unable to determine start command from image metadata."))
   911  					Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(0))
   912  					Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.BadDocker}))
   913  				})
   914  			})
   915  		})
   916  
   917  		Context("when the timeout flag is not passed", func() {
   918  			It("defaults the timeout to something reasonable", func() {
   919  				fakeDockerMetadataFetcher.FetchMetadataReturns(&docker_metadata_fetcher.ImageMetadata{StartCommand: []string{""}}, nil)
   920  				fakeAppExaminer.RunningAppInstancesInfoReturns(1, false, nil)
   921  
   922  				args := []string{
   923  					"app-to-timeout",
   924  					"fun-org/app",
   925  				}
   926  				test_helpers.ExecuteCommandWithArgs(createCommand, args)
   927  
   928  				Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
   929  				createAppParams := fakeAppRunner.CreateAppArgsForCall(0)
   930  				Expect(createAppParams.Timeout).To(Equal(app_runner_command_factory.DefaultPollingTimeout))
   931  			})
   932  		})
   933  
   934  		Describe("polling for the app to start after desiring the app", func() {
   935  			It("polls for the app to start with correct number of instances, outputting logs while the app starts", func() {
   936  				fakeDockerMetadataFetcher.FetchMetadataReturns(&docker_metadata_fetcher.ImageMetadata{}, nil)
   937  				fakeAppExaminer.RunningAppInstancesInfoReturns(0, false, nil)
   938  				fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{
   939  					Routes: route_helpers.Routes{
   940  						AppRoutes: []route_helpers.AppRoute{
   941  							{
   942  								Hostnames: []string{"cool-web-app.192.168.11.11.xip.io", "cool-web-app-8080.192.168.11.11.xip.io"},
   943  								Port:      8080,
   944  							},
   945  						},
   946  					},
   947  				}, nil)
   948  
   949  				args := []string{
   950  					"--instances=10",
   951  					"cool-web-app",
   952  					"superfun/app",
   953  					"--",
   954  					"/start-me-please",
   955  				}
   956  				doneChan := test_helpers.AsyncExecuteCommandWithArgs(createCommand, args)
   957  
   958  				Eventually(outputBuffer).Should(test_helpers.SayLine("Creating App: cool-web-app"))
   959  
   960  				Expect(fakeTailedLogsOutputter.OutputTailedLogsCallCount()).To(Equal(1))
   961  				Expect(fakeTailedLogsOutputter.OutputTailedLogsArgsForCall(0)).To(Equal("cool-web-app"))
   962  
   963  				Expect(fakeAppExaminer.RunningAppInstancesInfoCallCount()).To(Equal(1))
   964  				Expect(fakeAppExaminer.RunningAppInstancesInfoArgsForCall(0)).To(Equal("cool-web-app"))
   965  
   966  				fakeClock.IncrementBySeconds(1)
   967  				Expect(fakeTailedLogsOutputter.StopOutputtingCallCount()).To(Equal(0))
   968  
   969  				fakeAppExaminer.RunningAppInstancesInfoReturns(9, false, nil)
   970  				fakeClock.IncrementBySeconds(1)
   971  				Expect(doneChan).NotTo(BeClosed())
   972  				Expect(fakeTailedLogsOutputter.StopOutputtingCallCount()).To(Equal(0))
   973  
   974  				fakeAppExaminer.RunningAppInstancesInfoReturns(10, false, nil)
   975  				fakeClock.IncrementBySeconds(1)
   976  
   977  				Eventually(doneChan, 3).Should(BeClosed())
   978  
   979  				Expect(outputBuffer).To(test_helpers.SayNewLine())
   980  				Expect(outputBuffer).To(test_helpers.SayLine(colors.Green("cool-web-app is now running.")))
   981  				Expect(outputBuffer).To(test_helpers.SayLine("App is reachable at:"))
   982  				Expect(outputBuffer).To(test_helpers.SayLine(colors.Green("http://cool-web-app.192.168.11.11.xip.io")))
   983  
   984  				Expect(fakeTailedLogsOutputter.StopOutputtingCallCount()).To(Equal(1))
   985  			})
   986  
   987  			Context("when the app does not start before the timeout elapses", func() {
   988  				It("alerts the user the app took too long to start", func() {
   989  					fakeDockerMetadataFetcher.FetchMetadataReturns(&docker_metadata_fetcher.ImageMetadata{}, nil)
   990  					fakeAppExaminer.RunningAppInstancesInfoReturns(0, false, nil)
   991  					fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{
   992  						Routes: route_helpers.Routes{
   993  							AppRoutes: []route_helpers.AppRoute{
   994  								{
   995  									Hostnames: []string{"cool-web-app.192.168.11.11.xip.io", "cool-web-app-8080.192.168.11.11.xip.io"},
   996  									Port:      8080,
   997  								},
   998  							},
   999  						},
  1000  					}, nil)
  1001  
  1002  					args := []string{
  1003  						"cool-web-app",
  1004  						"superfun/app",
  1005  						"--",
  1006  						"/start-me-please",
  1007  					}
  1008  					doneChan := test_helpers.AsyncExecuteCommandWithArgs(createCommand, args)
  1009  
  1010  					Eventually(outputBuffer).Should(test_helpers.SayLine("Creating App: cool-web-app"))
  1011  
  1012  					fakeClock.IncrementBySeconds(120)
  1013  
  1014  					Eventually(doneChan, 3).Should(BeClosed())
  1015  
  1016  					Expect(outputBuffer).To(test_helpers.SayLine(colors.Red("Timed out waiting for the container to come up.")))
  1017  					Expect(outputBuffer).To(test_helpers.SayLine("This typically happens because docker layers can take time to download."))
  1018  					Expect(outputBuffer).To(test_helpers.SayLine("Lattice is still downloading your application in the background."))
  1019  					Expect(outputBuffer).To(test_helpers.SayLine("To view logs:"))
  1020  					Expect(outputBuffer).To(test_helpers.SayLine("ltc logs cool-web-app"))
  1021  					Expect(outputBuffer).To(test_helpers.SayLine("To view status:"))
  1022  					Expect(outputBuffer).To(test_helpers.SayLine("ltc status cool-web-app"))
  1023  					Expect(outputBuffer).To(test_helpers.SayLine("App will be reachable at:"))
  1024  					Expect(outputBuffer).To(test_helpers.SayLine(colors.Green("http://cool-web-app.192.168.11.11.xip.io")))
  1025  				})
  1026  			})
  1027  
  1028  			Context("when there is a placement error when polling for the app to start", func() {
  1029  				It("prints an error message and exits", func() {
  1030  					fakeDockerMetadataFetcher.FetchMetadataReturns(&docker_metadata_fetcher.ImageMetadata{}, nil)
  1031  					fakeAppExaminer.RunningAppInstancesInfoReturns(0, false, nil)
  1032  					args := []string{
  1033  						"--instances=10",
  1034  						"--ports=3000",
  1035  						"--working-dir=/applications",
  1036  						"cool-web-app",
  1037  						"superfun/app",
  1038  						"--",
  1039  						"/start-me-please",
  1040  					}
  1041  
  1042  					doneChan := test_helpers.AsyncExecuteCommandWithArgs(createCommand, args)
  1043  
  1044  					Eventually(outputBuffer).Should(test_helpers.SayLine("Monitoring the app on port 3000..."))
  1045  					Eventually(outputBuffer).Should(test_helpers.SayLine("Creating App: cool-web-app"))
  1046  
  1047  					Expect(fakeAppExaminer.RunningAppInstancesInfoCallCount()).To(Equal(1))
  1048  					Expect(fakeAppExaminer.RunningAppInstancesInfoArgsForCall(0)).To(Equal("cool-web-app"))
  1049  
  1050  					fakeClock.IncrementBySeconds(1)
  1051  					Expect(fakeTailedLogsOutputter.StopOutputtingCallCount()).To(Equal(0))
  1052  					Expect(fakeExitHandler.ExitCalledWith).To(BeEmpty())
  1053  
  1054  					fakeAppExaminer.RunningAppInstancesInfoReturns(9, true, nil)
  1055  					fakeClock.IncrementBySeconds(1)
  1056  					Eventually(doneChan, 3).Should(BeClosed())
  1057  
  1058  					Expect(outputBuffer).To(test_helpers.SayLine(colors.Red("Error, could not place all instances: insufficient resources. Try requesting fewer instances or reducing the requested memory or disk capacity.")))
  1059  					Expect(outputBuffer).NotTo(test_helpers.Say("Timed out waiting for the container"))
  1060  
  1061  					Expect(fakeTailedLogsOutputter.StopOutputtingCallCount()).To(Equal(1))
  1062  					Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.PlacementError}))
  1063  				})
  1064  			})
  1065  		})
  1066  
  1067  		Context("invalid syntax", func() {
  1068  			It("validates the CPU weight is in 1-100", func() {
  1069  				args := []string{
  1070  					"cool-app",
  1071  					"greatapp/greatapp",
  1072  					"--cpu-weight=0",
  1073  				}
  1074  				test_helpers.ExecuteCommandWithArgs(createCommand, args)
  1075  
  1076  				Expect(outputBuffer).To(test_helpers.SayLine("Incorrect Usage: Invalid CPU Weight"))
  1077  				Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(0))
  1078  				Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.InvalidSyntax}))
  1079  			})
  1080  
  1081  			It("validates that the name and dockerPath are passed in", func() {
  1082  				args := []string{
  1083  					"justonearg",
  1084  				}
  1085  
  1086  				test_helpers.ExecuteCommandWithArgs(createCommand, args)
  1087  
  1088  				Expect(outputBuffer).To(test_helpers.SayLine("Incorrect Usage: <app-name> and <docker-image> are required"))
  1089  				Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(0))
  1090  			})
  1091  
  1092  			It("validates that the terminator -- is passed in when a start command is specified", func() {
  1093  				args := []string{
  1094  					"cool-web-app",
  1095  					"superfun/app",
  1096  					"not-the-terminator",
  1097  					"start-me-up",
  1098  				}
  1099  				test_helpers.ExecuteCommandWithArgs(createCommand, args)
  1100  
  1101  				Expect(outputBuffer).To(test_helpers.SayLine("Incorrect Usage: '--' Required before start command"))
  1102  				Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(0))
  1103  			})
  1104  		})
  1105  
  1106  		Context("when the docker repo url is malformed", func() {
  1107  			It("outputs an error", func() {
  1108  				args := []string{
  1109  					"cool-web-app",
  1110  					"¥¥¥Bad-Docker¥¥¥",
  1111  					"--",
  1112  					"/start-me-please",
  1113  				}
  1114  				test_helpers.ExecuteCommandWithArgs(createCommand, args)
  1115  
  1116  				Expect(outputBuffer).To(test_helpers.Say("repository name component must match"))
  1117  				Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.CommandFailed}))
  1118  			})
  1119  		})
  1120  
  1121  		Context("when the app runner returns an error", func() {
  1122  			It("outputs error messages", func() {
  1123  				fakeAppRunner.CreateAppReturns(errors.New("Major Fault"))
  1124  				args := []string{
  1125  					"cool-web-app",
  1126  					"superfun/app",
  1127  					"--",
  1128  					"/start-me-please",
  1129  				}
  1130  				test_helpers.ExecuteCommandWithArgs(createCommand, args)
  1131  
  1132  				Expect(outputBuffer).To(test_helpers.SayLine("Error creating app: Major Fault"))
  1133  				Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.CommandFailed}))
  1134  			})
  1135  		})
  1136  
  1137  		Context("when a malformed tcp routes flag is passed", func() {
  1138  			It("errors out when the container port is not an int", func() {
  1139  				args := []string{
  1140  					"cool-web-app",
  1141  					"superfun/app",
  1142  					"--tcp-route=woo:50000",
  1143  					"--",
  1144  					"/start-me-please",
  1145  				}
  1146  				test_helpers.ExecuteCommandWithArgs(createCommand, args)
  1147  
  1148  				Expect(outputBuffer).To(test_helpers.SayLine(app_runner_command_factory.InvalidPortErrorMessage))
  1149  				Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(0))
  1150  				Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.InvalidSyntax}))
  1151  			})
  1152  		})
  1153  
  1154  		It("creates a Docker based app with tcp routes as specified in the command via the AppRunner", func() {
  1155  			fakeAppExaminer.RunningAppInstancesInfoReturns(1, false, nil)
  1156  			fakeAppExaminer.AppStatusReturns(app_examiner.AppInfo{
  1157  				Routes: route_helpers.Routes{
  1158  					TcpRoutes: []route_helpers.TcpRoute{
  1159  						{
  1160  							ExternalPort: 50000,
  1161  							Port:         5222,
  1162  						},
  1163  						{
  1164  							ExternalPort: 50001,
  1165  							Port:         5223,
  1166  						},
  1167  					},
  1168  				},
  1169  			}, nil)
  1170  
  1171  			args := []string{
  1172  				"--ports=5222",
  1173  				"--tcp-route=50000",
  1174  				"--tcp-route=50001:5223",
  1175  				"cool-web-app",
  1176  				"superfun/app:mycooltag",
  1177  				"--",
  1178  				"/start-me-please",
  1179  			}
  1180  			test_helpers.ExecuteCommandWithArgs(createCommand, args)
  1181  
  1182  			Expect(fakeDockerMetadataFetcher.FetchMetadataCallCount()).To(Equal(1))
  1183  			Expect(fakeDockerMetadataFetcher.FetchMetadataArgsForCall(0)).To(Equal("superfun/app:mycooltag"))
  1184  
  1185  			Expect(fakeAppRunner.CreateAppCallCount()).To(Equal(1))
  1186  			createAppParams := fakeAppRunner.CreateAppArgsForCall(0)
  1187  
  1188  			Expect(createAppParams.Name).To(Equal("cool-web-app"))
  1189  			Expect(createAppParams.StartCommand).To(Equal("/start-me-please"))
  1190  			Expect(createAppParams.RootFS).To(Equal("docker:///superfun/app#mycooltag"))
  1191  			Expect(createAppParams.Instances).To(Equal(1))
  1192  			Expect(createAppParams.Monitor.Method).To(Equal(app_runner.PortMonitor))
  1193  
  1194  			Expect(createAppParams.TcpRoutes).To(ContainExactly(app_runner.TcpRoutes{
  1195  				{ExternalPort: 50000, Port: 5222},
  1196  				{ExternalPort: 50001, Port: 5223},
  1197  			}))
  1198  			Expect(createAppParams.NoRoutes).To(BeFalse())
  1199  
  1200  			Expect(outputBuffer).To(test_helpers.SayLine("Creating App: cool-web-app"))
  1201  			Expect(outputBuffer).To(test_helpers.SayLine(colors.Green("cool-web-app is now running.")))
  1202  			Expect(outputBuffer).To(test_helpers.SayLine("App is reachable at:"))
  1203  
  1204  			Expect(outputBuffer).To(test_helpers.SayLine(colors.Green(domain + ":50000")))
  1205  			Expect(outputBuffer).To(test_helpers.SayLine(colors.Green(domain + ":50001")))
  1206  		})
  1207  	})
  1208  
  1209  })