diff --git a/cmd/arduino-app-cli/app/restart.go b/cmd/arduino-app-cli/app/restart.go index 3462a9a5..892f108f 100644 --- a/cmd/arduino-app-cli/app/restart.go +++ b/cmd/arduino-app-cli/app/restart.go @@ -16,10 +16,18 @@ package app import ( + "context" + "fmt" + "github.com/spf13/cobra" + "golang.org/x/text/cases" + "golang.org/x/text/language" "github.com/arduino/arduino-app-cli/cmd/arduino-app-cli/completion" + "github.com/arduino/arduino-app-cli/cmd/arduino-app-cli/internal/servicelocator" "github.com/arduino/arduino-app-cli/cmd/feedback" + "github.com/arduino/arduino-app-cli/internal/orchestrator" + "github.com/arduino/arduino-app-cli/internal/orchestrator/app" "github.com/arduino/arduino-app-cli/internal/orchestrator/config" ) @@ -32,17 +40,63 @@ func newRestartCmd(cfg config.Configuration) *cobra.Command { if len(args) == 0 { return cmd.Help() } - app, err := Load(args[0]) + appToStart, err := Load(args[0]) if err != nil { feedback.Fatal(err.Error(), feedback.ErrBadArgument) - return nil - } - if err := stopHandler(cmd.Context(), app); err != nil { - feedback.Warnf("failed to stop app: %s", err.Error()) } - return startHandler(cmd.Context(), cfg, app) + return restartHandler(cmd.Context(), cfg, appToStart) }, ValidArgsFunction: completion.ApplicationNames(cfg), } return cmd } + +func restartHandler(ctx context.Context, cfg config.Configuration, app app.ArduinoApp) error { + out, _, getResult := feedback.OutputStreams() + + stream := orchestrator.RestartApp( + ctx, + servicelocator.GetDockerClient(), + servicelocator.GetProvisioner(), + servicelocator.GetModelsIndex(), + servicelocator.GetBricksIndex(), + app, + cfg, + servicelocator.GetStaticStore(), + ) + for message := range stream { + switch message.GetType() { + case orchestrator.ProgressType: + fmt.Fprintf(out, "Progress[%s]: %.0f%%\n", message.GetProgress().Name, message.GetProgress().Progress) + case orchestrator.InfoType: + fmt.Fprintln(out, "[INFO]", message.GetData()) + case orchestrator.ErrorType: + errMesg := cases.Title(language.AmericanEnglish).String(message.GetError().Error()) + feedback.Fatal(fmt.Sprintf("[ERROR] %s", errMesg), feedback.ErrGeneric) + return nil + } + } + + outputResult := getResult() + feedback.PrintResult(restartAppResult{ + AppName: app.Name, + Status: "restarted", + Output: outputResult, + }) + + return nil +} + +type restartAppResult struct { + AppName string `json:"app_name"` + Status string `json:"status"` + Output *feedback.OutputStreamsResult `json:"output,omitempty"` +} + +func (r restartAppResult) String() string { + return fmt.Sprintf("✓ App %q restarted successfully", r.AppName) +} + +func (r restartAppResult) Data() interface{} { + return r +} diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go index 41890fa4..884515be 100644 --- a/internal/orchestrator/orchestrator.go +++ b/internal/orchestrator/orchestrator.go @@ -131,6 +131,9 @@ func StartApp( yield(StreamMessage{error: fmt.Errorf("app %q is running", running.Name)}) return } + if !yield(StreamMessage{data: fmt.Sprintf("Starting app %q", app.Name)}) { + return + } if err := setStatusLeds(LedTriggerNone); err != nil { slog.Debug("unable to set status leds", slog.String("error", err.Error())) @@ -379,6 +382,9 @@ func stopAppWithCmd(ctx context.Context, app app.ArduinoApp, cmd string) iter.Se ctx, cancel := context.WithCancel(ctx) defer cancel() + if !yield(StreamMessage{data: fmt.Sprintf("Stopping app %q", app.Name)}) { + return + } if err := setStatusLeds(LedTriggerDefault); err != nil { slog.Debug("unable to set status leds", slog.String("error", err.Error())) } @@ -427,6 +433,46 @@ func StopAndDestroyApp(ctx context.Context, app app.ArduinoApp) iter.Seq[StreamM return stopAppWithCmd(ctx, app, "down") } +func RestartApp( + ctx context.Context, + docker command.Cli, + provisioner *Provision, + modelsIndex *modelsindex.ModelsIndex, + bricksIndex *bricksindex.BricksIndex, + appToStart app.ArduinoApp, + cfg config.Configuration, + staticStore *store.StaticStore, +) iter.Seq[StreamMessage] { + return func(yield func(StreamMessage) bool) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + runningApp, err := getRunningApp(ctx, docker.Client()) + if err != nil { + yield(StreamMessage{error: err}) + return + } + + if runningApp != nil { + if runningApp.FullPath.String() != appToStart.FullPath.String() { + yield(StreamMessage{error: fmt.Errorf("another app %q is running", runningApp.Name)}) + return + } + + stopStream := StopApp(ctx, *runningApp) + for msg := range stopStream { + if !yield(msg) { + return + } + if msg.error != nil { + return + } + } + } + startStream := StartApp(ctx, docker, provisioner, modelsIndex, bricksIndex, appToStart, cfg, staticStore) + startStream(yield) + } +} + func StartDefaultApp( ctx context.Context, docker command.Cli,