images.go 21.3 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"
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
	f, err := gguf.Open(m.ModelPath)
77
	if err == nil {
78
		defer f.Close()
79

80
81
		if f.KeyValue("pooling_type").Valid() {
			capabilities = append(capabilities, model.CapabilityEmbedding)
82
		} else {
83
84
85
86
87
			// 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)
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
		}
	} 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)
	}

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

112
	// Check for thinking capability
113
	openingTag, closingTag := thinking.InferTags(m.Template.Template)
114
115
116
117
	if openingTag != "" && closingTag != "" {
		capabilities = append(capabilities, model.CapabilityThinking)
	}

118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
	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,
134
		model.CapabilityThinking:   errCapabilityThinking,
135
136
137
138
139
	}

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

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

149
	var err error
150
	if len(errs) > 0 {
151
152
153
154
155
156
157
158
		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
159
160
	}

161
	return err
162
163
}

Michael Yang's avatar
Michael Yang committed
164
func (m *Model) String() string {
165
	var modelfile parser.Modelfile
Michael Yang's avatar
Michael Yang committed
166

167
	modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
168
169
170
		Name: "model",
		Args: m.ModelPath,
	})
171

Michael Yang's avatar
Michael Yang committed
172
	for _, adapter := range m.AdapterPaths {
173
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
174
175
			Name: "adapter",
			Args: adapter,
Michael Yang's avatar
Michael Yang committed
176
		})
177
178
	}

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

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

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

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

	for _, license := range m.License {
218
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
219
220
221
			Name: "license",
			Args: license,
		})
222
223
224
	}

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

Michael Yang's avatar
Michael Yang committed
231
	return modelfile.String()
232
233
}

234
type ConfigV2 struct {
235
236
237
238
239
240
	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"`

241
	// required by spec
242
243
	Architecture string `json:"architecture"`
	OS           string `json:"os"`
244
	RootFS       RootFS `json:"rootfs"`
245
246
247
248
249
250
251
}

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

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

Michael Yang's avatar
Michael Yang committed
258
	f, err := os.Open(fp)
259
	if err != nil {
Michael Yang's avatar
Michael Yang committed
260
		return nil, "", err
261
	}
Michael Yang's avatar
Michael Yang committed
262
	defer f.Close()
263

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

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

Michael Yang's avatar
Michael Yang committed
271
	return &manifest, hex.EncodeToString(sha256sum.Sum(nil)), nil
272
273
274
}

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

	model := &Model{
282
283
284
		Name:      mp.GetFullTagname(),
		ShortName: mp.GetShortTagname(),
		Digest:    digest,
Michael Yang's avatar
Michael Yang committed
285
		Template:  template.DefaultTemplate,
286
287
	}

288
289
290
291
292
	if manifest.Config.Digest != "" {
		filename, err := GetBlobsPath(manifest.Config.Digest)
		if err != nil {
			return nil, err
		}
293

294
295
296
297
298
		configFile, err := os.Open(filename)
		if err != nil {
			return nil, err
		}
		defer configFile.Close()
299

300
301
302
		if err := json.NewDecoder(configFile).Decode(&model.Config); err != nil {
			return nil, err
		}
303
304
	}

305
	for _, layer := range manifest.Layers {
Patrick Devine's avatar
Patrick Devine committed
306
		filename, err := GetBlobsPath(layer.Digest)
307
308
309
310
		if err != nil {
			return nil, err
		}

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

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

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

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

	return model, nil
}

Michael Yang's avatar
Michael Yang committed
374
func CopyModel(src, dst model.Name) error {
375
376
377
378
379
380
381
	if !dst.IsFullyQualified() {
		return model.Unqualified(dst)
	}
	if !src.IsFullyQualified() {
		return model.Unqualified(src)
	}

382
383
384
385
	if src.Filepath() == dst.Filepath() {
		return nil
	}

Michael Yang's avatar
Michael Yang committed
386
	manifests, err := GetManifestPath()
387
388
389
390
	if err != nil {
		return err
	}

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

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

Michael Yang's avatar
Michael Yang committed
403
	dstfile, err := os.Create(dstpath)
Patrick Devine's avatar
Patrick Devine committed
404
405
406
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
407
	defer dstfile.Close()
Patrick Devine's avatar
Patrick Devine committed
408

Michael Yang's avatar
Michael Yang committed
409
410
	_, err = io.Copy(dstfile, srcfile)
	return err
Patrick Devine's avatar
Patrick Devine committed
411
412
}

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

Michael Yang's avatar
Michael Yang committed
420
	for _, manifest := range manifests {
Michael Yang's avatar
Michael Yang committed
421
422
423
424
425
		for _, layer := range manifest.Layers {
			delete(deleteMap, layer.Digest)
		}

		delete(deleteMap, manifest.Config.Digest)
Michael Yang's avatar
Michael Yang committed
426
	}
427
428

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

441
442
443
444
	return nil
}

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

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

	for _, blob := range blobs {
		name := blob.Name()
459
		name = strings.ReplaceAll(name, "-", ":")
460
461
462
463
464
465
466
467
468
469
470

		_, 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
471
		}
472
473

		deleteMap[name] = struct{}{}
474
475
	}

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

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

483
	slog.Info(fmt.Sprintf("total unused blobs removed: %d", len(deleteMap)))
484
485
486
487

	return nil
}

Michael Yang's avatar
Michael Yang committed
488
489
490
491
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
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
521
func PushModel(ctx context.Context, name string, regOpts *registryOptions, fn func(api.ProgressResponse)) error {
522
	mp := ParseModelPath(name)
523
524
	fn(api.ProgressResponse{Status: "retrieving manifest"})

525
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
CYJiang's avatar
CYJiang committed
526
		return errInsecureProtocol
527
528
	}

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

535
	var layers []Layer
Jeffrey Morgan's avatar
Jeffrey Morgan committed
536
	layers = append(layers, manifest.Layers...)
537
	if manifest.Config.Digest != "" {
538
		layers = append(layers, manifest.Config)
539
	}
540
541

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

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

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

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

565
	fn(api.ProgressResponse{Status: "success"})
566
567
568
569

	return nil
}

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

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

589
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
CYJiang's avatar
CYJiang committed
590
		return errInsecureProtocol
591
	}
592

593
	fn(api.ProgressResponse{Status: "pulling manifest"})
594

595
	manifest, err = pullModelManifest(ctx, mp, regOpts)
596
	if err != nil {
597
		return fmt.Errorf("pull model manifest: %s", err)
598
599
	}

600
	var layers []Layer
Bruce MacDonald's avatar
Bruce MacDonald committed
601
	layers = append(layers, manifest.Layers...)
602
	if manifest.Config.Digest != "" {
603
		layers = append(layers, manifest.Config)
604
	}
605

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

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

643
	fn(api.ProgressResponse{Status: "writing manifest"})
644

645
	manifestJSON, err := json.Marshal(manifest)
646
647
648
649
	if err != nil {
		return err
	}

650
	fp, err := mp.GetManifestPath()
651
652
653
	if err != nil {
		return err
	}
654
655
656
	if err := os.MkdirAll(filepath.Dir(fp), 0o755); err != nil {
		return err
	}
657

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

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

671
	fn(api.ProgressResponse{Status: "success"})
672
673
674
675

	return nil
}

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

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

Michael Yang's avatar
Michael Yang committed
687
	var m Manifest
688
689
690
691
	if err := json.NewDecoder(resp.Body).Decode(&m); err != nil {
		return nil, err
	}

Michael Yang's avatar
Michael Yang committed
692
	return &m, err
693
694
695
}

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

Michael Yang's avatar
Michael Yang committed
703
	return fmt.Sprintf("sha256:%x", h.Sum(nil)), n
704
705
}

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

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

Michael Yang's avatar
Michael Yang committed
716
717
			return nil, err
		}
Michael Yang's avatar
Michael Yang committed
718
719
720

		switch {
		case resp.StatusCode == http.StatusUnauthorized:
721
722
			resp.Body.Close()

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

Michael Yang's avatar
Michael Yang committed
751
	return nil, errUnauthorized
Michael Yang's avatar
Michael Yang committed
752
753
}

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

791
	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
792
793
794
795
796
797
798
799
800
801

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

		req.ContentLength = contentLength
	}

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

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

Michael Yang's avatar
Michael Yang committed
839
	return registryChallenge{
Patrick Devine's avatar
Patrick Devine committed
840
841
842
843
844
845
		Realm:   getValue(authStr, "realm"),
		Service: getValue(authStr, "service"),
		Scope:   getValue(authStr, "scope"),
	}
}

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

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

	return nil
}