images.go 19.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"
26
	"github.com/ollama/ollama/llm"
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
38
var (
	errCapabilities         = errors.New("does not support")
	errCapabilityCompletion = errors.New("completion")
	errCapabilityTools      = errors.New("tools")
	errCapabilityInsert     = errors.New("insert")
)
Michael Yang's avatar
Michael Yang committed
39

Michael Yang's avatar
Michael Yang committed
40
41
type Capability string

Michael Yang's avatar
tools  
Michael Yang committed
42
43
44
const (
	CapabilityCompletion = Capability("completion")
	CapabilityTools      = Capability("tools")
45
	CapabilityInsert     = Capability("insert")
Michael Yang's avatar
tools  
Michael Yang committed
46
)
Michael Yang's avatar
Michael Yang committed
47

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

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

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

	Template *template.Template
72
73
}

Michael Yang's avatar
Michael Yang committed
74
75
76
77
// CheckCapabilities checks if the model has the specified capabilities returning an error describing
// any missing or unknown capabilities
func (m *Model) CheckCapabilities(caps ...Capability) error {
	var errs []error
Michael Yang's avatar
Michael Yang committed
78
79
80
	for _, cap := range caps {
		switch cap {
		case CapabilityCompletion:
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
			f, err := os.Open(m.ModelPath)
			if err != nil {
				slog.Error("couldn't open model file", "error", err)
				continue
			}
			defer f.Close()

			// TODO(mxyng): decode the GGML into model to avoid doing this multiple times
			ggml, _, err := llm.DecodeGGML(f, 0)
			if err != nil {
				slog.Error("couldn't decode ggml", "error", err)
				continue
			}

			if _, ok := ggml.KV()[fmt.Sprintf("%s.pooling_type", ggml.KV().Architecture())]; ok {
Michael Yang's avatar
Michael Yang committed
96
				errs = append(errs, errCapabilityCompletion)
Michael Yang's avatar
Michael Yang committed
97
			}
Michael Yang's avatar
tools  
Michael Yang committed
98
99
		case CapabilityTools:
			if !slices.Contains(m.Template.Vars(), "tools") {
100
101
102
103
104
105
				errs = append(errs, errCapabilityTools)
			}
		case CapabilityInsert:
			vars := m.Template.Vars()
			if !slices.Contains(vars, "suffix") {
				errs = append(errs, errCapabilityInsert)
Michael Yang's avatar
tools  
Michael Yang committed
106
			}
Michael Yang's avatar
Michael Yang committed
107
108
		default:
			slog.Error("unknown capability", "capability", cap)
Michael Yang's avatar
Michael Yang committed
109
			return fmt.Errorf("unknown capability: %s", cap)
Michael Yang's avatar
Michael Yang committed
110
111
112
		}
	}

Michael Yang's avatar
Michael Yang committed
113
	if err := errors.Join(errs...); err != nil {
114
		return fmt.Errorf("%w %w", errCapabilities, errors.Join(errs...))
Michael Yang's avatar
Michael Yang committed
115
116
117
	}

	return nil
118
119
}

Michael Yang's avatar
Michael Yang committed
120
func (m *Model) String() string {
121
	var modelfile parser.Modelfile
Michael Yang's avatar
Michael Yang committed
122

123
	modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
124
125
126
		Name: "model",
		Args: m.ModelPath,
	})
127

Michael Yang's avatar
Michael Yang committed
128
	for _, adapter := range m.AdapterPaths {
129
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
130
131
			Name: "adapter",
			Args: adapter,
Michael Yang's avatar
Michael Yang committed
132
		})
133
134
	}

Michael Yang's avatar
Michael Yang committed
135
	for _, projector := range m.ProjectorPaths {
136
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
137
138
			Name: "model",
			Args: projector,
Michael Yang's avatar
Michael Yang committed
139
		})
140
141
	}

Michael Yang's avatar
Michael Yang committed
142
	if m.Template != nil {
143
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
144
			Name: "template",
Michael Yang's avatar
Michael Yang committed
145
			Args: m.Template.String(),
Michael Yang's avatar
Michael Yang committed
146
		})
147
148
	}

Michael Yang's avatar
Michael Yang committed
149
	if m.System != "" {
150
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
151
152
			Name: "system",
			Args: m.System,
Michael Yang's avatar
Michael Yang committed
153
		})
154
155
156
157
158
159
	}

	for k, v := range m.Options {
		switch v := v.(type) {
		case []any:
			for _, s := range v {
160
				modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
161
162
163
					Name: k,
					Args: fmt.Sprintf("%v", s),
				})
164
165
			}
		default:
166
			modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
167
168
169
				Name: k,
				Args: fmt.Sprintf("%v", v),
			})
170
171
172
173
		}
	}

	for _, license := range m.License {
174
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
175
176
177
			Name: "license",
			Args: license,
		})
178
179
180
	}

	for _, msg := range m.Messages {
181
		modelfile.Commands = append(modelfile.Commands, parser.Command{
Michael Yang's avatar
Michael Yang committed
182
			Name: "message",
Michael Yang's avatar
Michael Yang committed
183
			Args: fmt.Sprintf("%s: %s", msg.Role, msg.Content),
Michael Yang's avatar
Michael Yang committed
184
		})
185
186
	}

Michael Yang's avatar
Michael Yang committed
187
	return modelfile.String()
188
189
}

190
type ConfigV2 struct {
191
192
193
194
195
196
	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"`

197
	// required by spec
198
199
	Architecture string `json:"architecture"`
	OS           string `json:"os"`
200
	RootFS       RootFS `json:"rootfs"`
201
202
203
204
205
206
207
}

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

Michael Yang's avatar
Michael Yang committed
208
func GetManifest(mp ModelPath) (*Manifest, string, error) {
209
	fp, err := mp.GetManifestPath()
210
	if err != nil {
Patrick Devine's avatar
Patrick Devine committed
211
		return nil, "", err
212
	}
213

Michael Yang's avatar
Michael Yang committed
214
	f, err := os.Open(fp)
215
	if err != nil {
Michael Yang's avatar
Michael Yang committed
216
		return nil, "", err
217
	}
Michael Yang's avatar
Michael Yang committed
218
	defer f.Close()
219

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

Michael Yang's avatar
Michael Yang committed
222
223
	var manifest Manifest
	if err := json.NewDecoder(io.TeeReader(f, sha256sum)).Decode(&manifest); err != nil {
Patrick Devine's avatar
Patrick Devine committed
224
		return nil, "", err
225
226
	}

Michael Yang's avatar
Michael Yang committed
227
	return &manifest, hex.EncodeToString(sha256sum.Sum(nil)), nil
228
229
230
}

func GetModel(name string) (*Model, error) {
231
	mp := ParseModelPath(name)
Patrick Devine's avatar
Patrick Devine committed
232
	manifest, digest, err := GetManifest(mp)
233
234
235
236
237
	if err != nil {
		return nil, err
	}

	model := &Model{
238
239
240
		Name:      mp.GetFullTagname(),
		ShortName: mp.GetShortTagname(),
		Digest:    digest,
Michael Yang's avatar
Michael Yang committed
241
		Template:  template.DefaultTemplate,
242
243
	}

244
245
246
247
248
	if manifest.Config.Digest != "" {
		filename, err := GetBlobsPath(manifest.Config.Digest)
		if err != nil {
			return nil, err
		}
249

250
251
252
253
254
		configFile, err := os.Open(filename)
		if err != nil {
			return nil, err
		}
		defer configFile.Close()
255

256
257
258
		if err := json.NewDecoder(configFile).Decode(&model.Config); err != nil {
			return nil, err
		}
259
260
	}

261
	for _, layer := range manifest.Layers {
Patrick Devine's avatar
Patrick Devine committed
262
		filename, err := GetBlobsPath(layer.Digest)
263
264
265
266
		if err != nil {
			return nil, err
		}

267
268
269
		switch layer.MediaType {
		case "application/vnd.ollama.image.model":
			model.ModelPath = filename
270
			model.ParentModel = layer.From
271
		case "application/vnd.ollama.image.embed":
272
273
			// Deprecated in versions  > 0.1.2
			// TODO: remove this warning in a future version
274
			slog.Info("WARNING: model contains embeddings, but embeddings in modelfiles have been deprecated and will be ignored.")
275
276
		case "application/vnd.ollama.image.adapter":
			model.AdapterPaths = append(model.AdapterPaths, filename)
Michael Yang's avatar
Michael Yang committed
277
278
		case "application/vnd.ollama.image.projector":
			model.ProjectorPaths = append(model.ProjectorPaths, filename)
Michael Yang's avatar
Michael Yang committed
279
280
		case "application/vnd.ollama.image.prompt",
			"application/vnd.ollama.image.template":
281
282
283
284
285
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}

Michael Yang's avatar
Michael Yang committed
286
			model.Template, err = template.Parse(string(bts))
287
288
289
			if err != nil {
				return nil, err
			}
Michael Yang's avatar
Michael Yang committed
290
		case "application/vnd.ollama.image.system":
291
292
293
294
295
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}

Michael Yang's avatar
Michael Yang committed
296
			model.System = string(bts)
297
		case "application/vnd.ollama.image.params":
Michael Yang's avatar
Michael Yang committed
298
299
300
301
302
			params, err := os.Open(filename)
			if err != nil {
				return nil, err
			}
			defer params.Close()
303

304
			// parse model options parameters into a map so that we can see which fields have been specified explicitly
305
			if err = json.NewDecoder(params).Decode(&model.Options); err != nil {
306
307
				return nil, err
			}
308
309
310
311
312
313
314
315
316
317
		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
318
319
320
321
322
323
		case "application/vnd.ollama.image.license":
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}
			model.License = append(model.License, string(bts))
324
325
326
327
328
329
		}
	}

	return model, nil
}

Michael Yang's avatar
Michael Yang committed
330
func CopyModel(src, dst model.Name) error {
331
332
333
334
335
336
337
	if !dst.IsFullyQualified() {
		return model.Unqualified(dst)
	}
	if !src.IsFullyQualified() {
		return model.Unqualified(src)
	}

338
339
340
341
	if src.Filepath() == dst.Filepath() {
		return nil
	}

Michael Yang's avatar
Michael Yang committed
342
	manifests, err := GetManifestPath()
343
344
345
346
	if err != nil {
		return err
	}

347
	dstpath := filepath.Join(manifests, dst.Filepath())
Michael Yang's avatar
Michael Yang committed
348
	if err := os.MkdirAll(filepath.Dir(dstpath), 0o755); err != nil {
349
350
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
351

352
	srcpath := filepath.Join(manifests, src.Filepath())
Michael Yang's avatar
Michael Yang committed
353
	srcfile, err := os.Open(srcpath)
Patrick Devine's avatar
Patrick Devine committed
354
355
356
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
357
	defer srcfile.Close()
Patrick Devine's avatar
Patrick Devine committed
358

Michael Yang's avatar
Michael Yang committed
359
	dstfile, err := os.Create(dstpath)
Patrick Devine's avatar
Patrick Devine committed
360
361
362
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
363
	defer dstfile.Close()
Patrick Devine's avatar
Patrick Devine committed
364

Michael Yang's avatar
Michael Yang committed
365
366
	_, err = io.Copy(dstfile, srcfile)
	return err
Patrick Devine's avatar
Patrick Devine committed
367
368
}

Michael Yang's avatar
Michael Yang committed
369
func deleteUnusedLayers(deleteMap map[string]struct{}) error {
370
371
	// Ignore corrupt manifests to avoid blocking deletion of layers that are freshly orphaned
	manifests, err := Manifests(true)
372
373
374
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
375

Michael Yang's avatar
Michael Yang committed
376
	for _, manifest := range manifests {
Michael Yang's avatar
Michael Yang committed
377
378
379
380
381
		for _, layer := range manifest.Layers {
			delete(deleteMap, layer.Digest)
		}

		delete(deleteMap, manifest.Config.Digest)
Michael Yang's avatar
Michael Yang committed
382
	}
383
384

	// only delete the files which are still in the deleteMap
Michael Yang's avatar
Michael Yang committed
385
386
387
	for k := range deleteMap {
		fp, err := GetBlobsPath(k)
		if err != nil {
388
			slog.Info(fmt.Sprintf("couldn't get file path for '%s': %v", k, err))
Michael Yang's avatar
Michael Yang committed
389
390
			continue
		}
391
392
393
		if err := os.Remove(fp); err != nil {
			slog.Info(fmt.Sprintf("couldn't remove file '%s': %v", fp, err))
			continue
394
395
396
		}
	}

397
398
399
400
	return nil
}

func PruneLayers() error {
Michael Yang's avatar
Michael Yang committed
401
	deleteMap := make(map[string]struct{})
402
403
404
405
406
407
408
	p, err := GetBlobsPath("")
	if err != nil {
		return err
	}

	blobs, err := os.ReadDir(p)
	if err != nil {
409
		slog.Info(fmt.Sprintf("couldn't read dir '%s': %v", p, err))
410
411
412
413
414
		return err
	}

	for _, blob := range blobs {
		name := blob.Name()
415
		name = strings.ReplaceAll(name, "-", ":")
416
417
418
419
420
421
422
423
424
425
426

		_, 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
427
		}
428
429

		deleteMap[name] = struct{}{}
430
431
	}

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

Michael Yang's avatar
Michael Yang committed
434
	if err := deleteUnusedLayers(deleteMap); err != nil {
435
		slog.Error(fmt.Sprintf("couldn't remove unused layers: %v", err))
436
		return nil
437
438
	}

439
	slog.Info(fmt.Sprintf("total unused blobs removed: %d", len(deleteMap)))
440
441
442
443

	return nil
}

Michael Yang's avatar
Michael Yang committed
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
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
477
func PushModel(ctx context.Context, name string, regOpts *registryOptions, fn func(api.ProgressResponse)) error {
478
	mp := ParseModelPath(name)
479
480
	fn(api.ProgressResponse{Status: "retrieving manifest"})

481
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
Michael Yang's avatar
lint  
Michael Yang committed
482
		return errors.New("insecure protocol http")
483
484
	}

Patrick Devine's avatar
Patrick Devine committed
485
	manifest, _, err := GetManifest(mp)
486
	if err != nil {
487
		fn(api.ProgressResponse{Status: "couldn't retrieve manifest"})
488
489
490
		return err
	}

491
	var layers []Layer
Jeffrey Morgan's avatar
Jeffrey Morgan committed
492
	layers = append(layers, manifest.Layers...)
493
	if manifest.Config.Digest != "" {
494
		layers = append(layers, manifest.Config)
495
	}
496
497

	for _, layer := range layers {
Michael Yang's avatar
Michael Yang committed
498
		if err := uploadBlob(ctx, mp, layer, regOpts, fn); err != nil {
499
			slog.Info(fmt.Sprintf("error uploading blob: %v", err))
500
501
			return err
		}
502
503
	}

504
	fn(api.ProgressResponse{Status: "pushing manifest"})
Michael Yang's avatar
Michael Yang committed
505
506
	requestURL := mp.BaseURL()
	requestURL = requestURL.JoinPath("v2", mp.GetNamespaceRepository(), "manifests", mp.Tag)
507
508
509
510
511
512

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

Michael Yang's avatar
Michael Yang committed
513
514
	headers := make(http.Header)
	headers.Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json")
Michael Yang's avatar
Michael Yang committed
515
	resp, err := makeRequestWithRetry(ctx, http.MethodPut, requestURL, headers, bytes.NewReader(manifestJSON), regOpts)
516
517
518
519
520
	if err != nil {
		return err
	}
	defer resp.Body.Close()

521
	fn(api.ProgressResponse{Status: "success"})
522
523
524
525

	return nil
}

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

529
	// build deleteMap to prune unused layers
Michael Yang's avatar
Michael Yang committed
530
	deleteMap := make(map[string]struct{})
Michael Yang's avatar
Michael Yang committed
531
532
533
	manifest, _, err := GetManifest(mp)
	if errors.Is(err, os.ErrNotExist) {
		// noop
534
535
	} else if err != nil {
		slog.Warn("pulling model with bad existing manifest", "name", name, "error", err)
Michael Yang's avatar
Michael Yang committed
536
537
538
	} else {
		for _, l := range manifest.Layers {
			deleteMap[l.Digest] = struct{}{}
539
		}
Michael Yang's avatar
Michael Yang committed
540
541
		if manifest.Config.Digest != "" {
			deleteMap[manifest.Config.Digest] = struct{}{}
542
543
544
		}
	}

545
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
Michael Yang's avatar
lint  
Michael Yang committed
546
		return errors.New("insecure protocol http")
547
	}
548

549
	fn(api.ProgressResponse{Status: "pulling manifest"})
550

551
	manifest, err = pullModelManifest(ctx, mp, regOpts)
552
	if err != nil {
553
		return fmt.Errorf("pull model manifest: %s", err)
554
555
	}

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

562
	skipVerify := make(map[string]bool)
563
	for _, layer := range layers {
564
565
566
567
568
569
570
		cacheHit, err := downloadBlob(ctx, downloadOpts{
			mp:      mp,
			digest:  layer.Digest,
			regOpts: regOpts,
			fn:      fn,
		})
		if err != nil {
571
572
			return err
		}
573
		skipVerify[layer.Digest] = cacheHit
574
		delete(deleteMap, layer.Digest)
575
	}
576
	delete(deleteMap, manifest.Config.Digest)
577

Michael Yang's avatar
Michael Yang committed
578
579
	fn(api.ProgressResponse{Status: "verifying sha256 digest"})
	for _, layer := range layers {
580
581
582
		if skipVerify[layer.Digest] {
			continue
		}
Michael Yang's avatar
Michael Yang committed
583
		if err := verifyBlob(layer.Digest); err != nil {
584
585
586
587
588
589
590
591
			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
592
					slog.Info(fmt.Sprintf("couldn't remove file with digest mismatch '%s': %v", fp, err))
593
594
				}
			}
Michael Yang's avatar
Michael Yang committed
595
596
597
598
			return err
		}
	}

599
	fn(api.ProgressResponse{Status: "writing manifest"})
600

601
	manifestJSON, err := json.Marshal(manifest)
602
603
604
605
	if err != nil {
		return err
	}

606
	fp, err := mp.GetManifestPath()
607
608
609
	if err != nil {
		return err
	}
610
611
612
	if err := os.MkdirAll(filepath.Dir(fp), 0o755); err != nil {
		return err
	}
613

Bruce MacDonald's avatar
Bruce MacDonald committed
614
	err = os.WriteFile(fp, manifestJSON, 0o644)
615
	if err != nil {
616
		slog.Info(fmt.Sprintf("couldn't write to %s", fp))
617
618
619
		return err
	}

Michael Yang's avatar
Michael Yang committed
620
621
	if !envconfig.NoPrune() && len(deleteMap) > 0 {
		fn(api.ProgressResponse{Status: "removing unused layers"})
Michael Yang's avatar
Michael Yang committed
622
		if err := deleteUnusedLayers(deleteMap); err != nil {
623
			fn(api.ProgressResponse{Status: fmt.Sprintf("couldn't remove unused layers: %v", err)})
624
625
626
		}
	}

627
	fn(api.ProgressResponse{Status: "success"})
628
629
630
631

	return nil
}

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

Michael Yang's avatar
Michael Yang committed
635
636
	headers := make(http.Header)
	headers.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json")
Michael Yang's avatar
Michael Yang committed
637
	resp, err := makeRequestWithRetry(ctx, http.MethodGet, requestURL, headers, nil, regOpts)
638
639
640
641
642
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

Michael Yang's avatar
Michael Yang committed
643
	var m Manifest
644
645
646
647
	if err := json.NewDecoder(resp.Body).Decode(&m); err != nil {
		return nil, err
	}

Michael Yang's avatar
Michael Yang committed
648
	return &m, err
649
650
651
}

// GetSHA256Digest returns the SHA256 hash of a given buffer and returns it, and the size of buffer
Michael Yang's avatar
Michael Yang committed
652
func GetSHA256Digest(r io.Reader) (string, int64) {
Michael Yang's avatar
Michael Yang committed
653
654
655
656
657
658
	h := sha256.New()
	n, err := io.Copy(h, r)
	if err != nil {
		log.Fatal(err)
	}

Michael Yang's avatar
Michael Yang committed
659
	return fmt.Sprintf("sha256:%x", h.Sum(nil)), n
660
661
}

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

Michael Yang's avatar
Michael Yang committed
664
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
665
	for range 2 {
Michael Yang's avatar
Michael Yang committed
666
		resp, err := makeRequest(ctx, method, requestURL, headers, body, regOpts)
Michael Yang's avatar
Michael Yang committed
667
		if err != nil {
Michael Yang's avatar
Michael Yang committed
668
			if !errors.Is(err, context.Canceled) {
669
				slog.Info(fmt.Sprintf("request failed: %v", err))
Michael Yang's avatar
Michael Yang committed
670
671
			}

Michael Yang's avatar
Michael Yang committed
672
673
			return nil, err
		}
Michael Yang's avatar
Michael Yang committed
674
675
676

		switch {
		case resp.StatusCode == http.StatusUnauthorized:
677
678
			resp.Body.Close()

Michael Yang's avatar
Michael Yang committed
679
			// Handle authentication error with one retry
Michael Yang's avatar
Michael Yang committed
680
681
			challenge := parseRegistryChallenge(resp.Header.Get("www-authenticate"))
			token, err := getAuthorizationToken(ctx, challenge)
Michael Yang's avatar
Michael Yang committed
682
683
684
			if err != nil {
				return nil, err
			}
Michael Yang's avatar
Michael Yang committed
685
686
687
688
689
690
691
692
			regOpts.Token = token
			if body != nil {
				_, err = body.Seek(0, io.SeekStart)
				if err != nil {
					return nil, err
				}
			}
		case resp.StatusCode == http.StatusNotFound:
693
			resp.Body.Close()
Michael Yang's avatar
Michael Yang committed
694
695
			return nil, os.ErrNotExist
		case resp.StatusCode >= http.StatusBadRequest:
696
			defer resp.Body.Close()
Michael Yang's avatar
Michael Yang committed
697
698
699
700
701
702
703
			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
704
705
706
		}
	}

Michael Yang's avatar
Michael Yang committed
707
	return nil, errUnauthorized
Michael Yang's avatar
Michael Yang committed
708
709
}

710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
// 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
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
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)
		}
	}

747
	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
748
749
750
751
752
753
754
755
756
757

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

		req.ContentLength = contentLength
	}

758
	c := &http.Client{
759
		CheckRedirect: regOpts.CheckRedirect,
Michael Yang's avatar
Michael Yang committed
760
	}
761
762
763
764
765
766
	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
767
768
}

Patrick Devine's avatar
Patrick Devine committed
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
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
792
func parseRegistryChallenge(authStr string) registryChallenge {
Patrick Devine's avatar
Patrick Devine committed
793
794
	authStr = strings.TrimPrefix(authStr, "Bearer ")

Michael Yang's avatar
Michael Yang committed
795
	return registryChallenge{
Patrick Devine's avatar
Patrick Devine committed
796
797
798
799
800
801
		Realm:   getValue(authStr, "realm"),
		Service: getValue(authStr, "service"),
		Scope:   getValue(authStr, "scope"),
	}
}

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

Michael Yang's avatar
Michael Yang committed
804
805
806
807
808
809
810
811
812
813
814
815
816
817
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 {
818
		return fmt.Errorf("%w: want %s, got %s", errDigestMismatch, digest, fileDigest)
Michael Yang's avatar
Michael Yang committed
819
820
821
822
	}

	return nil
}