images.go 20.8 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"
Michael Yang's avatar
Michael Yang committed
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"
Michael Yang's avatar
Michael Yang committed
29
	"github.com/ollama/ollama/types/model"
30
	"github.com/ollama/ollama/version"
31
32
)

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

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

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

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

	Template *template.Template
67
68
}

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

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

78
		f, _, err := ggml.Decode(r, 1024)
79
		if err == nil {
Michael Yang's avatar
Michael Yang committed
80
			if _, ok := f.KV()[fmt.Sprintf("%s.pooling_type", f.KV().Architecture())]; ok {
81
82
83
				capabilities = append(capabilities, model.CapabilityEmbedding)
			} else {
				capabilities = append(capabilities, model.CapabilityCompletion)
84
			}
85
86
			if _, ok := f.KV()[fmt.Sprintf("%s.vision.block_count", f.KV().Architecture())]; ok {
				capabilities = append(capabilities, model.CapabilityVision)
Michael Yang's avatar
tools  
Michael Yang committed
87
			}
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
		} else {
			slog.Error("couldn't decode ggml", "error", err)
		}
	} 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)
	}

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

114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
	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,
	}

	for _, cap := range want {
		err, ok := capToErr[cap]
		if !ok {
Michael Yang's avatar
Michael Yang committed
135
			slog.Error("unknown capability", "capability", cap)
Michael Yang's avatar
Michael Yang committed
136
			return fmt.Errorf("unknown capability: %s", cap)
Michael Yang's avatar
Michael Yang committed
137
		}
138
139
140
141

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

144
	if len(errs) > 0 {
145
		return fmt.Errorf("%w %w", errCapabilities, errors.Join(errs...))
Michael Yang's avatar
Michael Yang committed
146
147
148
	}

	return nil
149
150
}

Michael Yang's avatar
Michael Yang committed
151
func (m *Model) String() string {
152
	var modelfile parser.Modelfile
Michael Yang's avatar
Michael Yang committed
153

154
	modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
155
156
157
		Name: "model",
		Args: m.ModelPath,
	})
158

Michael Yang's avatar
Michael Yang committed
159
	for _, adapter := range m.AdapterPaths {
160
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
161
162
			Name: "adapter",
			Args: adapter,
Michael Yang's avatar
Michael Yang committed
163
		})
164
165
	}

Michael Yang's avatar
Michael Yang committed
166
	for _, projector := range m.ProjectorPaths {
167
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
168
169
			Name: "model",
			Args: projector,
Michael Yang's avatar
Michael Yang committed
170
		})
171
172
	}

Michael Yang's avatar
Michael Yang committed
173
	if m.Template != nil {
174
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
175
			Name: "template",
Michael Yang's avatar
Michael Yang committed
176
			Args: m.Template.String(),
Michael Yang's avatar
Michael Yang committed
177
		})
178
179
	}

Michael Yang's avatar
Michael Yang committed
180
	if m.System != "" {
181
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
182
183
			Name: "system",
			Args: m.System,
Michael Yang's avatar
Michael Yang committed
184
		})
185
186
187
188
189
190
	}

	for k, v := range m.Options {
		switch v := v.(type) {
		case []any:
			for _, s := range v {
191
				modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
192
193
194
					Name: k,
					Args: fmt.Sprintf("%v", s),
				})
195
196
			}
		default:
197
			modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
198
199
200
				Name: k,
				Args: fmt.Sprintf("%v", v),
			})
201
202
203
204
		}
	}

	for _, license := range m.License {
205
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
206
207
208
			Name: "license",
			Args: license,
		})
209
210
211
	}

	for _, msg := range m.Messages {
212
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
213
			Name: "message",
Michael Yang's avatar
Michael Yang committed
214
			Args: fmt.Sprintf("%s: %s", msg.Role, msg.Content),
Michael Yang's avatar
Michael Yang committed
215
		})
216
217
	}

Michael Yang's avatar
Michael Yang committed
218
	return modelfile.String()
219
220
}

221
type ConfigV2 struct {
222
223
224
225
226
227
	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"`

228
	// required by spec
229
230
	Architecture string `json:"architecture"`
	OS           string `json:"os"`
231
	RootFS       RootFS `json:"rootfs"`
232
233
234
235
236
237
238
}

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

Michael Yang's avatar
Michael Yang committed
239
func GetManifest(mp ModelPath) (*Manifest, string, error) {
240
	fp, err := mp.GetManifestPath()
241
	if err != nil {
Patrick Devine's avatar
Patrick Devine committed
242
		return nil, "", err
243
	}
244

Michael Yang's avatar
Michael Yang committed
245
	f, err := os.Open(fp)
246
	if err != nil {
Michael Yang's avatar
Michael Yang committed
247
		return nil, "", err
248
	}
Michael Yang's avatar
Michael Yang committed
249
	defer f.Close()
250

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

Michael Yang's avatar
Michael Yang committed
253
254
	var manifest Manifest
	if err := json.NewDecoder(io.TeeReader(f, sha256sum)).Decode(&manifest); err != nil {
Patrick Devine's avatar
Patrick Devine committed
255
		return nil, "", err
256
257
	}

Michael Yang's avatar
Michael Yang committed
258
	return &manifest, hex.EncodeToString(sha256sum.Sum(nil)), nil
259
260
261
}

func GetModel(name string) (*Model, error) {
262
	mp := ParseModelPath(name)
Patrick Devine's avatar
Patrick Devine committed
263
	manifest, digest, err := GetManifest(mp)
264
265
266
267
268
	if err != nil {
		return nil, err
	}

	model := &Model{
269
270
271
		Name:      mp.GetFullTagname(),
		ShortName: mp.GetShortTagname(),
		Digest:    digest,
Michael Yang's avatar
Michael Yang committed
272
		Template:  template.DefaultTemplate,
273
274
	}

275
276
277
278
279
	if manifest.Config.Digest != "" {
		filename, err := GetBlobsPath(manifest.Config.Digest)
		if err != nil {
			return nil, err
		}
280

281
282
283
284
285
		configFile, err := os.Open(filename)
		if err != nil {
			return nil, err
		}
		defer configFile.Close()
286

287
288
289
		if err := json.NewDecoder(configFile).Decode(&model.Config); err != nil {
			return nil, err
		}
290
291
	}

292
	for _, layer := range manifest.Layers {
Patrick Devine's avatar
Patrick Devine committed
293
		filename, err := GetBlobsPath(layer.Digest)
294
295
296
297
		if err != nil {
			return nil, err
		}

298
299
300
		switch layer.MediaType {
		case "application/vnd.ollama.image.model":
			model.ModelPath = filename
301
			model.ParentModel = layer.From
302
		case "application/vnd.ollama.image.embed":
303
304
			// Deprecated in versions  > 0.1.2
			// TODO: remove this warning in a future version
305
			slog.Info("WARNING: model contains embeddings, but embeddings in modelfiles have been deprecated and will be ignored.")
306
307
		case "application/vnd.ollama.image.adapter":
			model.AdapterPaths = append(model.AdapterPaths, filename)
Michael Yang's avatar
Michael Yang committed
308
309
		case "application/vnd.ollama.image.projector":
			model.ProjectorPaths = append(model.ProjectorPaths, filename)
Michael Yang's avatar
Michael Yang committed
310
311
		case "application/vnd.ollama.image.prompt",
			"application/vnd.ollama.image.template":
312
313
314
315
316
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}

Michael Yang's avatar
Michael Yang committed
317
			model.Template, err = template.Parse(string(bts))
318
319
320
			if err != nil {
				return nil, err
			}
Michael Yang's avatar
Michael Yang committed
321
		case "application/vnd.ollama.image.system":
322
323
324
325
326
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}

Michael Yang's avatar
Michael Yang committed
327
			model.System = string(bts)
328
		case "application/vnd.ollama.image.params":
Michael Yang's avatar
Michael Yang committed
329
330
331
332
333
			params, err := os.Open(filename)
			if err != nil {
				return nil, err
			}
			defer params.Close()
334

335
			// parse model options parameters into a map so that we can see which fields have been specified explicitly
336
			if err = json.NewDecoder(params).Decode(&model.Options); err != nil {
337
338
				return nil, err
			}
339
340
341
342
343
344
345
346
347
348
		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
349
350
351
352
353
354
		case "application/vnd.ollama.image.license":
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}
			model.License = append(model.License, string(bts))
355
356
357
358
359
360
		}
	}

	return model, nil
}

Michael Yang's avatar
Michael Yang committed
361
func CopyModel(src, dst model.Name) error {
362
363
364
365
366
367
368
	if !dst.IsFullyQualified() {
		return model.Unqualified(dst)
	}
	if !src.IsFullyQualified() {
		return model.Unqualified(src)
	}

369
370
371
372
	if src.Filepath() == dst.Filepath() {
		return nil
	}

Michael Yang's avatar
Michael Yang committed
373
	manifests, err := GetManifestPath()
374
375
376
377
	if err != nil {
		return err
	}

378
	dstpath := filepath.Join(manifests, dst.Filepath())
Michael Yang's avatar
Michael Yang committed
379
	if err := os.MkdirAll(filepath.Dir(dstpath), 0o755); err != nil {
380
381
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
382

383
	srcpath := filepath.Join(manifests, src.Filepath())
Michael Yang's avatar
Michael Yang committed
384
	srcfile, err := os.Open(srcpath)
Patrick Devine's avatar
Patrick Devine committed
385
386
387
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
388
	defer srcfile.Close()
Patrick Devine's avatar
Patrick Devine committed
389

Michael Yang's avatar
Michael Yang committed
390
	dstfile, err := os.Create(dstpath)
Patrick Devine's avatar
Patrick Devine committed
391
392
393
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
394
	defer dstfile.Close()
Patrick Devine's avatar
Patrick Devine committed
395

Michael Yang's avatar
Michael Yang committed
396
397
	_, err = io.Copy(dstfile, srcfile)
	return err
Patrick Devine's avatar
Patrick Devine committed
398
399
}

Michael Yang's avatar
Michael Yang committed
400
func deleteUnusedLayers(deleteMap map[string]struct{}) error {
401
402
	// Ignore corrupt manifests to avoid blocking deletion of layers that are freshly orphaned
	manifests, err := Manifests(true)
403
404
405
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
406

Michael Yang's avatar
Michael Yang committed
407
	for _, manifest := range manifests {
Michael Yang's avatar
Michael Yang committed
408
409
410
411
412
		for _, layer := range manifest.Layers {
			delete(deleteMap, layer.Digest)
		}

		delete(deleteMap, manifest.Config.Digest)
Michael Yang's avatar
Michael Yang committed
413
	}
414
415

	// only delete the files which are still in the deleteMap
Michael Yang's avatar
Michael Yang committed
416
417
418
	for k := range deleteMap {
		fp, err := GetBlobsPath(k)
		if err != nil {
419
			slog.Info(fmt.Sprintf("couldn't get file path for '%s': %v", k, err))
Michael Yang's avatar
Michael Yang committed
420
421
			continue
		}
422
423
424
		if err := os.Remove(fp); err != nil {
			slog.Info(fmt.Sprintf("couldn't remove file '%s': %v", fp, err))
			continue
425
426
427
		}
	}

428
429
430
431
	return nil
}

func PruneLayers() error {
Michael Yang's avatar
Michael Yang committed
432
	deleteMap := make(map[string]struct{})
433
434
435
436
437
438
439
	p, err := GetBlobsPath("")
	if err != nil {
		return err
	}

	blobs, err := os.ReadDir(p)
	if err != nil {
440
		slog.Info(fmt.Sprintf("couldn't read dir '%s': %v", p, err))
441
442
443
444
445
		return err
	}

	for _, blob := range blobs {
		name := blob.Name()
446
		name = strings.ReplaceAll(name, "-", ":")
447
448
449
450
451
452
453
454
455
456
457

		_, 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
458
		}
459
460

		deleteMap[name] = struct{}{}
461
462
	}

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

Michael Yang's avatar
Michael Yang committed
465
	if err := deleteUnusedLayers(deleteMap); err != nil {
466
		slog.Error(fmt.Sprintf("couldn't remove unused layers: %v", err))
467
		return nil
468
469
	}

470
	slog.Info(fmt.Sprintf("total unused blobs removed: %d", len(deleteMap)))
471
472
473
474

	return nil
}

Michael Yang's avatar
Michael Yang committed
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
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
508
func PushModel(ctx context.Context, name string, regOpts *registryOptions, fn func(api.ProgressResponse)) error {
509
	mp := ParseModelPath(name)
510
511
	fn(api.ProgressResponse{Status: "retrieving manifest"})

512
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
CYJiang's avatar
CYJiang committed
513
		return errInsecureProtocol
514
515
	}

Patrick Devine's avatar
Patrick Devine committed
516
	manifest, _, err := GetManifest(mp)
517
	if err != nil {
518
		fn(api.ProgressResponse{Status: "couldn't retrieve manifest"})
519
520
521
		return err
	}

522
	var layers []Layer
Jeffrey Morgan's avatar
Jeffrey Morgan committed
523
	layers = append(layers, manifest.Layers...)
524
	if manifest.Config.Digest != "" {
525
		layers = append(layers, manifest.Config)
526
	}
527
528

	for _, layer := range layers {
Michael Yang's avatar
Michael Yang committed
529
		if err := uploadBlob(ctx, mp, layer, regOpts, fn); err != nil {
530
			slog.Info(fmt.Sprintf("error uploading blob: %v", err))
531
532
			return err
		}
533
534
	}

535
	fn(api.ProgressResponse{Status: "pushing manifest"})
Michael Yang's avatar
Michael Yang committed
536
537
	requestURL := mp.BaseURL()
	requestURL = requestURL.JoinPath("v2", mp.GetNamespaceRepository(), "manifests", mp.Tag)
538
539
540
541
542
543

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

Michael Yang's avatar
Michael Yang committed
544
545
	headers := make(http.Header)
	headers.Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json")
Michael Yang's avatar
Michael Yang committed
546
	resp, err := makeRequestWithRetry(ctx, http.MethodPut, requestURL, headers, bytes.NewReader(manifestJSON), regOpts)
547
548
549
550
551
	if err != nil {
		return err
	}
	defer resp.Body.Close()

552
	fn(api.ProgressResponse{Status: "success"})
553
554
555
556

	return nil
}

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

560
	// build deleteMap to prune unused layers
Michael Yang's avatar
Michael Yang committed
561
	deleteMap := make(map[string]struct{})
Michael Yang's avatar
Michael Yang committed
562
563
564
	manifest, _, err := GetManifest(mp)
	if errors.Is(err, os.ErrNotExist) {
		// noop
565
566
	} else if err != nil {
		slog.Warn("pulling model with bad existing manifest", "name", name, "error", err)
Michael Yang's avatar
Michael Yang committed
567
568
569
	} else {
		for _, l := range manifest.Layers {
			deleteMap[l.Digest] = struct{}{}
570
		}
Michael Yang's avatar
Michael Yang committed
571
572
		if manifest.Config.Digest != "" {
			deleteMap[manifest.Config.Digest] = struct{}{}
573
574
575
		}
	}

576
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
CYJiang's avatar
CYJiang committed
577
		return errInsecureProtocol
578
	}
579

580
	fn(api.ProgressResponse{Status: "pulling manifest"})
581

582
	manifest, err = pullModelManifest(ctx, mp, regOpts)
583
	if err != nil {
584
		return fmt.Errorf("pull model manifest: %s", err)
585
586
	}

587
	var layers []Layer
Bruce MacDonald's avatar
Bruce MacDonald committed
588
	layers = append(layers, manifest.Layers...)
589
	if manifest.Config.Digest != "" {
590
		layers = append(layers, manifest.Config)
591
	}
592

593
	skipVerify := make(map[string]bool)
594
	for _, layer := range layers {
595
596
597
598
599
600
601
		cacheHit, err := downloadBlob(ctx, downloadOpts{
			mp:      mp,
			digest:  layer.Digest,
			regOpts: regOpts,
			fn:      fn,
		})
		if err != nil {
602
603
			return err
		}
604
		skipVerify[layer.Digest] = cacheHit
605
		delete(deleteMap, layer.Digest)
606
	}
607
	delete(deleteMap, manifest.Config.Digest)
608

Michael Yang's avatar
Michael Yang committed
609
610
	fn(api.ProgressResponse{Status: "verifying sha256 digest"})
	for _, layer := range layers {
611
612
613
		if skipVerify[layer.Digest] {
			continue
		}
Michael Yang's avatar
Michael Yang committed
614
		if err := verifyBlob(layer.Digest); err != nil {
615
616
617
618
619
620
621
622
			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
623
					slog.Info(fmt.Sprintf("couldn't remove file with digest mismatch '%s': %v", fp, err))
624
625
				}
			}
Michael Yang's avatar
Michael Yang committed
626
627
628
629
			return err
		}
	}

630
	fn(api.ProgressResponse{Status: "writing manifest"})
631

632
	manifestJSON, err := json.Marshal(manifest)
633
634
635
636
	if err != nil {
		return err
	}

637
	fp, err := mp.GetManifestPath()
638
639
640
	if err != nil {
		return err
	}
641
642
643
	if err := os.MkdirAll(filepath.Dir(fp), 0o755); err != nil {
		return err
	}
644

Bruce MacDonald's avatar
Bruce MacDonald committed
645
	err = os.WriteFile(fp, manifestJSON, 0o644)
646
	if err != nil {
647
		slog.Info(fmt.Sprintf("couldn't write to %s", fp))
648
649
650
		return err
	}

Michael Yang's avatar
Michael Yang committed
651
652
	if !envconfig.NoPrune() && len(deleteMap) > 0 {
		fn(api.ProgressResponse{Status: "removing unused layers"})
Michael Yang's avatar
Michael Yang committed
653
		if err := deleteUnusedLayers(deleteMap); err != nil {
654
			fn(api.ProgressResponse{Status: fmt.Sprintf("couldn't remove unused layers: %v", err)})
655
656
657
		}
	}

658
	fn(api.ProgressResponse{Status: "success"})
659
660
661
662

	return nil
}

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

Michael Yang's avatar
Michael Yang committed
666
667
	headers := make(http.Header)
	headers.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json")
Michael Yang's avatar
Michael Yang committed
668
	resp, err := makeRequestWithRetry(ctx, http.MethodGet, requestURL, headers, nil, regOpts)
669
670
671
672
673
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

Michael Yang's avatar
Michael Yang committed
674
	var m Manifest
675
676
677
678
	if err := json.NewDecoder(resp.Body).Decode(&m); err != nil {
		return nil, err
	}

Michael Yang's avatar
Michael Yang committed
679
	return &m, err
680
681
682
}

// GetSHA256Digest returns the SHA256 hash of a given buffer and returns it, and the size of buffer
Michael Yang's avatar
Michael Yang committed
683
func GetSHA256Digest(r io.Reader) (string, int64) {
Michael Yang's avatar
Michael Yang committed
684
685
686
687
688
689
	h := sha256.New()
	n, err := io.Copy(h, r)
	if err != nil {
		log.Fatal(err)
	}

Michael Yang's avatar
Michael Yang committed
690
	return fmt.Sprintf("sha256:%x", h.Sum(nil)), n
691
692
}

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

Michael Yang's avatar
Michael Yang committed
695
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
696
	for range 2 {
Michael Yang's avatar
Michael Yang committed
697
		resp, err := makeRequest(ctx, method, requestURL, headers, body, regOpts)
Michael Yang's avatar
Michael Yang committed
698
		if err != nil {
Michael Yang's avatar
Michael Yang committed
699
			if !errors.Is(err, context.Canceled) {
700
				slog.Info(fmt.Sprintf("request failed: %v", err))
Michael Yang's avatar
Michael Yang committed
701
702
			}

Michael Yang's avatar
Michael Yang committed
703
704
			return nil, err
		}
Michael Yang's avatar
Michael Yang committed
705
706
707

		switch {
		case resp.StatusCode == http.StatusUnauthorized:
708
709
			resp.Body.Close()

Michael Yang's avatar
Michael Yang committed
710
			// Handle authentication error with one retry
Michael Yang's avatar
Michael Yang committed
711
712
			challenge := parseRegistryChallenge(resp.Header.Get("www-authenticate"))
			token, err := getAuthorizationToken(ctx, challenge)
Michael Yang's avatar
Michael Yang committed
713
714
715
			if err != nil {
				return nil, err
			}
Michael Yang's avatar
Michael Yang committed
716
717
718
719
720
721
722
723
			regOpts.Token = token
			if body != nil {
				_, err = body.Seek(0, io.SeekStart)
				if err != nil {
					return nil, err
				}
			}
		case resp.StatusCode == http.StatusNotFound:
724
			resp.Body.Close()
Michael Yang's avatar
Michael Yang committed
725
726
			return nil, os.ErrNotExist
		case resp.StatusCode >= http.StatusBadRequest:
727
			defer resp.Body.Close()
Michael Yang's avatar
Michael Yang committed
728
729
730
731
732
733
734
			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
735
736
737
		}
	}

Michael Yang's avatar
Michael Yang committed
738
	return nil, errUnauthorized
Michael Yang's avatar
Michael Yang committed
739
740
}

741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
// 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
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
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)
		}
	}

778
	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
779
780
781
782
783
784
785
786
787
788

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

		req.ContentLength = contentLength
	}

789
	c := &http.Client{
790
		CheckRedirect: regOpts.CheckRedirect,
Michael Yang's avatar
Michael Yang committed
791
	}
792
793
794
795
796
797
	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
798
799
}

Patrick Devine's avatar
Patrick Devine committed
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
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
823
func parseRegistryChallenge(authStr string) registryChallenge {
Patrick Devine's avatar
Patrick Devine committed
824
825
	authStr = strings.TrimPrefix(authStr, "Bearer ")

Michael Yang's avatar
Michael Yang committed
826
	return registryChallenge{
Patrick Devine's avatar
Patrick Devine committed
827
828
829
830
831
832
		Realm:   getValue(authStr, "realm"),
		Service: getValue(authStr, "service"),
		Scope:   getValue(authStr, "scope"),
	}
}

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

Michael Yang's avatar
Michael Yang committed
835
836
837
838
839
840
841
842
843
844
845
846
847
848
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 {
849
		return fmt.Errorf("%w: want %s, got %s", errDigestMismatch, digest, fileDigest)
Michael Yang's avatar
Michael Yang committed
850
851
852
853
	}

	return nil
}