images.go 22 KB
Newer Older
1
2
3
4
package server

import (
	"bytes"
5
	"context"
6
	"crypto/sha256"
Patrick Devine's avatar
Patrick Devine committed
7
	"encoding/hex"
8
9
10
11
12
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
13
	"log/slog"
14
	"net"
15
	"net/http"
Michael Yang's avatar
Michael Yang committed
16
	"net/url"
17
18
	"os"
	"path/filepath"
Michael Yang's avatar
Michael Yang committed
19
	"runtime"
20
	"slices"
Michael Yang's avatar
Michael Yang committed
21
	"strconv"
22
23
	"strings"

24
	"github.com/ollama/ollama/api"
Michael Yang's avatar
Michael Yang committed
25
	"github.com/ollama/ollama/envconfig"
26
	"github.com/ollama/ollama/fs/gguf"
Devon Rifkin's avatar
Devon Rifkin committed
27
	"github.com/ollama/ollama/model/parsers"
28
	"github.com/ollama/ollama/parser"
Michael Yang's avatar
Michael Yang committed
29
	"github.com/ollama/ollama/template"
30
	"github.com/ollama/ollama/thinking"
Michael Yang's avatar
Michael Yang committed
31
	"github.com/ollama/ollama/types/model"
32
	"github.com/ollama/ollama/version"
33
34
)

35
36
37
38
39
var (
	errCapabilities         = errors.New("does not support")
	errCapabilityCompletion = errors.New("completion")
	errCapabilityTools      = errors.New("tools")
	errCapabilityInsert     = errors.New("insert")
40
41
	errCapabilityVision     = errors.New("vision")
	errCapabilityEmbedding  = errors.New("embedding")
42
	errCapabilityThinking   = errors.New("thinking")
CYJiang's avatar
CYJiang committed
43
	errInsecureProtocol     = errors.New("insecure protocol http")
44
)
Michael Yang's avatar
Michael Yang committed
45

Michael Yang's avatar
Michael Yang committed
46
47
48
49
50
type registryOptions struct {
	Insecure bool
	Username string
	Password string
	Token    string
51
52

	CheckRedirect func(req *http.Request, via []*http.Request) error
Michael Yang's avatar
Michael Yang committed
53
54
}

55
type Model struct {
Michael Yang's avatar
Michael Yang committed
56
	Name           string `json:"name"`
57
	Config         ConfigV2
Michael Yang's avatar
Michael Yang committed
58
59
	ShortName      string
	ModelPath      string
60
	ParentModel    string
Michael Yang's avatar
Michael Yang committed
61
62
63
64
65
	AdapterPaths   []string
	ProjectorPaths []string
	System         string
	License        []string
	Digest         string
66
	Options        map[string]any
Michael Yang's avatar
Michael Yang committed
67
	Messages       []api.Message
Michael Yang's avatar
Michael Yang committed
68
69

	Template *template.Template
70
71
}

72
73
74
// Capabilities returns the capabilities that the model supports
func (m *Model) Capabilities() []model.Capability {
	capabilities := []model.Capability{}
75

76
	// Check for completion capability
77
	f, err := gguf.Open(m.ModelPath)
78
	if err == nil {
79
		defer f.Close()
80

81
82
		if f.KeyValue("pooling_type").Valid() {
			capabilities = append(capabilities, model.CapabilityEmbedding)
83
		} else {
84
85
86
87
88
			// If no embedding is specified, we assume the model supports completion
			capabilities = append(capabilities, model.CapabilityCompletion)
		}
		if f.KeyValue("vision.block_count").Valid() {
			capabilities = append(capabilities, model.CapabilityVision)
89
90
91
92
93
94
95
96
97
		}
	} else {
		slog.Error("couldn't open model file", "error", err)
	}

	if m.Template == nil {
		return capabilities
	}

Devon Rifkin's avatar
Devon Rifkin committed
98
	builtinParser := parsers.ParserForName(m.Config.Parser)
99
	// Check for tools capability
Devon Rifkin's avatar
Devon Rifkin committed
100
	if slices.Contains(m.Template.Vars(), "tools") || (builtinParser != nil && builtinParser.HasToolSupport()) {
101
102
103
104
105
106
107
108
		capabilities = append(capabilities, model.CapabilityTools)
	}

	// Check for insert capability
	if slices.Contains(m.Template.Vars(), "suffix") {
		capabilities = append(capabilities, model.CapabilityInsert)
	}

109
110
111
112
113
	// Check for vision capability in projector-based models
	if len(m.ProjectorPaths) > 0 {
		capabilities = append(capabilities, model.CapabilityVision)
	}

114
	// Check for thinking capability
115
	openingTag, closingTag := thinking.InferTags(m.Template.Template)
Michael Yang's avatar
Michael Yang committed
116
	hasTags := openingTag != "" && closingTag != ""
Devon Rifkin's avatar
Devon Rifkin committed
117
118
	isGptoss := slices.Contains([]string{"gptoss", "gpt-oss"}, m.Config.ModelFamily)
	if hasTags || isGptoss || (builtinParser != nil && builtinParser.HasThinkingSupport()) {
119
120
121
		capabilities = append(capabilities, model.CapabilityThinking)
	}

122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
	return capabilities
}

// CheckCapabilities checks if the model has the specified capabilities returning an error describing
// any missing or unknown capabilities
func (m *Model) CheckCapabilities(want ...model.Capability) error {
	available := m.Capabilities()
	var errs []error

	// Map capabilities to their corresponding error
	capToErr := map[model.Capability]error{
		model.CapabilityCompletion: errCapabilityCompletion,
		model.CapabilityTools:      errCapabilityTools,
		model.CapabilityInsert:     errCapabilityInsert,
		model.CapabilityVision:     errCapabilityVision,
		model.CapabilityEmbedding:  errCapabilityEmbedding,
138
		model.CapabilityThinking:   errCapabilityThinking,
139
140
141
142
143
	}

	for _, cap := range want {
		err, ok := capToErr[cap]
		if !ok {
Michael Yang's avatar
Michael Yang committed
144
			slog.Error("unknown capability", "capability", cap)
Michael Yang's avatar
Michael Yang committed
145
			return fmt.Errorf("unknown capability: %s", cap)
Michael Yang's avatar
Michael Yang committed
146
		}
147
148
149
150

		if !slices.Contains(available, cap) {
			errs = append(errs, err)
		}
Michael Yang's avatar
Michael Yang committed
151
152
	}

153
	var err error
154
	if len(errs) > 0 {
155
156
157
158
159
160
161
162
		err = fmt.Errorf("%w %w", errCapabilities, errors.Join(errs...))
	}

	if slices.Contains(errs, errCapabilityThinking) {
		if m.Config.ModelFamily == "qwen3" || model.ParseName(m.Name).Model == "deepseek-r1" {
			// append a message to the existing error
			return fmt.Errorf("%w. Pull the model again to get the latest version with full thinking support", err)
		}
Michael Yang's avatar
Michael Yang committed
163
164
	}

165
	return err
166
167
}

Michael Yang's avatar
Michael Yang committed
168
func (m *Model) String() string {
169
	var modelfile parser.Modelfile
Michael Yang's avatar
Michael Yang committed
170

171
	modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
172
173
174
		Name: "model",
		Args: m.ModelPath,
	})
175

Michael Yang's avatar
Michael Yang committed
176
	for _, adapter := range m.AdapterPaths {
177
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
178
179
			Name: "adapter",
			Args: adapter,
Michael Yang's avatar
Michael Yang committed
180
		})
181
182
	}

Michael Yang's avatar
Michael Yang committed
183
	for _, projector := range m.ProjectorPaths {
184
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
185
186
			Name: "model",
			Args: projector,
Michael Yang's avatar
Michael Yang committed
187
		})
188
189
	}

Michael Yang's avatar
Michael Yang committed
190
	if m.Template != nil {
191
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
192
			Name: "template",
Michael Yang's avatar
Michael Yang committed
193
			Args: m.Template.String(),
Michael Yang's avatar
Michael Yang committed
194
		})
195
196
	}

Michael Yang's avatar
Michael Yang committed
197
	if m.System != "" {
198
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
199
200
			Name: "system",
			Args: m.System,
Michael Yang's avatar
Michael Yang committed
201
		})
202
203
	}

Devon Rifkin's avatar
Devon Rifkin committed
204
205
206
207
208
209
210
211
212
213
214
215
216
217
	if m.Config.Renderer != "" {
		modelfile.Commands = append(modelfile.Commands, parser.Command{
			Name: "renderer",
			Args: m.Config.Renderer,
		})
	}

	if m.Config.Parser != "" {
		modelfile.Commands = append(modelfile.Commands, parser.Command{
			Name: "parser",
			Args: m.Config.Parser,
		})
	}

218
219
220
221
	for k, v := range m.Options {
		switch v := v.(type) {
		case []any:
			for _, s := range v {
222
				modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
223
224
225
					Name: k,
					Args: fmt.Sprintf("%v", s),
				})
226
227
			}
		default:
228
			modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
229
230
231
				Name: k,
				Args: fmt.Sprintf("%v", v),
			})
232
233
234
235
		}
	}

	for _, license := range m.License {
236
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
237
238
239
			Name: "license",
			Args: license,
		})
240
241
242
	}

	for _, msg := range m.Messages {
243
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
244
			Name: "message",
Michael Yang's avatar
Michael Yang committed
245
			Args: fmt.Sprintf("%s: %s", msg.Role, msg.Content),
Michael Yang's avatar
Michael Yang committed
246
		})
247
248
	}

Michael Yang's avatar
Michael Yang committed
249
	return modelfile.String()
250
251
}

252
type ConfigV2 struct {
253
254
255
256
257
	ModelFormat   string   `json:"model_format"`
	ModelFamily   string   `json:"model_family"`
	ModelFamilies []string `json:"model_families"`
	ModelType     string   `json:"model_type"`
	FileType      string   `json:"file_type"`
Devon Rifkin's avatar
Devon Rifkin committed
258
259
	Renderer      string   `json:"renderer,omitempty"`
	Parser        string   `json:"parser,omitempty"`
260

261
	// required by spec
262
263
	Architecture string `json:"architecture"`
	OS           string `json:"os"`
264
	RootFS       RootFS `json:"rootfs"`
265
266
267
268
269
270
271
}

type RootFS struct {
	Type    string   `json:"type"`
	DiffIDs []string `json:"diff_ids"`
}

Michael Yang's avatar
Michael Yang committed
272
func GetManifest(mp ModelPath) (*Manifest, string, error) {
273
	fp, err := mp.GetManifestPath()
274
	if err != nil {
Patrick Devine's avatar
Patrick Devine committed
275
		return nil, "", err
276
	}
277

Michael Yang's avatar
Michael Yang committed
278
	f, err := os.Open(fp)
279
	if err != nil {
Michael Yang's avatar
Michael Yang committed
280
		return nil, "", err
281
	}
Michael Yang's avatar
Michael Yang committed
282
	defer f.Close()
283

Michael Yang's avatar
Michael Yang committed
284
	sha256sum := sha256.New()
Patrick Devine's avatar
Patrick Devine committed
285

Michael Yang's avatar
Michael Yang committed
286
287
	var manifest Manifest
	if err := json.NewDecoder(io.TeeReader(f, sha256sum)).Decode(&manifest); err != nil {
Patrick Devine's avatar
Patrick Devine committed
288
		return nil, "", err
289
290
	}

Michael Yang's avatar
Michael Yang committed
291
	return &manifest, hex.EncodeToString(sha256sum.Sum(nil)), nil
292
293
294
}

func GetModel(name string) (*Model, error) {
295
	mp := ParseModelPath(name)
Patrick Devine's avatar
Patrick Devine committed
296
	manifest, digest, err := GetManifest(mp)
297
298
299
300
301
	if err != nil {
		return nil, err
	}

	model := &Model{
302
303
304
		Name:      mp.GetFullTagname(),
		ShortName: mp.GetShortTagname(),
		Digest:    digest,
Michael Yang's avatar
Michael Yang committed
305
		Template:  template.DefaultTemplate,
306
307
	}

308
309
310
311
312
	if manifest.Config.Digest != "" {
		filename, err := GetBlobsPath(manifest.Config.Digest)
		if err != nil {
			return nil, err
		}
313

314
315
316
317
318
		configFile, err := os.Open(filename)
		if err != nil {
			return nil, err
		}
		defer configFile.Close()
319

320
321
322
		if err := json.NewDecoder(configFile).Decode(&model.Config); err != nil {
			return nil, err
		}
323
324
	}

325
	for _, layer := range manifest.Layers {
Patrick Devine's avatar
Patrick Devine committed
326
		filename, err := GetBlobsPath(layer.Digest)
327
328
329
330
		if err != nil {
			return nil, err
		}

331
332
333
		switch layer.MediaType {
		case "application/vnd.ollama.image.model":
			model.ModelPath = filename
334
			model.ParentModel = layer.From
335
		case "application/vnd.ollama.image.embed":
336
337
			// Deprecated in versions  > 0.1.2
			// TODO: remove this warning in a future version
338
			slog.Info("WARNING: model contains embeddings, but embeddings in modelfiles have been deprecated and will be ignored.")
339
340
		case "application/vnd.ollama.image.adapter":
			model.AdapterPaths = append(model.AdapterPaths, filename)
Michael Yang's avatar
Michael Yang committed
341
342
		case "application/vnd.ollama.image.projector":
			model.ProjectorPaths = append(model.ProjectorPaths, filename)
Michael Yang's avatar
Michael Yang committed
343
344
		case "application/vnd.ollama.image.prompt",
			"application/vnd.ollama.image.template":
345
346
347
348
349
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}

Michael Yang's avatar
Michael Yang committed
350
			model.Template, err = template.Parse(string(bts))
351
352
353
			if err != nil {
				return nil, err
			}
Michael Yang's avatar
Michael Yang committed
354
		case "application/vnd.ollama.image.system":
355
356
357
358
359
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}

Michael Yang's avatar
Michael Yang committed
360
			model.System = string(bts)
361
		case "application/vnd.ollama.image.params":
Michael Yang's avatar
Michael Yang committed
362
363
364
365
366
			params, err := os.Open(filename)
			if err != nil {
				return nil, err
			}
			defer params.Close()
367

368
			// parse model options parameters into a map so that we can see which fields have been specified explicitly
369
			if err = json.NewDecoder(params).Decode(&model.Options); err != nil {
370
371
				return nil, err
			}
372
373
374
375
376
377
378
379
380
381
		case "application/vnd.ollama.image.messages":
			msgs, err := os.Open(filename)
			if err != nil {
				return nil, err
			}
			defer msgs.Close()

			if err = json.NewDecoder(msgs).Decode(&model.Messages); err != nil {
				return nil, err
			}
Patrick Devine's avatar
Patrick Devine committed
382
383
384
385
386
387
		case "application/vnd.ollama.image.license":
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}
			model.License = append(model.License, string(bts))
388
389
390
391
392
393
		}
	}

	return model, nil
}

Michael Yang's avatar
Michael Yang committed
394
func CopyModel(src, dst model.Name) error {
395
396
397
398
399
400
401
	if !dst.IsFullyQualified() {
		return model.Unqualified(dst)
	}
	if !src.IsFullyQualified() {
		return model.Unqualified(src)
	}

402
403
404
405
	if src.Filepath() == dst.Filepath() {
		return nil
	}

Michael Yang's avatar
Michael Yang committed
406
	manifests, err := GetManifestPath()
407
408
409
410
	if err != nil {
		return err
	}

411
	dstpath := filepath.Join(manifests, dst.Filepath())
Michael Yang's avatar
Michael Yang committed
412
	if err := os.MkdirAll(filepath.Dir(dstpath), 0o755); err != nil {
413
414
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
415

416
	srcpath := filepath.Join(manifests, src.Filepath())
Michael Yang's avatar
Michael Yang committed
417
	srcfile, err := os.Open(srcpath)
Patrick Devine's avatar
Patrick Devine committed
418
419
420
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
421
	defer srcfile.Close()
Patrick Devine's avatar
Patrick Devine committed
422

Michael Yang's avatar
Michael Yang committed
423
	dstfile, err := os.Create(dstpath)
Patrick Devine's avatar
Patrick Devine committed
424
425
426
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
427
	defer dstfile.Close()
Patrick Devine's avatar
Patrick Devine committed
428

Michael Yang's avatar
Michael Yang committed
429
430
	_, err = io.Copy(dstfile, srcfile)
	return err
Patrick Devine's avatar
Patrick Devine committed
431
432
}

Michael Yang's avatar
Michael Yang committed
433
func deleteUnusedLayers(deleteMap map[string]struct{}) error {
434
435
	// Ignore corrupt manifests to avoid blocking deletion of layers that are freshly orphaned
	manifests, err := Manifests(true)
436
437
438
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
439

Michael Yang's avatar
Michael Yang committed
440
	for _, manifest := range manifests {
Michael Yang's avatar
Michael Yang committed
441
442
443
444
445
		for _, layer := range manifest.Layers {
			delete(deleteMap, layer.Digest)
		}

		delete(deleteMap, manifest.Config.Digest)
Michael Yang's avatar
Michael Yang committed
446
	}
447
448

	// only delete the files which are still in the deleteMap
Michael Yang's avatar
Michael Yang committed
449
450
451
	for k := range deleteMap {
		fp, err := GetBlobsPath(k)
		if err != nil {
452
			slog.Info(fmt.Sprintf("couldn't get file path for '%s': %v", k, err))
Michael Yang's avatar
Michael Yang committed
453
454
			continue
		}
455
456
457
		if err := os.Remove(fp); err != nil {
			slog.Info(fmt.Sprintf("couldn't remove file '%s': %v", fp, err))
			continue
458
459
460
		}
	}

461
462
463
464
	return nil
}

func PruneLayers() error {
Michael Yang's avatar
Michael Yang committed
465
	deleteMap := make(map[string]struct{})
466
467
468
469
470
471
472
	p, err := GetBlobsPath("")
	if err != nil {
		return err
	}

	blobs, err := os.ReadDir(p)
	if err != nil {
473
		slog.Info(fmt.Sprintf("couldn't read dir '%s': %v", p, err))
474
475
476
477
478
		return err
	}

	for _, blob := range blobs {
		name := blob.Name()
479
		name = strings.ReplaceAll(name, "-", ":")
480
481
482
483
484
485
486
487
488
489
490

		_, err := GetBlobsPath(name)
		if err != nil {
			if errors.Is(err, ErrInvalidDigestFormat) {
				// remove invalid blobs (e.g. partial downloads)
				if err := os.Remove(filepath.Join(p, blob.Name())); err != nil {
					slog.Error("couldn't remove blob", "blob", blob.Name(), "error", err)
				}
			}

			continue
Michael Yang's avatar
Michael Yang committed
491
		}
492
493

		deleteMap[name] = struct{}{}
494
495
	}

496
	slog.Info(fmt.Sprintf("total blobs: %d", len(deleteMap)))
497

Michael Yang's avatar
Michael Yang committed
498
	if err := deleteUnusedLayers(deleteMap); err != nil {
499
		slog.Error(fmt.Sprintf("couldn't remove unused layers: %v", err))
500
		return nil
501
502
	}

503
	slog.Info(fmt.Sprintf("total unused blobs removed: %d", len(deleteMap)))
504
505
506
507

	return nil
}

Michael Yang's avatar
Michael Yang committed
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
func PruneDirectory(path string) error {
	info, err := os.Lstat(path)
	if err != nil {
		return err
	}

	if info.IsDir() && info.Mode()&os.ModeSymlink == 0 {
		entries, err := os.ReadDir(path)
		if err != nil {
			return err
		}

		for _, entry := range entries {
			if err := PruneDirectory(filepath.Join(path, entry.Name())); err != nil {
				return err
			}
		}

		entries, err = os.ReadDir(path)
		if err != nil {
			return err
		}

		if len(entries) > 0 {
			return nil
		}

		return os.Remove(path)
	}

	return nil
}

Michael Yang's avatar
Michael Yang committed
541
func PushModel(ctx context.Context, name string, regOpts *registryOptions, fn func(api.ProgressResponse)) error {
542
	mp := ParseModelPath(name)
543
544
	fn(api.ProgressResponse{Status: "retrieving manifest"})

545
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
CYJiang's avatar
CYJiang committed
546
		return errInsecureProtocol
547
548
	}

Patrick Devine's avatar
Patrick Devine committed
549
	manifest, _, err := GetManifest(mp)
550
	if err != nil {
551
		fn(api.ProgressResponse{Status: "couldn't retrieve manifest"})
552
553
554
		return err
	}

555
	var layers []Layer
Jeffrey Morgan's avatar
Jeffrey Morgan committed
556
	layers = append(layers, manifest.Layers...)
557
	if manifest.Config.Digest != "" {
558
		layers = append(layers, manifest.Config)
559
	}
560
561

	for _, layer := range layers {
Michael Yang's avatar
Michael Yang committed
562
		if err := uploadBlob(ctx, mp, layer, regOpts, fn); err != nil {
563
			slog.Info(fmt.Sprintf("error uploading blob: %v", err))
564
565
			return err
		}
566
567
	}

568
	fn(api.ProgressResponse{Status: "pushing manifest"})
Michael Yang's avatar
Michael Yang committed
569
570
	requestURL := mp.BaseURL()
	requestURL = requestURL.JoinPath("v2", mp.GetNamespaceRepository(), "manifests", mp.Tag)
571
572
573
574
575
576

	manifestJSON, err := json.Marshal(manifest)
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
577
578
	headers := make(http.Header)
	headers.Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json")
Michael Yang's avatar
Michael Yang committed
579
	resp, err := makeRequestWithRetry(ctx, http.MethodPut, requestURL, headers, bytes.NewReader(manifestJSON), regOpts)
580
581
582
583
584
	if err != nil {
		return err
	}
	defer resp.Body.Close()

585
	fn(api.ProgressResponse{Status: "success"})
586
587
588
589

	return nil
}

Michael Yang's avatar
Michael Yang committed
590
func PullModel(ctx context.Context, name string, regOpts *registryOptions, fn func(api.ProgressResponse)) error {
591
592
	mp := ParseModelPath(name)

593
	// build deleteMap to prune unused layers
Michael Yang's avatar
Michael Yang committed
594
	deleteMap := make(map[string]struct{})
Michael Yang's avatar
Michael Yang committed
595
596
597
	manifest, _, err := GetManifest(mp)
	if errors.Is(err, os.ErrNotExist) {
		// noop
598
599
	} else if err != nil {
		slog.Warn("pulling model with bad existing manifest", "name", name, "error", err)
Michael Yang's avatar
Michael Yang committed
600
601
602
	} else {
		for _, l := range manifest.Layers {
			deleteMap[l.Digest] = struct{}{}
603
		}
Michael Yang's avatar
Michael Yang committed
604
605
		if manifest.Config.Digest != "" {
			deleteMap[manifest.Config.Digest] = struct{}{}
606
607
608
		}
	}

609
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
CYJiang's avatar
CYJiang committed
610
		return errInsecureProtocol
611
	}
612

613
	fn(api.ProgressResponse{Status: "pulling manifest"})
614

615
	manifest, err = pullModelManifest(ctx, mp, regOpts)
616
	if err != nil {
617
		return fmt.Errorf("pull model manifest: %s", err)
618
619
	}

620
	var layers []Layer
Bruce MacDonald's avatar
Bruce MacDonald committed
621
	layers = append(layers, manifest.Layers...)
622
	if manifest.Config.Digest != "" {
623
		layers = append(layers, manifest.Config)
624
	}
625

626
	skipVerify := make(map[string]bool)
627
	for _, layer := range layers {
628
629
630
631
632
633
634
		cacheHit, err := downloadBlob(ctx, downloadOpts{
			mp:      mp,
			digest:  layer.Digest,
			regOpts: regOpts,
			fn:      fn,
		})
		if err != nil {
635
636
			return err
		}
637
		skipVerify[layer.Digest] = cacheHit
638
		delete(deleteMap, layer.Digest)
639
	}
640
	delete(deleteMap, manifest.Config.Digest)
641

Michael Yang's avatar
Michael Yang committed
642
643
	fn(api.ProgressResponse{Status: "verifying sha256 digest"})
	for _, layer := range layers {
644
645
646
		if skipVerify[layer.Digest] {
			continue
		}
Michael Yang's avatar
Michael Yang committed
647
		if err := verifyBlob(layer.Digest); err != nil {
648
649
650
651
652
653
654
655
			if errors.Is(err, errDigestMismatch) {
				// something went wrong, delete the blob
				fp, err := GetBlobsPath(layer.Digest)
				if err != nil {
					return err
				}
				if err := os.Remove(fp); err != nil {
					// log this, but return the original error
656
					slog.Info(fmt.Sprintf("couldn't remove file with digest mismatch '%s': %v", fp, err))
657
658
				}
			}
Michael Yang's avatar
Michael Yang committed
659
660
661
662
			return err
		}
	}

663
	fn(api.ProgressResponse{Status: "writing manifest"})
664

665
	manifestJSON, err := json.Marshal(manifest)
666
667
668
669
	if err != nil {
		return err
	}

670
	fp, err := mp.GetManifestPath()
671
672
673
	if err != nil {
		return err
	}
674
675
676
	if err := os.MkdirAll(filepath.Dir(fp), 0o755); err != nil {
		return err
	}
677

Bruce MacDonald's avatar
Bruce MacDonald committed
678
	err = os.WriteFile(fp, manifestJSON, 0o644)
679
	if err != nil {
680
		slog.Info(fmt.Sprintf("couldn't write to %s", fp))
681
682
683
		return err
	}

Michael Yang's avatar
Michael Yang committed
684
685
	if !envconfig.NoPrune() && len(deleteMap) > 0 {
		fn(api.ProgressResponse{Status: "removing unused layers"})
Michael Yang's avatar
Michael Yang committed
686
		if err := deleteUnusedLayers(deleteMap); err != nil {
687
			fn(api.ProgressResponse{Status: fmt.Sprintf("couldn't remove unused layers: %v", err)})
688
689
690
		}
	}

691
	fn(api.ProgressResponse{Status: "success"})
692
693
694
695

	return nil
}

Michael Yang's avatar
Michael Yang committed
696
func pullModelManifest(ctx context.Context, mp ModelPath, regOpts *registryOptions) (*Manifest, error) {
Michael Yang's avatar
Michael Yang committed
697
	requestURL := mp.BaseURL().JoinPath("v2", mp.GetNamespaceRepository(), "manifests", mp.Tag)
698

Michael Yang's avatar
Michael Yang committed
699
700
	headers := make(http.Header)
	headers.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json")
Michael Yang's avatar
Michael Yang committed
701
	resp, err := makeRequestWithRetry(ctx, http.MethodGet, requestURL, headers, nil, regOpts)
702
703
704
705
706
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

Michael Yang's avatar
Michael Yang committed
707
	var m Manifest
708
709
710
711
	if err := json.NewDecoder(resp.Body).Decode(&m); err != nil {
		return nil, err
	}

Michael Yang's avatar
Michael Yang committed
712
	return &m, err
713
714
715
}

// GetSHA256Digest returns the SHA256 hash of a given buffer and returns it, and the size of buffer
Michael Yang's avatar
Michael Yang committed
716
func GetSHA256Digest(r io.Reader) (string, int64) {
Michael Yang's avatar
Michael Yang committed
717
718
719
720
721
722
	h := sha256.New()
	n, err := io.Copy(h, r)
	if err != nil {
		log.Fatal(err)
	}

Michael Yang's avatar
Michael Yang committed
723
	return fmt.Sprintf("sha256:%x", h.Sum(nil)), n
724
725
}

Michael Yang's avatar
lint  
Michael Yang committed
726
var errUnauthorized = errors.New("unauthorized: access denied")
727

Michael Yang's avatar
Michael Yang committed
728
func makeRequestWithRetry(ctx context.Context, method string, requestURL *url.URL, headers http.Header, body io.ReadSeeker, regOpts *registryOptions) (*http.Response, error) {
Michael Yang's avatar
lint  
Michael Yang committed
729
	for range 2 {
Michael Yang's avatar
Michael Yang committed
730
		resp, err := makeRequest(ctx, method, requestURL, headers, body, regOpts)
Michael Yang's avatar
Michael Yang committed
731
		if err != nil {
Michael Yang's avatar
Michael Yang committed
732
			if !errors.Is(err, context.Canceled) {
733
				slog.Info(fmt.Sprintf("request failed: %v", err))
Michael Yang's avatar
Michael Yang committed
734
735
			}

Michael Yang's avatar
Michael Yang committed
736
737
			return nil, err
		}
Michael Yang's avatar
Michael Yang committed
738
739
740

		switch {
		case resp.StatusCode == http.StatusUnauthorized:
741
742
			resp.Body.Close()

Michael Yang's avatar
Michael Yang committed
743
			// Handle authentication error with one retry
Michael Yang's avatar
Michael Yang committed
744
745
			challenge := parseRegistryChallenge(resp.Header.Get("www-authenticate"))
			token, err := getAuthorizationToken(ctx, challenge)
Michael Yang's avatar
Michael Yang committed
746
747
748
			if err != nil {
				return nil, err
			}
Michael Yang's avatar
Michael Yang committed
749
750
751
752
753
754
755
756
			regOpts.Token = token
			if body != nil {
				_, err = body.Seek(0, io.SeekStart)
				if err != nil {
					return nil, err
				}
			}
		case resp.StatusCode == http.StatusNotFound:
757
			resp.Body.Close()
Michael Yang's avatar
Michael Yang committed
758
759
			return nil, os.ErrNotExist
		case resp.StatusCode >= http.StatusBadRequest:
760
			defer resp.Body.Close()
Michael Yang's avatar
Michael Yang committed
761
762
763
764
765
766
767
			responseBody, err := io.ReadAll(resp.Body)
			if err != nil {
				return nil, fmt.Errorf("%d: %s", resp.StatusCode, err)
			}
			return nil, fmt.Errorf("%d: %s", resp.StatusCode, responseBody)
		default:
			return resp, nil
Michael Yang's avatar
Michael Yang committed
768
769
770
		}
	}

Michael Yang's avatar
Michael Yang committed
771
	return nil, errUnauthorized
Michael Yang's avatar
Michael Yang committed
772
773
}

774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
// testMakeRequestDialContext specifies the dial function for the http client in
// makeRequest. It can be used to resolve hosts in model names to local
// addresses for testing. For example, the model name ("example.com/my/model")
// can be directed to push/pull from "127.0.0.1:1234".
//
// This is not safe to set across goroutines. It should be set in
// the main test goroutine, and not by tests marked to run in parallel with
// t.Parallel().
//
// It should be cleared after use, otherwise it will affect other tests.
//
// Ideally we would have some set this up the stack, but the code is not
// structured in a way that makes this easy, so this will have to do for now.
var testMakeRequestDialContext func(ctx context.Context, network, addr string) (net.Conn, error)

Michael Yang's avatar
Michael Yang committed
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
func makeRequest(ctx context.Context, method string, requestURL *url.URL, headers http.Header, body io.Reader, regOpts *registryOptions) (*http.Response, error) {
	if requestURL.Scheme != "http" && regOpts != nil && regOpts.Insecure {
		requestURL.Scheme = "http"
	}

	req, err := http.NewRequestWithContext(ctx, method, requestURL.String(), body)
	if err != nil {
		return nil, err
	}

	if headers != nil {
		req.Header = headers
	}

	if regOpts != nil {
		if regOpts.Token != "" {
			req.Header.Set("Authorization", "Bearer "+regOpts.Token)
		} else if regOpts.Username != "" && regOpts.Password != "" {
			req.SetBasicAuth(regOpts.Username, regOpts.Password)
		}
	}

811
	req.Header.Set("User-Agent", fmt.Sprintf("ollama/%s (%s %s) Go/%s", version.Version, runtime.GOARCH, runtime.GOOS, runtime.Version()))
Michael Yang's avatar
Michael Yang committed
812
813
814
815
816
817
818
819
820
821

	if s := req.Header.Get("Content-Length"); s != "" {
		contentLength, err := strconv.ParseInt(s, 10, 64)
		if err != nil {
			return nil, err
		}

		req.ContentLength = contentLength
	}

822
	c := &http.Client{
823
		CheckRedirect: regOpts.CheckRedirect,
Michael Yang's avatar
Michael Yang committed
824
	}
825
826
827
828
829
830
	if testMakeRequestDialContext != nil {
		tr := http.DefaultTransport.(*http.Transport).Clone()
		tr.DialContext = testMakeRequestDialContext
		c.Transport = tr
	}
	return c.Do(req)
Michael Yang's avatar
Michael Yang committed
831
832
}

Patrick Devine's avatar
Patrick Devine committed
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
func getValue(header, key string) string {
	startIdx := strings.Index(header, key+"=")
	if startIdx == -1 {
		return ""
	}

	// Move the index to the starting quote after the key.
	startIdx += len(key) + 2
	endIdx := startIdx

	for endIdx < len(header) {
		if header[endIdx] == '"' {
			if endIdx+1 < len(header) && header[endIdx+1] != ',' { // If the next character isn't a comma, continue
				endIdx++
				continue
			}
			break
		}
		endIdx++
	}
	return header[startIdx:endIdx]
}

Michael Yang's avatar
Michael Yang committed
856
func parseRegistryChallenge(authStr string) registryChallenge {
Patrick Devine's avatar
Patrick Devine committed
857
858
	authStr = strings.TrimPrefix(authStr, "Bearer ")

Michael Yang's avatar
Michael Yang committed
859
	return registryChallenge{
Patrick Devine's avatar
Patrick Devine committed
860
861
862
863
864
865
		Realm:   getValue(authStr, "realm"),
		Service: getValue(authStr, "service"),
		Scope:   getValue(authStr, "scope"),
	}
}

866
var errDigestMismatch = errors.New("digest mismatch, file must be downloaded again")
867

Michael Yang's avatar
Michael Yang committed
868
869
870
871
872
873
874
875
876
877
878
879
880
881
func verifyBlob(digest string) error {
	fp, err := GetBlobsPath(digest)
	if err != nil {
		return err
	}

	f, err := os.Open(fp)
	if err != nil {
		return err
	}
	defer f.Close()

	fileDigest, _ := GetSHA256Digest(f)
	if digest != fileDigest {
882
		return fmt.Errorf("%w: want %s, got %s", errDigestMismatch, digest, fileDigest)
Michael Yang's avatar
Michael Yang committed
883
884
885
886
	}

	return nil
}