Files
LocalAI/scripts/backends/cmd/backendcatalog/main_test.go
T
Ettore Di Giacinto 1d8d3e3421 feat(backends): install a backend from a library entry
A library can now list backends. An entry of kind backend names the
image of the backend gallery by digest, and POST /backends/library/install
installs it on the local node. The gallery stays the source of truth and
its install path is the one used: the library resolves the entry, takes
the signature policy from the library, pins the image by digest and calls
the same install as the gallery. Installing a name that came from a
gallery, another library or by hand is refused and changes nothing.

The signature policy of a backend library names the signer with an
anchored identityRegex, so images signed by the merge workflow on master
and on tags both verify. A pattern that is not anchored is refused.

An install is bounded by a size limit per kind: add-ons keep 512 MiB and
a backend image defaults to 8 GiB, set with --addons-backend-max-bytes.
The limit reads the manifest after the signature check and before any
layer is fetched. The metadata of an installed backend records the
library it came from.

scripts/backends/cmd/backendcatalog renders entries from
backend/index.yaml and pins them against the registry. The pilot is the
CPU build of local-store, in gallery/backends/index.yaml. The library is
registered on the first start only with --addons-backend-library, which
is off by default.

Assisted-by: Claude Code:claude-sonnet-5-5 go
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-10-21 20:15:10 +00:00

67 lines
1.7 KiB
Go

// SPDX-License-Identifier: MIT
package main
import (
"strings"
"testing"
"github.com/mudler/LocalAI/core/services/addons/catalog"
)
const fakeGallery = `
- &demo
name: "demo"
description: |
A demo
backend.
capabilities:
default: "cpu-demo"
- !!merge <<: *demo
name: "cpu-demo"
alias: "demo"
uri: "quay.io/example/backends:latest-cpu-demo"
- !!merge <<: *demo
name: "cpu-demo-development"
uri: "quay.io/example/backends:master-cpu-demo"
`
func fakeDigest(image, platform string) (string, error) {
return "sha256:" + strings.Repeat("ab", 32), nil
}
func TestRenderPinsTheGalleryImage(t *testing.T) {
got, err := render([]byte(fakeGallery), []string{"cpu-demo"}, "linux", "amd64", fakeDigest)
if err != nil {
t.Fatal(err)
}
if len(got) != 1 {
t.Fatalf("want 1 entry, got %d", len(got))
}
r := got[0]
if r.EntryKind() != catalog.KindBackend || r.AddonID != "cpu-demo" || r.Alias != "demo" || r.Channel != "latest" ||
r.Artifact != "oci://quay.io/example/backends:latest-cpu-demo" || r.Version != "0.0.0-latest.abababababab" ||
r.Description != "A demo backend." {
t.Fatalf("unexpected entry: %+v", r)
}
data, err := catalog.EncodeIndex(got)
if err != nil {
t.Fatal(err)
}
read, err := catalog.ReadIndex(data, catalog.KindBackend)
if err != nil || len(read.Releases) != 1 || len(read.Skipped) != 0 {
t.Fatalf("the rendered entry does not read back: %v %+v", err, read)
}
}
func TestRenderRefusesWhatItCannotPin(t *testing.T) {
for name, tc := range map[string][]string{
"unknown": {"nope"},
"meta": {"demo"},
"master only": {"cpu-demo-development"},
} {
if _, err := render([]byte(fakeGallery), tc, "linux", "amd64", fakeDigest); err == nil {
t.Errorf("%s: want an error", name)
}
}
}