mirror of
https://github.com/mudler/LocalAI.git
synced 2026-11-06 12:33:16 -05:00
inspect verifies an add-on zip on this machine and shows what it holds. install checks it, uploads it to a running server and prints the page where the administrator confirms the install and the host access. The command never installs by itself. Assisted-by: Claude:claude-sonnet-5-5 go gofmt
118 lines
4.4 KiB
Go
118 lines
4.4 KiB
Go
// SPDX-License-Identifier: MIT
|
|
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/alecthomas/kong"
|
|
"github.com/mudler/LocalAI/core/services/addons"
|
|
"github.com/mudler/LocalAI/pkg/addons/zipformat"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
var _ = Describe("Add-on zip commands", func() {
|
|
manifest := `{"schemaVersion":1,"id":"sample","version":"1.2.3","executable":"bin/sample","os":"linux","arch":"amd64","host":{"min":1,"max":2},"api":{"min":2,"max":2},"protocol":{"min":1,"max":1},"dataFormat":{"min":1,"max":2},"capabilities":{"service":true,"hostOperations":{"required":["workspace.materialize"],"optional":["egress.check"]}}}`
|
|
writeZip := func() string {
|
|
var b bytes.Buffer
|
|
Expect(zipformat.Write(&b, "sample", "1.2.3", "linux", "amd64", "bin/sample", []zipformat.Entry{
|
|
{Name: "manifest.json", Data: []byte(manifest)}, {Name: "bin/sample", Data: []byte("x"), Exec: true},
|
|
})).To(Succeed())
|
|
p := filepath.Join(GinkgoT().TempDir(), "sample-1.2.3-linux-amd64.zip")
|
|
Expect(os.WriteFile(p, b.Bytes(), 0o600)).To(Succeed())
|
|
return p
|
|
}
|
|
parse := func(args ...string) (*AddonsCMD, error) {
|
|
var command struct {
|
|
Addons AddonsCMD `cmd:"" name:"addons"`
|
|
}
|
|
p, err := kong.New(&command)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, err = p.Parse(append([]string{"addons"}, args...))
|
|
return &command.Addons, err
|
|
}
|
|
|
|
It("shows what a zip holds and that nothing signs it", func() {
|
|
c, err := parse("inspect", writeZip())
|
|
Expect(err).NotTo(HaveOccurred())
|
|
var out bytes.Buffer
|
|
c.Inspect.output = &out
|
|
Expect(c.Inspect.Run(nil)).To(Succeed())
|
|
Expect(out.String()).To(ContainSubstring("sample 1.2.3"))
|
|
Expect(out.String()).To(ContainSubstring("linux/amd64"))
|
|
Expect(out.String()).To(ContainSubstring("required workspace.materialize; optional egress.check"))
|
|
Expect(out.String()).To(MatchRegexp(`sha256:\s+[0-9a-f]{64}`))
|
|
Expect(out.String()).To(ContainSubstring("local file, unsigned"))
|
|
})
|
|
|
|
It("refuses a file that is not an add-on zip", func() {
|
|
p := filepath.Join(GinkgoT().TempDir(), "x.zip")
|
|
Expect(os.WriteFile(p, []byte("nope"), 0o600)).To(Succeed())
|
|
c, err := parse("inspect", p)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(c.Inspect.Run(nil)).To(MatchError(ContainSubstring("not a zip")))
|
|
})
|
|
|
|
It("uploads the file with the key, and prints the page that finishes the install", func() {
|
|
path := writeZip()
|
|
raw, _ := os.ReadFile(path)
|
|
var gotAuth, gotName string
|
|
var gotBytes []byte
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
gotAuth = r.Header.Get("Authorization")
|
|
Expect(r.Method + " " + r.URL.Path).To(Equal("POST /api/addons/files"))
|
|
f, h, err := r.FormFile("file")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
gotName = h.Filename
|
|
gotBytes, _ = io.ReadAll(f)
|
|
_ = json.NewEncoder(w).Encode(addons.FileView{FileID: "0123456789abcdef0123456789abcdef", SHA256: zipSHA(raw), Relation: "new"})
|
|
}))
|
|
DeferCleanup(srv.Close)
|
|
c, err := parse("install", path, "--target", srv.URL, "--api-key", "k1")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
var out bytes.Buffer
|
|
c.Install.output = &out
|
|
Expect(c.Install.Run(nil)).To(Succeed())
|
|
Expect(gotAuth).To(Equal("Bearer k1"))
|
|
Expect(gotName).To(Equal("sample-1.2.3-linux-amd64.zip"))
|
|
Expect(gotBytes).To(Equal(raw))
|
|
Expect(out.String()).To(ContainSubstring(srv.URL + "/app/addons?file=0123456789abcdef0123456789abcdef"))
|
|
Expect(out.String()).To(ContainSubstring("in the browser"))
|
|
})
|
|
|
|
It("reports the reason the server gives", func() {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(400)
|
|
_, _ = w.Write([]byte(`{"error":{"type":"wrong_platform","code":400,"message":"This file is for linux/arm64; this host is linux/amd64"}}`))
|
|
}))
|
|
DeferCleanup(srv.Close)
|
|
c, err := parse("install", writeZip(), "--target", srv.URL)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
var out bytes.Buffer
|
|
c.Install.output = &out
|
|
Expect(c.Install.Run(nil)).To(MatchError(ContainSubstring("wrong_platform")))
|
|
})
|
|
|
|
It("rejects a target that is not a web address", func() {
|
|
c, err := parse("install", writeZip(), "--target", "ftp://x")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
c.Install.output = io.Discard
|
|
Expect(c.Install.Run(nil)).To(MatchError(ContainSubstring("--target")))
|
|
})
|
|
})
|
|
|
|
func zipSHA(b []byte) string {
|
|
s := sha256.Sum256(b)
|
|
return hex.EncodeToString(s[:])
|
|
}
|