"research/cognitive_planning/label_map_util.py" did not exist on "6b72b5cd6c692b1da8481e7ac0565f11cab9c6bd"
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
type PromptVars struct {
	System   string
	Prompt   string
	Response string
}
55

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

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

Bruce MacDonald's avatar
Bruce MacDonald committed
68
69
70
71
72
73
74
75
	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
}
76

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

	writePrompt := func() error {
		p, err := m.Prompt(currentVars)
		if err != nil {
			return err
		}
		prompt.WriteString(p)
		currentVars = PromptVars{}
		return nil
90
91
	}

Bruce MacDonald's avatar
Bruce MacDonald committed
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
119
120
121
122
	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)
		}
	}

	// Append the last set of vars if they are non-empty
	if currentVars.Prompt != "" || currentVars.System != "" {
		if err := writePrompt(); err != nil {
			return "", err
		}
123
124
	}

Bruce MacDonald's avatar
Bruce MacDonald committed
125
	return prompt.String(), nil
126
127
}

128
129
130
131
132
133
134
135
136
137
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
138
	Size      int64  `json:"size"`
Michael Yang's avatar
Michael Yang committed
139
	From      string `json:"from,omitempty"`
140
141
}

Michael Yang's avatar
Michael Yang committed
142
type LayerReader struct {
143
	Layer
Michael Yang's avatar
Michael Yang committed
144
	io.Reader
145
146
147
}

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

	// required by spec
155
156
157
158
159
160
161
162
163
	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
164
func (m *ManifestV2) GetTotalSize() (total int64) {
Patrick Devine's avatar
Patrick Devine committed
165
166
167
	for _, layer := range m.Layers {
		total += layer.Size
	}
Michael Yang's avatar
Michael Yang committed
168

Patrick Devine's avatar
Patrick Devine committed
169
170
171
172
	total += m.Config.Size
	return total
}

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

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

	var manifest *ManifestV2

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

Patrick Devine's avatar
Patrick Devine committed
190
191
192
	shaSum := sha256.Sum256(bts)
	shaStr := hex.EncodeToString(shaSum[:])

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

Patrick Devine's avatar
Patrick Devine committed
197
	return manifest, shaStr, nil
198
199
200
}

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

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

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

221
222
223
		switch layer.MediaType {
		case "application/vnd.ollama.image.model":
			model.ModelPath = filename
Patrick Devine's avatar
Patrick Devine committed
224
			model.OriginalModel = layer.From
225
		case "application/vnd.ollama.image.embed":
226
227
228
			// 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.")
229
230
		case "application/vnd.ollama.image.adapter":
			model.AdapterPaths = append(model.AdapterPaths, filename)
231
232
233
234
235
236
237
238
239
		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)
240
241
242
			if err != nil {
				return nil, err
			}
243
244

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

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

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

	return model, nil
}

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

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

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

Michael Yang's avatar
Michael Yang committed
297
298
299
	return abspath
}

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

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

Michael Yang's avatar
Michael Yang committed
308
	var layers []*LayerReader
Michael Yang's avatar
Michael Yang committed
309

310
	params := make(map[string][]string)
Michael Yang's avatar
Michael Yang committed
311
312
	fromParams := make(map[string]any)

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

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

				c.Args = blobPath
			}

328
			bin, err := os.Open(realpath(modelFileDir, c.Args))
329
			if err != nil {
Michael Yang's avatar
Michael Yang committed
330
331
332
333
334
335
336
				// 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 {
337
338
339
						return err
					}

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

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

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

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

Michael Yang's avatar
Michael Yang committed
365
366
367
368
				config.ModelFormat = fromConfig.ModelFormat
				config.ModelFamily = fromConfig.ModelFamily
				config.ModelType = fromConfig.ModelType
				config.FileType = fromConfig.FileType
Michael Yang's avatar
Michael Yang committed
369

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

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

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

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

					layer.From = modelpath.GetShortTagname()
					layers = append(layers, layer)
396
				}
Michael Yang's avatar
Michael Yang committed
397
398
399

				deleteMap[manifest.Config.Digest] = struct{}{}
				continue
400
			}
Michael Yang's avatar
Michael Yang committed
401
			defer bin.Close()
402

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

Michael Yang's avatar
Michael Yang committed
409
410
411
412
413
414
415
416
417
418
			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
			}
419

Michael Yang's avatar
Michael Yang committed
420
421
422
			layer.MediaType = mediatype
			layers = append(layers, layer)
		case "adapter":
423
424
425
426
427
428
429
430
431
			if strings.HasPrefix(c.Args, "@") {
				blobPath, err := GetBlobsPath(strings.TrimPrefix(c.Args, "@"))
				if err != nil {
					return err
				}

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

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

Michael Yang's avatar
Michael Yang committed
444
445
446
447
448
449
			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
450
451
452
453
454
			layer, err := CreateLayer(strings.NewReader(c.Args))
			if err != nil {
				return err
			}

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

Michael Yang's avatar
Michael Yang committed
462
			// remove duplicate layers
Michael Yang's avatar
Michael Yang committed
463
			layers = removeLayerFromLayers(layers, mediatype)
464

465
			layer, err := CreateLayer(strings.NewReader(c.Args))
466
			if err != nil {
467
				return err
468
			}
469

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

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

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

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

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

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

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

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

514
515
516
517
518
	digests, err := getLayerDigests(layers)
	if err != nil {
		return err
	}

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

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

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

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

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

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

548
	fn(api.ProgressResponse{Status: "success"})
549
550
551
	return nil
}

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

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

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

570
571
572
573
574
575
576
			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
577
			if _, err = io.Copy(out, layer.Reader); err != nil {
578
579
				return err
			}
Michael Yang's avatar
Michael Yang committed
580

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

	return nil
}

Michael Yang's avatar
Michael Yang committed
589
func CreateManifest(name string, cfg *LayerReader, layers []*Layer) error {
590
	mp := ParseModelPath(name)
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
	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
	}

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
	return os.WriteFile(fp, manifestJSON, 0o644)
615
616
}

Michael Yang's avatar
Michael Yang committed
617
func GetLayerWithBufferFromLayer(layer *Layer) (*LayerReader, error) {
Patrick Devine's avatar
Patrick Devine committed
618
	fp, err := GetBlobsPath(layer.Digest)
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
	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
637
func getLayerDigests(layers []*LayerReader) ([]string, error) {
638
639
640
641
642
643
644
645
646
647
648
	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
649
650
func CreateLayer(f io.ReadSeeker) (*LayerReader, error) {
	digest, size := GetSHA256Digest(f)
651
	f.Seek(0, io.SeekStart)
652

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

	return layer, nil
}

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

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

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

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

	return nil
}

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

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

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

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

		// 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)
729
		return nil
Michael Yang's avatar
Michael Yang committed
730
731
732
	}

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

	// only delete the files which are still in the deleteMap
Michael Yang's avatar
Michael Yang committed
737
738
739
740
741
742
743
744
745
	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)
746
747
				continue
			}
Michael Yang's avatar
Michael Yang committed
748
749
		} else {
			log.Printf("wanted to remove: %s", fp)
750
751
752
		}
	}

753
754
755
756
	return nil
}

func PruneLayers() error {
Michael Yang's avatar
Michael Yang committed
757
	deleteMap := make(map[string]struct{})
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
	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
774
775
776
		if strings.HasPrefix(name, "sha256:") {
			deleteMap[name] = struct{}{}
		}
777
778
779
780
781
782
783
784
785
786
787
788
789
790
	}

	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
791
792
793
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
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
}

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

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

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

842
	fp, err := mp.GetManifestPath()
843
844
845
846
847
848
849
850
851
852
853
854
	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
855
func ShowModelfile(model *Model) (string, error) {
Michael Yang's avatar
Michael Yang committed
856
	var mt struct {
Patrick Devine's avatar
Patrick Devine committed
857
		*Model
Michael Yang's avatar
Michael Yang committed
858
		From       string
Michael Yang's avatar
Michael Yang committed
859
		Parameters map[string][]any
Patrick Devine's avatar
Patrick Devine committed
860
861
	}

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

Michael Yang's avatar
Michael Yang committed
869
		mt.Parameters[k] = []any{v}
Patrick Devine's avatar
Patrick Devine committed
870
871
	}

Michael Yang's avatar
Michael Yang committed
872
873
	mt.Model = model
	mt.From = model.ModelPath
Patrick Devine's avatar
Patrick Devine committed
874

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

	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 }}"""
885
886

{{- if .System }}
Patrick Devine's avatar
Patrick Devine committed
887
SYSTEM """{{ .System }}"""
888
{{- end }}
889
890
891
892

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

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

	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
}

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

920
921
922
923
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
		return fmt.Errorf("insecure protocol http")
	}

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

	var layers []*Layer
Jeffrey Morgan's avatar
Jeffrey Morgan committed
931
	layers = append(layers, manifest.Layers...)
932
933
934
	layers = append(layers, &manifest.Config)

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

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

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

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

961
	fn(api.ProgressResponse{Status: "success"})
962
963
964
965

	return nil
}

966
func PullModel(ctx context.Context, name string, regOpts *RegistryOptions, fn func(api.ProgressResponse)) error {
967
968
	mp := ParseModelPath(name)

969
970
971
972
973
	var manifest *ManifestV2
	var err error
	var noprune string

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

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

990
991
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
		return fmt.Errorf("insecure protocol http")
992
	}
993

994
	fn(api.ProgressResponse{Status: "pulling manifest"})
995

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

	var layers []*Layer
Bruce MacDonald's avatar
Bruce MacDonald committed
1002
	layers = append(layers, manifest.Layers...)
1003
1004
1005
	layers = append(layers, &manifest.Config)

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

Michael Yang's avatar
Michael Yang committed
1020
1021
1022
	fn(api.ProgressResponse{Status: "verifying sha256 digest"})
	for _, layer := range layers {
		if err := verifyBlob(layer.Digest); err != nil {
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
			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
1034
1035
1036
1037
			return err
		}
	}

1038
	fn(api.ProgressResponse{Status: "writing manifest"})
1039

1040
	manifestJSON, err := json.Marshal(manifest)
1041
1042
1043
1044
	if err != nil {
		return err
	}

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

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

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

1067
	fn(api.ProgressResponse{Status: "success"})
1068
1069
1070
1071

	return nil
}

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

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

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

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

1102
	digest, size := GetSHA256Digest(bytes.NewBuffer(configJSON))
1103

Michael Yang's avatar
Michael Yang committed
1104
	layer := &LayerReader{
1105
1106
1107
1108
1109
		Layer: Layer{
			MediaType: "application/vnd.docker.container.image.v1+json",
			Digest:    digest,
			Size:      size,
		},
1110
		Reader: bytes.NewBuffer(configJSON),
1111
1112
1113
1114
1115
	}
	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
1116
func GetSHA256Digest(r io.Reader) (string, int64) {
Michael Yang's avatar
Michael Yang committed
1117
1118
1119
1120
1121
1122
	h := sha256.New()
	n, err := io.Copy(h, r)
	if err != nil {
		log.Fatal(err)
	}

Michael Yang's avatar
Michael Yang committed
1123
	return fmt.Sprintf("sha256:%x", h.Sum(nil)), n
1124
1125
}

1126
1127
var errUnauthorized = fmt.Errorf("unauthorized")

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

1135
1136
1137
1138
1139
1140
1141
1142
1143
		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
1144
1145
1146
		if err != nil {
			return nil, err
		}
1147
1148
1149
		regOpts.Token = token
		if body != nil {
			_, err = body.Seek(0, io.SeekStart)
Michael Yang's avatar
Michael Yang committed
1150
1151
1152
1153
			if err != nil {
				return nil, err
			}
		}
1154
1155
1156
1157
1158
1159
1160

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

		return resp, err
1161
1162
1163
1164
1165
1166
1167
1168
	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
1169
1170
	}

1171
	return resp, nil
Michael Yang's avatar
Michael Yang committed
1172
1173
}

Michael Yang's avatar
Michael Yang committed
1174
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
1175
	if requestURL.Scheme != "http" && regOpts != nil && regOpts.Insecure {
Michael Yang's avatar
Michael Yang committed
1176
		requestURL.Scheme = "http"
1177
1178
	}

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

Michael Yang's avatar
Michael Yang committed
1184
1185
1186
1187
	if headers != nil {
		req.Header = headers
	}

Michael Yang's avatar
Michael Yang committed
1188
1189
1190
1191
1192
1193
	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)
		}
1194
1195
	}

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

Michael Yang's avatar
Michael Yang committed
1198
1199
1200
1201
1202
1203
1204
1205
1206
	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
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
	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)
1219
1220
1221
1222
1223
1224
	if err != nil {
		return nil, err
	}

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

Patrick Devine's avatar
Patrick Devine committed
1226
1227
1228
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
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"),
	}
}

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

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

	return nil
}