feat(backends install): allow to specify name and alias during manual installation (#5971)

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
Ettore Di Giacinto
2025-08-10 10:05:53 +02:00
committed by GitHub
parent 240b790f29
commit 05757e2738
4 changed files with 96 additions and 72 deletions
+4 -2
View File
@@ -59,8 +59,10 @@ func New(opts ...config.AppOption) (*Application, error) {
log.Error().Err(err).Msg("error installing models")
}
if err := coreStartup.InstallExternalBackends(options.BackendGalleries, options.BackendsPath, nil, options.ExternalBackends...); err != nil {
log.Error().Err(err).Msg("error installing external backends")
for _, backend := range options.ExternalBackends {
if err := coreStartup.InstallExternalBackends(options.BackendGalleries, options.BackendsPath, nil, backend, "", ""); err != nil {
log.Error().Err(err).Msg("error installing external backend")
}
}
configLoaderOpts := options.ToConfigLoaderOptions()
+19 -19
View File
@@ -23,7 +23,9 @@ type BackendsList struct {
}
type BackendsInstall struct {
BackendArgs []string `arg:"" optional:"" name:"backends" help:"Backend configuration URLs to load"`
BackendArgs string `arg:"" optional:"" name:"backend" help:"Backend configuration URL to load"`
Name string `arg:"" optional:"" name:"name" help:"Name of the backend"`
Alias string `arg:"" optional:"" name:"alias" help:"Alias of the backend"`
BackendsCMDFlags `embed:""`
}
@@ -66,27 +68,25 @@ func (bi *BackendsInstall) Run(ctx *cliContext.Context) error {
log.Error().Err(err).Msg("unable to load galleries")
}
for _, backendName := range bi.BackendArgs {
progressBar := progressbar.NewOptions(
1000,
progressbar.OptionSetDescription(fmt.Sprintf("downloading backend %s", backendName)),
progressbar.OptionShowBytes(false),
progressbar.OptionClearOnFinish(),
)
progressCallback := func(fileName string, current string, total string, percentage float64) {
v := int(percentage * 10)
err := progressBar.Set(v)
if err != nil {
log.Error().Err(err).Str("filename", fileName).Int("value", v).Msg("error while updating progress bar")
}
}
err := startup.InstallExternalBackends(galleries, bi.BackendsPath, progressCallback, backendName)
progressBar := progressbar.NewOptions(
1000,
progressbar.OptionSetDescription(fmt.Sprintf("downloading backend %s", bi.BackendArgs)),
progressbar.OptionShowBytes(false),
progressbar.OptionClearOnFinish(),
)
progressCallback := func(fileName string, current string, total string, percentage float64) {
v := int(percentage * 10)
err := progressBar.Set(v)
if err != nil {
return err
log.Error().Err(err).Str("filename", fileName).Int("value", v).Msg("error while updating progress bar")
}
}
err := startup.InstallExternalBackends(galleries, bi.BackendsPath, progressCallback, bi.BackendArgs, bi.Name, bi.Alias)
if err != nil {
return err
}
return nil
}
+57 -39
View File
@@ -1,7 +1,6 @@
package startup
import (
"errors"
"fmt"
"path/filepath"
"strings"
@@ -13,49 +12,68 @@ import (
"github.com/rs/zerolog/log"
)
func InstallExternalBackends(galleries []config.Gallery, backendPath string, downloadStatus func(string, string, string, float64), backends ...string) error {
var errs error
func InstallExternalBackends(galleries []config.Gallery, backendPath string, downloadStatus func(string, string, string, float64), backend, name, alias string) error {
systemState, err := system.GetSystemState()
if err != nil {
return fmt.Errorf("failed to get system state: %w", err)
}
for _, backend := range backends {
uri := downloader.URI(backend)
switch {
case uri.LooksLikeDir():
name := filepath.Base(backend)
log.Info().Str("backend", backend).Str("name", name).Msg("Installing backend from path")
if err := gallery.InstallBackend(backendPath, &gallery.GalleryBackend{
Metadata: gallery.Metadata{
Name: name,
},
URI: backend,
}, downloadStatus); err != nil {
errs = errors.Join(err, fmt.Errorf("error installing backend %s", backend))
}
case uri.LooksLikeOCI():
name, err := uri.FilenameFromUrl()
if err != nil {
return fmt.Errorf("failed to get filename from URL: %w", err)
}
// strip extension if any
name = strings.TrimSuffix(name, filepath.Ext(name))
uri := downloader.URI(backend)
switch {
case uri.LooksLikeDir():
if name == "" { // infer it from the path
name = filepath.Base(backend)
}
log.Info().Str("backend", backend).Str("name", name).Msg("Installing backend from path")
if err := gallery.InstallBackend(backendPath, &gallery.GalleryBackend{
Metadata: gallery.Metadata{
Name: name,
},
Alias: alias,
URI: backend,
}, downloadStatus); err != nil {
return fmt.Errorf("error installing backend %s: %w", backend, err)
}
case uri.LooksLikeOCI() && !uri.LooksLikeOCIFile():
if name == "" {
return fmt.Errorf("specifying a name is required for OCI images")
}
log.Info().Str("backend", backend).Str("name", name).Msg("Installing backend from OCI image")
if err := gallery.InstallBackend(backendPath, &gallery.GalleryBackend{
Metadata: gallery.Metadata{
Name: name,
},
Alias: alias,
URI: backend,
}, downloadStatus); err != nil {
return fmt.Errorf("error installing backend %s: %w", backend, err)
}
case uri.LooksLikeOCIFile():
name, err := uri.FilenameFromUrl()
if err != nil {
return fmt.Errorf("failed to get filename from URL: %w", err)
}
// strip extension if any
name = strings.TrimSuffix(name, filepath.Ext(name))
log.Info().Str("backend", backend).Str("name", name).Msg("Installing backend from OCI image")
if err := gallery.InstallBackend(backendPath, &gallery.GalleryBackend{
Metadata: gallery.Metadata{
Name: name,
},
URI: backend,
}, downloadStatus); err != nil {
errs = errors.Join(err, fmt.Errorf("error installing backend %s", backend))
}
default:
err := gallery.InstallBackendFromGallery(galleries, systemState, backend, backendPath, downloadStatus, true)
if err != nil {
errs = errors.Join(err, fmt.Errorf("error installing backend %s", backend))
}
log.Info().Str("backend", backend).Str("name", name).Msg("Installing backend from OCI image")
if err := gallery.InstallBackend(backendPath, &gallery.GalleryBackend{
Metadata: gallery.Metadata{
Name: name,
},
Alias: alias,
URI: backend,
}, downloadStatus); err != nil {
return fmt.Errorf("error installing backend %s: %w", backend, err)
}
default:
if name != "" || alias != "" {
return fmt.Errorf("specifying a name or alias is not supported for this backend")
}
err := gallery.InstallBackendFromGallery(galleries, systemState, backend, backendPath, downloadStatus, true)
if err != nil {
return fmt.Errorf("error installing backend %s: %w", backend, err)
}
}
return errs
return nil
}
+16 -12
View File
@@ -98,19 +98,19 @@ func (uri URI) DownloadWithAuthorizationAndCallback(basePath string, authorizati
}
func (u URI) FilenameFromUrl() (string, error) {
f, err := filenameFromUrl(string(u))
if err != nil || f == "" {
f = utils.MD5(string(u))
if strings.HasSuffix(string(u), ".yaml") || strings.HasSuffix(string(u), ".yml") {
f = f + ".yaml"
}
err = nil
if f := filenameFromUrl(string(u)); f != "" {
return f, nil
}
return f, err
f := utils.MD5(string(u))
if strings.HasSuffix(string(u), ".yaml") || strings.HasSuffix(string(u), ".yml") {
f = f + ".yaml"
}
return f, nil
}
func filenameFromUrl(urlstr string) (string, error) {
func filenameFromUrl(urlstr string) string {
// strip anything after @
if strings.Contains(urlstr, "@") {
urlstr = strings.Split(urlstr, "@")[0]
@@ -118,13 +118,13 @@ func filenameFromUrl(urlstr string) (string, error) {
u, err := url.Parse(urlstr)
if err != nil {
return "", fmt.Errorf("error due to parsing url: %w", err)
return ""
}
x, err := url.QueryUnescape(u.EscapedPath())
if err != nil {
return "", fmt.Errorf("error due to escaping: %w", err)
return ""
}
return filepath.Base(x), nil
return filepath.Base(x)
}
func (u URI) LooksLikeURL() bool {
@@ -158,6 +158,10 @@ func (s URI) LooksLikeOCI() bool {
strings.HasPrefix(string(s), "docker.io")
}
func (s URI) LooksLikeOCIFile() bool {
return strings.HasPrefix(string(s), OCIFilePrefix)
}
func (s URI) ResolveURL() string {
switch {
case strings.HasPrefix(string(s), GithubURI2):