func (factory *AppExaminerCommandFactory) printDistribution() int {
	defer factory.ui.Say(cursor.ClearToEndOfDisplay())

	cells, err := factory.appExaminer.ListCells()
	if err != nil {
		factory.ui.Say("Error visualizing: " + err.Error())
		factory.ui.Say(cursor.ClearToEndOfLine())
		factory.ui.SayNewLine()
		return 1
	}

	for _, cell := range cells {
		factory.ui.Say(cell.CellID)
		if cell.Missing {
			factory.ui.Say(colors.Red("[MISSING]"))
		}
		factory.ui.Say(": ")

		if cell.RunningInstances == 0 && cell.ClaimedInstances == 0 && !cell.Missing {
			factory.ui.Say(colors.Red("empty"))
		} else {
			factory.ui.Say(colors.Green(strings.Repeat("•", cell.RunningInstances)))
			factory.ui.Say(colors.Yellow(strings.Repeat("•", cell.ClaimedInstances)))
		}
		factory.ui.Say(cursor.ClearToEndOfLine())
		factory.ui.SayNewLine()
	}

	return len(cells)
}
예제 #2
0
func (factory *AppRunnerCommandFactory) pollUntilAllInstancesRunning(pollTimeout time.Duration, appName string, instances int, action pollingAction) bool {
	placementErrorOccurred := false
	ok := factory.pollUntilSuccess(pollTimeout, func() bool {
		numberOfRunningInstances, placementError, _ := factory.AppExaminer.RunningAppInstancesInfo(appName)
		if placementError {
			factory.UI.SayLine(colors.Red("Error, could not place all instances: insufficient resources. Try requesting fewer instances or reducing the requested memory or disk capacity."))
			placementErrorOccurred = true
			return true
		}
		return numberOfRunningInstances == instances
	}, true)

	if placementErrorOccurred {
		factory.ExitHandler.Exit(exit_codes.PlacementError)
		return false
	} else if !ok {
		if action == pollingStart {
			factory.UI.SayLine(colors.Red("Timed out waiting for the container to come up."))
			factory.UI.SayLine("This typically happens because docker layers can take time to download.")
			factory.UI.SayLine("Lattice is still downloading your application in the background.")
		} else {
			factory.UI.SayLine(colors.Red("Timed out waiting for the container to scale."))
			factory.UI.SayLine("Lattice is still scaling your application in the background.")
		}
		factory.UI.SayLine(fmt.Sprintf("To view logs:\n\tltc logs %s", appName))
		factory.UI.SayLine(fmt.Sprintf("To view status:\n\tltc status %s", appName))
		factory.UI.SayNewLine()
	}
	return ok
}
func colorInstances(appInfo app_examiner.AppInfo) string {
	instances := fmt.Sprintf("%d/%d", appInfo.ActualRunningInstances, appInfo.DesiredInstances)
	if appInfo.ActualRunningInstances == appInfo.DesiredInstances {
		return colors.Green(instances)
	} else if appInfo.ActualRunningInstances == 0 {
		return colors.Red(instances)
	}

	return colors.Yellow(instances)
}
func (factory *DropletRunnerCommandFactory) buildDroplet(context *cli.Context) {
	pathFlag := context.String("path")
	dropletName := context.Args().First()
	buildpackUrl := context.Args().Get(1)

	if dropletName == "" || buildpackUrl == "" {
		factory.UI.SayIncorrectUsage("")
		factory.ExitHandler.Exit(exit_codes.InvalidSyntax)
		return
	}

	archivePath, err := makeTar(pathFlag)
	if err != nil {
		factory.UI.Say(fmt.Sprintf("Error tarring . to %s: %s", archivePath, err))
		factory.ExitHandler.Exit(exit_codes.FileSystemError)
		return
	}

	if err = factory.dropletRunner.UploadBits(dropletName, archivePath); err != nil {
		factory.UI.Say(fmt.Sprintf("Error uploading to %s: %s", dropletName, err))
		factory.ExitHandler.Exit(exit_codes.CommandFailed)
		return
	}

	taskName := "build-droplet-" + dropletName
	if err = factory.dropletRunner.BuildDroplet(taskName, dropletName, buildpackUrl); err != nil {
		factory.UI.Say(fmt.Sprintf("Error submitting build of %s: %s", dropletName, err))
		factory.ExitHandler.Exit(exit_codes.CommandFailed)
		return
	}

	factory.UI.SayLine("Submitted build of " + dropletName)

	go factory.TailedLogsOutputter.OutputTailedLogs(taskName)
	defer factory.TailedLogsOutputter.StopOutputting()

	ok, taskState := factory.waitForBuildTask(2*time.Minute, taskName)
	if ok {
		if taskState.Failed {
			factory.UI.SayLine("Build failed: " + taskState.FailureReason)
		} else {
			factory.UI.SayLine("Build completed")
		}
	} else {
		factory.UI.Say(colors.Red("Timed out waiting for the build to complete."))
		factory.UI.SayNewLine()
		factory.UI.SayLine("Lattice is still building your application in the background.")

		factory.UI.SayLine(fmt.Sprintf("To view logs:\n\tltc logs %s", taskName))
		factory.UI.SayLine(fmt.Sprintf("To view status:\n\tltc status %s", taskName))
		factory.UI.SayNewLine()
	}
}
func (factory *TaskRunnerCommandFactory) deleteTask(context *cli.Context) {
	taskGuid := context.Args().First()
	if taskGuid == "" {
		factory.ui.SayIncorrectUsage("Please input a valid TASK_GUID")
		factory.exitHandler.Exit(exit_codes.InvalidSyntax)
		return
	}

	if err := factory.taskRunner.DeleteTask(taskGuid); err != nil {
		factory.ui.SayLine(fmt.Sprintf(colors.Red("Error deleting %s: %s"), taskGuid, err.Error()))
		factory.exitHandler.Exit(exit_codes.CommandFailed)
		return
	}
	factory.ui.SayLine(colors.Green("OK"))
}
func (factory *TaskExaminerCommandFactory) task(context *cli.Context) {
	taskName := context.Args().First()
	if taskName == "" {
		factory.ui.SayIncorrectUsage("")
		factory.exitHandler.Exit(exit_codes.InvalidSyntax)
		return
	}

	taskInfo, err := factory.taskExaminer.TaskStatus(taskName)
	if err != nil {
		if err.Error() == task_examiner.TaskNotFoundErrorMessage {
			factory.ui.Say(colors.Red(fmt.Sprintf("No task '%s' was found", taskName)))
			factory.exitHandler.Exit(exit_codes.CommandFailed)
			return
		}
		factory.ui.Say(colors.Red("Error fetching task result: " + err.Error()))
		factory.exitHandler.Exit(exit_codes.CommandFailed)
		return
	}

	w := tabwriter.NewWriter(factory.ui, 9, 8, 1, '\t', 0)

	fmt.Fprintf(w, "%s\t%s\n", "Task Name", taskInfo.TaskGuid)
	fmt.Fprintf(w, "%s\t%s\n", "Cell ID", taskInfo.CellID)
	if taskInfo.State == "PENDING" || taskInfo.State == "CLAIMED" || taskInfo.State == "RUNNING" {
		fmt.Fprintf(w, "%s\t%s\n", "Status", colors.Yellow(taskInfo.State))
	} else if (taskInfo.State == "COMPLETED" || taskInfo.State == "RESOLVING") && !taskInfo.Failed {
		fmt.Fprintf(w, "%s\t%s\n", "Status", colors.Green(taskInfo.State))
		fmt.Fprintf(w, "%s\t%s\n", "Result", taskInfo.Result)
	} else if taskInfo.Failed {
		fmt.Fprintf(w, "%s\t%s\n", "Status", colors.Red(taskInfo.State))
		fmt.Fprintf(w, "%s\t%s\n", "Failure Reason", taskInfo.FailureReason)
	}

	w.Flush()
}
func (factory *DropletRunnerCommandFactory) waitForBuildTask(pollTimeout time.Duration, taskName string) (bool, task_examiner.TaskInfo) {
	var taskInfo task_examiner.TaskInfo
	ok := factory.pollUntilSuccess(pollTimeout, func() bool {
		var err error
		taskInfo, err = factory.taskExaminer.TaskStatus(taskName)
		if err != nil {
			factory.UI.SayLine(colors.Red("Error requesting task status: %s"), err)
			return true
		}

		return taskInfo.State != "RUNNING" && taskInfo.State != "PENDING"
	})

	return ok, taskInfo
}
					args := []string{
						"cool-web-app",
						"superfun/app",
						"--",
						"/start-me-please",
					}
					doneChan := test_helpers.AsyncExecuteCommandWithArgs(createCommand, args)

					Eventually(outputBuffer).Should(test_helpers.SayLine("Creating App: cool-web-app"))

					fakeClock.IncrementBySeconds(120)

					Eventually(doneChan).Should(BeClosed())

					Expect(outputBuffer).To(test_helpers.SayLine(colors.Red("Timed out waiting for the container to come up.")))
					Expect(outputBuffer).To(test_helpers.SayLine("This typically happens because docker layers can take time to download."))
					Expect(outputBuffer).To(test_helpers.SayLine("Lattice is still downloading your application in the background."))
					Expect(outputBuffer).To(test_helpers.SayLine("To view logs:"))
					Expect(outputBuffer).To(test_helpers.SayLine("ltc logs cool-web-app"))
					Expect(outputBuffer).To(test_helpers.SayLine("To view status:"))
					Expect(outputBuffer).To(test_helpers.SayLine("ltc status cool-web-app"))
					Expect(outputBuffer).To(test_helpers.SayLine("App will be reachable at:"))
					Expect(outputBuffer).To(test_helpers.SayLine(colors.Green("http://cool-web-app.192.168.11.11.xip.io")))
				})
			})

			Context("when there is a placement error when polling for the app to start", func() {
				It("prints an error message and exits", func() {
					fakeDockerMetadataFetcher.FetchMetadataReturns(&docker_metadata_fetcher.ImageMetadata{}, nil)
					fakeAppExaminer.RunningAppInstancesInfoReturns(0, false, nil)
					args := []string{
						"droppo-the-clown",
						"http://some.url/for/buildpack",
						"-t",
						"17s",
					}
					doneChan := test_helpers.AsyncExecuteCommandWithArgs(buildDropletCommand, args)

					Eventually(outputBuffer).Should(test_helpers.SayLine("Submitted build of droppo-the-clown"))

					fakeClock.IncrementBySeconds(17)

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

					Expect(outputBuffer).To(test_helpers.SayLine(colors.Red("Timed out waiting for the build to complete.")))
					Expect(outputBuffer).To(test_helpers.SayLine("Lattice is still building your application in the background."))
					Expect(outputBuffer).To(test_helpers.SayLine("To view logs:"))
					Expect(outputBuffer).To(test_helpers.SayLine("ltc logs build-droplet-droppo-the-clown"))
					Expect(outputBuffer).To(test_helpers.SayLine("To view status:"))
					Expect(outputBuffer).To(test_helpers.SayLine("ltc status build-droplet-droppo-the-clown"))
				})
			})

			Context("when the build completes", func() {
				It("alerts the user of a complete but failed build", func() {
					fakeTaskExaminer.TaskStatusReturns(task_examiner.TaskInfo{State: "PENDING"}, nil)

					args := []string{"droppo-the-clown", "http://some.url/for/buildpack"}
					doneChan := test_helpers.AsyncExecuteCommandWithArgs(buildDropletCommand, args)
					Result:        "",
					State:         "COMPLETED",
				},
			}
			fakeTaskExaminer.ListTasksReturns(listTasks, nil)

			test_helpers.ExecuteCommandWithArgs(listAppsCommand, []string{})

			Expect(outputBuffer).To(test_helpers.Say(colors.Bold("App Name")))
			Expect(outputBuffer).To(test_helpers.Say(colors.Bold("Instances")))
			Expect(outputBuffer).To(test_helpers.Say(colors.Bold("DiskMB")))
			Expect(outputBuffer).To(test_helpers.Say(colors.Bold("MemoryMB")))
			Expect(outputBuffer).To(test_helpers.Say(colors.Bold("Route")))

			Expect(outputBuffer).To(test_helpers.Say(colors.Bold("process1")))
			Expect(outputBuffer).To(test_helpers.Say(colors.Red("0/21")))
			Expect(outputBuffer).To(test_helpers.Say(colors.NoColor("100")))
			Expect(outputBuffer).To(test_helpers.Say(colors.NoColor("50")))
			Expect(outputBuffer).To(test_helpers.Say("alldaylong.com => 54321"))

			Expect(outputBuffer).To(test_helpers.Say(colors.Bold("process2")))
			Expect(outputBuffer).To(test_helpers.Say(colors.Yellow("9/8")))
			Expect(outputBuffer).To(test_helpers.Say(colors.NoColor("400")))
			Expect(outputBuffer).To(test_helpers.Say(colors.NoColor("30")))
			Expect(outputBuffer).To(test_helpers.Say("never.io => 1234"))

			Expect(outputBuffer).To(test_helpers.Say(colors.Bold("process3")))
			Expect(outputBuffer).To(test_helpers.Say(colors.Green("5/5")))
			Expect(outputBuffer).To(test_helpers.Say(colors.NoColor("600")))
			Expect(outputBuffer).To(test_helpers.Say(colors.NoColor("90")))
			Expect(outputBuffer).To(test_helpers.Say("allthetime.com => 1234, herewego.org => 1234"))
			test_helpers.ExecuteCommandWithArgs(deleteTaskCommand, []string{"task-guid-1"})

			Expect(outputBuffer).To(test_helpers.SayLine(colors.Green("OK")))
		})

		It("returns error when fail to delete the task", func() {
			taskInfo := task_examiner.TaskInfo{
				TaskGuid: "task-guid-1",
				State:    "COMPLETED",
			}
			fakeTaskExaminer.TaskStatusReturns(taskInfo, nil)
			fakeTaskRunner.DeleteTaskReturns(errors.New("task in unknown state"))

			test_helpers.ExecuteCommandWithArgs(deleteTaskCommand, []string{"task-guid-1"})

			Expect(outputBuffer).To(test_helpers.SayLine(colors.Red("Error deleting task-guid-1: " + "task in unknown state")))
			Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.CommandFailed}))
		})

		It("fails with usage", func() {
			test_helpers.ExecuteCommandWithArgs(deleteTaskCommand, []string{})

			Expect(outputBuffer).To(test_helpers.SayLine("Please input a valid TASK_GUID"))
			Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.InvalidSyntax}))
		})
	})

	Describe("CancelTaskCommand", func() {
		var cancelTaskCommand cli.Command

		BeforeEach(func() {
func (factory *DropletRunnerCommandFactory) buildDroplet(context *cli.Context) {
	pathFlag := context.String("path")
	envFlag := context.StringSlice("env")
	timeoutFlag := context.Duration("timeout")
	dropletName := context.Args().First()
	buildpack := context.Args().Get(1)

	if dropletName == "" || buildpack == "" {
		factory.UI.SayIncorrectUsage("")
		factory.ExitHandler.Exit(exit_codes.InvalidSyntax)
		return
	}

	var buildpackUrl string
	if knownBuildpackUrl, ok := knownBuildpacks[buildpack]; ok {
		buildpackUrl = knownBuildpackUrl
	} else if _, err := url.ParseRequestURI(buildpack); err == nil {
		buildpackUrl = buildpack
	} else {
		factory.UI.SayIncorrectUsage(fmt.Sprintf("invalid buildpack %s", buildpack))
		factory.ExitHandler.Exit(exit_codes.InvalidSyntax)
		return
	}

	archivePath, err := factory.makeTar(pathFlag)
	if err != nil {
		factory.UI.Say(fmt.Sprintf("Error tarring %s: %s", pathFlag, err))
		factory.ExitHandler.Exit(exit_codes.FileSystemError)
		return
	}

	if err = factory.dropletRunner.UploadBits(dropletName, archivePath); err != nil {
		factory.UI.Say(fmt.Sprintf("Error uploading to %s: %s", dropletName, err))
		factory.ExitHandler.Exit(exit_codes.CommandFailed)
		return
	}

	environment := factory.AppRunnerCommandFactory.BuildEnvironment(envFlag)

	taskName := "build-droplet-" + dropletName
	if err = factory.dropletRunner.BuildDroplet(taskName, dropletName, buildpackUrl, environment); err != nil {
		factory.UI.Say(fmt.Sprintf("Error submitting build of %s: %s", dropletName, err))
		factory.ExitHandler.Exit(exit_codes.CommandFailed)
		return
	}

	factory.UI.SayLine("Submitted build of " + dropletName)

	go factory.TailedLogsOutputter.OutputTailedLogs(taskName)
	defer factory.TailedLogsOutputter.StopOutputting()

	ok, taskState := factory.waitForBuildTask(timeoutFlag, taskName)
	if ok {
		if taskState.Failed {
			factory.UI.SayLine("Build failed: " + taskState.FailureReason)
			factory.ExitHandler.Exit(exit_codes.CommandFailed)
		} else {
			factory.UI.SayLine("Build completed")
		}
	} else {
		factory.UI.Say(colors.Red("Timed out waiting for the build to complete."))
		factory.UI.SayNewLine()
		factory.UI.SayLine("Lattice is still building your application in the background.")

		factory.UI.SayLine(fmt.Sprintf("To view logs:\n\tltc logs %s", taskName))
		factory.UI.SayLine(fmt.Sprintf("To view status:\n\tltc status %s", taskName))
		factory.UI.SayNewLine()
	}
}
예제 #13
0
)

var _ = Describe("colors", func() {

	itShouldNotColorizeWhitespace := func(colorizer func(text string) string) {
		It("returns a string without color codes when only whitespace is passed in", func() {
			Expect(colorizer("  ")).To(Equal("  "))
			Expect(colorizer("\n")).To(Equal("\n"))
			Expect(colorizer("\t")).To(Equal("\t"))
			Expect(colorizer("\r")).To(Equal("\r"))
		})
	}

	Describe("Red", func() {
		It("adds the red color code", func() {
			Expect(colors.Red("ERROR NOT GOOD")).To(Equal("\x1b[91mERROR NOT GOOD\x1b[0m"))
		})

		itShouldNotColorizeWhitespace(colors.Red)
	})

	Describe("Green", func() {
		It("adds the green color code", func() {
			Expect(colors.Green("TOO GOOD")).To(Equal("\x1b[32mTOO GOOD\x1b[0m"))
		})

		itShouldNotColorizeWhitespace(colors.Green)
	})

	Describe("Cyan", func() {
		It("adds the cyan color code", func() {
				State:         "COMPLETED",
				CellID:        "cell-01",
				Failed:        true,
				FailureReason: "womp womp",
				Result:        "",
			}
			fakeTaskExaminer.TaskStatusReturns(taskInfo, nil)

			test_helpers.ExecuteCommandWithArgs(taskCommand, []string{"boop"})

			Expect(outputBuffer).To(test_helpers.Say("Task Name"))
			Expect(outputBuffer).To(test_helpers.Say("boop"))
			Expect(outputBuffer).To(test_helpers.Say("Cell ID"))
			Expect(outputBuffer).To(test_helpers.Say("cell-01"))
			Expect(outputBuffer).To(test_helpers.Say("Status"))
			Expect(outputBuffer).To(test_helpers.Say(colors.Red("COMPLETED")))
			Expect(outputBuffer).NotTo(test_helpers.Say("Result"))
			Expect(outputBuffer).To(test_helpers.Say("Failure Reason"))
			Expect(outputBuffer).To(test_helpers.Say("womp womp"))

			Expect(fakeTaskExaminer.TaskStatusCallCount()).To(Equal(1))
			Expect(fakeTaskExaminer.TaskStatusArgsForCall(0)).To(Equal("boop"))
		})

		It("bails out when no task name passed", func() {
			test_helpers.ExecuteCommandWithArgs(taskCommand, []string{})

			Expect(outputBuffer).To(test_helpers.SayIncorrectUsage())
			Expect(fakeTaskExaminer.TaskStatusCallCount()).To(Equal(0))
			Expect(fakeExitHandler.ExitCalledWith).To(Equal([]int{exit_codes.InvalidSyntax}))
		})
			Expect(presentation.ColorInstanceState(instanceInfo)).To(Equal(colors.Green(string(receptor.ActualLRPStateRunning))))
		})

		It("colors CLAIMED yellow", func() {
			instanceInfo := app_examiner.InstanceInfo{State: string(receptor.ActualLRPStateClaimed)}
			Expect(presentation.ColorInstanceState(instanceInfo)).To(Equal(colors.Yellow(string(receptor.ActualLRPStateClaimed))))
		})

		Context("when there is a placement error", func() {
			It("colors UNCLAIMED red", func() {
				instanceInfo := app_examiner.InstanceInfo{
					State:          string(receptor.ActualLRPStateUnclaimed),
					PlacementError: "I misplaced my cells. Uh oh.",
				}

				Expect(presentation.ColorInstanceState(instanceInfo)).To(Equal(colors.Red(string(receptor.ActualLRPStateUnclaimed))))
			})
		})

		Context("when there is not a placement error", func() {
			It("colors UNCLAIMED cyan", func() {
				instanceInfo := app_examiner.InstanceInfo{State: string(receptor.ActualLRPStateUnclaimed)}
				Expect(presentation.ColorInstanceState(instanceInfo)).To(Equal(colors.Cyan(string(receptor.ActualLRPStateUnclaimed))))
			})
		})

		It("colors INVALID red", func() {
			instanceInfo := app_examiner.InstanceInfo{State: string(receptor.ActualLRPStateInvalid)}
			Expect(presentation.ColorInstanceState(instanceInfo)).To(Equal(colors.Red(string(receptor.ActualLRPStateInvalid))))
		})
func (factory *DropletRunnerCommandFactory) buildDroplet(context *cli.Context) {
	pathFlag := context.String("path")
	cpuWeightFlag := context.Int("cpu-weight")
	memoryMBFlag := context.Int("memory-mb")
	diskMBFlag := context.Int("disk-mb")
	envFlag := context.StringSlice("env")
	timeoutFlag := context.Duration("timeout")
	dropletName := context.Args().First()
	buildpack := context.Args().Get(1)

	if dropletName == "" || buildpack == "" {
		factory.UI.SayIncorrectUsage("")
		factory.ExitHandler.Exit(exit_codes.InvalidSyntax)
		return
	}

	var buildpackUrl string
	if knownBuildpackUrl, ok := knownBuildpacks[buildpack]; ok {
		buildpackUrl = knownBuildpackUrl
	} else if _, err := url.ParseRequestURI(buildpack); err == nil {
		buildpackUrl = buildpack
	} else {
		factory.UI.SayIncorrectUsage(fmt.Sprintf("invalid buildpack %s", buildpack))
		factory.ExitHandler.Exit(exit_codes.InvalidSyntax)
		return
	}

	if cpuWeightFlag < 1 || cpuWeightFlag > 100 {
		factory.UI.SayIncorrectUsage("invalid CPU Weight")
		factory.ExitHandler.Exit(exit_codes.InvalidSyntax)
		return
	}

	if !factory.ensureBlobStoreVerified() {
		return
	}

	var archivePath string
	var err error

	if factory.zipper.IsZipFile(pathFlag) {
		tmpDir, err := ioutil.TempDir("", "rezip")
		if err != nil {
			factory.UI.SayLine(fmt.Sprintf("Error re-archiving %s: %s", pathFlag, err))
			factory.ExitHandler.Exit(exit_codes.FileSystemError)
			return
		}
		defer os.RemoveAll(tmpDir)

		if err := factory.zipper.Unzip(pathFlag, tmpDir); err != nil {
			factory.UI.SayLine(fmt.Sprintf("Error unarchiving %s: %s", pathFlag, err))
			factory.ExitHandler.Exit(exit_codes.FileSystemError)
			return
		}

		archivePath, err = factory.zipper.Zip(tmpDir, factory.cfIgnore)
		if err != nil {
			factory.UI.SayLine(fmt.Sprintf("Error re-archiving %s: %s", pathFlag, err))
			factory.ExitHandler.Exit(exit_codes.FileSystemError)
			return
		}
		defer os.Remove(archivePath)
	} else {
		archivePath, err = factory.zipper.Zip(pathFlag, factory.cfIgnore)
		if err != nil {
			factory.UI.SayLine(fmt.Sprintf("Error archiving %s: %s", pathFlag, err))
			factory.ExitHandler.Exit(exit_codes.FileSystemError)
			return
		}
		defer os.Remove(archivePath)
	}

	factory.UI.SayLine("Uploading application bits...")

	if err := factory.dropletRunner.UploadBits(dropletName, archivePath); err != nil {
		factory.UI.SayLine(fmt.Sprintf("Error uploading %s: %s", dropletName, err))
		factory.ExitHandler.Exit(exit_codes.CommandFailed)
		return
	}

	factory.UI.SayLine("Uploaded.")

	environment := factory.AppRunnerCommandFactory.BuildEnvironment(envFlag)

	taskName := "build-droplet-" + dropletName
	if err := factory.dropletRunner.BuildDroplet(taskName, dropletName, buildpackUrl, environment, memoryMBFlag, cpuWeightFlag, diskMBFlag); err != nil {
		factory.UI.SayLine(fmt.Sprintf("Error submitting build of %s: %s", dropletName, err))
		factory.ExitHandler.Exit(exit_codes.CommandFailed)
		return
	}

	factory.UI.SayLine("Submitted build of " + dropletName)

	go factory.TailedLogsOutputter.OutputTailedLogs(taskName)
	defer factory.TailedLogsOutputter.StopOutputting()

	ok, taskState := factory.waitForBuildTask(timeoutFlag, taskName)
	if ok {
		if taskState.Failed {
			factory.UI.SayLine("Build failed: " + taskState.FailureReason)
			factory.ExitHandler.Exit(exit_codes.CommandFailed)
		} else {
			factory.UI.SayLine("Build completed")
		}
	} else {
		factory.UI.SayLine(colors.Red("Timed out waiting for the build to complete."))
		factory.UI.SayLine("Lattice is still building your application in the background.")

		factory.UI.SayLine(fmt.Sprintf("To view logs:\n\tltc logs %s", taskName))
		factory.UI.SayLine(fmt.Sprintf("To view status:\n\tltc status %s", taskName))
		factory.UI.SayNewLine()
	}
}