Skip to content

Commit 4170e9d

Browse files
authored
[CLI] Add a "restart" app command (#37)
The CLI provides a command to start and stop an app/example. Often it's useful to have a restart command, that will stop and start again the same app. This is needed only for the CLI interface. The command already exists. But error management should be improved: restarting an app X when app Y is running does not show a clear error if the app running is not the one being restarted: error if no app is running: just start the app
1 parent 1b200b1 commit 4170e9d

File tree

2 files changed

+106
-6
lines changed

2 files changed

+106
-6
lines changed

cmd/arduino-app-cli/app/restart.go

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,18 @@
1616
package app
1717

1818
import (
19+
"context"
20+
"fmt"
21+
1922
"github.com/spf13/cobra"
23+
"golang.org/x/text/cases"
24+
"golang.org/x/text/language"
2025

2126
"github.com/arduino/arduino-app-cli/cmd/arduino-app-cli/completion"
27+
"github.com/arduino/arduino-app-cli/cmd/arduino-app-cli/internal/servicelocator"
2228
"github.com/arduino/arduino-app-cli/cmd/feedback"
29+
"github.com/arduino/arduino-app-cli/internal/orchestrator"
30+
"github.com/arduino/arduino-app-cli/internal/orchestrator/app"
2331
"github.com/arduino/arduino-app-cli/internal/orchestrator/config"
2432
)
2533

@@ -32,17 +40,63 @@ func newRestartCmd(cfg config.Configuration) *cobra.Command {
3240
if len(args) == 0 {
3341
return cmd.Help()
3442
}
35-
app, err := Load(args[0])
43+
appToStart, err := Load(args[0])
3644
if err != nil {
3745
feedback.Fatal(err.Error(), feedback.ErrBadArgument)
38-
return nil
39-
}
40-
if err := stopHandler(cmd.Context(), app); err != nil {
41-
feedback.Warnf("failed to stop app: %s", err.Error())
4246
}
43-
return startHandler(cmd.Context(), cfg, app)
47+
return restartHandler(cmd.Context(), cfg, appToStart)
4448
},
4549
ValidArgsFunction: completion.ApplicationNames(cfg),
4650
}
4751
return cmd
4852
}
53+
54+
func restartHandler(ctx context.Context, cfg config.Configuration, app app.ArduinoApp) error {
55+
out, _, getResult := feedback.OutputStreams()
56+
57+
stream := orchestrator.RestartApp(
58+
ctx,
59+
servicelocator.GetDockerClient(),
60+
servicelocator.GetProvisioner(),
61+
servicelocator.GetModelsIndex(),
62+
servicelocator.GetBricksIndex(),
63+
app,
64+
cfg,
65+
servicelocator.GetStaticStore(),
66+
)
67+
for message := range stream {
68+
switch message.GetType() {
69+
case orchestrator.ProgressType:
70+
fmt.Fprintf(out, "Progress[%s]: %.0f%%\n", message.GetProgress().Name, message.GetProgress().Progress)
71+
case orchestrator.InfoType:
72+
fmt.Fprintln(out, "[INFO]", message.GetData())
73+
case orchestrator.ErrorType:
74+
errMesg := cases.Title(language.AmericanEnglish).String(message.GetError().Error())
75+
feedback.Fatal(fmt.Sprintf("[ERROR] %s", errMesg), feedback.ErrGeneric)
76+
return nil
77+
}
78+
}
79+
80+
outputResult := getResult()
81+
feedback.PrintResult(restartAppResult{
82+
AppName: app.Name,
83+
Status: "restarted",
84+
Output: outputResult,
85+
})
86+
87+
return nil
88+
}
89+
90+
type restartAppResult struct {
91+
AppName string `json:"app_name"`
92+
Status string `json:"status"`
93+
Output *feedback.OutputStreamsResult `json:"output,omitempty"`
94+
}
95+
96+
func (r restartAppResult) String() string {
97+
return fmt.Sprintf("✓ App %q restarted successfully", r.AppName)
98+
}
99+
100+
func (r restartAppResult) Data() interface{} {
101+
return r
102+
}

internal/orchestrator/orchestrator.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ func StartApp(
131131
yield(StreamMessage{error: fmt.Errorf("app %q is running", running.Name)})
132132
return
133133
}
134+
if !yield(StreamMessage{data: fmt.Sprintf("Starting app %q", app.Name)}) {
135+
return
136+
}
134137

135138
if err := setStatusLeds(LedTriggerNone); err != nil {
136139
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
379382
ctx, cancel := context.WithCancel(ctx)
380383
defer cancel()
381384

385+
if !yield(StreamMessage{data: fmt.Sprintf("Stopping app %q", app.Name)}) {
386+
return
387+
}
382388
if err := setStatusLeds(LedTriggerDefault); err != nil {
383389
slog.Debug("unable to set status leds", slog.String("error", err.Error()))
384390
}
@@ -427,6 +433,46 @@ func StopAndDestroyApp(ctx context.Context, app app.ArduinoApp) iter.Seq[StreamM
427433
return stopAppWithCmd(ctx, app, "down")
428434
}
429435

436+
func RestartApp(
437+
ctx context.Context,
438+
docker command.Cli,
439+
provisioner *Provision,
440+
modelsIndex *modelsindex.ModelsIndex,
441+
bricksIndex *bricksindex.BricksIndex,
442+
appToStart app.ArduinoApp,
443+
cfg config.Configuration,
444+
staticStore *store.StaticStore,
445+
) iter.Seq[StreamMessage] {
446+
return func(yield func(StreamMessage) bool) {
447+
ctx, cancel := context.WithCancel(ctx)
448+
defer cancel()
449+
runningApp, err := getRunningApp(ctx, docker.Client())
450+
if err != nil {
451+
yield(StreamMessage{error: err})
452+
return
453+
}
454+
455+
if runningApp != nil {
456+
if runningApp.FullPath.String() != appToStart.FullPath.String() {
457+
yield(StreamMessage{error: fmt.Errorf("another app %q is running", runningApp.Name)})
458+
return
459+
}
460+
461+
stopStream := StopApp(ctx, *runningApp)
462+
for msg := range stopStream {
463+
if !yield(msg) {
464+
return
465+
}
466+
if msg.error != nil {
467+
return
468+
}
469+
}
470+
}
471+
startStream := StartApp(ctx, docker, provisioner, modelsIndex, bricksIndex, appToStart, cfg, staticStore)
472+
startStream(yield)
473+
}
474+
}
475+
430476
func StartDefaultApp(
431477
ctx context.Context,
432478
docker command.Cli,

0 commit comments

Comments
 (0)