images.go 28.4 KB
Newer Older
1
2
3
package server

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

Michael Yang's avatar
Michael Yang committed
25
26
	"golang.org/x/exp/slices"

27
	"github.com/jmorganca/ollama/api"
28
	"github.com/jmorganca/ollama/convert"
29
	"github.com/jmorganca/ollama/llm"
30
	"github.com/jmorganca/ollama/parser"
Michael Yang's avatar
Michael Yang committed
31
	"github.com/jmorganca/ollama/version"
32
33
)

Michael Yang's avatar
Michael Yang committed
34
35
36
37
38
39
40
type registryOptions struct {
	Insecure bool
	Username string
	Password string
	Token    string
}

41
type Model struct {
Michael Yang's avatar
Michael Yang committed
42
	Name           string `json:"name"`
43
	Config         ConfigV2
Michael Yang's avatar
Michael Yang committed
44
45
	ShortName      string
	ModelPath      string
46
	ParentModel    string
Michael Yang's avatar
Michael Yang committed
47
48
49
50
51
52
	AdapterPaths   []string
	ProjectorPaths []string
	Template       string
	System         string
	License        []string
	Digest         string
Patrick Devine's avatar
Patrick Devine committed
53
	Size           int64
Michael Yang's avatar
Michael Yang committed
54
	Options        map[string]interface{}
55
56
57
	Messages       []Message
}

58
59
60
61
func (m *Model) IsEmbedding() bool {
	return slices.Contains(m.Config.ModelFamilies, "bert") || slices.Contains(m.Config.ModelFamilies, "nomic-bert")
}

62
63
64
type Message struct {
	Role    string `json:"role"`
	Content string `json:"content"`
65
66
67
68
69
}

type ManifestV2 struct {
	SchemaVersion int      `json:"schemaVersion"`
	MediaType     string   `json:"mediaType"`
Michael Yang's avatar
Michael Yang committed
70
	Config        *Layer   `json:"config"`
71
72
73
74
	Layers        []*Layer `json:"layers"`
}

type ConfigV2 struct {
75
76
77
78
79
80
	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"`

81
	// required by spec
82
83
	Architecture string `json:"architecture"`
	OS           string `json:"os"`
84
	RootFS       RootFS `json:"rootfs"`
85
86
}

Michael Yang's avatar
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
109
110
111
112
113
114
115
116
func (c *ConfigV2) SetModelFormat(format string) {
	if c.ModelFormat == "" {
		c.ModelFormat = format
	}
}

func (c *ConfigV2) SetModelFamily(families ...string) {
	for _, family := range families {
		if c.ModelFamily == "" {
			c.ModelFamily = family
		}

		if !slices.Contains(c.ModelFamilies, family) {
			c.ModelFamilies = append(c.ModelFamilies, family)
		}
	}
}

func (c *ConfigV2) SetModelType(modelType string) {
	if c.ModelType == "" {
		c.ModelType = modelType
	}
}

func (c *ConfigV2) SetFileType(fileType string) {
	if c.FileType == "" {
		c.FileType = fileType
	}
}

117
118
119
120
121
type RootFS struct {
	Type    string   `json:"type"`
	DiffIDs []string `json:"diff_ids"`
}

Michael Yang's avatar
Michael Yang committed
122
func (m *ManifestV2) GetTotalSize() (total int64) {
Patrick Devine's avatar
Patrick Devine committed
123
124
125
	for _, layer := range m.Layers {
		total += layer.Size
	}
Michael Yang's avatar
Michael Yang committed
126

Patrick Devine's avatar
Patrick Devine committed
127
128
129
130
	total += m.Config.Size
	return total
}

Patrick Devine's avatar
Patrick Devine committed
131
func GetManifest(mp ModelPath) (*ManifestV2, string, error) {
132
	fp, err := mp.GetManifestPath()
133
	if err != nil {
Patrick Devine's avatar
Patrick Devine committed
134
		return nil, "", err
135
	}
136

137
	if _, err = os.Stat(fp); err != nil {
Patrick Devine's avatar
Patrick Devine committed
138
		return nil, "", err
139
140
141
142
	}

	var manifest *ManifestV2

143
	bts, err := os.ReadFile(fp)
144
	if err != nil {
Patrick Devine's avatar
Patrick Devine committed
145
		return nil, "", fmt.Errorf("couldn't open file '%s'", fp)
146
147
	}

Patrick Devine's avatar
Patrick Devine committed
148
149
150
	shaSum := sha256.Sum256(bts)
	shaStr := hex.EncodeToString(shaSum[:])

151
	if err := json.Unmarshal(bts, &manifest); err != nil {
Patrick Devine's avatar
Patrick Devine committed
152
		return nil, "", err
153
154
	}

Patrick Devine's avatar
Patrick Devine committed
155
	return manifest, shaStr, nil
156
157
158
}

func GetModel(name string) (*Model, error) {
159
	mp := ParseModelPath(name)
Patrick Devine's avatar
Patrick Devine committed
160
	manifest, digest, err := GetManifest(mp)
161
162
163
164
165
	if err != nil {
		return nil, err
	}

	model := &Model{
166
167
168
169
170
		Name:      mp.GetFullTagname(),
		ShortName: mp.GetShortTagname(),
		Digest:    digest,
		Template:  "{{ .Prompt }}",
		License:   []string{},
Patrick Devine's avatar
Patrick Devine committed
171
		Size:      manifest.GetTotalSize(),
172
173
	}

174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
	filename, err := GetBlobsPath(manifest.Config.Digest)
	if err != nil {
		return nil, err
	}

	configFile, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer configFile.Close()

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

189
	for _, layer := range manifest.Layers {
Patrick Devine's avatar
Patrick Devine committed
190
		filename, err := GetBlobsPath(layer.Digest)
191
192
193
194
		if err != nil {
			return nil, err
		}

195
196
197
		switch layer.MediaType {
		case "application/vnd.ollama.image.model":
			model.ModelPath = filename
198
			model.ParentModel = layer.From
199
		case "application/vnd.ollama.image.embed":
200
201
			// Deprecated in versions  > 0.1.2
			// TODO: remove this warning in a future version
202
			slog.Info("WARNING: model contains embeddings, but embeddings in modelfiles have been deprecated and will be ignored.")
203
204
		case "application/vnd.ollama.image.adapter":
			model.AdapterPaths = append(model.AdapterPaths, filename)
Michael Yang's avatar
Michael Yang committed
205
206
		case "application/vnd.ollama.image.projector":
			model.ProjectorPaths = append(model.ProjectorPaths, filename)
207
208
209
210
211
212
213
214
215
		case "application/vnd.ollama.image.template":
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}

			model.Template = string(bts)
		case "application/vnd.ollama.image.system":
			bts, err := os.ReadFile(filename)
216
217
218
			if err != nil {
				return nil, err
			}
219
220

			model.System = string(bts)
221
222
223
224
225
226
227
		case "application/vnd.ollama.image.prompt":
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}

			model.Template = string(bts)
228
		case "application/vnd.ollama.image.params":
Michael Yang's avatar
Michael Yang committed
229
230
231
232
233
			params, err := os.Open(filename)
			if err != nil {
				return nil, err
			}
			defer params.Close()
234

235
			// parse model options parameters into a map so that we can see which fields have been specified explicitly
236
			if err = json.NewDecoder(params).Decode(&model.Options); err != nil {
237
238
				return nil, err
			}
239
240
241
242
243
244
245
246
247
248
		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
249
250
251
252
253
254
		case "application/vnd.ollama.image.license":
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}
			model.License = append(model.License, string(bts))
255
256
257
258
259
260
		}
	}

	return model, nil
}

261
262
func realpath(mfDir, from string) string {
	abspath, err := filepath.Abs(from)
Michael Yang's avatar
Michael Yang committed
263
	if err != nil {
264
		return from
265
266
	}

Michael Yang's avatar
Michael Yang committed
267
	home, err := os.UserHomeDir()
268
	if err != nil {
Michael Yang's avatar
Michael Yang committed
269
		return abspath
270
271
	}

272
	if from == "~" {
Michael Yang's avatar
Michael Yang committed
273
		return home
274
275
276
277
278
279
280
	} else if strings.HasPrefix(from, "~/") {
		return filepath.Join(home, from[2:])
	}

	if _, err := os.Stat(filepath.Join(mfDir, from)); err == nil {
		// this is a file relative to the Modelfile
		return filepath.Join(mfDir, from)
281
282
	}

Michael Yang's avatar
Michael Yang committed
283
284
285
	return abspath
}

286
func CreateModel(ctx context.Context, name, modelFileDir string, commands []parser.Command, fn func(resp api.ProgressResponse)) error {
287
288
289
290
291
292
293
	deleteMap := make(map[string]struct{})
	if manifest, _, err := GetManifest(ParseModelPath(name)); err == nil {
		for _, layer := range append(manifest.Layers, manifest.Config) {
			deleteMap[layer.Digest] = struct{}{}
		}
	}

294
295
	config := ConfigV2{
		OS:           "linux",
Michael Yang's avatar
Michael Yang committed
296
		Architecture: "amd64",
Michael Yang's avatar
Michael Yang committed
297
298
299
		RootFS: RootFS{
			Type: "layers",
		},
300
301
	}

Michael Yang's avatar
Michael Yang committed
302
	var layers Layers
303
	messages := []string{}
Michael Yang's avatar
Michael Yang committed
304

305
	params := make(map[string][]string)
Michael Yang's avatar
Michael Yang committed
306
307
	fromParams := make(map[string]any)

308
	for _, c := range commands {
Michael Yang's avatar
Michael Yang committed
309
310
		mediatype := fmt.Sprintf("application/vnd.ollama.image.%s", c.Name)

311
312
		switch c.Name {
		case "model":
Michael Yang's avatar
Michael Yang committed
313
314
315
316
317
318
319
320
321
			if strings.HasPrefix(c.Args, "@") {
				blobPath, err := GetBlobsPath(strings.TrimPrefix(c.Args, "@"))
				if err != nil {
					return err
				}

				c.Args = blobPath
			}

322
323
324
325
			pathName := realpath(modelFileDir, c.Args)

			ggufName, err := convertSafetensors(name, pathName)
			if err != nil {
326
				var pathErr *fs.PathError
327
328
329
				switch {
				case errors.Is(err, zip.ErrFormat):
					// it's not a safetensor archive
330
331
				case errors.As(err, &pathErr):
					// it's not a file on disk, could be a model reference
332
333
334
335
336
337
338
339
340
341
342
				default:
					return err
				}
			}

			if ggufName != "" {
				pathName = ggufName
				defer os.RemoveAll(ggufName)
			}

			bin, err := os.Open(pathName)
343
			if err != nil {
Michael Yang's avatar
Michael Yang committed
344
345
346
347
348
349
				// not a file on disk so must be a model reference
				modelpath := ParseModelPath(c.Args)
				manifest, _, err := GetManifest(modelpath)
				switch {
				case errors.Is(err, os.ErrNotExist):
					fn(api.ProgressResponse{Status: "pulling model"})
Michael Yang's avatar
Michael Yang committed
350
					if err := PullModel(ctx, c.Args, &registryOptions{}, fn); err != nil {
351
352
353
						return err
					}

Michael Yang's avatar
Michael Yang committed
354
					manifest, _, err = GetManifest(modelpath)
355
356
357
					if err != nil {
						return err
					}
Michael Yang's avatar
Michael Yang committed
358
359
				case err != nil:
					return err
360
				}
361

362
				fn(api.ProgressResponse{Status: "reading model metadata"})
Michael Yang's avatar
Michael Yang committed
363
				fromConfigPath, err := GetBlobsPath(manifest.Config.Digest)
Michael Yang's avatar
Michael Yang committed
364
365
366
367
				if err != nil {
					return err
				}

Michael Yang's avatar
Michael Yang committed
368
				fromConfigFile, err := os.Open(fromConfigPath)
Michael Yang's avatar
Michael Yang committed
369
370
371
				if err != nil {
					return err
				}
Michael Yang's avatar
Michael Yang committed
372
				defer fromConfigFile.Close()
Michael Yang's avatar
Michael Yang committed
373

Michael Yang's avatar
Michael Yang committed
374
375
				var fromConfig ConfigV2
				if err := json.NewDecoder(fromConfigFile).Decode(&fromConfig); err != nil {
Michael Yang's avatar
Michael Yang committed
376
377
378
					return err
				}

Bruce MacDonald's avatar
Bruce MacDonald committed
379
380
381
382
383
				// if the model is still not in gguf format, error out
				if fromConfig.ModelFormat != "gguf" {
					return fmt.Errorf("%s is not in gguf format, this base model is not compatible with this version of ollama", c.Args)
				}

Michael Yang's avatar
Michael Yang committed
384
385
386
387
				config.SetModelFormat(fromConfig.ModelFormat)
				config.SetModelFamily(append(fromConfig.ModelFamilies, fromConfig.ModelFamily)...)
				config.SetModelType(fromConfig.ModelType)
				config.SetFileType(fromConfig.FileType)
Michael Yang's avatar
Michael Yang committed
388

Michael Yang's avatar
Michael Yang committed
389
390
391
392
				for _, layer := range manifest.Layers {
					deleteMap[layer.Digest] = struct{}{}
					if layer.MediaType == "application/vnd.ollama.image.params" {
						fromParamsPath, err := GetBlobsPath(layer.Digest)
Michael Yang's avatar
Michael Yang committed
393
394
395
396
						if err != nil {
							return err
						}

Michael Yang's avatar
Michael Yang committed
397
						fromParamsFile, err := os.Open(fromParamsPath)
Michael Yang's avatar
Michael Yang committed
398
399
400
						if err != nil {
							return err
						}
Michael Yang's avatar
Michael Yang committed
401
						defer fromParamsFile.Close()
Michael Yang's avatar
Michael Yang committed
402

Michael Yang's avatar
Michael Yang committed
403
						if err := json.NewDecoder(fromParamsFile).Decode(&fromParams); err != nil {
Michael Yang's avatar
Michael Yang committed
404
405
406
407
							return err
						}
					}

Michael Yang's avatar
Michael Yang committed
408
					layer, err := NewLayerFromLayer(layer.Digest, layer.MediaType, modelpath.GetShortTagname())
409
410
411
					if err != nil {
						return err
					}
Michael Yang's avatar
Michael Yang committed
412

Michael Yang's avatar
Michael Yang committed
413
					layers.Add(layer)
414
				}
Michael Yang's avatar
Michael Yang committed
415
416
417

				deleteMap[manifest.Config.Digest] = struct{}{}
				continue
418
			}
Michael Yang's avatar
Michael Yang committed
419
			defer bin.Close()
420

421
			var offset int64
Bruce MacDonald's avatar
Bruce MacDonald committed
422
		CREATE:
423
424
			for {
				fn(api.ProgressResponse{Status: "creating model layer"})
425

426
427
				bin.Seek(offset, io.SeekStart)
				ggml, err := llm.DecodeGGML(bin)
Bruce MacDonald's avatar
Bruce MacDonald committed
428
429
430
431
432
433
434
435
436
				if err != nil {
					switch {
					case errors.Is(err, io.EOF):
						break CREATE
					case errors.Is(err, llm.ErrUnsupportedFormat):
						return fmt.Errorf("model binary specified in FROM field is not a valid gguf format model, %w", err)
					default:
						return err
					}
437
				}
Michael Yang's avatar
Michael Yang committed
438

Michael Yang's avatar
Michael Yang committed
439
440
441
442
				config.SetModelFormat(ggml.Name())
				config.SetModelFamily(ggml.ModelFamily())
				config.SetModelType(ggml.ModelType())
				config.SetFileType(ggml.FileType())
443

444
445
446
447
				mediatype := mediatype
				if ggml.ModelFamily() == "clip" {
					mediatype = "application/vnd.ollama.image.projector"
				}
448

449
450
451
452
453
454
455
456
457
458
				sr := io.NewSectionReader(bin, offset, ggml.Size)
				layer, err := NewLayer(sr, mediatype)
				if err != nil {
					return err
				}

				layers.Add(layer)

				offset += ggml.Size
			}
Michael Yang's avatar
Michael Yang committed
459
		case "adapter":
460
461
462
463
464
465
466
467
			if strings.HasPrefix(c.Args, "@") {
				blobPath, err := GetBlobsPath(strings.TrimPrefix(c.Args, "@"))
				if err != nil {
					return err
				}

				c.Args = blobPath
			}
Bruce MacDonald's avatar
Bruce MacDonald committed
468

Michael Yang's avatar
Michael Yang committed
469
			fn(api.ProgressResponse{Status: "creating adapter layer"})
470
			bin, err := os.Open(realpath(modelFileDir, c.Args))
471
			if err != nil {
Michael Yang's avatar
Michael Yang committed
472
				return err
473
			}
Michael Yang's avatar
Michael Yang committed
474
			defer bin.Close()
475

Michael Yang's avatar
Michael Yang committed
476
			layer, err := NewLayer(bin, mediatype)
477
			if err != nil {
Michael Yang's avatar
Michael Yang committed
478
				return err
479
			}
Bruce MacDonald's avatar
Bruce MacDonald committed
480

Michael Yang's avatar
Michael Yang committed
481
			layers.Add(layer)
Michael Yang's avatar
Michael Yang committed
482
483
		case "license":
			fn(api.ProgressResponse{Status: "creating license layer"})
Michael Yang's avatar
Michael Yang committed
484
485
486

			bin := strings.NewReader(c.Args)
			layer, err := NewLayer(bin, mediatype)
Bruce MacDonald's avatar
Bruce MacDonald committed
487
488
489
490
			if err != nil {
				return err
			}

Michael Yang's avatar
Michael Yang committed
491
			layers.Add(layer)
Michael Yang's avatar
Michael Yang committed
492
493
494
		case "template", "system":
			fn(api.ProgressResponse{Status: fmt.Sprintf("creating %s layer", c.Name)})

Michael Yang's avatar
Michael Yang committed
495
496
			bin := strings.NewReader(c.Args)
			layer, err := NewLayer(bin, mediatype)
497
			if err != nil {
498
				return err
499
			}
500

Michael Yang's avatar
Michael Yang committed
501
			layers.Replace(layer)
502
503
		case "message":
			messages = append(messages, c.Args)
504
		default:
505
			params[c.Name] = append(params[c.Name], c.Args)
506
507
508
		}
	}

509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
	if len(messages) > 0 {
		fn(api.ProgressResponse{Status: "creating parameters layer"})

		msgs := make([]api.Message, 0)

		for _, m := range messages {
			// todo: handle images
			msg := strings.SplitN(m, ": ", 2)
			msgs = append(msgs, api.Message{Role: msg[0], Content: msg[1]})
		}

		var b bytes.Buffer
		if err := json.NewEncoder(&b).Encode(msgs); err != nil {
			return err
		}

		layer, err := NewLayer(&b, "application/vnd.ollama.image.messages")
		if err != nil {
			return err
		}

		layers.Replace(layer)
	}

Michael Yang's avatar
Michael Yang committed
533
	if len(params) > 0 {
Michael Yang's avatar
Michael Yang committed
534
		fn(api.ProgressResponse{Status: "creating parameters layer"})
Michael Yang's avatar
Michael Yang committed
535

536
		formattedParams, err := api.FormatParams(params)
537
		if err != nil {
Michael Yang's avatar
Michael Yang committed
538
			return err
539
		}
540

Michael Yang's avatar
Michael Yang committed
541
		for k, v := range fromParams {
Michael Yang's avatar
Michael Yang committed
542
543
544
545
546
			if _, ok := formattedParams[k]; !ok {
				formattedParams[k] = v
			}
		}

Patrick Devine's avatar
Patrick Devine committed
547
		// xxx - can this be removed?
Michael Yang's avatar
Michael Yang committed
548
		if config.ModelType == "65B" {
Michael Yang's avatar
Michael Yang committed
549
			if gqa, ok := formattedParams["gqa"].(int); ok && gqa == 8 {
Michael Yang's avatar
Michael Yang committed
550
551
552
553
				config.ModelType = "70B"
			}
		}

Michael Yang's avatar
Michael Yang committed
554
555
		var b bytes.Buffer
		if err := json.NewEncoder(&b).Encode(formattedParams); err != nil {
556
557
558
			return err
		}

Michael Yang's avatar
Michael Yang committed
559
		fn(api.ProgressResponse{Status: "creating config layer"})
Michael Yang's avatar
Michael Yang committed
560
		layer, err := NewLayer(&b, "application/vnd.ollama.image.params")
561
		if err != nil {
Michael Yang's avatar
Michael Yang committed
562
			return err
563
		}
Michael Yang's avatar
Michael Yang committed
564

Michael Yang's avatar
Michael Yang committed
565
		layers.Replace(layer)
566
567
	}

Michael Yang's avatar
Michael Yang committed
568
569
570
	digests := make([]string, len(layers.items))
	for i, layer := range layers.items {
		digests[i] = layer.Digest
571
572
	}

Michael Yang's avatar
Michael Yang committed
573
	config.RootFS.DiffIDs = digests
Michael Yang's avatar
Michael Yang committed
574

Michael Yang's avatar
Michael Yang committed
575
576
	var b bytes.Buffer
	if err := json.NewEncoder(&b).Encode(config); err != nil {
577
578
579
		return err
	}

Michael Yang's avatar
Michael Yang committed
580
581
	configLayer, err := NewLayer(&b, "application/vnd.docker.container.image.v1+json")
	if err != nil {
582
583
584
		return err
	}

Michael Yang's avatar
Michael Yang committed
585
	delete(deleteMap, configLayer.Digest)
586

Michael Yang's avatar
Michael Yang committed
587
588
	for _, layer := range append(layers.items, configLayer) {
		committed, err := layer.Commit()
589
590
591
		if err != nil {
			return err
		}
592

Michael Yang's avatar
Michael Yang committed
593
594
595
		status := "writing layer"
		if !committed {
			status = "using already created layer"
596
597
		}

Michael Yang's avatar
Michael Yang committed
598
		fn(api.ProgressResponse{Status: fmt.Sprintf("%s %s", status, layer.Digest)})
599

Michael Yang's avatar
Michael Yang committed
600
		delete(deleteMap, layer.Digest)
601
602
	}

Michael Yang's avatar
Michael Yang committed
603
604
	fn(api.ProgressResponse{Status: "writing manifest"})
	if err := WriteManifest(name, configLayer, layers.items); err != nil {
605
606
		return err
	}
607

Michael Yang's avatar
Michael Yang committed
608
609
610
	if noprune := os.Getenv("OLLAMA_NOPRUNE"); noprune == "" {
		if err := deleteUnusedLayers(nil, deleteMap, false); err != nil {
			return err
611
612
613
		}
	}

Michael Yang's avatar
Michael Yang committed
614
615
	fn(api.ProgressResponse{Status: "success"})
	return nil
616
617
}

618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
func convertSafetensors(name, fn string) (string, error) {
	r, err := zip.OpenReader(fn)
	if err != nil {
		return "", err
	}
	defer r.Close()

	tempDir, err := os.MkdirTemp("", "ollama-convert")
	if err != nil {
		return "", err
	}
	defer os.RemoveAll(tempDir)

	for _, f := range r.File {
		fpath := filepath.Join(tempDir, f.Name)
		outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
		if err != nil {
			return "", err
		}

		rc, err := f.Open()
		if err != nil {
			return "", err
		}

		_, err = io.Copy(outFile, rc)
		if err != nil {
			return "", err
		}

		outFile.Close()
		rc.Close()
	}

	params, err := convert.GetParams(tempDir)
	if err != nil {
		return "", err
	}

	SupportedArchs := []string{
		"MistralForCausalLM",
	}

	for _, arch := range params.Architectures {
		if !slices.Contains(SupportedArchs, arch) {
			return "", fmt.Errorf("this safetensors model is not yet supported")
		}
	}

	t, err := convert.GetSafeTensors(tempDir)
	if err != nil {
		return "", err
	}

	vocab, err := convert.LoadTokens(tempDir)
	if err != nil {
		return "", err
	}

	fn, err = convert.WriteGGUF(name, t, params, vocab)
	if err != nil {
		return "", err
	}

	return fn, nil
}

Patrick Devine's avatar
Patrick Devine committed
685
func CopyModel(src, dest string) error {
686
	srcModelPath := ParseModelPath(src)
687
	srcPath, err := srcModelPath.GetManifestPath()
688
689
690
691
	if err != nil {
		return err
	}

692
	destModelPath := ParseModelPath(dest)
693
	destPath, err := destModelPath.GetManifestPath()
Patrick Devine's avatar
Patrick Devine committed
694
695
696
	if err != nil {
		return err
	}
697
698
699
	if err := os.MkdirAll(filepath.Dir(destPath), 0o755); err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
700
701

	// copy the file
Michael Yang's avatar
Michael Yang committed
702
	input, err := os.ReadFile(srcPath)
Patrick Devine's avatar
Patrick Devine committed
703
704
705
706
707
	if err != nil {
		fmt.Println("Error reading file:", err)
		return err
	}

Michael Yang's avatar
Michael Yang committed
708
	err = os.WriteFile(destPath, input, 0o644)
Patrick Devine's avatar
Patrick Devine committed
709
710
711
712
713
714
715
716
	if err != nil {
		fmt.Println("Error reading file:", err)
		return err
	}

	return nil
}

Michael Yang's avatar
Michael Yang committed
717
func deleteUnusedLayers(skipModelPath *ModelPath, deleteMap map[string]struct{}, dryRun bool) error {
718
719
720
721
	fp, err := GetManifestPath()
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
722
723
724
725

	walkFunc := func(path string, info os.FileInfo, _ error) error {
		if info.IsDir() {
			return nil
726
727
		}

Michael Yang's avatar
Michael Yang committed
728
729
730
731
		dir, file := filepath.Split(path)
		dir = strings.Trim(strings.TrimPrefix(dir, fp), string(os.PathSeparator))
		tag := strings.Join([]string{dir, file}, ":")
		fmp := ParseModelPath(tag)
732

Michael Yang's avatar
Michael Yang committed
733
		// skip the manifest we're trying to delete
734
		if skipModelPath != nil && skipModelPath.GetFullTagname() == fmp.GetFullTagname() {
Michael Yang's avatar
Michael Yang committed
735
			return nil
736
		}
Michael Yang's avatar
Michael Yang committed
737
738
739
740

		// save (i.e. delete from the deleteMap) any files used in other manifests
		manifest, _, err := GetManifest(fmp)
		if err != nil {
Michael Yang's avatar
Michael Yang committed
741
			// nolint: nilerr
Michael Yang's avatar
Michael Yang committed
742
743
744
745
746
747
748
749
			return nil
		}

		for _, layer := range manifest.Layers {
			delete(deleteMap, layer.Digest)
		}

		delete(deleteMap, manifest.Config.Digest)
750
		return nil
Michael Yang's avatar
Michael Yang committed
751
752
753
	}

	if err := filepath.Walk(fp, walkFunc); err != nil {
Michael Yang's avatar
Michael Yang committed
754
755
		return err
	}
756
757

	// only delete the files which are still in the deleteMap
Michael Yang's avatar
Michael Yang committed
758
759
760
	for k := range deleteMap {
		fp, err := GetBlobsPath(k)
		if err != nil {
761
			slog.Info(fmt.Sprintf("couldn't get file path for '%s': %v", k, err))
Michael Yang's avatar
Michael Yang committed
762
763
764
765
			continue
		}
		if !dryRun {
			if err := os.Remove(fp); err != nil {
766
				slog.Info(fmt.Sprintf("couldn't remove file '%s': %v", fp, err))
767
768
				continue
			}
Michael Yang's avatar
Michael Yang committed
769
		} else {
770
			slog.Info(fmt.Sprintf("wanted to remove: %s", fp))
771
772
773
		}
	}

774
775
776
777
	return nil
}

func PruneLayers() error {
Michael Yang's avatar
Michael Yang committed
778
	deleteMap := make(map[string]struct{})
779
780
781
782
783
784
785
	p, err := GetBlobsPath("")
	if err != nil {
		return err
	}

	blobs, err := os.ReadDir(p)
	if err != nil {
786
		slog.Info(fmt.Sprintf("couldn't read dir '%s': %v", p, err))
787
788
789
790
791
792
793
794
		return err
	}

	for _, blob := range blobs {
		name := blob.Name()
		if runtime.GOOS == "windows" {
			name = strings.ReplaceAll(name, "-", ":")
		}
Michael Yang's avatar
Michael Yang committed
795
796
797
		if strings.HasPrefix(name, "sha256:") {
			deleteMap[name] = struct{}{}
		}
798
799
	}

800
	slog.Info(fmt.Sprintf("total blobs: %d", len(deleteMap)))
801
802
803
804
805
806

	err = deleteUnusedLayers(nil, deleteMap, false)
	if err != nil {
		return err
	}

807
	slog.Info(fmt.Sprintf("total unused blobs removed: %d", len(deleteMap)))
808
809
810
811

	return nil
}

Michael Yang's avatar
Michael Yang committed
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
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
}

845
846
847
848
849
850
851
func DeleteModel(name string) error {
	mp := ParseModelPath(name)
	manifest, _, err := GetManifest(mp)
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
852
	deleteMap := make(map[string]struct{})
853
	for _, layer := range manifest.Layers {
Michael Yang's avatar
Michael Yang committed
854
		deleteMap[layer.Digest] = struct{}{}
855
	}
Michael Yang's avatar
Michael Yang committed
856
	deleteMap[manifest.Config.Digest] = struct{}{}
857
858
859
860
861
862

	err = deleteUnusedLayers(&mp, deleteMap, false)
	if err != nil {
		return err
	}

863
	fp, err := mp.GetManifestPath()
864
865
866
867
868
	if err != nil {
		return err
	}
	err = os.Remove(fp)
	if err != nil {
869
		slog.Info(fmt.Sprintf("couldn't remove manifest file '%s': %v", fp, err))
870
871
872
873
874
875
		return err
	}

	return nil
}

Patrick Devine's avatar
Patrick Devine committed
876
func ShowModelfile(model *Model) (string, error) {
Michael Yang's avatar
Michael Yang committed
877
	var mt struct {
Patrick Devine's avatar
Patrick Devine committed
878
		*Model
Michael Yang's avatar
Michael Yang committed
879
		From       string
Michael Yang's avatar
Michael Yang committed
880
		Parameters map[string][]any
Patrick Devine's avatar
Patrick Devine committed
881
882
	}

Michael Yang's avatar
Michael Yang committed
883
	mt.Parameters = make(map[string][]any)
Patrick Devine's avatar
Patrick Devine committed
884
	for k, v := range model.Options {
Michael Yang's avatar
Michael Yang committed
885
886
887
		if s, ok := v.([]any); ok {
			mt.Parameters[k] = s
			continue
Patrick Devine's avatar
Patrick Devine committed
888
889
		}

Michael Yang's avatar
Michael Yang committed
890
		mt.Parameters[k] = []any{v}
Patrick Devine's avatar
Patrick Devine committed
891
892
	}

Michael Yang's avatar
Michael Yang committed
893
894
	mt.Model = model
	mt.From = model.ModelPath
Patrick Devine's avatar
Patrick Devine committed
895

896
897
	if model.ParentModel != "" {
		mt.From = model.ParentModel
Patrick Devine's avatar
Patrick Devine committed
898
899
900
901
902
903
904
905
	}

	modelFile := `# Modelfile generated by "ollama show"
# To build a new Modelfile based on this one, replace the FROM line with:
# FROM {{ .ShortName }}

FROM {{ .From }}
TEMPLATE """{{ .Template }}"""
906
907

{{- if .System }}
Patrick Devine's avatar
Patrick Devine committed
908
SYSTEM """{{ .System }}"""
909
{{- end }}
910
911
912
913

{{- range $adapter := .AdapterPaths }}
ADAPTER {{ $adapter }}
{{- end }}
Michael Yang's avatar
Michael Yang committed
914

Michael Yang's avatar
Michael Yang committed
915
916
917
918
{{- range $k, $v := .Parameters }}
{{- range $parameter := $v }}
PARAMETER {{ $k }} {{ printf "%#v" $parameter }}
{{- end }}
Michael Yang's avatar
Michael Yang committed
919
{{- end }}`
Patrick Devine's avatar
Patrick Devine committed
920
921
922

	tmpl, err := template.New("").Parse(modelFile)
	if err != nil {
923
		slog.Info(fmt.Sprintf("error parsing template: %q", err))
Patrick Devine's avatar
Patrick Devine committed
924
925
926
927
928
929
		return "", err
	}

	var buf bytes.Buffer

	if err = tmpl.Execute(&buf, mt); err != nil {
930
		slog.Info(fmt.Sprintf("error executing template: %q", err))
Patrick Devine's avatar
Patrick Devine committed
931
932
933
934
935
936
		return "", err
	}

	return buf.String(), nil
}

Michael Yang's avatar
Michael Yang committed
937
func PushModel(ctx context.Context, name string, regOpts *registryOptions, fn func(api.ProgressResponse)) error {
938
	mp := ParseModelPath(name)
939
940
	fn(api.ProgressResponse{Status: "retrieving manifest"})

941
942
943
944
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
		return fmt.Errorf("insecure protocol http")
	}

Patrick Devine's avatar
Patrick Devine committed
945
	manifest, _, err := GetManifest(mp)
946
	if err != nil {
947
		fn(api.ProgressResponse{Status: "couldn't retrieve manifest"})
948
949
950
951
		return err
	}

	var layers []*Layer
Jeffrey Morgan's avatar
Jeffrey Morgan committed
952
	layers = append(layers, manifest.Layers...)
Michael Yang's avatar
Michael Yang committed
953
	layers = append(layers, manifest.Config)
954
955

	for _, layer := range layers {
Michael Yang's avatar
Michael Yang committed
956
		if err := uploadBlob(ctx, mp, layer, regOpts, fn); err != nil {
957
			slog.Info(fmt.Sprintf("error uploading blob: %v", err))
958
959
960
			if errors.Is(err, errUnauthorized) {
				return fmt.Errorf("unable to push %s, make sure this namespace exists and you are authorized to push to it", ParseModelPath(name).GetNamespaceRepository())
			}
961
962
			return err
		}
963
964
	}

965
	fn(api.ProgressResponse{Status: "pushing manifest"})
Michael Yang's avatar
Michael Yang committed
966
967
	requestURL := mp.BaseURL()
	requestURL = requestURL.JoinPath("v2", mp.GetNamespaceRepository(), "manifests", mp.Tag)
968
969
970
971
972
973

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

Michael Yang's avatar
Michael Yang committed
974
975
	headers := make(http.Header)
	headers.Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json")
Michael Yang's avatar
Michael Yang committed
976
	resp, err := makeRequestWithRetry(ctx, http.MethodPut, requestURL, headers, bytes.NewReader(manifestJSON), regOpts)
977
978
979
980
981
	if err != nil {
		return err
	}
	defer resp.Body.Close()

982
	fn(api.ProgressResponse{Status: "success"})
983
984
985
986

	return nil
}

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

990
991
992
993
994
	var manifest *ManifestV2
	var err error
	var noprune string

	// build deleteMap to prune unused layers
Michael Yang's avatar
Michael Yang committed
995
	deleteMap := make(map[string]struct{})
996
997
998
999
1000
1001
1002
1003
1004

	if noprune = os.Getenv("OLLAMA_NOPRUNE"); noprune == "" {
		manifest, _, err = GetManifest(mp)
		if err != nil && !errors.Is(err, os.ErrNotExist) {
			return err
		}

		if manifest != nil {
			for _, l := range manifest.Layers {
Michael Yang's avatar
Michael Yang committed
1005
				deleteMap[l.Digest] = struct{}{}
1006
			}
Michael Yang's avatar
Michael Yang committed
1007
			deleteMap[manifest.Config.Digest] = struct{}{}
1008
1009
1010
		}
	}

1011
1012
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
		return fmt.Errorf("insecure protocol http")
1013
	}
1014

1015
	fn(api.ProgressResponse{Status: "pulling manifest"})
1016

1017
	manifest, err = pullModelManifest(ctx, mp, regOpts)
1018
	if err != nil {
1019
		return fmt.Errorf("pull model manifest: %s", err)
1020
1021
1022
	}

	var layers []*Layer
Bruce MacDonald's avatar
Bruce MacDonald committed
1023
	layers = append(layers, manifest.Layers...)
Michael Yang's avatar
Michael Yang committed
1024
	layers = append(layers, manifest.Config)
1025
1026

	for _, layer := range layers {
1027
1028
1029
1030
1031
1032
1033
1034
		if err := downloadBlob(
			ctx,
			downloadOpts{
				mp:      mp,
				digest:  layer.Digest,
				regOpts: regOpts,
				fn:      fn,
			}); err != nil {
1035
1036
			return err
		}
1037
		delete(deleteMap, layer.Digest)
1038
	}
1039
	delete(deleteMap, manifest.Config.Digest)
1040

Michael Yang's avatar
Michael Yang committed
1041
1042
1043
	fn(api.ProgressResponse{Status: "verifying sha256 digest"})
	for _, layer := range layers {
		if err := verifyBlob(layer.Digest); err != nil {
1044
1045
1046
1047
1048
1049
1050
1051
			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
1052
					slog.Info(fmt.Sprintf("couldn't remove file with digest mismatch '%s': %v", fp, err))
1053
1054
				}
			}
Michael Yang's avatar
Michael Yang committed
1055
1056
1057
1058
			return err
		}
	}

1059
	fn(api.ProgressResponse{Status: "writing manifest"})
1060

1061
	manifestJSON, err := json.Marshal(manifest)
1062
1063
1064
1065
	if err != nil {
		return err
	}

1066
	fp, err := mp.GetManifestPath()
1067
1068
1069
	if err != nil {
		return err
	}
1070
1071
1072
	if err := os.MkdirAll(filepath.Dir(fp), 0o755); err != nil {
		return err
	}
1073

Bruce MacDonald's avatar
Bruce MacDonald committed
1074
	err = os.WriteFile(fp, manifestJSON, 0o644)
1075
	if err != nil {
1076
		slog.Info(fmt.Sprintf("couldn't write to %s", fp))
1077
1078
1079
		return err
	}

1080
1081
1082
1083
1084
1085
1086
1087
	if noprune == "" {
		fn(api.ProgressResponse{Status: "removing any unused layers"})
		err = deleteUnusedLayers(nil, deleteMap, false)
		if err != nil {
			return err
		}
	}

1088
	fn(api.ProgressResponse{Status: "success"})
1089
1090
1091
1092

	return nil
}

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

Michael Yang's avatar
Michael Yang committed
1096
1097
	headers := make(http.Header)
	headers.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json")
Michael Yang's avatar
Michael Yang committed
1098
	resp, err := makeRequestWithRetry(ctx, http.MethodGet, requestURL, headers, nil, regOpts)
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	var m *ManifestV2
	if err := json.NewDecoder(resp.Body).Decode(&m); err != nil {
		return nil, err
	}

	return m, err
}

// GetSHA256Digest returns the SHA256 hash of a given buffer and returns it, and the size of buffer
Michael Yang's avatar
Michael Yang committed
1113
func GetSHA256Digest(r io.Reader) (string, int64) {
Michael Yang's avatar
Michael Yang committed
1114
1115
1116
1117
1118
1119
	h := sha256.New()
	n, err := io.Copy(h, r)
	if err != nil {
		log.Fatal(err)
	}

Michael Yang's avatar
Michael Yang committed
1120
	return fmt.Sprintf("sha256:%x", h.Sum(nil)), n
1121
1122
}

1123
1124
var errUnauthorized = fmt.Errorf("unauthorized")

Michael Yang's avatar
Michael Yang committed
1125
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
Michael Yang committed
1126
	for i := 0; i < 2; i++ {
Michael Yang's avatar
Michael Yang committed
1127
		resp, err := makeRequest(ctx, method, requestURL, headers, body, regOpts)
Michael Yang's avatar
Michael Yang committed
1128
		if err != nil {
Michael Yang's avatar
Michael Yang committed
1129
			if !errors.Is(err, context.Canceled) {
1130
				slog.Info(fmt.Sprintf("request failed: %v", err))
Michael Yang's avatar
Michael Yang committed
1131
1132
			}

Michael Yang's avatar
Michael Yang committed
1133
1134
			return nil, err
		}
Michael Yang's avatar
Michael Yang committed
1135
1136
1137
1138

		switch {
		case resp.StatusCode == http.StatusUnauthorized:
			// Handle authentication error with one retry
Michael Yang's avatar
Michael Yang committed
1139
1140
			challenge := parseRegistryChallenge(resp.Header.Get("www-authenticate"))
			token, err := getAuthorizationToken(ctx, challenge)
Michael Yang's avatar
Michael Yang committed
1141
1142
1143
			if err != nil {
				return nil, err
			}
Michael Yang's avatar
Michael Yang committed
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
			regOpts.Token = token
			if body != nil {
				_, err = body.Seek(0, io.SeekStart)
				if err != nil {
					return nil, err
				}
			}
		case resp.StatusCode == http.StatusNotFound:
			return nil, os.ErrNotExist
		case resp.StatusCode >= http.StatusBadRequest:
			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
1161
1162
1163
		}
	}

Michael Yang's avatar
Michael Yang committed
1164
	return nil, errUnauthorized
Michael Yang's avatar
Michael Yang committed
1165
1166
}

Michael Yang's avatar
Michael Yang committed
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
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)
		}
	}

	req.Header.Set("User-Agent", fmt.Sprintf("ollama/%s (%s %s) Go/%s", version.Version, runtime.GOARCH, runtime.GOOS, runtime.Version()))

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

		req.ContentLength = contentLength
	}

Michael Yang's avatar
Michael Yang committed
1200
	resp, err := http.DefaultClient.Do(req)
Michael Yang's avatar
Michael Yang committed
1201
1202
1203
1204
1205
1206
1207
	if err != nil {
		return nil, err
	}

	return resp, nil
}

Patrick Devine's avatar
Patrick Devine committed
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
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
1231
func parseRegistryChallenge(authStr string) registryChallenge {
Patrick Devine's avatar
Patrick Devine committed
1232
1233
	authStr = strings.TrimPrefix(authStr, "Bearer ")

Michael Yang's avatar
Michael Yang committed
1234
	return registryChallenge{
Patrick Devine's avatar
Patrick Devine committed
1235
1236
1237
1238
1239
1240
		Realm:   getValue(authStr, "realm"),
		Service: getValue(authStr, "service"),
		Scope:   getValue(authStr, "scope"),
	}
}

1241
1242
var errDigestMismatch = fmt.Errorf("digest mismatch, file must be downloaded again")

Michael Yang's avatar
Michael Yang committed
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
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 {
1257
		return fmt.Errorf("%w: want %s, got %s", errDigestMismatch, digest, fileDigest)
Michael Yang's avatar
Michael Yang committed
1258
1259
1260
1261
	}

	return nil
}