images.go 28.7 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
657
658
	for _, f := range r.File {
		fpath := filepath.Join(tempDir, f.Name)
		outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
		if err != nil {
			return "", err
		}

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

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

		outFile.Close()
		rc.Close()
	}

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

	SupportedArchs := []string{
		"MistralForCausalLM",
659
		"GemmaForCausalLM",
660
661
662
663
664
665
666
667
	}

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

668
669
	fn(api.ProgressResponse{Status: "processing safetensors"})
	t, err := convert.GetSafeTensors(tempDir, params)
670
671
672
673
	if err != nil {
		return "", err
	}

674
	vocab, err := convert.LoadTokens(tempDir, params)
675
676
677
678
	if err != nil {
		return "", err
	}

679
680
	fn(api.ProgressResponse{Status: "converting model"})
	path, err = convert.WriteGGUF(name, t, params, vocab)
681
682
683
684
	if err != nil {
		return "", err
	}

685
	return path, nil
686
687
}

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

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

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

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

	return nil
}

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

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

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

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

		// 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
744
			// nolint: nilerr
Michael Yang's avatar
Michael Yang committed
745
746
747
748
749
750
751
752
			return nil
		}

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

		delete(deleteMap, manifest.Config.Digest)
753
		return nil
Michael Yang's avatar
Michael Yang committed
754
755
756
	}

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

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

777
778
779
780
	return nil
}

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

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

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

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

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

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

	return nil
}

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

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

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

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

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

	return nil
}

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

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

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

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

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

	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 }}"""
907
908

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

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

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

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

	var buf bytes.Buffer

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

	return buf.String(), nil
}

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

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

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

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

	for _, layer := range layers {
Michael Yang's avatar
Michael Yang committed
957
		if err := uploadBlob(ctx, mp, layer, regOpts, fn); err != nil {
958
			slog.Info(fmt.Sprintf("error uploading blob: %v", err))
959
960
961
			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())
			}
962
963
			return err
		}
964
965
	}

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

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

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

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

	return nil
}

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

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

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

	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
1006
				deleteMap[l.Digest] = struct{}{}
1007
			}
Michael Yang's avatar
Michael Yang committed
1008
			deleteMap[manifest.Config.Digest] = struct{}{}
1009
1010
1011
		}
	}

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

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

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

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

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

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

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

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

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

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

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

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

	return nil
}

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

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

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

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

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

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

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

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

Michael Yang's avatar
Michael Yang committed
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
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
1201
	resp, err := http.DefaultClient.Do(req)
Michael Yang's avatar
Michael Yang committed
1202
1203
1204
1205
1206
1207
1208
	if err != nil {
		return nil, err
	}

	return resp, nil
}

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

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

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

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

	return nil
}