From 9040206d840dec33bd57ac04034d7d8a0b5d88b1 Mon Sep 17 00:00:00 2001 From: Giulio Pilotto Date: Mon, 17 Nov 2025 18:10:54 +0100 Subject: [PATCH 1/2] check app status in stop cmd --- cmd/arduino-app-cli/app/stop.go | 3 ++- internal/api/api.go | 2 +- internal/api/handlers/app_delete.go | 8 ++++++-- internal/api/handlers/app_stop.go | 2 +- internal/orchestrator/orchestrator.go | 28 +++++++++++++++++++-------- internal/orchestrator/system.go | 2 +- 6 files changed, 31 insertions(+), 14 deletions(-) diff --git a/cmd/arduino-app-cli/app/stop.go b/cmd/arduino-app-cli/app/stop.go index debf40e7..eff55dbd 100644 --- a/cmd/arduino-app-cli/app/stop.go +++ b/cmd/arduino-app-cli/app/stop.go @@ -22,6 +22,7 @@ import ( "github.com/spf13/cobra" "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" @@ -53,7 +54,7 @@ func newStopCmd(cfg config.Configuration) *cobra.Command { func stopHandler(ctx context.Context, app app.ArduinoApp) error { out, _, getResult := feedback.OutputStreams() - for message := range orchestrator.StopApp(ctx, app) { + for message := range orchestrator.StopApp(ctx, servicelocator.GetDockerClient(), app) { switch message.GetType() { case orchestrator.ProgressType: fmt.Fprintf(out, "Progress[%s]: %.0f%%\n", message.GetProgress().Name, message.GetProgress().Progress) diff --git a/internal/api/api.go b/internal/api/api.go index 08d31d84..65c66771 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -81,7 +81,7 @@ func NewHTTPRouter( mux.Handle("POST /v1/apps/{appID}/start", handlers.HandleAppStart(dockerClient, provisioner, modelsIndex, bricksIndex, idProvider, cfg, staticStore)) mux.Handle("POST /v1/apps/{appID}/stop", handlers.HandleAppStop(dockerClient, idProvider)) mux.Handle("POST /v1/apps/{appID}/clone", handlers.HandleAppClone(dockerClient, idProvider, cfg)) - mux.Handle("DELETE /v1/apps/{appID}", handlers.HandleAppDelete(idProvider)) + mux.Handle("DELETE /v1/apps/{appID}", handlers.HandleAppDelete(dockerClient, idProvider)) mux.Handle("GET /v1/apps/{appID}/exposed-ports", handlers.HandleAppPorts(bricksIndex, idProvider)) mux.Handle("PUT /v1/apps/{appID}/sketch/libraries/{libRef}", handlers.HandleSketchAddLibrary(idProvider)) mux.Handle("DELETE /v1/apps/{appID}/sketch/libraries/{libRef}", handlers.HandleSketchRemoveLibrary(idProvider)) diff --git a/internal/api/handlers/app_delete.go b/internal/api/handlers/app_delete.go index 864dd405..5e423fdc 100644 --- a/internal/api/handlers/app_delete.go +++ b/internal/api/handlers/app_delete.go @@ -23,9 +23,13 @@ import ( "github.com/arduino/arduino-app-cli/internal/orchestrator" "github.com/arduino/arduino-app-cli/internal/orchestrator/app" "github.com/arduino/arduino-app-cli/internal/render" + "github.com/docker/cli/cli/command" ) -func HandleAppDelete(idProvider *app.IDProvider) http.HandlerFunc { +func HandleAppDelete( + dockerClient command.Cli, + idProvider *app.IDProvider, +) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { id, err := idProvider.IDFromBase64(r.PathValue("appID")) if err != nil { @@ -44,7 +48,7 @@ func HandleAppDelete(idProvider *app.IDProvider) http.HandlerFunc { return } - err = orchestrator.DeleteApp(r.Context(), app) + err = orchestrator.DeleteApp(r.Context(), dockerClient, app) if err != nil { slog.Error("Unable to delete the app", slog.String("error", err.Error())) render.EncodeResponse(w, http.StatusInternalServerError, models.ErrorResponse{Details: "unable to delete the app"}) diff --git a/internal/api/handlers/app_stop.go b/internal/api/handlers/app_stop.go index 0b9ec8c0..f5a9979c 100644 --- a/internal/api/handlers/app_stop.go +++ b/internal/api/handlers/app_stop.go @@ -60,7 +60,7 @@ func HandleAppStop( type log struct { Message string `json:"message"` } - for item := range orchestrator.StopApp(r.Context(), app) { + for item := range orchestrator.StopApp(r.Context(), dockerClient, app) { switch item.GetType() { case orchestrator.ProgressType: sseStream.Send(render.SSEEvent{Type: "progress", Data: progress(*item.GetProgress())}) diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go index 19181998..73532b4c 100644 --- a/internal/orchestrator/orchestrator.go +++ b/internal/orchestrator/orchestrator.go @@ -377,11 +377,21 @@ func getVideoDevices() map[int]string { return deviceMap } -func stopAppWithCmd(ctx context.Context, app app.ArduinoApp, cmd string) iter.Seq[StreamMessage] { +func stopAppWithCmd(ctx context.Context, docker command.Cli, app app.ArduinoApp, cmd string) iter.Seq[StreamMessage] { return func(yield func(StreamMessage) bool) { ctx, cancel := context.WithCancel(ctx) defer cancel() + appStatus, err := getAppStatus(ctx, docker, app) + if err != nil { + yield(StreamMessage{error: err}) + return + } + if appStatus.Status != StatusStarting && appStatus.Status != StatusRunning { + yield(StreamMessage{error: fmt.Errorf("App %q is not running", app.Name)}) + return + } + if !yield(StreamMessage{data: fmt.Sprintf("Stopping app %q", app.Name)}) { return } @@ -395,6 +405,8 @@ func stopAppWithCmd(ctx context.Context, app app.ArduinoApp, cmd string) iter.Se return } }) + + fmt.Printf("this is the main sketch path:%s", app.MainSketchPath) if app.MainSketchPath != nil { // TODO: check that the app sketch is running before attempting to stop it. @@ -425,12 +437,12 @@ func stopAppWithCmd(ctx context.Context, app app.ArduinoApp, cmd string) iter.Se } } -func StopApp(ctx context.Context, app app.ArduinoApp) iter.Seq[StreamMessage] { - return stopAppWithCmd(ctx, app, "stop") +func StopApp(ctx context.Context, dockerClient command.Cli, app app.ArduinoApp) iter.Seq[StreamMessage] { + return stopAppWithCmd(ctx, dockerClient, app, "stop") } -func StopAndDestroyApp(ctx context.Context, app app.ArduinoApp) iter.Seq[StreamMessage] { - return stopAppWithCmd(ctx, app, "down") +func StopAndDestroyApp(ctx context.Context, dockerClient command.Cli, app app.ArduinoApp) iter.Seq[StreamMessage] { + return stopAppWithCmd(ctx, dockerClient, app, "down") } func RestartApp( @@ -458,7 +470,7 @@ func RestartApp( return } - stopStream := StopApp(ctx, *runningApp) + stopStream := StopApp(ctx, docker, *runningApp) for msg := range stopStream { if !yield(msg) { return @@ -888,8 +900,8 @@ func CloneApp( return CloneAppResponse{ID: id}, nil } -func DeleteApp(ctx context.Context, app app.ArduinoApp) error { - for msg := range StopApp(ctx, app) { +func DeleteApp(ctx context.Context, dockerClient command.Cli, app app.ArduinoApp) error { + for msg := range StopApp(ctx, dockerClient, app) { if msg.error != nil { return fmt.Errorf("failed to stop app: %w", msg.error) } diff --git a/internal/orchestrator/system.go b/internal/orchestrator/system.go index 341d6c55..4d9f9a60 100644 --- a/internal/orchestrator/system.go +++ b/internal/orchestrator/system.go @@ -235,7 +235,7 @@ func SystemCleanup(ctx context.Context, cfg config.Configuration, staticStore *s feedback.Warnf("failed to get running app - %v", err) } if runningApp != nil { - for item := range StopAndDestroyApp(ctx, *runningApp) { + for item := range StopAndDestroyApp(ctx, docker, *runningApp) { if item.GetType() == ErrorType { feedback.Warnf("failed to stop and destroy running app - %v", item.GetError()) break From 55084efb3373b76c0d2cc76442c360b2d9a8b470 Mon Sep 17 00:00:00 2001 From: Giulio Date: Tue, 18 Nov 2025 09:30:32 +0100 Subject: [PATCH 2/2] Update internal/orchestrator/orchestrator.go Co-authored-by: Luca Rinaldi --- internal/api/handlers/app_delete.go | 3 ++- internal/orchestrator/cache.go | 2 +- internal/orchestrator/orchestrator.go | 16 +++++++++++----- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/internal/api/handlers/app_delete.go b/internal/api/handlers/app_delete.go index 5e423fdc..6503d124 100644 --- a/internal/api/handlers/app_delete.go +++ b/internal/api/handlers/app_delete.go @@ -19,11 +19,12 @@ import ( "log/slog" "net/http" + "github.com/docker/cli/cli/command" + "github.com/arduino/arduino-app-cli/internal/api/models" "github.com/arduino/arduino-app-cli/internal/orchestrator" "github.com/arduino/arduino-app-cli/internal/orchestrator/app" "github.com/arduino/arduino-app-cli/internal/render" - "github.com/docker/cli/cli/command" ) func HandleAppDelete( diff --git a/internal/orchestrator/cache.go b/internal/orchestrator/cache.go index 62b3a950..b4134ff6 100644 --- a/internal/orchestrator/cache.go +++ b/internal/orchestrator/cache.go @@ -32,7 +32,7 @@ func CleanAppCache( return ErrCleanCacheRunningApp } // We try to remove docker related resources at best effort - for range StopAndDestroyApp(ctx, app) { + for range StopAndDestroyApp(ctx, docker, app) { // just consume the iterator } } diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go index 73532b4c..7f5b6e93 100644 --- a/internal/orchestrator/orchestrator.go +++ b/internal/orchestrator/orchestrator.go @@ -388,7 +388,7 @@ func stopAppWithCmd(ctx context.Context, docker command.Cli, app app.ArduinoApp, return } if appStatus.Status != StatusStarting && appStatus.Status != StatusRunning { - yield(StreamMessage{error: fmt.Errorf("App %q is not running", app.Name)}) + yield(StreamMessage{data: fmt.Sprintf("app %q is not running", app.Name)}) return } @@ -406,7 +406,6 @@ func stopAppWithCmd(ctx context.Context, docker command.Cli, app app.ArduinoApp, } }) - fmt.Printf("this is the main sketch path:%s", app.MainSketchPath) if app.MainSketchPath != nil { // TODO: check that the app sketch is running before attempting to stop it. @@ -901,11 +900,18 @@ func CloneApp( } func DeleteApp(ctx context.Context, dockerClient command.Cli, app app.ArduinoApp) error { - for msg := range StopApp(ctx, dockerClient, app) { - if msg.error != nil { - return fmt.Errorf("failed to stop app: %w", msg.error) + + runningApp, err := getRunningApp(ctx, dockerClient.Client()) + if err != nil { + return err + } + if runningApp != nil && runningApp.FullPath.EqualsTo(app.FullPath) { + // We try to remove docker related resources at best effort + for range StopAndDestroyApp(ctx, dockerClient, app) { + // just consume the iterator } } + return app.FullPath.RemoveAll() }