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