Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cmd/arduino-app-cli/system/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func NewSystemCmd(cfg config.Configuration) *cobra.Command {
}

cmd.AddCommand(newDownloadImageCmd(cfg))
cmd.AddCommand(newUpdateCmd())
cmd.AddCommand(newUpdateCmd(cfg))
cmd.AddCommand(newCleanUpCmd(cfg, servicelocator.GetDockerClient()))
cmd.AddCommand(newNetworkModeCmd())
cmd.AddCommand(newKeyboardSetCmd())
Expand All @@ -64,7 +64,7 @@ func newDownloadImageCmd(cfg config.Configuration) *cobra.Command {
return cmd
}

func newUpdateCmd() *cobra.Command {
func newUpdateCmd(cfg config.Configuration) *cobra.Command {
var onlyArduino bool
var forceYes bool
cmd := &cobra.Command{
Expand All @@ -76,7 +76,7 @@ func newUpdateCmd() *cobra.Command {

updater := getUpdater()

pkgs, err := updater.ListUpgradablePackages(cmd.Context(), filterFunc)
pkgs, err := updater.ListUpgradablePackages(cfg, cmd.Context(), filterFunc)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ func NewHTTPRouter(
mux.Handle("PUT /v1/properties/{key}", handlers.HandlePropertyUpsert(cfg))
mux.Handle("DELETE /v1/properties/{key}", handlers.HandlePropertyDelete(cfg))

mux.Handle("GET /v1/system/update/check", handlers.HandleCheckUpgradable(updater))
mux.Handle("GET /v1/system/update/check", handlers.HandleCheckUpgradable(cfg, updater))
mux.Handle("GET /v1/system/update/events", handlers.HandleUpdateEvents(updater))
mux.Handle("PUT /v1/system/update/apply", handlers.HandleUpdateApply(updater))
mux.Handle("PUT /v1/system/update/apply", handlers.HandleUpdateApply(cfg, updater))
mux.Handle("GET /v1/system/resources", handlers.HandleSystemResources())

mux.Handle("GET /v1/models", handlers.HandleModelsList(modelsIndex))
Expand Down
9 changes: 5 additions & 4 deletions internal/api/handlers/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ import (
"log/slog"

"github.com/arduino/arduino-app-cli/internal/api/models"
"github.com/arduino/arduino-app-cli/internal/orchestrator/config"
"github.com/arduino/arduino-app-cli/internal/render"
"github.com/arduino/arduino-app-cli/internal/update"
)

func HandleCheckUpgradable(updater *update.Manager) http.HandlerFunc {
func HandleCheckUpgradable(cfg config.Configuration, updater *update.Manager) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
queryParams := r.URL.Query()

Expand All @@ -40,7 +41,7 @@ func HandleCheckUpgradable(updater *update.Manager) http.HandlerFunc {
filterFunc = update.MatchArduinoPackage
}

pkgs, err := updater.ListUpgradablePackages(r.Context(), filterFunc)
pkgs, err := updater.ListUpgradablePackages(cfg, r.Context(), filterFunc)
if err != nil {
code := update.GetUpdateErrorCode(err)
if code == update.OperationInProgressCode {
Expand Down Expand Up @@ -69,7 +70,7 @@ type UpdateCheckResult struct {
Packages []update.UpgradablePackage `json:"updates"`
}

func HandleUpdateApply(updater *update.Manager) http.HandlerFunc {
func HandleUpdateApply(cfg config.Configuration, updater *update.Manager) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
queryParams := r.URL.Query()
onlyArduinoPackages := false
Expand All @@ -82,7 +83,7 @@ func HandleUpdateApply(updater *update.Manager) http.HandlerFunc {
filterFunc = update.MatchArduinoPackage
}

pkgs, err := updater.ListUpgradablePackages(r.Context(), filterFunc)
pkgs, err := updater.ListUpgradablePackages(cfg, r.Context(), filterFunc)
if err != nil {
code := update.GetUpdateErrorCode(err)
if code == update.OperationInProgressCode {
Expand Down
16 changes: 16 additions & 0 deletions internal/orchestrator/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"strings"

"github.com/arduino/go-paths-helper"
semver "go.bug.st/relaxed-semver"
)

// runnerVersion do not edit, this is generate with `task generate:assets`
Expand All @@ -40,6 +41,7 @@ type Configuration struct {
RunnerVersion string
AllowRoot bool
LibrariesAPIURL *url.URL
VersionConstraint semver.Constraint
}

func NewFromEnv() (Configuration, error) {
Expand Down Expand Up @@ -106,6 +108,19 @@ func NewFromEnv() (Configuration, error) {
return Configuration{}, fmt.Errorf("invalid LIBRARIES_API_URL: %w", err)
}

constraintStr := os.Getenv("ARDUINO_APP_CLI__UPDATE_VERSION_CONSTRAINT")

if constraintStr == "" {
constraintStr = "<1.0.0"
}

// Parsing usando relaxed-semver
constraint, err := semver.ParseConstraint(constraintStr)
if err != nil {
return Configuration{}, fmt.Errorf("invalid version constraint: %w", err)
}
slog.Debug("Using update version constraint", slog.String("constraint", constraintStr))

c := Configuration{
appsDir: appsDir,
dataDir: dataDir,
Expand All @@ -116,6 +131,7 @@ func NewFromEnv() (Configuration, error) {
RunnerVersion: runnerVersion,
AllowRoot: allowRoot,
LibrariesAPIURL: parsedLibrariesURL,
VersionConstraint: constraint,
}
if err := c.init(); err != nil {
return Configuration{}, err
Expand Down
10 changes: 7 additions & 3 deletions internal/update/apt/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"go.bug.st/f"

"github.com/arduino/arduino-app-cli/internal/orchestrator"
"github.com/arduino/arduino-app-cli/internal/orchestrator/config"
"github.com/arduino/arduino-app-cli/internal/update"
)

Expand All @@ -47,7 +48,7 @@ func New() *Service {
// It runs the `apt-get update` command before listing the packages to ensure the package list is up to date.
// It filters the packages using the provided matcher function.
// It returns a slice of UpgradablePackage or an error if the command fails.
func (s *Service) ListUpgradablePackages(ctx context.Context, matcher func(update.UpgradablePackage) bool) ([]update.UpgradablePackage, error) {
func (s *Service) ListUpgradablePackages(cfg config.Configuration, ctx context.Context, matcher func(update.UpgradablePackage) bool) ([]update.UpgradablePackage, error) {
if !s.lock.TryLock() {
return nil, update.ErrOperationAlreadyInProgress
}
Expand All @@ -73,7 +74,7 @@ func (s *Service) ListUpgradablePackages(ctx context.Context, matcher func(updat
// UpgradePackages upgrades the specified packages using the `apt-get upgrade` command.
// It publishes events to subscribers during the upgrade process.
// It returns an error if the upgrade is already in progress or if the upgrade command fails.
func (s *Service) UpgradePackages(ctx context.Context, names []string, eventCB update.EventCallback) error {
func (s *Service) UpgradePackages(ctx context.Context, packages []update.PackageInfo, eventCB update.EventCallback) error {
if !s.lock.TryLock() {
return update.ErrOperationAlreadyInProgress
}
Expand All @@ -91,7 +92,10 @@ func (s *Service) UpgradePackages(ctx context.Context, names []string, eventCB u
return
}
}()

names := make([]string, 0, len(packages))
for _, pkg := range packages {
names = append(names, pkg.Name)
}
eventCB(update.NewDataEvent(update.StartEvent, "Upgrade is starting"))
stream := runUpgradeCommand(ctx, names)
for line, err := range stream {
Expand Down
141 changes: 98 additions & 43 deletions internal/update/arduino/arduino.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,20 @@ package arduino

import (
"context"
"errors"
"fmt"
"log/slog"
"strconv"
"strings"
"sync"

"github.com/arduino/arduino-cli/commands"
"github.com/arduino/arduino-cli/commands/cmderrors"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/sirupsen/logrus"
semver "go.bug.st/relaxed-semver"

"github.com/arduino/arduino-app-cli/internal/helpers"
"github.com/arduino/arduino-app-cli/internal/orchestrator"
"github.com/arduino/arduino-app-cli/internal/orchestrator/config"
"github.com/arduino/arduino-app-cli/internal/update"
)

Expand All @@ -53,7 +55,7 @@ func setConfig(ctx context.Context, srv rpc.ArduinoCoreServiceServer) error {
}

// ListUpgradablePackages implements ServiceUpdater.
func (a *ArduinoPlatformUpdater) ListUpgradablePackages(ctx context.Context, _ func(update.UpgradablePackage) bool) ([]update.UpgradablePackage, error) {
func (a *ArduinoPlatformUpdater) ListUpgradablePackages(cfg config.Configuration, ctx context.Context, _ func(update.UpgradablePackage) bool) ([]update.UpgradablePackage, error) {
if !a.lock.TryLock() {
return nil, update.ErrOperationAlreadyInProgress
}
Expand Down Expand Up @@ -120,21 +122,83 @@ func (a *ArduinoPlatformUpdater) ListUpgradablePackages(ctx context.Context, _ f
if platformSummary == nil {
return nil, nil // No platform found
}
releasesMap := platformSummary.GetReleases()

if platformSummary.GetLatestVersion() == platformSummary.GetInstalledVersion() {
return nil, nil // No update available
releases := make([]string, 0, len(releasesMap))

for k := range releasesMap {
releases = append(releases, k)
}
bestVersion, err := findBestCandidate(
platformSummary.GetInstalledVersion(),
releases,
cfg.VersionConstraint,
)

if bestVersion == "" || err != nil {
return nil, nil
}
return []update.UpgradablePackage{{
Type: update.Arduino,
Name: "arduino:zephyr",
FromVersion: platformSummary.GetInstalledVersion(),
ToVersion: platformSummary.GetLatestVersion(),
ToVersion: bestVersion,
}}, nil
}

func findBestCandidate(installedStr string, availableVersions []string, constraint semver.Constraint) (string, error) {
installedV, err := semver.Parse(installedStr)
if err != nil {
return "", fmt.Errorf("invalid installed version '%s': %w", installedStr, err)
}

if !constraint.Match(installedV) {

vStr := string(installedV.NormalizedString())
vStr = strings.TrimPrefix(vStr, "v")
parts := strings.Split(vStr, ".")
if len(parts) > 0 {
majorInt, err := strconv.Atoi(parts[0])
if err == nil {
newConstraintStr := fmt.Sprintf("<%d.0.0", majorInt+1)
newC, err := semver.ParseConstraint(newConstraintStr)
if err == nil {
constraint = newC
}
}
}
}

var bestUpdateV *semver.Version

for _, verStr := range availableVersions {
candidate, err := semver.Parse(verStr)
if err != nil {
continue
}

if !constraint.Match(candidate) {
continue
}

if candidate.LessThan(installedV) {
continue
}

if bestUpdateV == nil || candidate.GreaterThan(bestUpdateV) {
bestUpdateV = candidate
}
}

if bestUpdateV == nil {
return "", nil
}

return bestUpdateV.String(), nil
}

// UpgradePackages implements ServiceUpdater.
func (a *ArduinoPlatformUpdater) UpgradePackages(ctx context.Context, names []string, eventCB update.EventCallback) error {
func (a *ArduinoPlatformUpdater) UpgradePackages(ctx context.Context, packages []update.PackageInfo, eventCB update.EventCallback) error {
if !a.lock.TryLock() {
return update.ErrOperationAlreadyInProgress
}
Expand Down Expand Up @@ -185,49 +249,40 @@ func (a *ArduinoPlatformUpdater) UpgradePackages(ctx context.Context, names []st
}
}

stream, respCB := commands.PlatformUpgradeStreamResponseToCallbackFunction(
stream := commands.PlatformInstallStreamResponseToCallbackFunction(
ctx,
downloadProgressCB,
taskProgressCB,
)
if err := srv.PlatformUpgrade(
&rpc.PlatformUpgradeRequest{
Instance: inst,
PlatformPackage: "arduino",
Architecture: "zephyr",
SkipPostInstall: false,
SkipPreUninstall: false,
},
stream,
); err != nil {
var alreadyPresent *cmderrors.PlatformAlreadyAtTheLatestVersionError
if errors.As(err, &alreadyPresent) {
eventCB(update.NewDataEvent(update.UpgradeLineEvent, alreadyPresent.Error()))
return nil
}

var notFound *cmderrors.PlatformNotFoundError
if !errors.As(err, &notFound) {
return fmt.Errorf("error upgrading platform: %w", err)
var targetVersion string

const CorePackageName = "arduino"

for _, pkg := range packages {
if pkg.Name == CorePackageName {
targetVersion = pkg.ToVersion
break
}
// If the platform is not found, we will try to install it
err := srv.PlatformInstall(
&rpc.PlatformInstallRequest{
Instance: inst,
PlatformPackage: "arduino",
Architecture: "zephyr",
},
commands.PlatformInstallStreamResponseToCallbackFunction(
ctx,
downloadProgressCB,
taskProgressCB,
),
)
if err != nil {
return fmt.Errorf("error installing platform: %w", err)
}

if targetVersion == "" {
if len(packages) > 0 {
return fmt.Errorf("no package of type '%s' found in the upgrade request", CorePackageName)
}
} else if respCB().GetPlatform() == nil {
return fmt.Errorf("platform upgrade failed")
return fmt.Errorf("package list is empty")
}

if err := srv.PlatformInstall(
&rpc.PlatformInstallRequest{
Instance: inst,
PlatformPackage: "arduino",
Architecture: "zephyr",
Version: targetVersion,
},
stream,
); err != nil {
return fmt.Errorf("error installing platform version %s: %w", targetVersion, err)
}

cbw := orchestrator.NewCallbackWriter(func(line string) {
Expand Down
Loading