2023-07-17 22:44:21 -07:00
package server
import (
2023-08-21 21:56:56 -07:00
"errors"
2023-07-17 22:44:21 -07:00
"fmt"
2023-08-21 18:38:31 -07:00
"net/url"
2023-07-17 22:44:21 -07:00
"os"
"path/filepath"
2024-05-05 11:46:12 -07:00
"regexp"
2023-07-17 22:44:21 -07:00
"strings"
2024-06-13 15:52:03 -04:00
"github.com/ollama/ollama/envconfig"
2023-07-17 22:44:21 -07: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-21 21:56:56 -07:00
var (
2024-05-05 11:46:12 -07:00
ErrInvalidImageFormat = errors . New ( "invalid image format" )
ErrInvalidProtocol = errors . New ( "invalid protocol scheme" )
ErrInsecureProtocol = errors . New ( "insecure protocol http" )
ErrInvalidDigestFormat = errors . New ( "invalid digest format" )
2023-08-21 21:56:56 -07:00
)
2023-08-22 09:39:42 -07:00
func ParseModelPath ( name string ) ModelPath {
2023-08-21 21:56:56 -07:00
mp := ModelPath {
ProtocolScheme : DefaultProtocolScheme ,
Registry : DefaultRegistry ,
Namespace : DefaultNamespace ,
Repository : "" ,
Tag : DefaultTag ,
}
2023-07-17 22:44:21 -07:00
2023-08-21 18:38:31 -07:00
before , after , found := strings . Cut ( name , "://" )
if found {
mp . ProtocolScheme = before
name = after
2023-08-21 21:56:56 -07:00
}
2024-01-16 16:48:05 -08:00
name = strings . ReplaceAll ( name , string ( os . PathSeparator ) , "/" )
2023-12-15 15:50:51 -08:00
parts := strings . Split ( name , "/" )
2023-08-22 09:39:42 -07:00
switch len ( parts ) {
2023-07-17 22:44:21 -07:00
case 3 :
2023-08-22 09:39:42 -07:00
mp . Registry = parts [ 0 ]
mp . Namespace = parts [ 1 ]
mp . Repository = parts [ 2 ]
2023-07-17 22:44:21 -07:00
case 2 :
2023-08-22 09:39:42 -07:00
mp . Namespace = parts [ 0 ]
mp . Repository = parts [ 1 ]
2023-07-17 22:44:21 -07:00
case 1 :
2023-08-22 09:39:42 -07:00
mp . Repository = parts [ 0 ]
2023-07-17 22:44:21 -07:00
}
2023-08-22 09:39:42 -07:00
if repo , tag , found := strings . Cut ( mp . Repository , ":" ) ; found {
2023-08-21 21:56:56 -07:00
mp . Repository = repo
mp . Tag = tag
2023-07-17 22:44:21 -07:00
}
2023-08-22 09:39:42 -07:00
return mp
2023-07-17 22:44:21 -07:00
}
2023-11-29 15:54:29 -05:00
var errModelPathInvalid = errors . New ( "invalid model path" )
2023-07-17 22:44:21 -07: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-21 15:42:19 -07: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-17 22:44:21 -07:00
}
2023-07-21 15:42:19 -07:00
return fmt . Sprintf ( "%s/%s/%s:%s" , mp . Registry , mp . Namespace , mp . Repository , mp . Tag )
2023-07-17 22:44:21 -07:00
}
2023-10-27 10:19:59 -04: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-08-27 17:56:04 -07:00
if p := filepath . Join ( mp . Registry , mp . Namespace , mp . Repository , mp . Tag ) ; filepath . IsLocal ( p ) {
return filepath . Join ( envconfig . Models ( ) , "manifests" , p ) , nil
}
return "" , errModelPathInvalid
2023-07-17 22:44:21 -07:00
}
2023-08-21 18:38:31 -07:00
func ( mp ModelPath ) BaseURL ( ) * url . URL {
return & url . URL {
Scheme : mp . ProtocolScheme ,
Host : mp . Registry ,
}
}
2023-07-18 09:09:45 -07:00
func GetManifestPath ( ) ( string , error ) {
2024-07-03 17:07:42 -07:00
path := filepath . Join ( envconfig . Models ( ) , "manifests" )
2023-09-06 14:30:08 -07:00
if err := os . MkdirAll ( path , 0 o755 ) ; err != nil {
2023-09-05 17:10:40 -07:00
return "" , err
}
return path , nil
2023-07-18 09:09:45 -07:00
}
2023-07-17 22:44:21 -07:00
func GetBlobsPath ( digest string ) ( string , error ) {
2024-05-05 11:46:12 -07: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-14 20:18:06 -07:00
digest = strings . ReplaceAll ( digest , ":" , "-" )
2024-07-03 17:07:42 -07:00
path := filepath . Join ( envconfig . Models ( ) , "blobs" , digest )
2023-09-11 14:54:52 -07:00
dirPath := filepath . Dir ( path )
if digest == "" {
dirPath = path
}
if err := os . MkdirAll ( dirPath , 0 o755 ) ; err != nil {
2023-07-17 22:44:21 -07:00
return "" , err
}
2023-07-18 11:24:19 -07:00
return path , nil
2023-07-17 22:44:21 -07:00
}