images.go 21.4 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/ggml"
27
	"github.com/ollama/ollama/parser"
Michael Yang's avatar
Michael Yang committed
28
	"github.com/ollama/ollama/template"
29
	"github.com/ollama/ollama/thinking"
Michael Yang's avatar
Michael Yang committed
30
	"github.com/ollama/ollama/types/model"
31
	"github.com/ollama/ollama/version"
32
33
)

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

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

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

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

	Template *template.Template
69
70
}

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

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

80
81
82
83
84
85
86
87
88
89
		f, err := ggml.Decode(r, 1024)
		if err == nil {
			if _, ok := f.KV()[fmt.Sprintf("%s.pooling_type", f.KV().Architecture())]; ok {
				capabilities = append(capabilities, model.CapabilityEmbedding)
			} else {
				capabilities = append(capabilities, model.CapabilityCompletion)
			}
			if _, ok := f.KV()[fmt.Sprintf("%s.vision.block_count", f.KV().Architecture())]; ok {
				capabilities = append(capabilities, model.CapabilityVision)
			}
90
		} else {
91
			slog.Error("couldn't decode ggml", "error", err)
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
		}
	} else {
		slog.Error("couldn't open model file", "error", err)
	}

	if m.Template == nil {
		return capabilities
	}

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

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

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

116
	// Check for thinking capability
117
	openingTag, closingTag := thinking.InferTags(m.Template.Template)
118
119
120
121
	if openingTag != "" && closingTag != "" {
		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
204
205
206
207
	}

	for k, v := range m.Options {
		switch v := v.(type) {
		case []any:
			for _, s := range v {
208
				modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
209
210
211
					Name: k,
					Args: fmt.Sprintf("%v", s),
				})
212
213
			}
		default:
214
			modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
215
216
217
				Name: k,
				Args: fmt.Sprintf("%v", v),
			})
218
219
220
221
		}
	}

	for _, license := range m.License {
222
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
223
224
225
			Name: "license",
			Args: license,
		})
226
227
228
	}

	for _, msg := range m.Messages {
229
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
230
			Name: "message",
Michael Yang's avatar
Michael Yang committed
231
			Args: fmt.Sprintf("%s: %s", msg.Role, msg.Content),
Michael Yang's avatar
Michael Yang committed
232
		})
233
234
	}

Michael Yang's avatar
Michael Yang committed
235
	return modelfile.String()
236
237
}

238
type ConfigV2 struct {
239
240
241
242
243
244
	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"`

245
	// required by spec
246
247
	Architecture string `json:"architecture"`
	OS           string `json:"os"`
248
	RootFS       RootFS `json:"rootfs"`
249
250
251
252
253
254
255
}

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

Michael Yang's avatar
Michael Yang committed
256
func GetManifest(mp ModelPath) (*Manifest, string, error) {
257
	fp, err := mp.GetManifestPath()
258
	if err != nil {
Patrick Devine's avatar
Patrick Devine committed
259
		return nil, "", err
260
	}
261

Michael Yang's avatar
Michael Yang committed
262
	f, err := os.Open(fp)
263
	if err != nil {
Michael Yang's avatar
Michael Yang committed
264
		return nil, "", err
265
	}
Michael Yang's avatar
Michael Yang committed
266
	defer f.Close()
267

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

Michael Yang's avatar
Michael Yang committed
270
271
	var manifest Manifest
	if err := json.NewDecoder(io.TeeReader(f, sha256sum)).Decode(&manifest); err != nil {
Patrick Devine's avatar
Patrick Devine committed
272
		return nil, "", err
273
274
	}

Michael Yang's avatar
Michael Yang committed
275
	return &manifest, hex.EncodeToString(sha256sum.Sum(nil)), nil
276
277
278
}

func GetModel(name string) (*Model, error) {
279
	mp := ParseModelPath(name)
Patrick Devine's avatar
Patrick Devine committed
280
	manifest, digest, err := GetManifest(mp)
281
282
283
284
285
	if err != nil {
		return nil, err
	}

	model := &Model{
286
287
288
		Name:      mp.GetFullTagname(),
		ShortName: mp.GetShortTagname(),
		Digest:    digest,
Michael Yang's avatar
Michael Yang committed
289
		Template:  template.DefaultTemplate,
290
291
	}

292
293
294
295
296
	if manifest.Config.Digest != "" {
		filename, err := GetBlobsPath(manifest.Config.Digest)
		if err != nil {
			return nil, err
		}
297

298
299
300
301
302
		configFile, err := os.Open(filename)
		if err != nil {
			return nil, err
		}
		defer configFile.Close()
303

304
305
306
		if err := json.NewDecoder(configFile).Decode(&model.Config); err != nil {
			return nil, err
		}
307
308
	}

309
	for _, layer := range manifest.Layers {
Patrick Devine's avatar
Patrick Devine committed
310
		filename, err := GetBlobsPath(layer.Digest)
311
312
313
314
		if err != nil {
			return nil, err
		}

315
316
317
		switch layer.MediaType {
		case "application/vnd.ollama.image.model":
			model.ModelPath = filename
318
			model.ParentModel = layer.From
319
		case "application/vnd.ollama.image.embed":
320
321
			// Deprecated in versions  > 0.1.2
			// TODO: remove this warning in a future version
322
			slog.Info("WARNING: model contains embeddings, but embeddings in modelfiles have been deprecated and will be ignored.")
323
324
		case "application/vnd.ollama.image.adapter":
			model.AdapterPaths = append(model.AdapterPaths, filename)
Michael Yang's avatar
Michael Yang committed
325
326
		case "application/vnd.ollama.image.projector":
			model.ProjectorPaths = append(model.ProjectorPaths, filename)
Michael Yang's avatar
Michael Yang committed
327
328
		case "application/vnd.ollama.image.prompt",
			"application/vnd.ollama.image.template":
329
330
331
332
333
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}

Michael Yang's avatar
Michael Yang committed
334
			model.Template, err = template.Parse(string(bts))
335
336
337
			if err != nil {
				return nil, err
			}
Michael Yang's avatar
Michael Yang committed
338
		case "application/vnd.ollama.image.system":
339
340
341
342
343
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}

Michael Yang's avatar
Michael Yang committed
344
			model.System = string(bts)
345
		case "application/vnd.ollama.image.params":
Michael Yang's avatar
Michael Yang committed
346
347
348
349
350
			params, err := os.Open(filename)
			if err != nil {
				return nil, err
			}
			defer params.Close()
351

352
			// parse model options parameters into a map so that we can see which fields have been specified explicitly
353
			if err = json.NewDecoder(params).Decode(&model.Options); err != nil {
354
355
				return nil, err
			}
356
357
358
359
360
361
362
363
364
365
		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
366
367
368
369
370
371
		case "application/vnd.ollama.image.license":
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}
			model.License = append(model.License, string(bts))
372
373
374
375
376
377
		}
	}

	return model, nil
}

Michael Yang's avatar
Michael Yang committed
378
func CopyModel(src, dst model.Name) error {
379
380
381
382
383
384
385
	if !dst.IsFullyQualified() {
		return model.Unqualified(dst)
	}
	if !src.IsFullyQualified() {
		return model.Unqualified(src)
	}

386
387
388
389
	if src.Filepath() == dst.Filepath() {
		return nil
	}

Michael Yang's avatar
Michael Yang committed
390
	manifests, err := GetManifestPath()
391
392
393
394
	if err != nil {
		return err
	}

395
	dstpath := filepath.Join(manifests, dst.Filepath())
Michael Yang's avatar
Michael Yang committed
396
	if err := os.MkdirAll(filepath.Dir(dstpath), 0o755); err != nil {
397
398
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
399

400
	srcpath := filepath.Join(manifests, src.Filepath())
Michael Yang's avatar
Michael Yang committed
401
	srcfile, err := os.Open(srcpath)
Patrick Devine's avatar
Patrick Devine committed
402
403
404
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
405
	defer srcfile.Close()
Patrick Devine's avatar
Patrick Devine committed
406

Michael Yang's avatar
Michael Yang committed
407
	dstfile, err := os.Create(dstpath)
Patrick Devine's avatar
Patrick Devine committed
408
409
410
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
411
	defer dstfile.Close()
Patrick Devine's avatar
Patrick Devine committed
412

Michael Yang's avatar
Michael Yang committed
413
414
	_, err = io.Copy(dstfile, srcfile)
	return err
Patrick Devine's avatar
Patrick Devine committed
415
416
}

Michael Yang's avatar
Michael Yang committed
417
func deleteUnusedLayers(deleteMap map[string]struct{}) error {
418
419
	// Ignore corrupt manifests to avoid blocking deletion of layers that are freshly orphaned
	manifests, err := Manifests(true)
420
421
422
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
423

Michael Yang's avatar
Michael Yang committed
424
	for _, manifest := range manifests {
Michael Yang's avatar
Michael Yang committed
425
426
427
428
429
		for _, layer := range manifest.Layers {
			delete(deleteMap, layer.Digest)
		}

		delete(deleteMap, manifest.Config.Digest)
Michael Yang's avatar
Michael Yang committed
430
	}
431
432

	// only delete the files which are still in the deleteMap
Michael Yang's avatar
Michael Yang committed
433
434
435
	for k := range deleteMap {
		fp, err := GetBlobsPath(k)
		if err != nil {
436
			slog.Info(fmt.Sprintf("couldn't get file path for '%s': %v", k, err))
Michael Yang's avatar
Michael Yang committed
437
438
			continue
		}
439
440
441
		if err := os.Remove(fp); err != nil {
			slog.Info(fmt.Sprintf("couldn't remove file '%s': %v", fp, err))
			continue
442
443
444
		}
	}

445
446
447
448
	return nil
}

func PruneLayers() error {
Michael Yang's avatar
Michael Yang committed
449
	deleteMap := make(map[string]struct{})
450
451
452
453
454
455
456
	p, err := GetBlobsPath("")
	if err != nil {
		return err
	}

	blobs, err := os.ReadDir(p)
	if err != nil {
457
		slog.Info(fmt.Sprintf("couldn't read dir '%s': %v", p, err))
458
459
460
461
462
		return err
	}

	for _, blob := range blobs {
		name := blob.Name()
463
		name = strings.ReplaceAll(name, "-", ":")
464
465
466
467
468
469
470
471
472
473
474

		_, 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
475
		}
476
477

		deleteMap[name] = struct{}{}
478
479
	}

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

Michael Yang's avatar
Michael Yang committed
482
	if err := deleteUnusedLayers(deleteMap); err != nil {
483
		slog.Error(fmt.Sprintf("couldn't remove unused layers: %v", err))
484
		return nil
485
486
	}

487
	slog.Info(fmt.Sprintf("total unused blobs removed: %d", len(deleteMap)))
488
489
490
491

	return nil
}

Michael Yang's avatar
Michael Yang committed
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
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
525
func PushModel(ctx context.Context, name string, regOpts *registryOptions, fn func(api.ProgressResponse)) error {
526
	mp := ParseModelPath(name)
527
528
	fn(api.ProgressResponse{Status: "retrieving manifest"})

529
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
CYJiang's avatar
CYJiang committed
530
		return errInsecureProtocol
531
532
	}

Patrick Devine's avatar
Patrick Devine committed
533
	manifest, _, err := GetManifest(mp)
534
	if err != nil {
535
		fn(api.ProgressResponse{Status: "couldn't retrieve manifest"})
536
537
538
		return err
	}

539
	var layers []Layer
Jeffrey Morgan's avatar
Jeffrey Morgan committed
540
	layers = append(layers, manifest.Layers...)
541
	if manifest.Config.Digest != "" {
542
		layers = append(layers, manifest.Config)
543
	}
544
545

	for _, layer := range layers {
Michael Yang's avatar
Michael Yang committed
546
		if err := uploadBlob(ctx, mp, layer, regOpts, fn); err != nil {
547
			slog.Info(fmt.Sprintf("error uploading blob: %v", err))
548
549
			return err
		}
550
551
	}

552
	fn(api.ProgressResponse{Status: "pushing manifest"})
Michael Yang's avatar
Michael Yang committed
553
554
	requestURL := mp.BaseURL()
	requestURL = requestURL.JoinPath("v2", mp.GetNamespaceRepository(), "manifests", mp.Tag)
555
556
557
558
559
560

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

Michael Yang's avatar
Michael Yang committed
561
562
	headers := make(http.Header)
	headers.Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json")
Michael Yang's avatar
Michael Yang committed
563
	resp, err := makeRequestWithRetry(ctx, http.MethodPut, requestURL, headers, bytes.NewReader(manifestJSON), regOpts)
564
565
566
567
568
	if err != nil {
		return err
	}
	defer resp.Body.Close()

569
	fn(api.ProgressResponse{Status: "success"})
570
571
572
573

	return nil
}

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

577
	// build deleteMap to prune unused layers
Michael Yang's avatar
Michael Yang committed
578
	deleteMap := make(map[string]struct{})
Michael Yang's avatar
Michael Yang committed
579
580
581
	manifest, _, err := GetManifest(mp)
	if errors.Is(err, os.ErrNotExist) {
		// noop
582
583
	} else if err != nil {
		slog.Warn("pulling model with bad existing manifest", "name", name, "error", err)
Michael Yang's avatar
Michael Yang committed
584
585
586
	} else {
		for _, l := range manifest.Layers {
			deleteMap[l.Digest] = struct{}{}
587
		}
Michael Yang's avatar
Michael Yang committed
588
589
		if manifest.Config.Digest != "" {
			deleteMap[manifest.Config.Digest] = struct{}{}
590
591
592
		}
	}

593
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
CYJiang's avatar
CYJiang committed
594
		return errInsecureProtocol
595
	}
596

597
	fn(api.ProgressResponse{Status: "pulling manifest"})
598

599
	manifest, err = pullModelManifest(ctx, mp, regOpts)
600
	if err != nil {
601
		return fmt.Errorf("pull model manifest: %s", err)
602
603
	}

604
	var layers []Layer
Bruce MacDonald's avatar
Bruce MacDonald committed
605
	layers = append(layers, manifest.Layers...)
606
	if manifest.Config.Digest != "" {
607
		layers = append(layers, manifest.Config)
608
	}
609

610
	skipVerify := make(map[string]bool)
611
	for _, layer := range layers {
612
613
614
615
616
617
618
		cacheHit, err := downloadBlob(ctx, downloadOpts{
			mp:      mp,
			digest:  layer.Digest,
			regOpts: regOpts,
			fn:      fn,
		})
		if err != nil {
619
620
			return err
		}
621
		skipVerify[layer.Digest] = cacheHit
622
		delete(deleteMap, layer.Digest)
623
	}
624
	delete(deleteMap, manifest.Config.Digest)
625

Michael Yang's avatar
Michael Yang committed
626
627
	fn(api.ProgressResponse{Status: "verifying sha256 digest"})
	for _, layer := range layers {
628
629
630
		if skipVerify[layer.Digest] {
			continue
		}
Michael Yang's avatar
Michael Yang committed
631
		if err := verifyBlob(layer.Digest); err != nil {
632
633
634
635
636
637
638
639
			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
640
					slog.Info(fmt.Sprintf("couldn't remove file with digest mismatch '%s': %v", fp, err))
641
642
				}
			}
Michael Yang's avatar
Michael Yang committed
643
644
645
646
			return err
		}
	}

647
	fn(api.ProgressResponse{Status: "writing manifest"})
648

649
	manifestJSON, err := json.Marshal(manifest)
650
651
652
653
	if err != nil {
		return err
	}

654
	fp, err := mp.GetManifestPath()
655
656
657
	if err != nil {
		return err
	}
658
659
660
	if err := os.MkdirAll(filepath.Dir(fp), 0o755); err != nil {
		return err
	}
661

Bruce MacDonald's avatar
Bruce MacDonald committed
662
	err = os.WriteFile(fp, manifestJSON, 0o644)
663
	if err != nil {
664
		slog.Info(fmt.Sprintf("couldn't write to %s", fp))
665
666
667
		return err
	}

Michael Yang's avatar
Michael Yang committed
668
669
	if !envconfig.NoPrune() && len(deleteMap) > 0 {
		fn(api.ProgressResponse{Status: "removing unused layers"})
Michael Yang's avatar
Michael Yang committed
670
		if err := deleteUnusedLayers(deleteMap); err != nil {
671
			fn(api.ProgressResponse{Status: fmt.Sprintf("couldn't remove unused layers: %v", err)})
672
673
674
		}
	}

675
	fn(api.ProgressResponse{Status: "success"})
676
677
678
679

	return nil
}

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

Michael Yang's avatar
Michael Yang committed
683
684
	headers := make(http.Header)
	headers.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json")
Michael Yang's avatar
Michael Yang committed
685
	resp, err := makeRequestWithRetry(ctx, http.MethodGet, requestURL, headers, nil, regOpts)
686
687
688
689
690
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

Michael Yang's avatar
Michael Yang committed
691
	var m Manifest
692
693
694
695
	if err := json.NewDecoder(resp.Body).Decode(&m); err != nil {
		return nil, err
	}

Michael Yang's avatar
Michael Yang committed
696
	return &m, err
697
698
699
}

// GetSHA256Digest returns the SHA256 hash of a given buffer and returns it, and the size of buffer
Michael Yang's avatar
Michael Yang committed
700
func GetSHA256Digest(r io.Reader) (string, int64) {
Michael Yang's avatar
Michael Yang committed
701
702
703
704
705
706
	h := sha256.New()
	n, err := io.Copy(h, r)
	if err != nil {
		log.Fatal(err)
	}

Michael Yang's avatar
Michael Yang committed
707
	return fmt.Sprintf("sha256:%x", h.Sum(nil)), n
708
709
}

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

Michael Yang's avatar
Michael Yang committed
712
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
713
	for range 2 {
Michael Yang's avatar
Michael Yang committed
714
		resp, err := makeRequest(ctx, method, requestURL, headers, body, regOpts)
Michael Yang's avatar
Michael Yang committed
715
		if err != nil {
Michael Yang's avatar
Michael Yang committed
716
			if !errors.Is(err, context.Canceled) {
717
				slog.Info(fmt.Sprintf("request failed: %v", err))
Michael Yang's avatar
Michael Yang committed
718
719
			}

Michael Yang's avatar
Michael Yang committed
720
721
			return nil, err
		}
Michael Yang's avatar
Michael Yang committed
722
723
724

		switch {
		case resp.StatusCode == http.StatusUnauthorized:
725
726
			resp.Body.Close()

Michael Yang's avatar
Michael Yang committed
727
			// Handle authentication error with one retry
Michael Yang's avatar
Michael Yang committed
728
729
			challenge := parseRegistryChallenge(resp.Header.Get("www-authenticate"))
			token, err := getAuthorizationToken(ctx, challenge)
Michael Yang's avatar
Michael Yang committed
730
731
732
			if err != nil {
				return nil, err
			}
Michael Yang's avatar
Michael Yang committed
733
734
735
736
737
738
739
740
			regOpts.Token = token
			if body != nil {
				_, err = body.Seek(0, io.SeekStart)
				if err != nil {
					return nil, err
				}
			}
		case resp.StatusCode == http.StatusNotFound:
741
			resp.Body.Close()
Michael Yang's avatar
Michael Yang committed
742
743
			return nil, os.ErrNotExist
		case resp.StatusCode >= http.StatusBadRequest:
744
			defer resp.Body.Close()
Michael Yang's avatar
Michael Yang committed
745
746
747
748
749
750
751
			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
752
753
754
		}
	}

Michael Yang's avatar
Michael Yang committed
755
	return nil, errUnauthorized
Michael Yang's avatar
Michael Yang committed
756
757
}

758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
// 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
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
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)
		}
	}

795
	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
796
797
798
799
800
801
802
803
804
805

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

		req.ContentLength = contentLength
	}

806
	c := &http.Client{
807
		CheckRedirect: regOpts.CheckRedirect,
Michael Yang's avatar
Michael Yang committed
808
	}
809
810
811
812
813
814
	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
815
816
}

Patrick Devine's avatar
Patrick Devine committed
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
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
840
func parseRegistryChallenge(authStr string) registryChallenge {
Patrick Devine's avatar
Patrick Devine committed
841
842
	authStr = strings.TrimPrefix(authStr, "Bearer ")

Michael Yang's avatar
Michael Yang committed
843
	return registryChallenge{
Patrick Devine's avatar
Patrick Devine committed
844
845
846
847
848
849
		Realm:   getValue(authStr, "realm"),
		Service: getValue(authStr, "service"),
		Scope:   getValue(authStr, "scope"),
	}
}

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

Michael Yang's avatar
Michael Yang committed
852
853
854
855
856
857
858
859
860
861
862
863
864
865
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 {
866
		return fmt.Errorf("%w: want %s, got %s", errDigestMismatch, digest, fileDigest)
Michael Yang's avatar
Michael Yang committed
867
868
869
870
	}

	return nil
}