modelpath.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. package server
  2. import (
  3. "errors"
  4. "fmt"
  5. "net/url"
  6. "os"
  7. "path/filepath"
  8. "regexp"
  9. "strings"
  10. "github.com/ollama/ollama/envconfig"
  11. )
  12. type ModelPath struct {
  13. ProtocolScheme string
  14. Registry string
  15. Namespace string
  16. Repository string
  17. Tag string
  18. }
  19. const (
  20. DefaultRegistry = "registry.ollama.ai"
  21. DefaultNamespace = "library"
  22. DefaultTag = "latest"
  23. DefaultProtocolScheme = "https"
  24. )
  25. var (
  26. ErrInvalidImageFormat = errors.New("invalid image format")
  27. ErrInvalidProtocol = errors.New("invalid protocol scheme")
  28. ErrInsecureProtocol = errors.New("insecure protocol http")
  29. ErrInvalidDigestFormat = errors.New("invalid digest format")
  30. )
  31. func ParseModelPath(name string) ModelPath {
  32. mp := ModelPath{
  33. ProtocolScheme: DefaultProtocolScheme,
  34. Registry: DefaultRegistry,
  35. Namespace: DefaultNamespace,
  36. Repository: "",
  37. Tag: DefaultTag,
  38. }
  39. before, after, found := strings.Cut(name, "://")
  40. if found {
  41. mp.ProtocolScheme = before
  42. name = after
  43. }
  44. parts := strings.Split(filepath.ToSlash(name), "/")
  45. switch len(parts) {
  46. case 3:
  47. mp.Registry = parts[0]
  48. mp.Namespace = parts[1]
  49. mp.Repository = parts[2]
  50. case 2:
  51. mp.Namespace = parts[0]
  52. mp.Repository = parts[1]
  53. case 1:
  54. mp.Repository = parts[0]
  55. }
  56. if repo, tag, found := strings.Cut(mp.Repository, ":"); found {
  57. mp.Repository = repo
  58. mp.Tag = tag
  59. }
  60. return mp
  61. }
  62. var errModelPathInvalid = errors.New("invalid model path")
  63. func (mp ModelPath) Validate() error {
  64. if mp.Repository == "" {
  65. return fmt.Errorf("%w: model repository name is required", errModelPathInvalid)
  66. }
  67. if strings.Contains(mp.Tag, ":") {
  68. return fmt.Errorf("%w: ':' (colon) is not allowed in tag names", errModelPathInvalid)
  69. }
  70. return nil
  71. }
  72. func (mp ModelPath) GetNamespaceRepository() string {
  73. return fmt.Sprintf("%s/%s", mp.Namespace, mp.Repository)
  74. }
  75. func (mp ModelPath) GetFullTagname() string {
  76. return fmt.Sprintf("%s/%s/%s:%s", mp.Registry, mp.Namespace, mp.Repository, mp.Tag)
  77. }
  78. func (mp ModelPath) GetShortTagname() string {
  79. if mp.Registry == DefaultRegistry {
  80. if mp.Namespace == DefaultNamespace {
  81. return fmt.Sprintf("%s:%s", mp.Repository, mp.Tag)
  82. }
  83. return fmt.Sprintf("%s/%s:%s", mp.Namespace, mp.Repository, mp.Tag)
  84. }
  85. return fmt.Sprintf("%s/%s/%s:%s", mp.Registry, mp.Namespace, mp.Repository, mp.Tag)
  86. }
  87. // 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.
  88. func (mp ModelPath) GetManifestPath() (string, error) {
  89. dir := envconfig.ModelsDir
  90. return filepath.Join(dir, "manifests", mp.Registry, mp.Namespace, mp.Repository, mp.Tag), nil
  91. }
  92. func (mp ModelPath) BaseURL() *url.URL {
  93. return &url.URL{
  94. Scheme: mp.ProtocolScheme,
  95. Host: mp.Registry,
  96. }
  97. }
  98. func GetManifestPath() (string, error) {
  99. dir := envconfig.ModelsDir
  100. path := filepath.Join(dir, "manifests")
  101. if err := os.MkdirAll(path, 0o755); err != nil {
  102. return "", err
  103. }
  104. return path, nil
  105. }
  106. func GetBlobsPath(digest string) (string, error) {
  107. dir := envconfig.ModelsDir
  108. // only accept actual sha256 digests
  109. pattern := "^sha256[:-][0-9a-fA-F]{64}$"
  110. re := regexp.MustCompile(pattern)
  111. if digest != "" && !re.MatchString(digest) {
  112. return "", ErrInvalidDigestFormat
  113. }
  114. digest = strings.ReplaceAll(digest, ":", "-")
  115. path := filepath.Join(dir, "blobs", digest)
  116. dirPath := filepath.Dir(path)
  117. if digest == "" {
  118. dirPath = path
  119. }
  120. if err := os.MkdirAll(dirPath, 0o755); err != nil {
  121. return "", err
  122. }
  123. return path, nil
  124. }