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..6503d124 100644 --- a/internal/api/handlers/app_delete.go +++ b/internal/api/handlers/app_delete.go @@ -19,13 +19,18 @@ 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" ) -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 +49,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/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 19181998..7f5b6e93 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{data: fmt.Sprintf("app %q is not running", app.Name)}) + return + } + if !yield(StreamMessage{data: fmt.Sprintf("Stopping app %q", app.Name)}) { return } @@ -395,6 +405,7 @@ func stopAppWithCmd(ctx context.Context, app app.ArduinoApp, cmd string) iter.Se return } }) + if app.MainSketchPath != nil { // TODO: check that the app sketch is running before attempting to stop it. @@ -425,12 +436,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 +469,7 @@ func RestartApp( return } - stopStream := StopApp(ctx, *runningApp) + stopStream := StopApp(ctx, docker, *runningApp) for msg := range stopStream { if !yield(msg) { return @@ -888,12 +899,19 @@ func CloneApp( return CloneAppResponse{ID: id}, nil } -func DeleteApp(ctx context.Context, app app.ArduinoApp) error { - for msg := range StopApp(ctx, app) { - if msg.error != nil { - return fmt.Errorf("failed to stop app: %w", msg.error) +func DeleteApp(ctx context.Context, dockerClient command.Cli, app app.ArduinoApp) 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() } 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