diff --git a/cmd/arduino-app-cli/internal/servicelocator/servicelocator.go b/cmd/arduino-app-cli/internal/servicelocator/servicelocator.go index 2fd14a4b9..7cdf5d5f0 100644 --- a/cmd/arduino-app-cli/internal/servicelocator/servicelocator.go +++ b/cmd/arduino-app-cli/internal/servicelocator/servicelocator.go @@ -43,7 +43,7 @@ func Init(cfg config.Configuration) { var ( GetBricksIndex = sync.OnceValue(func() *bricksindex.BricksIndex { - return f.Must(bricksindex.Load(GetStaticStore().GetAssetsFolder())) + return f.Must(bricksindex.Load(GetStaticStore())) }) GetModelsIndex = sync.OnceValue(func() *modelsindex.ModelsIndex { diff --git a/internal/api/handlers/app_ports.go b/internal/api/handlers/app_ports.go index c405c3a3c..cb1d083f6 100644 --- a/internal/api/handlers/app_ports.go +++ b/internal/api/handlers/app_ports.go @@ -72,9 +72,8 @@ func buildAppPortResponse(appPorts []int, brickInfoMap map[string]BrickPortInfo) for _, p := range appPorts { response.Ports = append(response.Ports, port{ - Port: strconv.Itoa(p), - Source: "app.yaml", - ServiceName: "webview", + Port: strconv.Itoa(p), + Source: "app.yaml", }) } @@ -106,7 +105,7 @@ func GetBrickPortInfoByID(bricks []app.Brick, bricksIndex *bricksindex.BricksInd return nil, fmt.Errorf("brick %q not found in the index", brick.ID) } brickInfoByID[brick.ID] = BrickPortInfo{ - Ports: brickData.Ports, + Ports: brickData.GetPorts(), RequiresDisplay: brickData.RequiresDisplay, } } diff --git a/internal/e2e/daemon/brick_test.go b/internal/e2e/daemon/brick_test.go index 9f11e2c95..6e022ec26 100644 --- a/internal/e2e/daemon/brick_test.go +++ b/internal/e2e/daemon/brick_test.go @@ -70,7 +70,7 @@ func TestBricksList(t *testing.T) { require.NotEmpty(t, response.JSON200.Bricks) staticStore := store.NewStaticStore(paths.New("testdata", "assets", config.RunnerVersion).String()) - brickIndex, err := bricksindex.Load(staticStore.GetAssetsFolder()) + brickIndex, err := bricksindex.Load(staticStore) require.NoError(t, err) // Compare the response with the bricks index diff --git a/internal/orchestrator/archive_test.go b/internal/orchestrator/archive_test.go index 315ebea1e..ef0d9b417 100644 --- a/internal/orchestrator/archive_test.go +++ b/internal/orchestrator/archive_test.go @@ -33,10 +33,11 @@ import ( "github.com/arduino/arduino-app-cli/internal/orchestrator/app" "github.com/arduino/arduino-app-cli/internal/orchestrator/bricksindex" "github.com/arduino/arduino-app-cli/internal/orchestrator/config" + "github.com/arduino/arduino-app-cli/internal/store" ) func TestExportAppZip(t *testing.T) { - bricksIndex, err := bricksindex.Load(paths.New("testdata", "archive")) + bricksIndex, err := bricksindex.Load(store.NewStaticStore("testdata/archive")) require.NoError(t, err) type testCase struct { diff --git a/internal/orchestrator/bricks/bricks_test.go b/internal/orchestrator/bricks/bricks_test.go index 657038976..071f4180d 100644 --- a/internal/orchestrator/bricks/bricks_test.go +++ b/internal/orchestrator/bricks/bricks_test.go @@ -32,7 +32,7 @@ import ( ) func TestBrickCreate(t *testing.T) { - bricksIndex, err := bricksindex.Load(paths.New("testdata")) + bricksIndex, err := bricksindex.Load(store.NewStaticStore("testdata")) require.Nil(t, err) brickService := NewService(nil, bricksIndex, nil) @@ -102,7 +102,7 @@ func TestBrickCreate(t *testing.T) { require.Nil(t, err) err = paths.New("testdata/dummy-app").CopyDirTo(tempDummyApp) require.Nil(t, err) - bricksIndex, err := bricksindex.Load(paths.New("testdata")) + bricksIndex, err := bricksindex.Load(store.NewStaticStore("testdata")) require.Nil(t, err) brickService := NewService(nil, bricksIndex, nil) @@ -129,7 +129,7 @@ func TestBrickCreate(t *testing.T) { } func TestUpdateBrick(t *testing.T) { - bricksIndex, err := bricksindex.Load(paths.New("testdata")) + bricksIndex, err := bricksindex.Load(store.NewStaticStore("testdata")) require.Nil(t, err) brickService := NewService(nil, bricksIndex, nil) @@ -189,7 +189,7 @@ func TestUpdateBrick(t *testing.T) { tempDummyApp := paths.New("testdata/dummy-app.temp") require.Nil(t, tempDummyApp.RemoveAll()) require.Nil(t, paths.New("testdata/dummy-app").CopyDirTo(tempDummyApp)) - bricksIndex, err := bricksindex.Load(paths.New("testdata")) + bricksIndex, err := bricksindex.Load(store.NewStaticStore("testdata")) require.Nil(t, err) brickService := NewService(nil, bricksIndex, nil) @@ -218,7 +218,7 @@ func TestUpdateBrick(t *testing.T) { tempDummyApp := paths.New("testdata/dummy-app-for-update.temp") require.Nil(t, tempDummyApp.RemoveAll()) require.Nil(t, paths.New("testdata/dummy-app-for-update").CopyDirTo(tempDummyApp)) - bricksIndex, err := bricksindex.Load(paths.New("testdata")) + bricksIndex, err := bricksindex.Load(store.NewStaticStore("testdata")) require.Nil(t, err) brickService := NewService(nil, bricksIndex, nil) @@ -246,7 +246,7 @@ func TestUpdateBrick(t *testing.T) { tempDummyApp := paths.New("testdata/dummy-app-for-model.temp") require.Nil(t, tempDummyApp.RemoveAll()) require.Nil(t, paths.New("testdata/dummy-app-for-model").CopyDirTo(tempDummyApp)) - bricksIndex, err := bricksindex.Load(paths.New("testdata")) + bricksIndex, err := bricksindex.Load(store.NewStaticStore("testdata")) require.NoError(t, err) modelsIndex, err := modelsindex.Load(paths.New("testdata"), paths.New("not_exixsting_path")) require.NoError(t, err) diff --git a/internal/orchestrator/bricksindex/bricks_index.go b/internal/orchestrator/bricksindex/bricks_index.go index 46003b0e0..5705e19b4 100644 --- a/internal/orchestrator/bricksindex/bricks_index.go +++ b/internal/orchestrator/bricksindex/bricks_index.go @@ -16,14 +16,14 @@ package bricksindex import ( - "io" "iter" "slices" + "strings" - "github.com/arduino/go-paths-helper" yaml "github.com/goccy/go-yaml" "github.com/arduino/arduino-app-cli/internal/orchestrator/peripherals" + "github.com/arduino/arduino-app-cli/internal/store" ) type BricksIndex struct { @@ -65,6 +65,8 @@ type Brick struct { ModelName string `yaml:"model_name,omitempty"` MountDevicesIntoContainer bool `yaml:"mount_devices_into_container,omitempty"` RequiredDevices []peripherals.DeviceClass `yaml:"required_devices,omitempty"` + + containerPorts []string `yaml:"-"` } func (b Brick) GetVariable(name string) (BrickVariable, bool) { @@ -87,19 +89,64 @@ func (b Brick) GetDefaultVariables() iter.Seq2[string, string] { } } -func unmarshalBricksIndex(content io.Reader) (*BricksIndex, error) { - var index BricksIndex - if err := yaml.NewDecoder(content).Decode(&index); err != nil { - return nil, err - } - return &index, nil +func (b Brick) GetPorts() []string { + ports := make([]string, 0, len(b.Ports)+len(b.containerPorts)) + ports = append(ports, b.Ports...) + ports = append(ports, b.containerPorts...) + slices.Sort(ports) + return slices.Compact(ports) } -func Load(dir *paths.Path) (*BricksIndex, error) { - content, err := dir.Join("bricks-list.yaml").Open() +func Load(staticstore *store.StaticStore) (*BricksIndex, error) { + content, err := staticstore.GetAssetsFolder().Join("bricks-list.yaml").Open() if err != nil { return nil, err } defer content.Close() - return unmarshalBricksIndex(content) + + var index BricksIndex + if err := yaml.NewDecoder(content).Decode(&index); err != nil { + return nil, err + } + + // Parse the compose file to read the extra ports. + for i := range index.Bricks { + composeFilePath, err := staticstore.GetBrickComposeFilePathFromID(index.Bricks[i].ID) + if err != nil { + return nil, err + } + + if !index.Bricks[i].RequireContainer || composeFilePath.NotExist() { + continue + } + + f, err := composeFilePath.Open() + if err != nil { + return nil, err + } + defer f.Close() + + var compose struct { + Services map[string]struct { + Ports []string `yaml:"ports,omitempty"` + } `yaml:"services"` + } + if err := yaml.NewDecoder(f).Decode(&compose); err != nil { + return nil, err + } + + for _, service := range compose.Services { + for _, portStr := range service.Ports { + if strings.Contains(portStr, ":") { + parts := strings.Split(portStr, ":") + hostPort := parts[len(parts)-2] // Extract the host port (the one before the last colon) + index.Bricks[i].containerPorts = append(index.Bricks[i].containerPorts, hostPort) + } else { + index.Bricks[i].containerPorts = append(index.Bricks[i].containerPorts, portStr) + } + } + } + } + + return &index, nil } diff --git a/internal/orchestrator/bricksindex/bricks_index_test.go b/internal/orchestrator/bricksindex/bricks_index_test.go index 4f1b5795c..d22682eb2 100644 --- a/internal/orchestrator/bricksindex/bricks_index_test.go +++ b/internal/orchestrator/bricksindex/bricks_index_test.go @@ -23,10 +23,11 @@ import ( "github.com/stretchr/testify/require" "github.com/arduino/arduino-app-cli/internal/orchestrator/peripherals" + "github.com/arduino/arduino-app-cli/internal/store" ) func TestGenerateBricksIndexFromFile(t *testing.T) { - index, err := Load(paths.New("testdata")) + index, err := Load(store.NewStaticStore("testdata")) require.NoError(t, err) // Check if ports are correctly set @@ -210,7 +211,7 @@ func TestBricksIndexYAMLFormats(t *testing.T) { err := os.WriteFile(brickIndex.String(), []byte(tc.yamlContent), 0600) require.NoError(t, err) - index, err := Load(paths.New(tempDir)) + index, err := Load(store.NewStaticStore(tempDir)) if tc.expectedError != "" { require.Error(t, err) require.Contains(t, err.Error(), tc.expectedError) diff --git a/internal/orchestrator/models_test.go b/internal/orchestrator/models_test.go index 21a7c6923..1487e0c99 100644 --- a/internal/orchestrator/models_test.go +++ b/internal/orchestrator/models_test.go @@ -34,10 +34,11 @@ import ( "github.com/arduino/arduino-app-cli/internal/api/edgeimpulse" "github.com/arduino/arduino-app-cli/internal/orchestrator/bricksindex" "github.com/arduino/arduino-app-cli/internal/orchestrator/modelsindex" + "github.com/arduino/arduino-app-cli/internal/store" ) func TestBuildBrickConfigForEIModel(t *testing.T) { - brickIndex, err := bricksindex.Load(paths.New("bricksindex/testdata")) + brickIndex, err := bricksindex.Load(store.NewStaticStore("bricksindex/testdata")) if err != nil { t.Fatalf("failed to load bricks index: %v", err) } diff --git a/internal/orchestrator/orchestrator_test.go b/internal/orchestrator/orchestrator_test.go index a0327dc70..6e0b6af1e 100644 --- a/internal/orchestrator/orchestrator_test.go +++ b/internal/orchestrator/orchestrator_test.go @@ -33,6 +33,7 @@ import ( "github.com/arduino/arduino-app-cli/internal/orchestrator/bricksindex" "github.com/arduino/arduino-app-cli/internal/orchestrator/config" "github.com/arduino/arduino-app-cli/internal/orchestrator/modelsindex" + "github.com/arduino/arduino-app-cli/internal/store" ) func TestCloneApp(t *testing.T) { @@ -476,7 +477,7 @@ bricks: `) err = cfg.AssetsDir().Join("bricks-list.yaml").WriteFile(bricksIndexContent) require.NoError(t, err) - bricksIndex, err := bricksindex.Load(cfg.AssetsDir()) + bricksIndex, err := bricksindex.Load(store.NewStaticStore(cfg.AssetsDir().String())) assert.NoError(t, err) modelsIndexContent := []byte(` @@ -558,7 +559,7 @@ bricks: `) err = cfg.AssetsDir().Join("bricks-list.yaml").WriteFile(bricksIndexContent) require.NoError(t, err) - bricksIndex, err := bricksindex.Load(cfg.AssetsDir()) + bricksIndex, err := bricksindex.Load(store.NewStaticStore(cfg.AssetsDir().String())) assert.NoError(t, err) modelsIndexContent := []byte(` @@ -645,7 +646,7 @@ bricks: `) err = cfg.AssetsDir().Join("bricks-list.yaml").WriteFile(bricksIndexContent) require.NoError(t, err) - bricksIndex, err := bricksindex.Load(cfg.AssetsDir()) + bricksIndex, err := bricksindex.Load(store.NewStaticStore(cfg.AssetsDir().String())) assert.NoError(t, err) modelsIndexContent := []byte(` diff --git a/internal/orchestrator/provision_test.go b/internal/orchestrator/provision_test.go index 48f8d591c..e46bb6cf6 100644 --- a/internal/orchestrator/provision_test.go +++ b/internal/orchestrator/provision_test.go @@ -108,7 +108,7 @@ bricks: require.NoError(t, err) // Override brick index with custom test content - bricksIndex, err := bricksindex.Load(cfg.AssetsDir()) + bricksIndex, err := bricksindex.Load(store.NewStaticStore(cfg.AssetsDir().String())) require.Nil(t, err, "Failed to load bricks index with custom content") br, ok := bricksIndex.FindBrickByID("arduino:video_object_detection") @@ -345,7 +345,7 @@ bricks: err := cfg.AssetsDir().Join("bricks-list.yaml").WriteFile(bricksIndexContent) require.NoError(t, err) - bricksIndex, err := bricksindex.Load(cfg.AssetsDir()) + bricksIndex, err := bricksindex.Load(store.NewStaticStore(cfg.AssetsDir().String())) require.Nil(t, err, "Failed to load bricks index with custom content") br, ok := bricksIndex.FindBrickByID("arduino:dbstorage_tsstore") require.True(t, ok, "Brick arduino:dbstorage_tsstore should exist in the index") @@ -512,7 +512,7 @@ bricks: err := cfg.AssetsDir().Join("bricks-list.yaml").WriteFile(bricksIndexContent) require.NoError(t, err) - bricksIndex, err := bricksindex.Load(cfg.AssetsDir()) + bricksIndex, err := bricksindex.Load(store.NewStaticStore(cfg.AssetsDir().String())) require.Nil(t, err, "Failed to load bricks index with custom content") br, ok := bricksIndex.FindBrickByID("arduino:dbstorage_tsstore") require.True(t, ok, "Brick arduino:dbstorage_tsstore should exist in the index")