2023-07-18 13:44:21 +08:00
package server
import (
2023-08-22 12:56:56 +08:00
"errors"
2023-07-18 13:44:21 +08:00
"fmt"
2024-12-12 07:29:59 +08:00
"io/fs"
2023-08-22 09:38:31 +08:00
"net/url"
2023-07-18 13:44:21 +08:00
"os"
"path/filepath"
2024-05-06 02:46:12 +08:00
"regexp"
2023-07-18 13:44:21 +08:00
"strings"
2024-06-14 03:52:03 +08:00
"github.com/ollama/ollama/envconfig"
2024-12-12 07:29:59 +08:00
"github.com/ollama/ollama/types/model"
2023-07-18 13:44:21 +08:00
)
type ModelPath struct {
ProtocolScheme string
Registry string
Namespace string
Repository string
Tag string
}
const (
DefaultRegistry = "registry.ollama.ai"
DefaultNamespace = "library"
DefaultTag = "latest"
DefaultProtocolScheme = "https"
)
2023-08-22 12:56:56 +08:00
var (
2024-05-06 02:46:12 +08:00
ErrInvalidImageFormat = errors . New ( "invalid image format" )
2025-03-29 02:50:22 +08:00
ErrInvalidDigestFormat = errors . New ( "invalid digest format" )
2024-05-06 02:46:12 +08:00
ErrInvalidProtocol = errors . New ( "invalid protocol scheme" )
ErrInsecureProtocol = errors . New ( "insecure protocol http" )
2025-03-29 02:50:22 +08:00
ErrModelPathInvalid = errors . New ( "invalid model path" )
2023-08-22 12:56:56 +08:00
)
2023-08-23 00:39:42 +08:00
func ParseModelPath ( name string ) ModelPath {
2023-08-22 12:56:56 +08:00
mp := ModelPath {
ProtocolScheme : DefaultProtocolScheme ,
Registry : DefaultRegistry ,
Namespace : DefaultNamespace ,
Repository : "" ,
Tag : DefaultTag ,
}
2023-07-18 13:44:21 +08:00
2023-08-22 09:38:31 +08:00
before , after , found := strings . Cut ( name , "://" )
if found {
mp . ProtocolScheme = before
name = after
2023-08-22 12:56:56 +08:00
}
2024-01-17 08:48:05 +08:00
name = strings . ReplaceAll ( name , string ( os . PathSeparator ) , "/" )
2023-12-16 07:50:51 +08:00
parts := strings . Split ( name , "/" )
2023-08-23 00:39:42 +08:00
switch len ( parts ) {
2023-07-18 13:44:21 +08:00
case 3 :
2023-08-23 00:39:42 +08:00
mp . Registry = parts [ 0 ]
mp . Namespace = parts [ 1 ]
mp . Repository = parts [ 2 ]
2023-07-18 13:44:21 +08:00
case 2 :
2023-08-23 00:39:42 +08:00
mp . Namespace = parts [ 0 ]
mp . Repository = parts [ 1 ]
2023-07-18 13:44:21 +08:00
case 1 :
2023-08-23 00:39:42 +08:00
mp . Repository = parts [ 0 ]
2023-07-18 13:44:21 +08:00
}
2023-08-23 00:39:42 +08:00
if repo , tag , found := strings . Cut ( mp . Repository , ":" ) ; found {
2023-08-22 12:56:56 +08:00
mp . Repository = repo
mp . Tag = tag
2023-07-18 13:44:21 +08:00
}
2023-08-23 00:39:42 +08:00
return mp
2023-07-18 13:44:21 +08:00
}
func ( mp ModelPath ) GetNamespaceRepository ( ) string {
return fmt . Sprintf ( "%s/%s" , mp . Namespace , mp . Repository )
}
func ( mp ModelPath ) GetFullTagname ( ) string {
return fmt . Sprintf ( "%s/%s/%s:%s" , mp . Registry , mp . Namespace , mp . Repository , mp . Tag )
}
func ( mp ModelPath ) GetShortTagname ( ) string {
2023-07-22 06:42:19 +08:00
if mp . Registry == DefaultRegistry {
if mp . Namespace == DefaultNamespace {
return fmt . Sprintf ( "%s:%s" , mp . Repository , mp . Tag )
}
return fmt . Sprintf ( "%s/%s:%s" , mp . Namespace , mp . Repository , mp . Tag )
2023-07-18 13:44:21 +08:00
}
2023-07-22 06:42:19 +08:00
return fmt . Sprintf ( "%s/%s/%s:%s" , mp . Registry , mp . Namespace , mp . Repository , mp . Tag )
2023-07-18 13:44:21 +08:00
}
2023-10-27 22:19:59 +08:00
// GetManifestPath returns the path to the manifest file for the given model path, it is up to the caller to create the directory if it does not exist.
func ( mp ModelPath ) GetManifestPath ( ) ( string , error ) {
2024-12-12 07:29:59 +08:00
name := model . Name {
Host : mp . Registry ,
Namespace : mp . Namespace ,
Model : mp . Repository ,
Tag : mp . Tag ,
2024-08-28 08:56:04 +08:00
}
2024-12-12 07:29:59 +08:00
if ! name . IsValid ( ) {
return "" , fs . ErrNotExist
}
return filepath . Join ( envconfig . Models ( ) , "manifests" , name . Filepath ( ) ) , nil
2023-07-18 13:44:21 +08:00
}
2023-08-22 09:38:31 +08:00
func ( mp ModelPath ) BaseURL ( ) * url . URL {
return & url . URL {
Scheme : mp . ProtocolScheme ,
Host : mp . Registry ,
}
}
2023-07-19 00:09:45 +08:00
func GetManifestPath ( ) ( string , error ) {
2024-07-04 08:07:42 +08:00
path := filepath . Join ( envconfig . Models ( ) , "manifests" )
2023-09-07 05:30:08 +08:00
if err := os . MkdirAll ( path , 0 o755 ) ; err != nil {
2025-05-25 04:17:04 +08:00
return "" , fmt . Errorf ( "%w: ensure path elements are traversable" , err )
2023-09-06 08:10:40 +08:00
}
return path , nil
2023-07-19 00:09:45 +08:00
}
2023-07-18 13:44:21 +08:00
func GetBlobsPath ( digest string ) ( string , error ) {
2024-05-06 02:46:12 +08:00
// only accept actual sha256 digests
pattern := "^sha256[:-][0-9a-fA-F]{64}$"
re := regexp . MustCompile ( pattern )
if digest != "" && ! re . MatchString ( digest ) {
return "" , ErrInvalidDigestFormat
}
2024-03-15 11:18:06 +08:00
digest = strings . ReplaceAll ( digest , ":" , "-" )
2024-07-04 08:07:42 +08:00
path := filepath . Join ( envconfig . Models ( ) , "blobs" , digest )
2023-09-12 05:54:52 +08:00
dirPath := filepath . Dir ( path )
if digest == "" {
dirPath = path
}
if err := os . MkdirAll ( dirPath , 0 o755 ) ; err != nil {
2025-05-25 04:17:04 +08:00
return "" , fmt . Errorf ( "%w: ensure path elements are traversable" , err )
2023-07-18 13:44:21 +08:00
}
2023-07-19 02:24:19 +08:00
return path , nil
2023-07-18 13:44:21 +08:00
}