"vscode:/vscode.git/clone" did not exist on "f9a377f6501b92896263a8210b45bfcaabe89f2a"
images.go 19.9 KB
Newer Older
1
2
3
4
package server

import (
	"bytes"
5
	"context"
6
	"crypto/sha256"
Patrick Devine's avatar
Patrick Devine committed
7
	"encoding/hex"
8
9
10
11
12
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
13
	"log/slog"
14
	"net"
15
	"net/http"
Michael Yang's avatar
Michael Yang committed
16
	"net/url"
17
18
	"os"
	"path/filepath"
Michael Yang's avatar
Michael Yang committed
19
	"runtime"
20
	"slices"
Michael Yang's avatar
Michael Yang committed
21
	"strconv"
22
23
	"strings"

24
	"github.com/ollama/ollama/api"
Michael Yang's avatar
Michael Yang committed
25
	"github.com/ollama/ollama/envconfig"
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")
CYJiang's avatar
CYJiang committed
38
	errInsecureProtocol     = errors.New("insecure protocol http")
39
)
Michael Yang's avatar
Michael Yang committed
40

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

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

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

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

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

	Template *template.Template
73
74
}

Michael Yang's avatar
Michael Yang committed
75
76
77
78
// 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
79
80
81
	for _, cap := range caps {
		switch cap {
		case CapabilityCompletion:
Michael Yang's avatar
Michael Yang committed
82
			r, err := os.Open(m.ModelPath)
83
84
85
86
			if err != nil {
				slog.Error("couldn't open model file", "error", err)
				continue
			}
Michael Yang's avatar
Michael Yang committed
87
			defer r.Close()
88
89

			// TODO(mxyng): decode the GGML into model to avoid doing this multiple times
Michael Yang's avatar
Michael Yang committed
90
			f, _, err := ggml.Decode(r, 0)
91
92
93
94
95
			if err != nil {
				slog.Error("couldn't decode ggml", "error", err)
				continue
			}

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

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

	return nil
119
120
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	return model, nil
}

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

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

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

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

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

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

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

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

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

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

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

398
399
400
401
	return nil
}

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

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

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

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

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

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

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

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

	return nil
}

Michael Yang's avatar
Michael Yang committed
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
477
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
478
func PushModel(ctx context.Context, name string, regOpts *registryOptions, fn func(api.ProgressResponse)) error {
479
	mp := ParseModelPath(name)
480
481
	fn(api.ProgressResponse{Status: "retrieving manifest"})

482
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
CYJiang's avatar
CYJiang committed
483
		return errInsecureProtocol
484
485
	}

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

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

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

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

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

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

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

	return nil
}

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

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

546
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
CYJiang's avatar
CYJiang committed
547
		return errInsecureProtocol
548
	}
549

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

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

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

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

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

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

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

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

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

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

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

	return nil
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		req.ContentLength = contentLength
	}

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

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

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

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

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

	return nil
}