images.go 21.9 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         model.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
78
79
80
81
82
83
84
85
86
87
88
89
90
	if m.ModelPath != "" {
		f, err := gguf.Open(m.ModelPath)
		if err == nil {
			defer f.Close()

			if f.KeyValue("pooling_type").Valid() {
				capabilities = append(capabilities, model.CapabilityEmbedding)
			} else {
				// 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)
			}
91
		} else {
92
			slog.Error("couldn't open model file", "error", err)
93
		}
94
95
96
	} else if len(m.Config.Capabilities) > 0 {
		for _, c := range m.Config.Capabilities {
			capabilities = append(capabilities, model.Capability(c))
97
98
		}
	} else {
99
		slog.Warn("unknown capabilities for model", "model", m.Name)
100
101
102
103
104
105
	}

	if m.Template == nil {
		return capabilities
	}

Devon Rifkin's avatar
Devon Rifkin committed
106
	builtinParser := parsers.ParserForName(m.Config.Parser)
107
	// Check for tools capability
108
109
110
111
112
	v, err := m.Template.Vars()
	if err != nil {
		slog.Warn("model template contains errors", "error", err)
	}
	if slices.Contains(v, "tools") || (builtinParser != nil && builtinParser.HasToolSupport()) {
113
114
115
116
		capabilities = append(capabilities, model.CapabilityTools)
	}

	// Check for insert capability
117
	if slices.Contains(v, "suffix") {
118
119
120
		capabilities = append(capabilities, model.CapabilityInsert)
	}

121
122
123
124
125
	// Check for vision capability in projector-based models
	if len(m.ProjectorPaths) > 0 {
		capabilities = append(capabilities, model.CapabilityVision)
	}

126
127
128
129
130
	// Skip the thinking check if it's already set
	if slices.Contains(capabilities, "thinking") {
		return capabilities
	}

131
	// Check for thinking capability
132
	openingTag, closingTag := thinking.InferTags(m.Template.Template)
Michael Yang's avatar
Michael Yang committed
133
	hasTags := openingTag != "" && closingTag != ""
Devon Rifkin's avatar
Devon Rifkin committed
134
135
	isGptoss := slices.Contains([]string{"gptoss", "gpt-oss"}, m.Config.ModelFamily)
	if hasTags || isGptoss || (builtinParser != nil && builtinParser.HasThinkingSupport()) {
136
137
138
		capabilities = append(capabilities, model.CapabilityThinking)
	}

139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
	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,
155
		model.CapabilityThinking:   errCapabilityThinking,
156
157
158
159
160
	}

	for _, cap := range want {
		err, ok := capToErr[cap]
		if !ok {
Michael Yang's avatar
Michael Yang committed
161
			slog.Error("unknown capability", "capability", cap)
Michael Yang's avatar
Michael Yang committed
162
			return fmt.Errorf("unknown capability: %s", cap)
Michael Yang's avatar
Michael Yang committed
163
		}
164
165
166
167

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

170
	var err error
171
	if len(errs) > 0 {
172
173
174
175
176
177
178
179
		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
180
181
	}

182
	return err
183
184
}

Michael Yang's avatar
Michael Yang committed
185
func (m *Model) String() string {
186
	var modelfile parser.Modelfile
Michael Yang's avatar
Michael Yang committed
187

188
	modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
189
190
191
		Name: "model",
		Args: m.ModelPath,
	})
192

Michael Yang's avatar
Michael Yang committed
193
	for _, adapter := range m.AdapterPaths {
194
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
195
196
			Name: "adapter",
			Args: adapter,
Michael Yang's avatar
Michael Yang committed
197
		})
198
199
	}

Michael Yang's avatar
Michael Yang committed
200
	for _, projector := range m.ProjectorPaths {
201
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
202
203
			Name: "model",
			Args: projector,
Michael Yang's avatar
Michael Yang committed
204
		})
205
206
	}

Michael Yang's avatar
Michael Yang committed
207
	if m.Template != nil {
208
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
209
			Name: "template",
Michael Yang's avatar
Michael Yang committed
210
			Args: m.Template.String(),
Michael Yang's avatar
Michael Yang committed
211
		})
212
213
	}

Michael Yang's avatar
Michael Yang committed
214
	if m.System != "" {
215
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
216
217
			Name: "system",
			Args: m.System,
Michael Yang's avatar
Michael Yang committed
218
		})
219
220
	}

Devon Rifkin's avatar
Devon Rifkin committed
221
222
223
224
225
226
227
228
229
230
231
232
233
234
	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,
		})
	}

235
236
237
238
	for k, v := range m.Options {
		switch v := v.(type) {
		case []any:
			for _, s := range v {
239
				modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
240
241
242
					Name: k,
					Args: fmt.Sprintf("%v", s),
				})
243
244
			}
		default:
245
			modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
246
247
248
				Name: k,
				Args: fmt.Sprintf("%v", v),
			})
249
250
251
252
		}
	}

	for _, license := range m.License {
253
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
254
255
256
			Name: "license",
			Args: license,
		})
257
258
259
	}

	for _, msg := range m.Messages {
260
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
261
			Name: "message",
Michael Yang's avatar
Michael Yang committed
262
			Args: fmt.Sprintf("%s: %s", msg.Role, msg.Content),
Michael Yang's avatar
Michael Yang committed
263
		})
264
265
	}

Michael Yang's avatar
Michael Yang committed
266
	return modelfile.String()
267
268
}

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

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

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

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

Michael Yang's avatar
Michael Yang committed
288
	return &manifest, hex.EncodeToString(sha256sum.Sum(nil)), nil
289
290
291
}

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

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

305
306
307
308
309
	if manifest.Config.Digest != "" {
		filename, err := GetBlobsPath(manifest.Config.Digest)
		if err != nil {
			return nil, err
		}
310

311
312
313
314
315
		configFile, err := os.Open(filename)
		if err != nil {
			return nil, err
		}
		defer configFile.Close()
316

317
318
319
		if err := json.NewDecoder(configFile).Decode(&model.Config); err != nil {
			return nil, err
		}
320
321
	}

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

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

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

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

365
			// parse model options parameters into a map so that we can see which fields have been specified explicitly
366
			if err = json.NewDecoder(params).Decode(&model.Options); err != nil {
367
368
				return nil, err
			}
369
370
371
372
373
374
375
376
377
378
		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
379
380
381
382
383
384
		case "application/vnd.ollama.image.license":
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}
			model.License = append(model.License, string(bts))
385
386
387
388
389
390
		}
	}

	return model, nil
}

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

399
400
401
402
	if src.Filepath() == dst.Filepath() {
		return nil
	}

Michael Yang's avatar
Michael Yang committed
403
	manifests, err := GetManifestPath()
404
405
406
407
	if err != nil {
		return err
	}

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

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

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

Michael Yang's avatar
Michael Yang committed
426
427
	_, err = io.Copy(dstfile, srcfile)
	return err
Patrick Devine's avatar
Patrick Devine committed
428
429
}

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

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

		delete(deleteMap, manifest.Config.Digest)
Michael Yang's avatar
Michael Yang committed
443
	}
444
445

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

458
459
460
461
	return nil
}

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

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

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

		_, 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
488
		}
489
490

		deleteMap[name] = struct{}{}
491
492
	}

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

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

500
	slog.Info(fmt.Sprintf("total unused blobs removed: %d", len(deleteMap)))
501
502
503
504

	return nil
}

Michael Yang's avatar
Michael Yang committed
505
506
507
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
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
538
func PushModel(ctx context.Context, name string, regOpts *registryOptions, fn func(api.ProgressResponse)) error {
539
	mp := ParseModelPath(name)
540
541
	fn(api.ProgressResponse{Status: "retrieving manifest"})

542
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
CYJiang's avatar
CYJiang committed
543
		return errInsecureProtocol
544
545
	}

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

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

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

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

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

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

582
	fn(api.ProgressResponse{Status: "success"})
583
584
585
586

	return nil
}

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

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

606
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
CYJiang's avatar
CYJiang committed
607
		return errInsecureProtocol
608
	}
609

610
	fn(api.ProgressResponse{Status: "pulling manifest"})
611

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

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

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

Michael Yang's avatar
Michael Yang committed
639
640
	fn(api.ProgressResponse{Status: "verifying sha256 digest"})
	for _, layer := range layers {
641
642
643
		if skipVerify[layer.Digest] {
			continue
		}
Michael Yang's avatar
Michael Yang committed
644
		if err := verifyBlob(layer.Digest); err != nil {
645
646
647
648
649
650
651
652
			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
653
					slog.Info(fmt.Sprintf("couldn't remove file with digest mismatch '%s': %v", fp, err))
654
655
				}
			}
Michael Yang's avatar
Michael Yang committed
656
657
658
659
			return err
		}
	}

660
	fn(api.ProgressResponse{Status: "writing manifest"})
661

662
	manifestJSON, err := json.Marshal(manifest)
663
664
665
666
	if err != nil {
		return err
	}

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

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

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

688
	fn(api.ProgressResponse{Status: "success"})
689
690
691
692

	return nil
}

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

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

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

Michael Yang's avatar
Michael Yang committed
709
	return &m, err
710
711
712
}

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

Michael Yang's avatar
Michael Yang committed
720
	return fmt.Sprintf("sha256:%x", h.Sum(nil)), n
721
722
}

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

Michael Yang's avatar
Michael Yang committed
725
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
726
	for range 2 {
Michael Yang's avatar
Michael Yang committed
727
		resp, err := makeRequest(ctx, method, requestURL, headers, body, regOpts)
Michael Yang's avatar
Michael Yang committed
728
		if err != nil {
Michael Yang's avatar
Michael Yang committed
729
			if !errors.Is(err, context.Canceled) {
730
				slog.Info(fmt.Sprintf("request failed: %v", err))
Michael Yang's avatar
Michael Yang committed
731
732
			}

Michael Yang's avatar
Michael Yang committed
733
734
			return nil, err
		}
Michael Yang's avatar
Michael Yang committed
735
736
737

		switch {
		case resp.StatusCode == http.StatusUnauthorized:
738
739
			resp.Body.Close()

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

Michael Yang's avatar
Michael Yang committed
768
	return nil, errUnauthorized
Michael Yang's avatar
Michael Yang committed
769
770
}

771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
// 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
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
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)
		}
	}

808
	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
809
810
811
812
813
814
815
816
817
818

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

		req.ContentLength = contentLength
	}

819
	c := &http.Client{
820
		CheckRedirect: regOpts.CheckRedirect,
Michael Yang's avatar
Michael Yang committed
821
	}
822
823
824
825
826
827
	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
828
829
}

Patrick Devine's avatar
Patrick Devine committed
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
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
853
func parseRegistryChallenge(authStr string) registryChallenge {
Patrick Devine's avatar
Patrick Devine committed
854
855
	authStr = strings.TrimPrefix(authStr, "Bearer ")

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

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

Michael Yang's avatar
Michael Yang committed
865
866
867
868
869
870
871
872
873
874
875
876
877
878
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 {
879
		return fmt.Errorf("%w: want %s, got %s", errDigestMismatch, digest, fileDigest)
Michael Yang's avatar
Michael Yang committed
880
881
882
883
	}

	return nil
}