Handle garm-agent tools upload/sync

This change adds the ability to manage garm-agent tools downloads. Users
can:

* Set an upstream releases page (github releases api)
* Enable sync from upstream. In this case, GARM will automatically download
  garm-agent tools from the releases page and save them in the internal
  object store
* Manually upload tools. Manually uploaded tools for an OS/arch combination
  will never be overwritten by auto-sync. Usrs will need to delete manually
  uploaded tools to enable sync for that os/arch release.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
Gabriel Adrian Samfira 2026-02-07 14:54:12 +02:00 committed by Gabriel
parent c29e8d4459
commit def4b4aaf1
29 changed files with 2755 additions and 97 deletions

View file

@ -17,7 +17,9 @@ package sql
import (
"errors"
"fmt"
"log/slog"
"net/url"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
@ -25,6 +27,7 @@ import (
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/database/common"
"github.com/cloudbase/garm/params"
garmUtil "github.com/cloudbase/garm/util"
"github.com/cloudbase/garm/util/appdefaults"
)
@ -39,17 +42,30 @@ func dbControllerToCommonController(dbInfo ControllerInfo) (params.ControllerInf
}
ret := params.ControllerInfo{
ControllerID: dbInfo.ControllerID,
MetadataURL: dbInfo.MetadataURL,
WebhookURL: dbInfo.WebhookBaseURL,
ControllerWebhookURL: url,
CallbackURL: dbInfo.CallbackURL,
AgentURL: dbInfo.AgentURL,
MinimumJobAgeBackoff: dbInfo.MinimumJobAgeBackoff,
Version: appdefaults.GetVersion(),
GARMAgentReleasesURL: dbInfo.GARMAgentReleasesURL,
SyncGARMAgentTools: dbInfo.SyncGARMAgentTools,
ControllerID: dbInfo.ControllerID,
MetadataURL: dbInfo.MetadataURL,
WebhookURL: dbInfo.WebhookBaseURL,
ControllerWebhookURL: url,
CallbackURL: dbInfo.CallbackURL,
AgentURL: dbInfo.AgentURL,
MinimumJobAgeBackoff: dbInfo.MinimumJobAgeBackoff,
Version: appdefaults.GetVersion(),
GARMAgentReleasesURL: dbInfo.GARMAgentReleasesURL,
SyncGARMAgentTools: dbInfo.SyncGARMAgentTools,
CachedGARMAgentReleaseFetchedAt: dbInfo.CachedGARMAgentReleaseFetchedAt,
CachedGARMAgentRelease: dbInfo.CachedGARMAgentRelease,
}
// Parse cached release data to populate CachedGARMAgentTools
if len(dbInfo.CachedGARMAgentRelease) > 0 {
tools, err := garmUtil.ParseToolsFromRelease(dbInfo.CachedGARMAgentRelease)
if err != nil {
slog.Warn("failed to parse cached tools during DB conversion", "error", err)
} else {
ret.CachedGARMAgentTools = tools
}
}
return ret, nil
}
@ -183,3 +199,36 @@ func (s *sqlDatabase) UpdateController(info params.UpdateControllerParams) (para
}
return paramInfo, nil
}
func (s *sqlDatabase) UpdateCachedGARMAgentRelease(releaseData []byte, fetchedAt time.Time) error {
var dbInfo ControllerInfo
err := s.conn.Transaction(func(tx *gorm.DB) error {
q := tx.Model(&ControllerInfo{}).First(&dbInfo)
if q.Error != nil {
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return fmt.Errorf("error fetching controller info: %w", runnerErrors.ErrNotFound)
}
return fmt.Errorf("error fetching controller info: %w", q.Error)
}
dbInfo.CachedGARMAgentRelease = releaseData
dbInfo.CachedGARMAgentReleaseFetchedAt = &fetchedAt
q = tx.Save(&dbInfo)
if q.Error != nil {
return fmt.Errorf("error saving controller info: %w", q.Error)
}
return nil
})
if err != nil {
return fmt.Errorf("error updating cached release: %w", err)
}
paramInfo, err := dbControllerToCommonController(dbInfo)
if err != nil {
return fmt.Errorf("error converting controller info: %w", err)
}
s.sendNotify(common.ControllerEntityType, common.UpdateOperation, paramInfo)
return nil
}