images.go 28.5 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
28
	"github.com/ollama/ollama/api"
	"github.com/ollama/ollama/convert"
Michael Yang's avatar
Michael Yang committed
29
	"github.com/ollama/ollama/format"
30
31
32
	"github.com/ollama/ollama/llm"
	"github.com/ollama/ollama/parser"
	"github.com/ollama/ollama/version"
33
34
)

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

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

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

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

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

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

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

Michael Yang's avatar
Michael Yang committed
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
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
	}
}

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

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

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

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

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

	var manifest *ManifestV2

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

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

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

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

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

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

175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
	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
	}

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

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

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

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

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

	return model, nil
}

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

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

273
	if from == "~" {
Michael Yang's avatar
Michael Yang committed
274
		return home
275
276
277
278
279
280
281
	} 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)
282
283
	}

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

287
func CreateModel(ctx context.Context, name, modelFileDir string, commands []parser.Command, fn func(resp api.ProgressResponse)) error {
288
289
290
291
292
293
294
	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{}{}
		}
	}

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

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

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

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

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

				c.Args = blobPath
			}

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

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

			if ggufName != "" {
				pathName = ggufName
340
				slog.Debug(fmt.Sprintf("new image layer path: %s", pathName))
341
342
343
344
				defer os.RemoveAll(ggufName)
			}

			bin, err := os.Open(pathName)
345
			if err != nil {
Michael Yang's avatar
Michael Yang committed
346
347
348
349
350
351
				// 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
352
					if err := PullModel(ctx, c.Args, &registryOptions{}, fn); err != nil {
353
354
355
						return err
					}

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

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

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

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

Bruce MacDonald's avatar
Bruce MacDonald committed
381
382
383
384
385
				// 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
386
387
388
389
				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
390

Michael Yang's avatar
Michael Yang committed
391
392
393
394
				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
395
396
397
398
						if err != nil {
							return err
						}

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

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

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

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

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

423
424
425
			var offset int64
			for {
				fn(api.ProgressResponse{Status: "creating model layer"})
426
427
428
				if _, err := bin.Seek(offset, io.SeekStart); err != nil {
					return err
				}
429

Michael Yang's avatar
Michael Yang committed
430
431
432
433
434
435
436
				ggml, size, err := llm.DecodeGGML(bin)
				if errors.Is(err, io.EOF) {
					break
				} else if errors.Is(err, llm.ErrUnsupportedFormat) {
					return fmt.Errorf("model binary specified in FROM field is not a valid gguf format model, %w", err)
				} else if err != nil {
					return err
437
				}
Michael Yang's avatar
Michael Yang committed
438

Michael Yang's avatar
Michael Yang committed
439
				config.SetModelFormat(ggml.Name())
Michael Yang's avatar
Michael Yang committed
440
441
442
				config.SetModelFamily(ggml.KV().Architecture())
				config.SetModelType(format.HumanNumber(ggml.KV().ParameterCount()))
				config.SetFileType(ggml.KV().FileType())
443

444
				mediatype := mediatype
Michael Yang's avatar
Michael Yang committed
445
				if ggml.KV().Architecture() == "clip" {
446
447
					mediatype = "application/vnd.ollama.image.projector"
				}
448

Michael Yang's avatar
Michael Yang committed
449
				sr := io.NewSectionReader(bin, offset, size)
450
451
452
453
454
455
456
				layer, err := NewLayer(sr, mediatype)
				if err != nil {
					return err
				}

				layers.Add(layer)

Michael Yang's avatar
Michael Yang committed
457
				offset += size
458
			}
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
			_, size, err := llm.DecodeGGML(bin)
Michael Yang's avatar
Michael Yang committed
477
478
479
480
			if err != nil {
				return err
			}

Michael Yang's avatar
Michael Yang committed
481
			sr := io.NewSectionReader(bin, 0, size)
Michael Yang's avatar
Michael Yang committed
482
			layer, err := NewLayer(sr, mediatype)
483
			if err != nil {
Michael Yang's avatar
Michael Yang committed
484
				return err
485
			}
Bruce MacDonald's avatar
Bruce MacDonald committed
486

Michael Yang's avatar
Michael Yang committed
487
			layers.Add(layer)
Michael Yang's avatar
Michael Yang committed
488
489
		case "license":
			fn(api.ProgressResponse{Status: "creating license layer"})
Michael Yang's avatar
Michael Yang committed
490
491
492

			bin := strings.NewReader(c.Args)
			layer, err := NewLayer(bin, mediatype)
Bruce MacDonald's avatar
Bruce MacDonald committed
493
494
495
496
			if err != nil {
				return err
			}

Michael Yang's avatar
Michael Yang committed
497
			layers.Add(layer)
Michael Yang's avatar
Michael Yang committed
498
499
500
		case "template", "system":
			fn(api.ProgressResponse{Status: fmt.Sprintf("creating %s layer", c.Name)})

Michael Yang's avatar
Michael Yang committed
501
502
			bin := strings.NewReader(c.Args)
			layer, err := NewLayer(bin, mediatype)
503
			if err != nil {
504
				return err
505
			}
506

Michael Yang's avatar
Michael Yang committed
507
			layers.Replace(layer)
508
509
		case "message":
			messages = append(messages, c.Args)
510
		default:
511
			params[c.Name] = append(params[c.Name], c.Args)
512
513
514
		}
	}

515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
	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
539
	if len(params) > 0 {
Michael Yang's avatar
Michael Yang committed
540
		fn(api.ProgressResponse{Status: "creating parameters layer"})
Michael Yang's avatar
Michael Yang committed
541

542
		formattedParams, err := api.FormatParams(params)
543
		if err != nil {
Michael Yang's avatar
Michael Yang committed
544
			return err
545
		}
546

Michael Yang's avatar
Michael Yang committed
547
		for k, v := range fromParams {
Michael Yang's avatar
Michael Yang committed
548
549
550
551
552
			if _, ok := formattedParams[k]; !ok {
				formattedParams[k] = v
			}
		}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

617
618
func convertSafetensors(name, path string, fn func(resp api.ProgressResponse)) (string, error) {
	r, err := zip.OpenReader(path)
619
620
621
622
623
624
625
626
627
628
629
	if err != nil {
		return "", err
	}
	defer r.Close()

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

630
	fn(api.ProgressResponse{Status: "unpacking model metadata"})
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
	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
	}

657
658
659
	mArch, err := convert.GetModelArchFromParams(name, tempDir, params)
	if err != nil {
		return "", err
660
661
	}

662
	fn(api.ProgressResponse{Status: "processing safetensors"})
663
	if err := mArch.GetTensors(); err != nil {
664
665
666
		return "", err
	}

667
	if err := mArch.LoadVocab(); err != nil {
668
669
670
		return "", err
	}

671
	fn(api.ProgressResponse{Status: "converting model"})
672
	path, err = mArch.WriteGGUF()
673
674
675
676
	if err != nil {
		return "", err
	}

677
	return path, nil
678
679
}

Patrick Devine's avatar
Patrick Devine committed
680
func CopyModel(src, dest string) error {
681
	srcModelPath := ParseModelPath(src)
682
	srcPath, err := srcModelPath.GetManifestPath()
683
684
685
686
	if err != nil {
		return err
	}

687
	destModelPath := ParseModelPath(dest)
688
	destPath, err := destModelPath.GetManifestPath()
Patrick Devine's avatar
Patrick Devine committed
689
690
691
	if err != nil {
		return err
	}
692
693
694
	if err := os.MkdirAll(filepath.Dir(destPath), 0o755); err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
695
696

	// copy the file
Michael Yang's avatar
Michael Yang committed
697
	input, err := os.ReadFile(srcPath)
Patrick Devine's avatar
Patrick Devine committed
698
699
700
701
702
	if err != nil {
		fmt.Println("Error reading file:", err)
		return err
	}

Michael Yang's avatar
Michael Yang committed
703
	err = os.WriteFile(destPath, input, 0o644)
Patrick Devine's avatar
Patrick Devine committed
704
705
706
707
708
709
710
711
	if err != nil {
		fmt.Println("Error reading file:", err)
		return err
	}

	return nil
}

Michael Yang's avatar
Michael Yang committed
712
func deleteUnusedLayers(skipModelPath *ModelPath, deleteMap map[string]struct{}, dryRun bool) error {
713
714
715
716
	fp, err := GetManifestPath()
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
717
718
719
720

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

Michael Yang's avatar
Michael Yang committed
723
724
725
726
		dir, file := filepath.Split(path)
		dir = strings.Trim(strings.TrimPrefix(dir, fp), string(os.PathSeparator))
		tag := strings.Join([]string{dir, file}, ":")
		fmp := ParseModelPath(tag)
727

Michael Yang's avatar
Michael Yang committed
728
		// skip the manifest we're trying to delete
729
		if skipModelPath != nil && skipModelPath.GetFullTagname() == fmp.GetFullTagname() {
Michael Yang's avatar
Michael Yang committed
730
			return nil
731
		}
Michael Yang's avatar
Michael Yang committed
732
733
734
735

		// 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
736
			// nolint: nilerr
Michael Yang's avatar
Michael Yang committed
737
738
739
740
741
742
743
744
			return nil
		}

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

		delete(deleteMap, manifest.Config.Digest)
745
		return nil
Michael Yang's avatar
Michael Yang committed
746
747
748
	}

	if err := filepath.Walk(fp, walkFunc); err != nil {
Michael Yang's avatar
Michael Yang committed
749
750
		return err
	}
751
752

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

769
770
771
772
	return nil
}

func PruneLayers() error {
Michael Yang's avatar
Michael Yang committed
773
	deleteMap := make(map[string]struct{})
774
775
776
777
778
779
780
	p, err := GetBlobsPath("")
	if err != nil {
		return err
	}

	blobs, err := os.ReadDir(p)
	if err != nil {
781
		slog.Info(fmt.Sprintf("couldn't read dir '%s': %v", p, err))
782
783
784
785
786
		return err
	}

	for _, blob := range blobs {
		name := blob.Name()
787
		name = strings.ReplaceAll(name, "-", ":")
Michael Yang's avatar
Michael Yang committed
788
789
790
		if strings.HasPrefix(name, "sha256:") {
			deleteMap[name] = struct{}{}
		}
791
792
	}

793
	slog.Info(fmt.Sprintf("total blobs: %d", len(deleteMap)))
794
795
796
797
798
799

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

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

	return nil
}

Michael Yang's avatar
Michael Yang committed
805
806
807
808
809
810
811
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
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
}

838
839
840
841
842
843
844
func DeleteModel(name string) error {
	mp := ParseModelPath(name)
	manifest, _, err := GetManifest(mp)
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
845
	deleteMap := make(map[string]struct{})
846
	for _, layer := range manifest.Layers {
Michael Yang's avatar
Michael Yang committed
847
		deleteMap[layer.Digest] = struct{}{}
848
	}
Michael Yang's avatar
Michael Yang committed
849
	deleteMap[manifest.Config.Digest] = struct{}{}
850
851
852
853
854
855

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

856
	fp, err := mp.GetManifestPath()
857
858
859
860
861
	if err != nil {
		return err
	}
	err = os.Remove(fp)
	if err != nil {
862
		slog.Info(fmt.Sprintf("couldn't remove manifest file '%s': %v", fp, err))
863
864
865
866
867
868
		return err
	}

	return nil
}

Patrick Devine's avatar
Patrick Devine committed
869
func ShowModelfile(model *Model) (string, error) {
Michael Yang's avatar
Michael Yang committed
870
	var mt struct {
Patrick Devine's avatar
Patrick Devine committed
871
		*Model
Michael Yang's avatar
Michael Yang committed
872
		From       string
Michael Yang's avatar
Michael Yang committed
873
		Parameters map[string][]any
Patrick Devine's avatar
Patrick Devine committed
874
875
	}

Michael Yang's avatar
Michael Yang committed
876
	mt.Parameters = make(map[string][]any)
Patrick Devine's avatar
Patrick Devine committed
877
	for k, v := range model.Options {
Michael Yang's avatar
Michael Yang committed
878
879
880
		if s, ok := v.([]any); ok {
			mt.Parameters[k] = s
			continue
Patrick Devine's avatar
Patrick Devine committed
881
882
		}

Michael Yang's avatar
Michael Yang committed
883
		mt.Parameters[k] = []any{v}
Patrick Devine's avatar
Patrick Devine committed
884
885
	}

Michael Yang's avatar
Michael Yang committed
886
887
	mt.Model = model
	mt.From = model.ModelPath
Patrick Devine's avatar
Patrick Devine committed
888

889
890
	if model.ParentModel != "" {
		mt.From = model.ParentModel
Patrick Devine's avatar
Patrick Devine committed
891
892
893
894
895
896
897
898
	}

	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 }}"""
899
900

{{- if .System }}
Patrick Devine's avatar
Patrick Devine committed
901
SYSTEM """{{ .System }}"""
902
{{- end }}
903
904
905
906

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

Michael Yang's avatar
Michael Yang committed
908
909
910
911
{{- range $k, $v := .Parameters }}
{{- range $parameter := $v }}
PARAMETER {{ $k }} {{ printf "%#v" $parameter }}
{{- end }}
Michael Yang's avatar
Michael Yang committed
912
{{- end }}`
Patrick Devine's avatar
Patrick Devine committed
913
914
915

	tmpl, err := template.New("").Parse(modelFile)
	if err != nil {
916
		slog.Info(fmt.Sprintf("error parsing template: %q", err))
Patrick Devine's avatar
Patrick Devine committed
917
918
919
920
921
922
		return "", err
	}

	var buf bytes.Buffer

	if err = tmpl.Execute(&buf, mt); err != nil {
923
		slog.Info(fmt.Sprintf("error executing template: %q", err))
Patrick Devine's avatar
Patrick Devine committed
924
925
926
927
928
929
		return "", err
	}

	return buf.String(), nil
}

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

934
935
936
937
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
		return fmt.Errorf("insecure protocol http")
	}

Patrick Devine's avatar
Patrick Devine committed
938
	manifest, _, err := GetManifest(mp)
939
	if err != nil {
940
		fn(api.ProgressResponse{Status: "couldn't retrieve manifest"})
941
942
943
944
		return err
	}

	var layers []*Layer
Jeffrey Morgan's avatar
Jeffrey Morgan committed
945
	layers = append(layers, manifest.Layers...)
Michael Yang's avatar
Michael Yang committed
946
	layers = append(layers, manifest.Config)
947
948

	for _, layer := range layers {
Michael Yang's avatar
Michael Yang committed
949
		if err := uploadBlob(ctx, mp, layer, regOpts, fn); err != nil {
950
			slog.Info(fmt.Sprintf("error uploading blob: %v", err))
951
952
953
			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())
			}
954
955
			return err
		}
956
957
	}

958
	fn(api.ProgressResponse{Status: "pushing manifest"})
Michael Yang's avatar
Michael Yang committed
959
960
	requestURL := mp.BaseURL()
	requestURL = requestURL.JoinPath("v2", mp.GetNamespaceRepository(), "manifests", mp.Tag)
961
962
963
964
965
966

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

Michael Yang's avatar
Michael Yang committed
967
968
	headers := make(http.Header)
	headers.Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json")
Michael Yang's avatar
Michael Yang committed
969
	resp, err := makeRequestWithRetry(ctx, http.MethodPut, requestURL, headers, bytes.NewReader(manifestJSON), regOpts)
970
971
972
973
974
	if err != nil {
		return err
	}
	defer resp.Body.Close()

975
	fn(api.ProgressResponse{Status: "success"})
976
977
978
979

	return nil
}

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

983
984
985
986
987
	var manifest *ManifestV2
	var err error
	var noprune string

	// build deleteMap to prune unused layers
Michael Yang's avatar
Michael Yang committed
988
	deleteMap := make(map[string]struct{})
989
990
991
992
993
994
995
996
997

	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
998
				deleteMap[l.Digest] = struct{}{}
999
			}
Michael Yang's avatar
Michael Yang committed
1000
			deleteMap[manifest.Config.Digest] = struct{}{}
1001
1002
1003
		}
	}

1004
1005
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
		return fmt.Errorf("insecure protocol http")
1006
	}
1007

1008
	fn(api.ProgressResponse{Status: "pulling manifest"})
1009

1010
	manifest, err = pullModelManifest(ctx, mp, regOpts)
1011
	if err != nil {
1012
		return fmt.Errorf("pull model manifest: %s", err)
1013
1014
1015
	}

	var layers []*Layer
Bruce MacDonald's avatar
Bruce MacDonald committed
1016
	layers = append(layers, manifest.Layers...)
Michael Yang's avatar
Michael Yang committed
1017
	layers = append(layers, manifest.Config)
1018
1019

	for _, layer := range layers {
1020
1021
1022
1023
1024
1025
1026
1027
		if err := downloadBlob(
			ctx,
			downloadOpts{
				mp:      mp,
				digest:  layer.Digest,
				regOpts: regOpts,
				fn:      fn,
			}); err != nil {
1028
1029
			return err
		}
1030
		delete(deleteMap, layer.Digest)
1031
	}
1032
	delete(deleteMap, manifest.Config.Digest)
1033

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

1052
	fn(api.ProgressResponse{Status: "writing manifest"})
1053

1054
	manifestJSON, err := json.Marshal(manifest)
1055
1056
1057
1058
	if err != nil {
		return err
	}

1059
	fp, err := mp.GetManifestPath()
1060
1061
1062
	if err != nil {
		return err
	}
1063
1064
1065
	if err := os.MkdirAll(filepath.Dir(fp), 0o755); err != nil {
		return err
	}
1066

Bruce MacDonald's avatar
Bruce MacDonald committed
1067
	err = os.WriteFile(fp, manifestJSON, 0o644)
1068
	if err != nil {
1069
		slog.Info(fmt.Sprintf("couldn't write to %s", fp))
1070
1071
1072
		return err
	}

1073
1074
1075
1076
1077
1078
1079
1080
	if noprune == "" {
		fn(api.ProgressResponse{Status: "removing any unused layers"})
		err = deleteUnusedLayers(nil, deleteMap, false)
		if err != nil {
			return err
		}
	}

1081
	fn(api.ProgressResponse{Status: "success"})
1082
1083
1084
1085

	return nil
}

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

Michael Yang's avatar
Michael Yang committed
1089
1090
	headers := make(http.Header)
	headers.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json")
Michael Yang's avatar
Michael Yang committed
1091
	resp, err := makeRequestWithRetry(ctx, http.MethodGet, requestURL, headers, nil, regOpts)
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
	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
1106
func GetSHA256Digest(r io.Reader) (string, int64) {
Michael Yang's avatar
Michael Yang committed
1107
1108
1109
1110
1111
1112
	h := sha256.New()
	n, err := io.Copy(h, r)
	if err != nil {
		log.Fatal(err)
	}

Michael Yang's avatar
Michael Yang committed
1113
	return fmt.Sprintf("sha256:%x", h.Sum(nil)), n
1114
1115
}

1116
1117
var errUnauthorized = fmt.Errorf("unauthorized")

Michael Yang's avatar
Michael Yang committed
1118
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
1119
	for i := 0; i < 2; i++ {
Michael Yang's avatar
Michael Yang committed
1120
		resp, err := makeRequest(ctx, method, requestURL, headers, body, regOpts)
Michael Yang's avatar
Michael Yang committed
1121
		if err != nil {
Michael Yang's avatar
Michael Yang committed
1122
			if !errors.Is(err, context.Canceled) {
1123
				slog.Info(fmt.Sprintf("request failed: %v", err))
Michael Yang's avatar
Michael Yang committed
1124
1125
			}

Michael Yang's avatar
Michael Yang committed
1126
1127
			return nil, err
		}
Michael Yang's avatar
Michael Yang committed
1128
1129
1130
1131

		switch {
		case resp.StatusCode == http.StatusUnauthorized:
			// Handle authentication error with one retry
Michael Yang's avatar
Michael Yang committed
1132
1133
			challenge := parseRegistryChallenge(resp.Header.Get("www-authenticate"))
			token, err := getAuthorizationToken(ctx, challenge)
Michael Yang's avatar
Michael Yang committed
1134
1135
1136
			if err != nil {
				return nil, err
			}
Michael Yang's avatar
Michael Yang committed
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
			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
1154
1155
1156
		}
	}

Michael Yang's avatar
Michael Yang committed
1157
	return nil, errUnauthorized
Michael Yang's avatar
Michael Yang committed
1158
1159
}

Michael Yang's avatar
Michael Yang committed
1160
1161
1162
1163
1164
1165
1166
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
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
1193
	resp, err := http.DefaultClient.Do(req)
Michael Yang's avatar
Michael Yang committed
1194
1195
1196
1197
1198
1199
1200
	if err != nil {
		return nil, err
	}

	return resp, nil
}

Patrick Devine's avatar
Patrick Devine committed
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
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
1224
func parseRegistryChallenge(authStr string) registryChallenge {
Patrick Devine's avatar
Patrick Devine committed
1225
1226
	authStr = strings.TrimPrefix(authStr, "Bearer ")

Michael Yang's avatar
Michael Yang committed
1227
	return registryChallenge{
Patrick Devine's avatar
Patrick Devine committed
1228
1229
1230
1231
1232
1233
		Realm:   getValue(authStr, "realm"),
		Service: getValue(authStr, "service"),
		Scope:   getValue(authStr, "scope"),
	}
}

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

Michael Yang's avatar
Michael Yang committed
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
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 {
1250
		return fmt.Errorf("%w: want %s, got %s", errDigestMismatch, digest, fileDigest)
Michael Yang's avatar
Michael Yang committed
1251
1252
1253
1254
	}

	return nil
}