Skip to content

Commit f5804ec

Browse files
lucarin91dido18
andauthored
feat(orchestrator): add app status sse handler
Co-authored-by: Davide <davideneri18@gmail.com>
1 parent 273b7c7 commit f5804ec

File tree

6 files changed

+127
-16
lines changed

6 files changed

+127
-16
lines changed

internal/api/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ func NewHTTPRouter(
5858

5959
mux.Handle("GET /v1/apps", handlers.HandleAppList(dockerClient, idProvider, cfg))
6060
mux.Handle("POST /v1/apps", handlers.HandleAppCreate(idProvider, cfg))
61+
mux.Handle("GET /v1/apps/events", handlers.HandlerAppStatus(dockerClient, idProvider, cfg))
6162

6263
mux.Handle("GET /v1/apps/{appID}", handlers.HandleAppDetails(dockerClient, bricksIndex, idProvider, cfg))
6364
mux.Handle("PATCH /v1/apps/{appID}", handlers.HandleAppDetailsEdits(dockerClient, bricksIndex, idProvider, cfg))

internal/api/handlers/app_start.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"log/slog"
55
"net/http"
66

7+
"github.com/docker/cli/cli/command"
8+
79
"github.com/arduino/arduino-app-cli/internal/api/models"
810
"github.com/arduino/arduino-app-cli/internal/orchestrator"
911
"github.com/arduino/arduino-app-cli/internal/orchestrator/app"
@@ -12,8 +14,6 @@ import (
1214
"github.com/arduino/arduino-app-cli/internal/orchestrator/modelsindex"
1315
"github.com/arduino/arduino-app-cli/internal/store"
1416
"github.com/arduino/arduino-app-cli/pkg/render"
15-
16-
"github.com/docker/cli/cli/command"
1717
)
1818

1919
func HandleAppStart(
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package handlers
2+
3+
import (
4+
"log/slog"
5+
"net/http"
6+
"time"
7+
8+
"github.com/docker/cli/cli/command"
9+
10+
"github.com/arduino/arduino-app-cli/internal/api/models"
11+
"github.com/arduino/arduino-app-cli/internal/orchestrator"
12+
"github.com/arduino/arduino-app-cli/internal/orchestrator/app"
13+
"github.com/arduino/arduino-app-cli/internal/orchestrator/config"
14+
"github.com/arduino/arduino-app-cli/pkg/render"
15+
)
16+
17+
func HandlerAppStatus(
18+
dockerCli command.Cli,
19+
idProvider *app.IDProvider,
20+
cfg config.Configuration,
21+
) http.HandlerFunc {
22+
return func(w http.ResponseWriter, r *http.Request) {
23+
sseStream, err := render.NewSSEStream(r.Context(), w)
24+
if err != nil {
25+
slog.Error("Unable to create SSE stream", slog.String("error", err.Error()))
26+
render.EncodeResponse(w, http.StatusInternalServerError, models.ErrorResponse{Details: "unable to create SSE stream"})
27+
return
28+
}
29+
defer sseStream.Close()
30+
31+
// TODO: maybe we should limit the size of this cache?
32+
stateCache := make(map[string]orchestrator.Status)
33+
34+
for {
35+
apps, err := orchestrator.AppStatus(r.Context(), cfg, dockerCli.Client(), idProvider)
36+
if err != nil {
37+
slog.Error("Unable to get apps status", slog.String("error", err.Error()))
38+
render.EncodeResponse(w, http.StatusInternalServerError, models.ErrorResponse{Details: "unable to get apps status"})
39+
return
40+
}
41+
for _, a := range apps {
42+
status, exist := stateCache[a.ID.String()]
43+
if !exist || status != a.Status {
44+
sseStream.Send(render.SSEEvent{Type: "app", Data: a})
45+
}
46+
47+
stateCache[a.ID.String()] = a.Status
48+
}
49+
50+
select {
51+
case <-r.Context().Done():
52+
return
53+
case <-time.After(1 * time.Second):
54+
}
55+
}
56+
}
57+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package orchestrator
2+
3+
import (
4+
"context"
5+
"log/slog"
6+
7+
dockerClient "github.com/docker/docker/client"
8+
9+
"github.com/arduino/arduino-app-cli/internal/orchestrator/app"
10+
"github.com/arduino/arduino-app-cli/internal/orchestrator/config"
11+
)
12+
13+
func AppStatus(ctx context.Context, cfg config.Configuration, docker dockerClient.APIClient, idProvider *app.IDProvider) ([]AppInfo, error) {
14+
appsStatus, err := getAppsStatus(ctx, docker)
15+
if err != nil {
16+
return nil, err
17+
}
18+
19+
defaultApp, err := GetDefaultApp(cfg)
20+
if err != nil {
21+
slog.Warn("unable to get default app", slog.String("error", err.Error()))
22+
}
23+
24+
apps := make([]AppInfo, 0, len(appsStatus))
25+
for _, a := range appsStatus {
26+
// FIXME: create an helper function to transform an app.ArduinoApp into an ortchestrator.AppInfo
27+
28+
app, err := app.Load(a.AppPath.String())
29+
if err != nil {
30+
slog.Warn("error loading app", "appPath", a.AppPath.String(), "error", err)
31+
return nil, err
32+
}
33+
34+
id, err := idProvider.IDFromPath(a.AppPath)
35+
if err != nil {
36+
return nil, err
37+
}
38+
39+
isDefault := defaultApp != nil && defaultApp.FullPath.EqualsTo(app.FullPath)
40+
41+
apps = append(apps, AppInfo{
42+
ID: id,
43+
Name: app.Descriptor.Name,
44+
Description: app.Descriptor.Description,
45+
Icon: app.Descriptor.Icon,
46+
Status: a.Status,
47+
Example: id.IsExample(),
48+
Default: isDefault,
49+
})
50+
}
51+
52+
return apps, nil
53+
}

internal/orchestrator/helpers.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
"github.com/arduino/arduino-app-cli/internal/orchestrator/config"
2020
)
2121

22-
type AppStatus struct {
22+
type AppStatusInfo struct {
2323
AppPath *paths.Path
2424
Status Status
2525
}
@@ -34,8 +34,8 @@ type AppStatus struct {
3434
// failed: at least one failed
3535
// stopping: at least one stopping
3636
// starting: at least one starting
37-
func parseAppStatus(containers []container.Summary) []AppStatus {
38-
apps := make([]AppStatus, 0, len(containers))
37+
func parseAppStatus(containers []container.Summary) []AppStatusInfo {
38+
apps := make([]AppStatusInfo, 0, len(containers))
3939
appsStatusMap := make(map[string][]Status)
4040
for _, c := range containers {
4141
appPath, ok := c.Labels[DockerAppPathLabel]
@@ -46,7 +46,7 @@ func parseAppStatus(containers []container.Summary) []AppStatus {
4646
}
4747

4848
appendResult := func(appPath *paths.Path, status Status) {
49-
apps = append(apps, AppStatus{
49+
apps = append(apps, AppStatusInfo{
5050
AppPath: appPath,
5151
Status: status,
5252
})
@@ -90,8 +90,8 @@ func parseAppStatus(containers []container.Summary) []AppStatus {
9090
func getAppsStatus(
9191
ctx context.Context,
9292
docker dockerClient.APIClient,
93-
) ([]AppStatus, error) {
94-
getPythonApp := func() ([]AppStatus, error) {
93+
) ([]AppStatusInfo, error) {
94+
getPythonApp := func() ([]AppStatusInfo, error) {
9595
containers, err := docker.ContainerList(ctx, container.ListOptions{
9696
All: true,
9797
Filters: filters.NewArgs(filters.Arg("label", DockerAppLabel+"=true")),
@@ -105,12 +105,12 @@ func getAppsStatus(
105105
return parseAppStatus(containers), nil
106106
}
107107

108-
getSketchApp := func() ([]AppStatus, error) {
108+
getSketchApp := func() ([]AppStatusInfo, error) {
109109
// TODO: implement this function
110110
return nil, nil
111111
}
112112

113-
for _, get := range [](func() ([]AppStatus, error)){getPythonApp, getSketchApp} {
113+
for _, get := range [](func() ([]AppStatusInfo, error)){getPythonApp, getSketchApp} {
114114
apps, err := get()
115115
if err != nil {
116116
return nil, err
@@ -126,16 +126,16 @@ func getAppStatus(
126126
ctx context.Context,
127127
docker command.Cli,
128128
app app.ArduinoApp,
129-
) (AppStatus, error) {
129+
) (AppStatusInfo, error) {
130130
apps, err := getAppsStatus(ctx, docker.Client())
131131
if err != nil {
132-
return AppStatus{}, fmt.Errorf("failed to get app status: %w", err)
132+
return AppStatusInfo{}, fmt.Errorf("failed to get app status: %w", err)
133133
}
134-
idx := slices.IndexFunc(apps, func(a AppStatus) bool {
134+
idx := slices.IndexFunc(apps, func(a AppStatusInfo) bool {
135135
return a.AppPath.String() == app.FullPath.String()
136136
})
137137
if idx == -1 {
138-
return AppStatus{}, fmt.Errorf("app %s not found", app.FullPath)
138+
return AppStatusInfo{}, fmt.Errorf("app %s not found", app.FullPath)
139139
}
140140
return apps[idx], nil
141141
}
@@ -148,7 +148,7 @@ func getRunningApp(
148148
if err != nil {
149149
return nil, fmt.Errorf("failed to get running apps: %w", err)
150150
}
151-
idx := slices.IndexFunc(apps, func(a AppStatus) bool {
151+
idx := slices.IndexFunc(apps, func(a AppStatusInfo) bool {
152152
return a.Status == StatusRunning || a.Status == StatusStarting
153153
})
154154
if idx == -1 {

internal/orchestrator/orchestrator.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,7 @@ func ListApps(
557557
}
558558

559559
var status Status
560-
if idx := slices.IndexFunc(apps, func(a AppStatus) bool {
560+
if idx := slices.IndexFunc(apps, func(a AppStatusInfo) bool {
561561
return a.AppPath.EqualsTo(app.FullPath)
562562
}); idx != -1 {
563563
status = apps[idx].Status

0 commit comments

Comments
 (0)