Skip to content

Commit fb09372

Browse files
committed
Merge remote-tracking branch 'origin/main' into use-public-repo
2 parents c36b2a9 + cdbb28c commit fb09372

File tree

24 files changed

+735
-58
lines changed

24 files changed

+735
-58
lines changed

Taskfile.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ tasks:
167167
cmds:
168168
- docker rm -f adbd
169169

170-
board:install-arduino-app-cli:
170+
board:install:
171171
desc: Install arduino-app-cli on the board
172172
interactive: true
173173
cmds:

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+
}

cmd/arduino-app-cli/brick/bricks.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,18 @@ package brick
1717

1818
import (
1919
"github.com/spf13/cobra"
20+
21+
"github.com/arduino/arduino-app-cli/internal/orchestrator/config"
2022
)
2123

22-
func NewBrickCmd() *cobra.Command {
24+
func NewBrickCmd(cfg config.Configuration) *cobra.Command {
2325
appCmd := &cobra.Command{
2426
Use: "brick",
2527
Short: "Manage Arduino Bricks",
2628
}
2729

2830
appCmd.AddCommand(newBricksListCmd())
29-
appCmd.AddCommand(newBricksDetailsCmd())
31+
appCmd.AddCommand(newBricksDetailsCmd(cfg))
3032

3133
return appCmd
3234
}

cmd/arduino-app-cli/brick/details.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,23 @@ import (
2525
"github.com/arduino/arduino-app-cli/cmd/arduino-app-cli/internal/servicelocator"
2626
"github.com/arduino/arduino-app-cli/cmd/feedback"
2727
"github.com/arduino/arduino-app-cli/internal/orchestrator/bricks"
28+
"github.com/arduino/arduino-app-cli/internal/orchestrator/config"
2829
)
2930

30-
func newBricksDetailsCmd() *cobra.Command {
31+
func newBricksDetailsCmd(cfg config.Configuration) *cobra.Command {
3132
return &cobra.Command{
3233
Use: "details",
3334
Short: "Details of a specific brick",
3435
Args: cobra.ExactArgs(1),
3536
Run: func(cmd *cobra.Command, args []string) {
36-
bricksDetailsHandler(args[0])
37+
bricksDetailsHandler(args[0], cfg)
3738
},
3839
}
3940
}
4041

41-
func bricksDetailsHandler(id string) {
42-
res, err := servicelocator.GetBrickService().BricksDetails(id)
42+
func bricksDetailsHandler(id string, cfg config.Configuration) {
43+
res, err := servicelocator.GetBrickService().BricksDetails(id, servicelocator.GetAppIDProvider(),
44+
cfg)
4345
if err != nil {
4446
if errors.Is(err, bricks.ErrBrickNotFound) {
4547
feedback.Fatal(err.Error(), feedback.ErrBadArgument)

cmd/arduino-app-cli/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func run(configuration cfg.Configuration) error {
7171

7272
rootCmd.AddCommand(
7373
app.NewAppCmd(configuration),
74-
brick.NewBrickCmd(),
74+
brick.NewBrickCmd(configuration),
7575
completion.NewCompletionCommand(),
7676
daemon.NewDaemonCmd(configuration, Version),
7777
properties.NewPropertiesCmd(configuration),

debian/arduino-app-cli/etc/systemd/system/arduino-app-cli.service

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
Description=Arduino App CLI daemon Service
33
After=network-online.target docker.service arduino-router.service
44
Wants=network-online.target docker.service arduino-router.service
5-
Requires=docker.service arduino-router.service
65

76
[Service]
87
ExecStart=/usr/bin/arduino-app-cli daemon --port 8800 --log-level error

internal/api/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func NewHTTPRouter(
5656
mux.Handle("GET /v1/version", handlers.HandlerVersion(version))
5757
mux.Handle("GET /v1/config", handlers.HandleConfig(cfg))
5858
mux.Handle("GET /v1/bricks", handlers.HandleBrickList(brickService))
59-
mux.Handle("GET /v1/bricks/{brickID}", handlers.HandleBrickDetails(brickService))
59+
mux.Handle("GET /v1/bricks/{brickID}", handlers.HandleBrickDetails(brickService, idProvider, cfg))
6060

6161
mux.Handle("GET /v1/properties", handlers.HandlePropertyKeys(cfg))
6262
mux.Handle("GET /v1/properties/{key}", handlers.HandlePropertyGet(cfg))

internal/api/handlers/bricks.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/arduino/arduino-app-cli/internal/api/models"
2727
"github.com/arduino/arduino-app-cli/internal/orchestrator/app"
2828
"github.com/arduino/arduino-app-cli/internal/orchestrator/bricks"
29+
"github.com/arduino/arduino-app-cli/internal/orchestrator/config"
2930
"github.com/arduino/arduino-app-cli/internal/render"
3031
)
3132

@@ -153,14 +154,15 @@ func HandleBrickCreate(
153154
}
154155
}
155156

156-
func HandleBrickDetails(brickService *bricks.Service) http.HandlerFunc {
157+
func HandleBrickDetails(brickService *bricks.Service, idProvider *app.IDProvider,
158+
cfg config.Configuration) http.HandlerFunc {
157159
return func(w http.ResponseWriter, r *http.Request) {
158160
id := r.PathValue("brickID")
159161
if id == "" {
160162
render.EncodeResponse(w, http.StatusBadRequest, models.ErrorResponse{Details: "id must be set"})
161163
return
162164
}
163-
res, err := brickService.BricksDetails(id)
165+
res, err := brickService.BricksDetails(id, idProvider, cfg)
164166
if err != nil {
165167
if errors.Is(err, bricks.ErrBrickNotFound) {
166168
details := fmt.Sprintf("brick with id %q not found", id)

internal/api/handlers/monitor.go

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,24 +83,53 @@ func monitorStream(mon net.Conn, ws *websocket.Conn) {
8383
}()
8484
}
8585

86+
func splitOrigin(origin string) (scheme, host, port string, err error) {
87+
parts := strings.SplitN(origin, "://", 2)
88+
if len(parts) != 2 {
89+
return "", "", "", fmt.Errorf("invalid origin format: %s", origin)
90+
}
91+
scheme = parts[0]
92+
hostPort := parts[1]
93+
hostParts := strings.SplitN(hostPort, ":", 2)
94+
host = hostParts[0]
95+
if len(hostParts) == 2 {
96+
port = hostParts[1]
97+
} else {
98+
port = "*"
99+
}
100+
return scheme, host, port, nil
101+
}
102+
86103
func checkOrigin(origin string, allowedOrigins []string) bool {
104+
scheme, host, port, err := splitOrigin(origin)
105+
if err != nil {
106+
slog.Error("WebSocket origin check failed", slog.String("origin", origin), slog.String("error", err.Error()))
107+
return false
108+
}
87109
for _, allowed := range allowedOrigins {
88-
if strings.HasSuffix(allowed, "*") {
89-
// String ends with *, match the prefix
90-
if strings.HasPrefix(origin, strings.TrimSuffix(allowed, "*")) {
91-
return true
92-
}
93-
} else {
94-
// Exact match
95-
if allowed == origin {
96-
return true
97-
}
110+
allowedScheme, allowedHost, allowedPort, err := splitOrigin(allowed)
111+
if err != nil {
112+
panic(err)
113+
}
114+
if allowedScheme != scheme {
115+
continue
98116
}
117+
if allowedHost != host && allowedHost != "*" {
118+
continue
119+
}
120+
if allowedPort != port && allowedPort != "*" {
121+
continue
122+
}
123+
return true
99124
}
125+
slog.Error("WebSocket origin check failed", slog.String("origin", origin))
100126
return false
101127
}
102128

103129
func HandleMonitorWS(allowedOrigins []string) http.HandlerFunc {
130+
// Do a dry-run of checkorigin, so it can panic if misconfigured now, not on first request
131+
_ = checkOrigin("http://localhost", allowedOrigins)
132+
104133
upgrader := websocket.Upgrader{
105134
ReadBufferSize: 1024,
106135
WriteBufferSize: 1024,
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// This file is part of arduino-app-cli.
2+
//
3+
// Copyright 2025 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-app-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to license@arduino.cc.
15+
16+
package handlers
17+
18+
import (
19+
"testing"
20+
21+
"github.com/stretchr/testify/require"
22+
)
23+
24+
func TestCheckOrigin(t *testing.T) {
25+
origins := []string{
26+
"wails://wails",
27+
"wails://wails.localhost:*",
28+
"http://wails.localhost:*",
29+
"http://localhost:*",
30+
"https://localhost:*",
31+
"http://example.com:7000",
32+
"https://*:443",
33+
}
34+
35+
allow := func(origin string) {
36+
require.True(t, checkOrigin(origin, origins), "Expected origin %s to be allowed", origin)
37+
}
38+
deny := func(origin string) {
39+
require.False(t, checkOrigin(origin, origins), "Expected origin %s to be denied", origin)
40+
}
41+
allow("wails://wails")
42+
allow("wails://wails:8000")
43+
allow("http://wails.localhost")
44+
allow("http://localhost")
45+
allow("http://example.com:7000")
46+
allow("https://blah.com:443")
47+
deny("wails://evil.com")
48+
deny("https://wails.localhost:8000")
49+
deny("http://example.com:8000")
50+
deny("http://blah.com:443")
51+
deny("https://blah.com:8080")
52+
}

0 commit comments

Comments
 (0)