mirror of
https://github.com/mudler/LocalAI.git
synced 2025-05-27 22:15:00 +00:00
feat(oci): support OCI images and Ollama models (#2628)
* Support specifying oci:// and ollama:// for model URLs Fixes: https://github.com/mudler/LocalAI/issues/2527 Fixes: https://github.com/mudler/LocalAI/issues/1028 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Lower watcher warnings Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Allow to install ollama models from CLI Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fixup tests Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Do not keep file ownership Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Skip test on darwin Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
parent
e265a618d9
commit
f569237a50
19 changed files with 638 additions and 97 deletions
153
pkg/oci/image.go
Normal file
153
pkg/oci/image.go
Normal file
|
@ -0,0 +1,153 @@
|
|||
package oci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/archive"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/logs"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
|
||||
)
|
||||
|
||||
// ref: https://github.com/mudler/luet/blob/master/pkg/helpers/docker/docker.go#L117
|
||||
type staticAuth struct {
|
||||
auth *registrytypes.AuthConfig
|
||||
}
|
||||
|
||||
func (s staticAuth) Authorization() (*authn.AuthConfig, error) {
|
||||
if s.auth == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return &authn.AuthConfig{
|
||||
Username: s.auth.Username,
|
||||
Password: s.auth.Password,
|
||||
Auth: s.auth.Auth,
|
||||
IdentityToken: s.auth.IdentityToken,
|
||||
RegistryToken: s.auth.RegistryToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var defaultRetryBackoff = remote.Backoff{
|
||||
Duration: 1.0 * time.Second,
|
||||
Factor: 3.0,
|
||||
Jitter: 0.1,
|
||||
Steps: 3,
|
||||
}
|
||||
|
||||
var defaultRetryPredicate = func(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, io.EOF) || errors.Is(err, syscall.EPIPE) || errors.Is(err, syscall.ECONNRESET) || strings.Contains(err.Error(), "connection refused") {
|
||||
logs.Warn.Printf("retrying %v", err)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ExtractOCIImage will extract a given targetImage into a given targetDestination
|
||||
func ExtractOCIImage(img v1.Image, targetDestination string) error {
|
||||
reader := mutate.Extract(img)
|
||||
|
||||
_, err := archive.Apply(context.Background(), targetDestination, reader, archive.WithNoSameOwner())
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func ParseImageParts(image string) (tag, repository, dstimage string) {
|
||||
tag = "latest"
|
||||
repository = "library"
|
||||
if strings.Contains(image, ":") {
|
||||
parts := strings.Split(image, ":")
|
||||
image = parts[0]
|
||||
tag = parts[1]
|
||||
}
|
||||
if strings.Contains("/", image) {
|
||||
parts := strings.Split(image, "/")
|
||||
repository = parts[0]
|
||||
image = parts[1]
|
||||
}
|
||||
dstimage = image
|
||||
return tag, repository, image
|
||||
}
|
||||
|
||||
// GetImage if returns the proper image to pull with transport and auth
|
||||
// tries local daemon first and then fallbacks into remote
|
||||
// if auth is nil, it will try to use the default keychain https://github.com/google/go-containerregistry/tree/main/pkg/authn#tldr-for-consumers-of-this-package
|
||||
func GetImage(targetImage, targetPlatform string, auth *registrytypes.AuthConfig, t http.RoundTripper) (v1.Image, error) {
|
||||
var platform *v1.Platform
|
||||
var image v1.Image
|
||||
var err error
|
||||
|
||||
if targetPlatform != "" {
|
||||
platform, err = v1.ParsePlatform(targetPlatform)
|
||||
if err != nil {
|
||||
return image, err
|
||||
}
|
||||
} else {
|
||||
platform, err = v1.ParsePlatform(fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH))
|
||||
if err != nil {
|
||||
return image, err
|
||||
}
|
||||
}
|
||||
|
||||
ref, err := name.ParseReference(targetImage)
|
||||
if err != nil {
|
||||
return image, err
|
||||
}
|
||||
|
||||
if t == nil {
|
||||
t = http.DefaultTransport
|
||||
}
|
||||
|
||||
tr := transport.NewRetry(t,
|
||||
transport.WithRetryBackoff(defaultRetryBackoff),
|
||||
transport.WithRetryPredicate(defaultRetryPredicate),
|
||||
)
|
||||
|
||||
opts := []remote.Option{
|
||||
remote.WithTransport(tr),
|
||||
remote.WithPlatform(*platform),
|
||||
}
|
||||
if auth != nil {
|
||||
opts = append(opts, remote.WithAuth(staticAuth{auth}))
|
||||
} else {
|
||||
opts = append(opts, remote.WithAuthFromKeychain(authn.DefaultKeychain))
|
||||
}
|
||||
|
||||
image, err = remote.Image(ref, opts...)
|
||||
|
||||
return image, err
|
||||
}
|
||||
|
||||
func GetOCIImageSize(targetImage, targetPlatform string, auth *registrytypes.AuthConfig, t http.RoundTripper) (int64, error) {
|
||||
var size int64
|
||||
var img v1.Image
|
||||
var err error
|
||||
|
||||
img, err = GetImage(targetImage, targetPlatform, auth, t)
|
||||
if err != nil {
|
||||
return size, err
|
||||
}
|
||||
layers, _ := img.Layers()
|
||||
for _, layer := range layers {
|
||||
s, _ := layer.Size()
|
||||
size += s
|
||||
}
|
||||
|
||||
return size, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue