images.go 28.6 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
13
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
Michael Yang's avatar
Michael Yang committed
14
	"net/url"
15
16
	"os"
	"path/filepath"
Michael Yang's avatar
Michael Yang committed
17
	"runtime"
18
19
	"strconv"
	"strings"
Quinn Slack's avatar
Quinn Slack committed
20
	"text/template"
21

Michael Yang's avatar
Michael Yang committed
22
23
	"golang.org/x/exp/slices"

24
	"github.com/jmorganca/ollama/api"
25
	"github.com/jmorganca/ollama/llm"
26
	"github.com/jmorganca/ollama/parser"
Michael Yang's avatar
Michael Yang committed
27
	"github.com/jmorganca/ollama/version"
28
29
)

30
31
32
33
type RegistryOptions struct {
	Insecure bool
	Username string
	Password string
Patrick Devine's avatar
Patrick Devine committed
34
	Token    string
35
36
}

37
type Model struct {
Patrick Devine's avatar
Patrick Devine committed
38
39
40
41
42
43
44
45
46
47
	Name          string `json:"name"`
	ShortName     string
	ModelPath     string
	OriginalModel string
	AdapterPaths  []string
	Template      string
	System        string
	License       []string
	Digest        string
	Options       map[string]interface{}
48
49
}

Bruce MacDonald's avatar
Bruce MacDonald committed
50
51
52
53
54
55
type PromptVars struct {
	System   string
	Prompt   string
	Response string
	First    bool
}
56

Bruce MacDonald's avatar
Bruce MacDonald committed
57
58
59
func (m *Model) Prompt(p PromptVars) (string, error) {
	var prompt strings.Builder
	tmpl, err := template.New("").Parse(m.Template)
60
61
62
63
	if err != nil {
		return "", err
	}

Bruce MacDonald's avatar
Bruce MacDonald committed
64
65
66
	if p.System == "" {
		// use the default system prompt for this model if one is not specified
		p.System = m.System
67
68
	}

Bruce MacDonald's avatar
Bruce MacDonald committed
69
70
71
72
73
74
75
76
	var sb strings.Builder
	if err := tmpl.Execute(&sb, p); err != nil {
		return "", err
	}
	prompt.WriteString(sb.String())
	prompt.WriteString(p.Response)
	return prompt.String(), nil
}
77

Bruce MacDonald's avatar
Bruce MacDonald committed
78
79
80
81
82
func (m *Model) ChatPrompt(msgs []api.Message) (string, error) {
	// build the prompt from the list of messages
	var prompt strings.Builder
	currentVars := PromptVars{
		First: true,
Bruce MacDonald's avatar
Bruce MacDonald committed
83
84
	}

Bruce MacDonald's avatar
Bruce MacDonald committed
85
86
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
117
118
	writePrompt := func() error {
		p, err := m.Prompt(currentVars)
		if err != nil {
			return err
		}
		prompt.WriteString(p)
		currentVars = PromptVars{}
		return nil
	}

	for _, msg := range msgs {
		switch msg.Role {
		case "system":
			if currentVars.Prompt != "" || currentVars.System != "" {
				if err := writePrompt(); err != nil {
					return "", err
				}
			}
			currentVars.System = msg.Content
		case "user":
			if currentVars.Prompt != "" || currentVars.System != "" {
				if err := writePrompt(); err != nil {
					return "", err
				}
			}
			currentVars.Prompt = msg.Content
		case "assistant":
			currentVars.Response = msg.Content
			if err := writePrompt(); err != nil {
				return "", err
			}
		default:
			return "", fmt.Errorf("invalid role: %s, role must be one of [system, user, assistant]", msg.Role)
		}
119
120
	}

Bruce MacDonald's avatar
Bruce MacDonald committed
121
122
123
124
125
126
127
128
	// Append the last set of vars if they are non-empty
	if currentVars.Prompt != "" || currentVars.System != "" {
		if err := writePrompt(); err != nil {
			return "", err
		}
	}

	return prompt.String(), nil
129
130
}

131
132
133
134
135
136
137
138
139
140
type ManifestV2 struct {
	SchemaVersion int      `json:"schemaVersion"`
	MediaType     string   `json:"mediaType"`
	Config        Layer    `json:"config"`
	Layers        []*Layer `json:"layers"`
}

type Layer struct {
	MediaType string `json:"mediaType"`
	Digest    string `json:"digest"`
Michael Yang's avatar
Michael Yang committed
141
	Size      int64  `json:"size"`
Michael Yang's avatar
Michael Yang committed
142
	From      string `json:"from,omitempty"`
143
144
}

Michael Yang's avatar
Michael Yang committed
145
type LayerReader struct {
146
	Layer
Michael Yang's avatar
Michael Yang committed
147
	io.Reader
148
149
150
}

type ConfigV2 struct {
Michael Yang's avatar
Michael Yang committed
151
152
153
154
155
	ModelFormat string `json:"model_format"`
	ModelFamily string `json:"model_family"`
	ModelType   string `json:"model_type"`
	FileType    string `json:"file_type"`
	RootFS      RootFS `json:"rootfs"`
156
157

	// required by spec
158
159
160
161
162
163
164
165
166
	Architecture string `json:"architecture"`
	OS           string `json:"os"`
}

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

Michael Yang's avatar
Michael Yang committed
167
func (m *ManifestV2) GetTotalSize() (total int64) {
Patrick Devine's avatar
Patrick Devine committed
168
169
170
	for _, layer := range m.Layers {
		total += layer.Size
	}
Michael Yang's avatar
Michael Yang committed
171

Patrick Devine's avatar
Patrick Devine committed
172
173
174
175
	total += m.Config.Size
	return total
}

Patrick Devine's avatar
Patrick Devine committed
176
func GetManifest(mp ModelPath) (*ManifestV2, string, error) {
177
	fp, err := mp.GetManifestPath()
178
	if err != nil {
Patrick Devine's avatar
Patrick Devine committed
179
		return nil, "", err
180
	}
181

182
	if _, err = os.Stat(fp); err != nil {
Patrick Devine's avatar
Patrick Devine committed
183
		return nil, "", err
184
185
186
187
	}

	var manifest *ManifestV2

188
	bts, err := os.ReadFile(fp)
189
	if err != nil {
Patrick Devine's avatar
Patrick Devine committed
190
		return nil, "", fmt.Errorf("couldn't open file '%s'", fp)
191
192
	}

Patrick Devine's avatar
Patrick Devine committed
193
194
195
	shaSum := sha256.Sum256(bts)
	shaStr := hex.EncodeToString(shaSum[:])

196
	if err := json.Unmarshal(bts, &manifest); err != nil {
Patrick Devine's avatar
Patrick Devine committed
197
		return nil, "", err
198
199
	}

Patrick Devine's avatar
Patrick Devine committed
200
	return manifest, shaStr, nil
201
202
203
}

func GetModel(name string) (*Model, error) {
204
	mp := ParseModelPath(name)
Patrick Devine's avatar
Patrick Devine committed
205
	manifest, digest, err := GetManifest(mp)
206
207
208
209
210
	if err != nil {
		return nil, err
	}

	model := &Model{
211
212
213
214
215
		Name:      mp.GetFullTagname(),
		ShortName: mp.GetShortTagname(),
		Digest:    digest,
		Template:  "{{ .Prompt }}",
		License:   []string{},
216
217
218
	}

	for _, layer := range manifest.Layers {
Patrick Devine's avatar
Patrick Devine committed
219
		filename, err := GetBlobsPath(layer.Digest)
220
221
222
223
		if err != nil {
			return nil, err
		}

224
225
226
		switch layer.MediaType {
		case "application/vnd.ollama.image.model":
			model.ModelPath = filename
Patrick Devine's avatar
Patrick Devine committed
227
			model.OriginalModel = layer.From
228
		case "application/vnd.ollama.image.embed":
229
230
231
			// Deprecated in versions  > 0.1.2
			// TODO: remove this warning in a future version
			log.Print("WARNING: model contains embeddings, but embeddings in modelfiles have been deprecated and will be ignored.")
232
233
		case "application/vnd.ollama.image.adapter":
			model.AdapterPaths = append(model.AdapterPaths, filename)
234
235
236
237
238
239
240
241
242
		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)
243
244
245
			if err != nil {
				return nil, err
			}
246
247

			model.System = string(bts)
248
249
250
251
252
253
254
		case "application/vnd.ollama.image.prompt":
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}

			model.Template = string(bts)
255
		case "application/vnd.ollama.image.params":
Michael Yang's avatar
Michael Yang committed
256
257
258
259
260
			params, err := os.Open(filename)
			if err != nil {
				return nil, err
			}
			defer params.Close()
261

262
			// parse model options parameters into a map so that we can see which fields have been specified explicitly
263
			if err = json.NewDecoder(params).Decode(&model.Options); err != nil {
264
265
				return nil, err
			}
Patrick Devine's avatar
Patrick Devine committed
266
267
268
269
270
271
		case "application/vnd.ollama.image.license":
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}
			model.License = append(model.License, string(bts))
272
273
274
275
276
277
		}
	}

	return model, nil
}

278
279
func realpath(mfDir, from string) string {
	abspath, err := filepath.Abs(from)
Michael Yang's avatar
Michael Yang committed
280
	if err != nil {
281
		return from
282
283
	}

Michael Yang's avatar
Michael Yang committed
284
	home, err := os.UserHomeDir()
285
	if err != nil {
Michael Yang's avatar
Michael Yang committed
286
		return abspath
287
288
	}

289
	if from == "~" {
Michael Yang's avatar
Michael Yang committed
290
		return home
291
292
293
294
295
296
297
	} 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)
298
299
	}

Michael Yang's avatar
Michael Yang committed
300
301
302
	return abspath
}

303
func CreateModel(ctx context.Context, name, modelFileDir string, commands []parser.Command, fn func(resp api.ProgressResponse)) error {
304
305
	config := ConfigV2{
		OS:           "linux",
Michael Yang's avatar
Michael Yang committed
306
		Architecture: "amd64",
307
308
	}

Michael Yang's avatar
Michael Yang committed
309
310
	deleteMap := make(map[string]struct{})

Michael Yang's avatar
Michael Yang committed
311
	var layers []*LayerReader
Michael Yang's avatar
Michael Yang committed
312

313
	params := make(map[string][]string)
Michael Yang's avatar
Michael Yang committed
314
315
	fromParams := make(map[string]any)

316
	for _, c := range commands {
Michael Yang's avatar
Michael Yang committed
317
318
319
		log.Printf("[%s] - %s", c.Name, c.Args)
		mediatype := fmt.Sprintf("application/vnd.ollama.image.%s", c.Name)

320
321
		switch c.Name {
		case "model":
Michael Yang's avatar
Michael Yang committed
322
323
324
325
326
327
328
329
330
			if strings.HasPrefix(c.Args, "@") {
				blobPath, err := GetBlobsPath(strings.TrimPrefix(c.Args, "@"))
				if err != nil {
					return err
				}

				c.Args = blobPath
			}

331
			bin, err := os.Open(realpath(modelFileDir, c.Args))
332
			if err != nil {
Michael Yang's avatar
Michael Yang committed
333
334
335
336
337
338
339
				// 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"})
					if err := PullModel(ctx, c.Args, &RegistryOptions{}, fn); err != nil {
340
341
342
						return err
					}

Michael Yang's avatar
Michael Yang committed
343
					manifest, _, err = GetManifest(modelpath)
344
345
346
					if err != nil {
						return err
					}
Michael Yang's avatar
Michael Yang committed
347
348
				case err != nil:
					return err
349
				}
350

351
				fn(api.ProgressResponse{Status: "reading model metadata"})
Michael Yang's avatar
Michael Yang committed
352
				fromConfigPath, err := GetBlobsPath(manifest.Config.Digest)
Michael Yang's avatar
Michael Yang committed
353
354
355
356
				if err != nil {
					return err
				}

Michael Yang's avatar
Michael Yang committed
357
				fromConfigFile, err := os.Open(fromConfigPath)
Michael Yang's avatar
Michael Yang committed
358
359
360
				if err != nil {
					return err
				}
Michael Yang's avatar
Michael Yang committed
361
				defer fromConfigFile.Close()
Michael Yang's avatar
Michael Yang committed
362

Michael Yang's avatar
Michael Yang committed
363
364
				var fromConfig ConfigV2
				if err := json.NewDecoder(fromConfigFile).Decode(&fromConfig); err != nil {
Michael Yang's avatar
Michael Yang committed
365
366
367
					return err
				}

Michael Yang's avatar
Michael Yang committed
368
369
370
371
				config.ModelFormat = fromConfig.ModelFormat
				config.ModelFamily = fromConfig.ModelFamily
				config.ModelType = fromConfig.ModelType
				config.FileType = fromConfig.FileType
Michael Yang's avatar
Michael Yang committed
372

Michael Yang's avatar
Michael Yang committed
373
374
375
376
				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
377
378
379
380
						if err != nil {
							return err
						}

Michael Yang's avatar
Michael Yang committed
381
						fromParamsFile, err := os.Open(fromParamsPath)
Michael Yang's avatar
Michael Yang committed
382
383
384
						if err != nil {
							return err
						}
Michael Yang's avatar
Michael Yang committed
385
						defer fromParamsFile.Close()
Michael Yang's avatar
Michael Yang committed
386

Michael Yang's avatar
Michael Yang committed
387
						if err := json.NewDecoder(fromParamsFile).Decode(&fromParams); err != nil {
Michael Yang's avatar
Michael Yang committed
388
389
390
391
							return err
						}
					}

Michael Yang's avatar
Michael Yang committed
392
					layer, err := GetLayerWithBufferFromLayer(layer)
393
394
395
					if err != nil {
						return err
					}
Michael Yang's avatar
Michael Yang committed
396
397
398

					layer.From = modelpath.GetShortTagname()
					layers = append(layers, layer)
399
				}
Michael Yang's avatar
Michael Yang committed
400
401
402

				deleteMap[manifest.Config.Digest] = struct{}{}
				continue
403
			}
Michael Yang's avatar
Michael Yang committed
404
			defer bin.Close()
405

Michael Yang's avatar
Michael Yang committed
406
407
			fn(api.ProgressResponse{Status: "creating model layer"})
			ggml, err := llm.DecodeGGML(bin)
Michael Yang's avatar
Michael Yang committed
408
409
			if err != nil {
				return err
410
411
			}

Michael Yang's avatar
Michael Yang committed
412
413
414
415
416
417
418
419
420
421
			config.ModelFormat = ggml.Name()
			config.ModelFamily = ggml.ModelFamily()
			config.ModelType = ggml.ModelType()
			config.FileType = ggml.FileType()

			bin.Seek(0, io.SeekStart)
			layer, err := CreateLayer(bin)
			if err != nil {
				return err
			}
422

Michael Yang's avatar
Michael Yang committed
423
424
425
			layer.MediaType = mediatype
			layers = append(layers, layer)
		case "adapter":
426
427
428
429
430
431
432
433
			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
434

Michael Yang's avatar
Michael Yang committed
435
			fn(api.ProgressResponse{Status: "creating adapter layer"})
436
			bin, err := os.Open(realpath(modelFileDir, c.Args))
437
			if err != nil {
Michael Yang's avatar
Michael Yang committed
438
				return err
439
			}
Michael Yang's avatar
Michael Yang committed
440
			defer bin.Close()
441

Michael Yang's avatar
Michael Yang committed
442
			layer, err := CreateLayer(bin)
443
			if err != nil {
Michael Yang's avatar
Michael Yang committed
444
				return err
445
			}
Bruce MacDonald's avatar
Bruce MacDonald committed
446

Michael Yang's avatar
Michael Yang committed
447
448
449
450
451
452
			if layer.Size > 0 {
				layer.MediaType = mediatype
				layers = append(layers, layer)
			}
		case "license":
			fn(api.ProgressResponse{Status: "creating license layer"})
Bruce MacDonald's avatar
Bruce MacDonald committed
453
454
455
456
457
			layer, err := CreateLayer(strings.NewReader(c.Args))
			if err != nil {
				return err
			}

458
			if layer.Size > 0 {
Michael Yang's avatar
Michael Yang committed
459
				layer.MediaType = mediatype
460
461
				layers = append(layers, layer)
			}
Michael Yang's avatar
Michael Yang committed
462
463
464
		case "template", "system":
			fn(api.ProgressResponse{Status: fmt.Sprintf("creating %s layer", c.Name)})

Michael Yang's avatar
Michael Yang committed
465
			// remove duplicate layers
Michael Yang's avatar
Michael Yang committed
466
			layers = removeLayerFromLayers(layers, mediatype)
467

468
			layer, err := CreateLayer(strings.NewReader(c.Args))
469
			if err != nil {
470
				return err
471
			}
472

473
			if layer.Size > 0 {
Michael Yang's avatar
Michael Yang committed
474
				layer.MediaType = mediatype
475
476
				layers = append(layers, layer)
			}
477
		default:
478
			params[c.Name] = append(params[c.Name], c.Args)
479
480
481
		}
	}

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

485
		formattedParams, err := api.FormatParams(params)
486
		if err != nil {
Michael Yang's avatar
Michael Yang committed
487
			return err
488
		}
489

Michael Yang's avatar
Michael Yang committed
490
		for k, v := range fromParams {
Michael Yang's avatar
Michael Yang committed
491
492
493
494
495
			if _, ok := formattedParams[k]; !ok {
				formattedParams[k] = v
			}
		}

Michael Yang's avatar
Michael Yang committed
496
		if config.ModelType == "65B" {
Michael Yang's avatar
Michael Yang committed
497
			if gqa, ok := formattedParams["gqa"].(int); ok && gqa == 8 {
Michael Yang's avatar
Michael Yang committed
498
499
500
501
				config.ModelType = "70B"
			}
		}

Michael Yang's avatar
Michael Yang committed
502
503
		var b bytes.Buffer
		if err := json.NewEncoder(&b).Encode(formattedParams); err != nil {
504
505
506
			return err
		}

Michael Yang's avatar
Michael Yang committed
507
508
		fn(api.ProgressResponse{Status: "creating config layer"})
		layer, err := CreateLayer(bytes.NewReader(b.Bytes()))
509
		if err != nil {
Michael Yang's avatar
Michael Yang committed
510
			return err
511
		}
Michael Yang's avatar
Michael Yang committed
512
513
514

		layer.MediaType = "application/vnd.ollama.image.params"
		layers = append(layers, layer)
515
516
	}

517
518
519
520
521
	digests, err := getLayerDigests(layers)
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
522
	configLayer, err := createConfigLayer(config, digests)
523
524
525
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
526
527
528

	layers = append(layers, configLayer)
	delete(deleteMap, configLayer.Digest)
529

Michael Yang's avatar
Michael Yang committed
530
	if err := SaveLayers(layers, fn, false); err != nil {
531
532
533
		return err
	}

Michael Yang's avatar
Michael Yang committed
534
535
536
537
538
539
	var contentLayers []*Layer
	for _, layer := range layers {
		contentLayers = append(contentLayers, &layer.Layer)
		delete(deleteMap, layer.Digest)
	}

540
	fn(api.ProgressResponse{Status: "writing manifest"})
Michael Yang's avatar
Michael Yang committed
541
	if err := CreateManifest(name, configLayer, contentLayers); err != nil {
542
543
544
		return err
	}

Michael Yang's avatar
Michael Yang committed
545
546
	if noprune := os.Getenv("OLLAMA_NOPRUNE"); noprune == "" {
		if err := deleteUnusedLayers(nil, deleteMap, false); err != nil {
547
548
549
550
			return err
		}
	}

551
	fn(api.ProgressResponse{Status: "success"})
552
553
554
	return nil
}

Michael Yang's avatar
Michael Yang committed
555
func removeLayerFromLayers(layers []*LayerReader, mediaType string) []*LayerReader {
Michael Yang's avatar
Michael Yang committed
556
557
558
	return slices.DeleteFunc(layers, func(layer *LayerReader) bool {
		return layer.MediaType == mediaType
	})
559
560
}

561
func SaveLayers(layers []*LayerReader, fn func(resp api.ProgressResponse), force bool) error {
562
563
	// Write each of the layers to disk
	for _, layer := range layers {
Patrick Devine's avatar
Patrick Devine committed
564
		fp, err := GetBlobsPath(layer.Digest)
565
566
567
		if err != nil {
			return err
		}
568
569

		_, err = os.Stat(fp)
570
		if os.IsNotExist(err) || force {
571
572
			fn(api.ProgressResponse{Status: fmt.Sprintf("writing layer %s", layer.Digest)})

573
574
575
576
577
578
579
			out, err := os.Create(fp)
			if err != nil {
				log.Printf("couldn't create %s", fp)
				return err
			}
			defer out.Close()

Michael Yang's avatar
Michael Yang committed
580
			if _, err = io.Copy(out, layer.Reader); err != nil {
581
582
				return err
			}
Michael Yang's avatar
Michael Yang committed
583

584
		} else {
585
			fn(api.ProgressResponse{Status: fmt.Sprintf("using already created layer %s", layer.Digest)})
586
587
588
589
590
591
		}
	}

	return nil
}

Michael Yang's avatar
Michael Yang committed
592
func CreateManifest(name string, cfg *LayerReader, layers []*Layer) error {
593
	mp := ParseModelPath(name)
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
	manifest := ManifestV2{
		SchemaVersion: 2,
		MediaType:     "application/vnd.docker.distribution.manifest.v2+json",
		Config: Layer{
			MediaType: cfg.MediaType,
			Size:      cfg.Size,
			Digest:    cfg.Digest,
		},
		Layers: layers,
	}

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

610
	fp, err := mp.GetManifestPath()
611
612
613
	if err != nil {
		return err
	}
614
615
616
	if err := os.MkdirAll(filepath.Dir(fp), 0o755); err != nil {
		return err
	}
617
	return os.WriteFile(fp, manifestJSON, 0o644)
618
619
}

Michael Yang's avatar
Michael Yang committed
620
func GetLayerWithBufferFromLayer(layer *Layer) (*LayerReader, error) {
Patrick Devine's avatar
Patrick Devine committed
621
	fp, err := GetBlobsPath(layer.Digest)
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
	if err != nil {
		return nil, err
	}

	file, err := os.Open(fp)
	if err != nil {
		return nil, fmt.Errorf("could not open blob: %w", err)
	}
	defer file.Close()

	newLayer, err := CreateLayer(file)
	if err != nil {
		return nil, err
	}
	newLayer.MediaType = layer.MediaType
	return newLayer, nil
}

Michael Yang's avatar
Michael Yang committed
640
func getLayerDigests(layers []*LayerReader) ([]string, error) {
641
642
643
644
645
646
647
648
649
650
651
	var digests []string
	for _, l := range layers {
		if l.Digest == "" {
			return nil, fmt.Errorf("layer is missing a digest")
		}
		digests = append(digests, l.Digest)
	}
	return digests, nil
}

// CreateLayer creates a Layer object from a given file
Michael Yang's avatar
Michael Yang committed
652
653
func CreateLayer(f io.ReadSeeker) (*LayerReader, error) {
	digest, size := GetSHA256Digest(f)
654
	f.Seek(0, io.SeekStart)
655

Michael Yang's avatar
Michael Yang committed
656
	layer := &LayerReader{
657
658
659
660
661
		Layer: Layer{
			MediaType: "application/vnd.docker.image.rootfs.diff.tar",
			Digest:    digest,
			Size:      size,
		},
Michael Yang's avatar
Michael Yang committed
662
		Reader: f,
663
664
665
666
667
	}

	return layer, nil
}

Patrick Devine's avatar
Patrick Devine committed
668
func CopyModel(src, dest string) error {
669
	srcModelPath := ParseModelPath(src)
670
	srcPath, err := srcModelPath.GetManifestPath()
671
672
673
674
	if err != nil {
		return err
	}

675
	destModelPath := ParseModelPath(dest)
676
	destPath, err := destModelPath.GetManifestPath()
Patrick Devine's avatar
Patrick Devine committed
677
678
679
	if err != nil {
		return err
	}
680
681
682
	if err := os.MkdirAll(filepath.Dir(destPath), 0o755); err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
683
684

	// copy the file
Michael Yang's avatar
Michael Yang committed
685
	input, err := os.ReadFile(srcPath)
Patrick Devine's avatar
Patrick Devine committed
686
687
688
689
690
	if err != nil {
		fmt.Println("Error reading file:", err)
		return err
	}

Michael Yang's avatar
Michael Yang committed
691
	err = os.WriteFile(destPath, input, 0o644)
Patrick Devine's avatar
Patrick Devine committed
692
693
694
695
696
697
698
699
	if err != nil {
		fmt.Println("Error reading file:", err)
		return err
	}

	return nil
}

Michael Yang's avatar
Michael Yang committed
700
func deleteUnusedLayers(skipModelPath *ModelPath, deleteMap map[string]struct{}, dryRun bool) error {
701
702
703
704
	fp, err := GetManifestPath()
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
705
706
707
708

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

Michael Yang's avatar
Michael Yang committed
711
712
713
714
		dir, file := filepath.Split(path)
		dir = strings.Trim(strings.TrimPrefix(dir, fp), string(os.PathSeparator))
		tag := strings.Join([]string{dir, file}, ":")
		fmp := ParseModelPath(tag)
715

Michael Yang's avatar
Michael Yang committed
716
		// skip the manifest we're trying to delete
717
		if skipModelPath != nil && skipModelPath.GetFullTagname() == fmp.GetFullTagname() {
Michael Yang's avatar
Michael Yang committed
718
			return nil
719
		}
Michael Yang's avatar
Michael Yang committed
720
721
722
723
724
725
726
727
728
729
730
731

		// save (i.e. delete from the deleteMap) any files used in other manifests
		manifest, _, err := GetManifest(fmp)
		if err != nil {
			return nil
		}

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

		delete(deleteMap, manifest.Config.Digest)
732
		return nil
Michael Yang's avatar
Michael Yang committed
733
734
735
	}

	if err := filepath.Walk(fp, walkFunc); err != nil {
Michael Yang's avatar
Michael Yang committed
736
737
		return err
	}
738
739

	// only delete the files which are still in the deleteMap
Michael Yang's avatar
Michael Yang committed
740
741
742
743
744
745
746
747
748
	for k := range deleteMap {
		fp, err := GetBlobsPath(k)
		if err != nil {
			log.Printf("couldn't get file path for '%s': %v", k, err)
			continue
		}
		if !dryRun {
			if err := os.Remove(fp); err != nil {
				log.Printf("couldn't remove file '%s': %v", fp, err)
749
750
				continue
			}
Michael Yang's avatar
Michael Yang committed
751
752
		} else {
			log.Printf("wanted to remove: %s", fp)
753
754
755
		}
	}

756
757
758
759
	return nil
}

func PruneLayers() error {
Michael Yang's avatar
Michael Yang committed
760
	deleteMap := make(map[string]struct{})
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
	p, err := GetBlobsPath("")
	if err != nil {
		return err
	}

	blobs, err := os.ReadDir(p)
	if err != nil {
		log.Printf("couldn't read dir '%s': %v", p, err)
		return err
	}

	for _, blob := range blobs {
		name := blob.Name()
		if runtime.GOOS == "windows" {
			name = strings.ReplaceAll(name, "-", ":")
		}
Michael Yang's avatar
Michael Yang committed
777
778
779
		if strings.HasPrefix(name, "sha256:") {
			deleteMap[name] = struct{}{}
		}
780
781
782
783
784
785
786
787
788
789
790
791
792
793
	}

	log.Printf("total blobs: %d", len(deleteMap))

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

	log.Printf("total unused blobs removed: %d", len(deleteMap))

	return nil
}

Michael Yang's avatar
Michael Yang committed
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
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
}

827
828
829
830
831
832
833
func DeleteModel(name string) error {
	mp := ParseModelPath(name)
	manifest, _, err := GetManifest(mp)
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
834
	deleteMap := make(map[string]struct{})
835
	for _, layer := range manifest.Layers {
Michael Yang's avatar
Michael Yang committed
836
		deleteMap[layer.Digest] = struct{}{}
837
	}
Michael Yang's avatar
Michael Yang committed
838
	deleteMap[manifest.Config.Digest] = struct{}{}
839
840
841
842
843
844

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

845
	fp, err := mp.GetManifestPath()
846
847
848
849
850
851
852
853
854
855
856
857
	if err != nil {
		return err
	}
	err = os.Remove(fp)
	if err != nil {
		log.Printf("couldn't remove manifest file '%s': %v", fp, err)
		return err
	}

	return nil
}

Patrick Devine's avatar
Patrick Devine committed
858
func ShowModelfile(model *Model) (string, error) {
Michael Yang's avatar
Michael Yang committed
859
	var mt struct {
Patrick Devine's avatar
Patrick Devine committed
860
		*Model
Michael Yang's avatar
Michael Yang committed
861
		From       string
Michael Yang's avatar
Michael Yang committed
862
		Parameters map[string][]any
Patrick Devine's avatar
Patrick Devine committed
863
864
	}

Michael Yang's avatar
Michael Yang committed
865
	mt.Parameters = make(map[string][]any)
Patrick Devine's avatar
Patrick Devine committed
866
	for k, v := range model.Options {
Michael Yang's avatar
Michael Yang committed
867
868
869
		if s, ok := v.([]any); ok {
			mt.Parameters[k] = s
			continue
Patrick Devine's avatar
Patrick Devine committed
870
871
		}

Michael Yang's avatar
Michael Yang committed
872
		mt.Parameters[k] = []any{v}
Patrick Devine's avatar
Patrick Devine committed
873
874
	}

Michael Yang's avatar
Michael Yang committed
875
876
	mt.Model = model
	mt.From = model.ModelPath
Patrick Devine's avatar
Patrick Devine committed
877

Michael Yang's avatar
Michael Yang committed
878
	if model.OriginalModel != "" {
Daniel Reis's avatar
Daniel Reis committed
879
		mt.From = model.OriginalModel
Patrick Devine's avatar
Patrick Devine committed
880
881
882
883
884
885
886
887
	}

	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 }}"""
888
889

{{- if .System }}
Patrick Devine's avatar
Patrick Devine committed
890
SYSTEM """{{ .System }}"""
891
{{- end }}
892
893
894
895

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

Michael Yang's avatar
Michael Yang committed
897
898
899
900
{{- range $k, $v := .Parameters }}
{{- range $parameter := $v }}
PARAMETER {{ $k }} {{ printf "%#v" $parameter }}
{{- end }}
Michael Yang's avatar
Michael Yang committed
901
{{- end }}`
Patrick Devine's avatar
Patrick Devine committed
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918

	tmpl, err := template.New("").Parse(modelFile)
	if err != nil {
		log.Printf("error parsing template: %q", err)
		return "", err
	}

	var buf bytes.Buffer

	if err = tmpl.Execute(&buf, mt); err != nil {
		log.Printf("error executing template: %q", err)
		return "", err
	}

	return buf.String(), nil
}

919
func PushModel(ctx context.Context, name string, regOpts *RegistryOptions, fn func(api.ProgressResponse)) error {
920
	mp := ParseModelPath(name)
921
922
	fn(api.ProgressResponse{Status: "retrieving manifest"})

923
924
925
926
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
		return fmt.Errorf("insecure protocol http")
	}

Patrick Devine's avatar
Patrick Devine committed
927
	manifest, _, err := GetManifest(mp)
928
	if err != nil {
929
		fn(api.ProgressResponse{Status: "couldn't retrieve manifest"})
930
931
932
933
		return err
	}

	var layers []*Layer
Jeffrey Morgan's avatar
Jeffrey Morgan committed
934
	layers = append(layers, manifest.Layers...)
935
936
937
	layers = append(layers, &manifest.Config)

	for _, layer := range layers {
Michael Yang's avatar
Michael Yang committed
938
		if err := uploadBlob(ctx, mp, layer, regOpts, fn); err != nil {
939
			log.Printf("error uploading blob: %v", err)
940
941
942
			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())
			}
943
944
			return err
		}
945
946
	}

947
	fn(api.ProgressResponse{Status: "pushing manifest"})
Michael Yang's avatar
Michael Yang committed
948
949
	requestURL := mp.BaseURL()
	requestURL = requestURL.JoinPath("v2", mp.GetNamespaceRepository(), "manifests", mp.Tag)
950
951
952
953
954
955

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

Michael Yang's avatar
Michael Yang committed
956
957
	headers := make(http.Header)
	headers.Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json")
Michael Yang's avatar
Michael Yang committed
958
	resp, err := makeRequestWithRetry(ctx, http.MethodPut, requestURL, headers, bytes.NewReader(manifestJSON), regOpts)
959
960
961
962
963
	if err != nil {
		return err
	}
	defer resp.Body.Close()

964
	fn(api.ProgressResponse{Status: "success"})
965
966
967
968

	return nil
}

969
func PullModel(ctx context.Context, name string, regOpts *RegistryOptions, fn func(api.ProgressResponse)) error {
970
971
	mp := ParseModelPath(name)

972
973
974
975
976
	var manifest *ManifestV2
	var err error
	var noprune string

	// build deleteMap to prune unused layers
Michael Yang's avatar
Michael Yang committed
977
	deleteMap := make(map[string]struct{})
978
979
980
981
982
983
984
985
986

	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
987
				deleteMap[l.Digest] = struct{}{}
988
			}
Michael Yang's avatar
Michael Yang committed
989
			deleteMap[manifest.Config.Digest] = struct{}{}
990
991
992
		}
	}

993
994
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
		return fmt.Errorf("insecure protocol http")
995
	}
996

997
	fn(api.ProgressResponse{Status: "pulling manifest"})
998

999
	manifest, err = pullModelManifest(ctx, mp, regOpts)
1000
	if err != nil {
1001
		return fmt.Errorf("pull model manifest: %s", err)
1002
1003
1004
	}

	var layers []*Layer
Bruce MacDonald's avatar
Bruce MacDonald committed
1005
	layers = append(layers, manifest.Layers...)
1006
1007
1008
	layers = append(layers, &manifest.Config)

	for _, layer := range layers {
1009
1010
1011
1012
1013
1014
1015
1016
		if err := downloadBlob(
			ctx,
			downloadOpts{
				mp:      mp,
				digest:  layer.Digest,
				regOpts: regOpts,
				fn:      fn,
			}); err != nil {
1017
1018
			return err
		}
1019
		delete(deleteMap, layer.Digest)
1020
	}
1021
	delete(deleteMap, manifest.Config.Digest)
1022

Michael Yang's avatar
Michael Yang committed
1023
1024
1025
	fn(api.ProgressResponse{Status: "verifying sha256 digest"})
	for _, layer := range layers {
		if err := verifyBlob(layer.Digest); err != nil {
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
			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
					log.Printf("couldn't remove file with digest mismatch '%s': %v", fp, err)
				}
			}
Michael Yang's avatar
Michael Yang committed
1037
1038
1039
1040
			return err
		}
	}

1041
	fn(api.ProgressResponse{Status: "writing manifest"})
1042

1043
	manifestJSON, err := json.Marshal(manifest)
1044
1045
1046
1047
	if err != nil {
		return err
	}

1048
	fp, err := mp.GetManifestPath()
1049
1050
1051
	if err != nil {
		return err
	}
1052
1053
1054
	if err := os.MkdirAll(filepath.Dir(fp), 0o755); err != nil {
		return err
	}
1055

Bruce MacDonald's avatar
Bruce MacDonald committed
1056
	err = os.WriteFile(fp, manifestJSON, 0o644)
1057
1058
1059
1060
1061
	if err != nil {
		log.Printf("couldn't write to %s", fp)
		return err
	}

1062
1063
1064
1065
1066
1067
1068
1069
	if noprune == "" {
		fn(api.ProgressResponse{Status: "removing any unused layers"})
		err = deleteUnusedLayers(nil, deleteMap, false)
		if err != nil {
			return err
		}
	}

1070
	fn(api.ProgressResponse{Status: "success"})
1071
1072
1073
1074

	return nil
}

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

Michael Yang's avatar
Michael Yang committed
1078
1079
	headers := make(http.Header)
	headers.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json")
Michael Yang's avatar
Michael Yang committed
1080
	resp, err := makeRequestWithRetry(ctx, http.MethodGet, requestURL, headers, nil, regOpts)
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
	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
}

1094
1095
1096
1097
func createConfigLayer(config ConfigV2, layers []string) (*LayerReader, error) {
	config.RootFS = RootFS{
		Type:    "layers",
		DiffIDs: layers,
1098
1099
1100
1101
1102
1103
1104
	}

	configJSON, err := json.Marshal(config)
	if err != nil {
		return nil, err
	}

1105
	digest, size := GetSHA256Digest(bytes.NewBuffer(configJSON))
1106

Michael Yang's avatar
Michael Yang committed
1107
	layer := &LayerReader{
1108
1109
1110
1111
1112
		Layer: Layer{
			MediaType: "application/vnd.docker.container.image.v1+json",
			Digest:    digest,
			Size:      size,
		},
1113
		Reader: bytes.NewBuffer(configJSON),
1114
1115
1116
1117
1118
	}
	return layer, nil
}

// GetSHA256Digest returns the SHA256 hash of a given buffer and returns it, and the size of buffer
Michael Yang's avatar
Michael Yang committed
1119
func GetSHA256Digest(r io.Reader) (string, int64) {
Michael Yang's avatar
Michael Yang committed
1120
1121
1122
1123
1124
1125
	h := sha256.New()
	n, err := io.Copy(h, r)
	if err != nil {
		log.Fatal(err)
	}

Michael Yang's avatar
Michael Yang committed
1126
	return fmt.Sprintf("sha256:%x", h.Sum(nil)), n
1127
1128
}

1129
1130
var errUnauthorized = fmt.Errorf("unauthorized")

Michael Yang's avatar
Michael Yang committed
1131
func makeRequestWithRetry(ctx context.Context, method string, requestURL *url.URL, headers http.Header, body io.ReadSeeker, regOpts *RegistryOptions) (*http.Response, error) {
1132
1133
1134
1135
1136
	resp, err := makeRequest(ctx, method, requestURL, headers, body, regOpts)
	if err != nil {
		if !errors.Is(err, context.Canceled) {
			log.Printf("request failed: %v", err)
		}
1137

1138
1139
1140
1141
1142
1143
1144
1145
1146
		return nil, err
	}

	switch {
	case resp.StatusCode == http.StatusUnauthorized:
		// Handle authentication error with one retry
		auth := resp.Header.Get("www-authenticate")
		authRedir := ParseAuthRedirectString(auth)
		token, err := getAuthToken(ctx, authRedir)
Michael Yang's avatar
Michael Yang committed
1147
1148
1149
		if err != nil {
			return nil, err
		}
1150
1151
1152
		regOpts.Token = token
		if body != nil {
			_, err = body.Seek(0, io.SeekStart)
Michael Yang's avatar
Michael Yang committed
1153
1154
1155
1156
			if err != nil {
				return nil, err
			}
		}
1157
1158
1159
1160
1161
1162
1163

		resp, err := makeRequest(ctx, method, requestURL, headers, body, regOpts)
		if resp.StatusCode == http.StatusUnauthorized {
			return nil, errUnauthorized
		}

		return resp, err
1164
1165
1166
1167
1168
1169
1170
1171
	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)
Michael Yang's avatar
Michael Yang committed
1172
1173
	}

1174
	return resp, nil
Michael Yang's avatar
Michael Yang committed
1175
1176
}

Michael Yang's avatar
Michael Yang committed
1177
func makeRequest(ctx context.Context, method string, requestURL *url.URL, headers http.Header, body io.Reader, regOpts *RegistryOptions) (*http.Response, error) {
Michael Yang's avatar
Michael Yang committed
1178
	if requestURL.Scheme != "http" && regOpts != nil && regOpts.Insecure {
Michael Yang's avatar
Michael Yang committed
1179
		requestURL.Scheme = "http"
1180
1181
	}

Michael Yang's avatar
Michael Yang committed
1182
	req, err := http.NewRequestWithContext(ctx, method, requestURL.String(), body)
1183
1184
1185
1186
	if err != nil {
		return nil, err
	}

Michael Yang's avatar
Michael Yang committed
1187
1188
1189
1190
	if headers != nil {
		req.Header = headers
	}

Michael Yang's avatar
Michael Yang committed
1191
1192
1193
1194
1195
1196
	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)
		}
1197
1198
	}

Michael Yang's avatar
Michael Yang committed
1199
	req.Header.Set("User-Agent", fmt.Sprintf("ollama/%s (%s %s) Go/%s", version.Version, runtime.GOARCH, runtime.GOOS, runtime.Version()))
1200

Michael Yang's avatar
Michael Yang committed
1201
1202
1203
1204
1205
1206
1207
1208
1209
	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
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
	proxyURL, err := http.ProxyFromEnvironment(req)
	if err != nil {
		return nil, err
	}

	client := http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyURL(proxyURL),
		},
	}

	resp, err := client.Do(req)
1222
1223
1224
1225
1226
1227
	if err != nil {
		return nil, err
	}

	return resp, nil
}
Michael Yang's avatar
Michael Yang committed
1228

Patrick Devine's avatar
Patrick Devine committed
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
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]
}

func ParseAuthRedirectString(authStr string) AuthRedirect {
	authStr = strings.TrimPrefix(authStr, "Bearer ")

	return AuthRedirect{
		Realm:   getValue(authStr, "realm"),
		Service: getValue(authStr, "service"),
		Scope:   getValue(authStr, "scope"),
	}
}

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

Michael Yang's avatar
Michael Yang committed
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
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 {
1278
		return fmt.Errorf("%w: want %s, got %s", errDigestMismatch, digest, fileDigest)
Michael Yang's avatar
Michael Yang committed
1279
1280
1281
1282
	}

	return nil
}