diff --git a/cmd/meroxa/root/apps/deploy.go b/cmd/meroxa/root/apps/deploy.go index 1077541aa..a16eba1f1 100644 --- a/cmd/meroxa/root/apps/deploy.go +++ b/cmd/meroxa/root/apps/deploy.go @@ -203,24 +203,25 @@ func (d *Deploy) createApplication(ctx context.Context, pipelineUUID, gitSha str var app *meroxa.Application app, err = d.client.GetApplication(ctx, appName) if err != nil { - d.logger.StopSpinner("\t") + d.logger.StopSpinnerWithStatus("\t", log.Failed) return err } if app.Pipeline.UUID.String != pipelineUUID { - d.logger.StopSpinner(fmt.Sprintf("\t 𐄂 unable to finish creating the %s Application because its entities are in an"+ - " unrecoverable state; try deleting and re-deploying", appName)) + d.logger.StopSpinnerWithStatus(fmt.Sprintf("unable to finish creating the %s Application because its entities are in an"+ + " unrecoverable state; try deleting and re-deploying", appName), log.Failed) - d.logger.StopSpinner("\t") + d.logger.StopSpinnerWithStatus("\t", log.Failed) // TODO: Rollback here? return fmt.Errorf("unable to finish creating application") } } - d.logger.StopSpinner("\t") + d.logger.StopSpinnerWithStatus("\t", log.Failed) return err } dashboardURL := fmt.Sprintf("https://dashboard.meroxa.io/v2/apps/%s/detail", res.UUID) - output := fmt.Sprintf("\t✔ Application %q successfully created!\n\n ✨ To visualize your application visit %s", res.Name, dashboardURL) + output := fmt.Sprintf("\t%s Application %q successfully created!\n\n ✨ To visualize your application visit %s", + d.logger.SuccessfulCheck(), res.Name, dashboardURL) d.logger.StopSpinner(output) d.logger.JSON(ctx, res) return nil @@ -289,7 +290,7 @@ func (d *Deploy) uploadFile(ctx context.Context, filePath, url string) error { fh, err := os.Open(filePath) if err != nil { - d.logger.StopSpinner("\t") + d.logger.StopSpinnerWithStatus("\t", log.Failed) return err } defer func(fh *os.File) { @@ -298,13 +299,13 @@ func (d *Deploy) uploadFile(ctx context.Context, filePath, url string) error { req, err := http.NewRequestWithContext(ctx, "PUT", url, fh) if err != nil { - d.logger.StopSpinner("\t") + d.logger.StopSpinnerWithStatus("\t", log.Failed) return err } fi, err := fh.Stat() if err != nil { - d.logger.StopSpinner("\t") + d.logger.StopSpinnerWithStatus("\t", log.Failed) return err } @@ -318,7 +319,7 @@ func (d *Deploy) uploadFile(ctx context.Context, filePath, url string) error { client := &http.Client{} res, err := client.Do(req) //nolint:bodyclose if err != nil { - d.logger.StopSpinner("\t") + d.logger.StopSpinnerWithStatus("\t", log.Failed) return err } defer func(Body io.ReadCloser) { @@ -328,7 +329,7 @@ func (d *Deploy) uploadFile(ctx context.Context, filePath, url string) error { } }(res.Body) - d.logger.StopSpinner("\t✔ Source uploaded!") + d.logger.StopSpinnerWithStatus("Source uploaded!", log.Successful) return nil } @@ -338,10 +339,10 @@ func (d *Deploy) getPlatformImage(ctx context.Context, appPath string) (string, s, err := d.client.CreateSource(ctx) if err != nil { d.logger.Errorf(ctx, "\t 𐄂 Unable to fetch source") - d.logger.StopSpinner("\t") + d.logger.StopSpinnerWithStatus("\t", log.Failed) return "", err } - d.logger.StopSpinner("\t✔ Platform source fetched!") + d.logger.StopSpinnerWithStatus("Platform source fetched!", log.Successful) err = d.uploadSource(ctx, appPath, s.PutUrl) if err != nil { @@ -355,23 +356,24 @@ func (d *Deploy) getPlatformImage(ctx context.Context, appPath string) (string, build, err := d.client.CreateBuild(ctx, buildInput) if err != nil { - d.logger.StopSpinner("\t") + d.logger.StopSpinnerWithStatus("\t", log.Failed) return "", err } for { b, err := d.client.GetBuild(ctx, build.Uuid) if err != nil { - d.logger.StopSpinner("\t") + d.logger.StopSpinnerWithStatus("\t", log.Failed) return "", err } switch b.Status.State { case "error": - d.logger.StopSpinner(fmt.Sprintf("\t 𐄂 build with uuid %q errored\nRun `meroxa build logs %s` for more information", b.Uuid, b.Uuid)) + msg := fmt.Sprintf("build with uuid %q errored\nRun `meroxa build logs %s` for more information", b.Uuid, b.Uuid) + d.logger.StopSpinnerWithStatus(msg, log.Failed) return "", fmt.Errorf("build with uuid %q errored", b.Uuid) case "complete": - d.logger.StopSpinner("\t✔ Process image built!") + d.logger.StopSpinnerWithStatus("Process image built!", log.Successful) return build.Image, nil } time.Sleep(pollDuration) @@ -385,21 +387,19 @@ func (d *Deploy) deployApp(ctx context.Context, imageName string) (string, error d.logger.StartSpinner("\t", fmt.Sprintf(" Deploying application %q...", d.appName)) switch d.lang { case GoLang: - output, err = turbineGo.RunDeployApp(ctx, d.logger, d.path, d.appName, imageName) + output, err = turbineGo.RunDeployApp(ctx, d.logger, d.path, imageName, d.appName) case JavaScript: - // TODO: @james - // TODO: Do less here!!! - output, err = turbineJS.Deploy(ctx, d.path, imageName, d.logger) + output, err = turbineJS.RunDeployApp(ctx, d.logger, d.path, imageName) case Python: // TODO: @eric } if err != nil { - d.logger.StopSpinner("\t𐄂 Deployment failed\n\n") + d.logger.StopSpinnerWithStatus("Deployment failed\n\n", log.Failed) return "", err } - d.logger.StopSpinner("\t✔ Deploy complete!") + d.logger.StopSpinnerWithStatus("Deploy complete!", log.Successful) return output, nil } @@ -421,10 +421,10 @@ func (d *Deploy) buildApp(ctx context.Context) error { // Dockerfile will already exist } if err != nil { - d.logger.StopSpinner("\t") + d.logger.StopSpinnerWithStatus("\t", log.Failed) return err } - d.logger.StopSpinner("\t✔ Application built!") + d.logger.StopSpinnerWithStatus("Application built!", log.Successful) return nil } @@ -445,17 +445,17 @@ func (d *Deploy) getAppImage(ctx context.Context) (string, error) { // TODO: @eric } if err != nil { - d.logger.StopSpinner("\t") + d.logger.StopSpinnerWithStatus("\t", log.Failed) return "", err } // If no need to build, return empty imageName which won't be utilized by the deploy process anyways if !needsToBuild { - d.logger.StopSpinner("\t✔ No need to create process image...") + d.logger.StopSpinnerWithStatus("No need to create process image...", log.Successful) return "", nil } - d.logger.StopSpinner("\t✔ Application processes found. Creating application image...") + d.logger.StopSpinnerWithStatus("Application processes found. Creating application image...", log.Successful) d.localDeploy.TempPath = d.tempPath d.localDeploy.Lang = d.lang diff --git a/cmd/meroxa/turbine_cli/golang/deploy.go b/cmd/meroxa/turbine_cli/golang/deploy.go index a151b8cc2..01584c8b7 100644 --- a/cmd/meroxa/turbine_cli/golang/deploy.go +++ b/cmd/meroxa/turbine_cli/golang/deploy.go @@ -13,7 +13,7 @@ import ( ) // RunDeployApp runs the binary previously built with the `--deploy` flag which should create all necessary resources. -func RunDeployApp(ctx context.Context, l log.Logger, appPath, appName, imageName string) (string, error) { +func RunDeployApp(ctx context.Context, l log.Logger, appPath, imageName, appName string) (string, error) { var cmd *exec.Cmd if imageName != "" { diff --git a/cmd/meroxa/turbine_cli/javascript/deploy.go b/cmd/meroxa/turbine_cli/javascript/deploy.go index b795fe2e7..fbca0608d 100644 --- a/cmd/meroxa/turbine_cli/javascript/deploy.go +++ b/cmd/meroxa/turbine_cli/javascript/deploy.go @@ -35,7 +35,7 @@ func BuildApp(path string) (string, error) { return strings.TrimSpace(string(output)), err } -func Deploy(ctx context.Context, path, imageName string, l log.Logger) (string, error) { +func RunDeployApp(ctx context.Context, l log.Logger, path, imageName string) (string, error) { cmd := exec.Command("npx", "turbine", "clideploy", imageName, path) accessToken, _, err := global.GetUserToken() diff --git a/cmd/meroxa/turbine_cli/local_deployment.go b/cmd/meroxa/turbine_cli/local_deployment.go index 23be02141..6aba22e9d 100644 --- a/cmd/meroxa/turbine_cli/local_deployment.go +++ b/cmd/meroxa/turbine_cli/local_deployment.go @@ -54,11 +54,11 @@ func (ld *LocalDeploy) GetDockerImageName(ctx context.Context, l log.Logger, app // this will go away when using the new service. err = ld.pushImage(l, fqImageName) if err != nil { - l.Errorf(ctx, "\t 𐄂 Unable to push image %q", fqImageName) + l.Errorf(ctx, "\t %s Unable to push image %q", l.FailedMark(), fqImageName) return "", err } - l.Infof(ctx, "\t✔ Image built!") + l.Infof(ctx, "\t%s Image built!", l.SuccessfulCheck()) return fqImageName, nil } @@ -66,7 +66,7 @@ func (ld *LocalDeploy) buildImage(ctx context.Context, l log.Logger, pwd, imageN l.Infof(ctx, "\t Building image %q located at %q", imageName, pwd) cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) if err != nil { - l.Errorf(ctx, "\t 𐄂 Unable to init docker client") + l.Errorf(ctx, "\t %s Unable to init docker client", l.FailedMark()) return err } @@ -87,7 +87,7 @@ func (ld *LocalDeploy) buildImage(ctx context.Context, l log.Logger, pwd, imageN ExcludePatterns: []string{".git", "fixtures"}, }) if err != nil { - l.Errorf(ctx, "\t 𐄂 Unable to create tar") + l.Errorf(ctx, "\t %s Unable to create tar", l.FailedMark()) return err } @@ -103,20 +103,20 @@ func (ld *LocalDeploy) buildImage(ctx context.Context, l log.Logger, pwd, imageN buildOptions, ) if err != nil { - l.Errorf(ctx, "\t 𐄂 Unable to build docker image") + l.Errorf(ctx, "\t %s Unable to build docker image", l.FailedMark()) return err } defer func(Body io.ReadCloser) { err = Body.Close() if err != nil { - l.Errorf(ctx, "\t 𐄂 Unable to close docker build response body; %s", err) + l.Errorf(ctx, "\t %s Unable to close docker build response body; %s", l.FailedMark(), err) } }(resp.Body) buf := new(strings.Builder) _, err = io.Copy(buf, resp.Body) if err != nil { - l.Errorf(ctx, "\t 𐄂 Unable to read image build response") + l.Errorf(ctx, "\t %s Unable to read image build response", l.FailedMark()) return err } dockerBuildOutput := buf.String() @@ -133,7 +133,7 @@ func (ld *LocalDeploy) buildImage(ctx context.Context, l log.Logger, pwd, imageN errMsg += "\n" + str[1] } err = fmt.Errorf("%s", errMsg) - l.Errorf(ctx, "\t 𐄂 Unable to build docker image") + l.Errorf(ctx, "\t %s Unable to build docker image", l.FailedMark()) return err } l.Info(ctx, dockerBuildOutput) diff --git a/cmd/meroxa/turbine_cli/utils.go b/cmd/meroxa/turbine_cli/utils.go index 1376e924c..c548efa26 100644 --- a/cmd/meroxa/turbine_cli/utils.go +++ b/cmd/meroxa/turbine_cli/utils.go @@ -174,7 +174,7 @@ func GitChecks(ctx context.Context, l log.Logger, appPath string) error { } return fmt.Errorf("unable to proceed with deployment because of uncommitted changes") } - l.Info(ctx, "\t✔ No uncommitted changes!") + l.Infof(ctx, "\t%s No uncommitted changes!", l.SuccessfulCheck()) return os.Chdir(pwd) } @@ -208,7 +208,7 @@ func ValidateBranch(ctx context.Context, l log.Logger, appPath string) error { if branchName != "main" && branchName != "master" { return fmt.Errorf("deployment allowed only from 'main' or 'master' branch, not %s", branchName) } - l.Infof(ctx, "\t✔ Deployment allowed from %s branch!", branchName) + l.Infof(ctx, "\t%s Deployment allowed from %s branch!", l.SuccessfulCheck(), branchName) err = os.Chdir(pwd) if err != nil { return err diff --git a/log/spinner.go b/log/spinner.go index 723701eed..e1baa46f7 100644 --- a/log/spinner.go +++ b/log/spinner.go @@ -1,16 +1,27 @@ package log import ( + "fmt" "io" "log" "time" + "github.com/fatih/color" + "github.com/briandowns/spinner" ) +const ( + Successful = "successful" + Failed = "failed" +) + type SpinnerLogger interface { StartSpinner(prefix, suffix string) StopSpinner(msg string) + StopSpinnerWithStatus(msg, status string) + SuccessfulCheck() string + FailedMark() string } func NewSpinnerLogger(out io.Writer) SpinnerLogger { @@ -33,3 +44,21 @@ func (l *spinnerLogger) StopSpinner(msg string) { l.s.Stop() l.l.Printf(msg) } + +func (l *spinnerLogger) StopSpinnerWithStatus(msg, status string) { + if status == Failed { + msg = fmt.Sprintf("\t%s %s", l.FailedMark(), msg) + } else if status == Successful { + msg = fmt.Sprintf("\t%s %s", l.SuccessfulCheck(), msg) + } + l.s.Stop() + l.l.Printf(msg) +} + +func (l *spinnerLogger) SuccessfulCheck() string { + return color.New(color.FgGreen).Sprintf("✔") +} + +func (l *spinnerLogger) FailedMark() string { + return color.New(color.FgRed).Sprintf("x") +}