images.go 28.7 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
	"reflect"
Michael Yang's avatar
Michael Yang committed
18
	"runtime"
19
20
	"strconv"
	"strings"
Quinn Slack's avatar
Quinn Slack committed
21
	"text/template"
22

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

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

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

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

51
func (m *Model) Prompt(request api.GenerateRequest) (string, error) {
52
53
54
55
56
57
	t := m.Template
	if request.Template != "" {
		t = request.Template
	}

	tmpl, err := template.New("").Parse(t)
58
59
60
61
62
	if err != nil {
		return "", err
	}

	var vars struct {
Michael Yang's avatar
Michael Yang committed
63
		First  bool
64
65
66
67
		System string
		Prompt string
	}

Michael Yang's avatar
Michael Yang committed
68
	vars.First = len(request.Context) == 0
69
	vars.System = m.System
70
71
	vars.Prompt = request.Prompt

72
73
74
75
	if request.System != "" {
		vars.System = request.System
	}

76
77
78
79
80
81
82
83
	var sb strings.Builder
	if err := tmpl.Execute(&sb, vars); err != nil {
		return "", err
	}

	return sb.String(), nil
}

84
85
86
87
88
89
90
91
92
93
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
94
	Size      int64  `json:"size"`
Michael Yang's avatar
Michael Yang committed
95
	From      string `json:"from,omitempty"`
96
97
}

Michael Yang's avatar
Michael Yang committed
98
type LayerReader struct {
99
	Layer
Michael Yang's avatar
Michael Yang committed
100
	io.Reader
101
102
103
}

type ConfigV2 struct {
Michael Yang's avatar
Michael Yang committed
104
105
106
107
108
	ModelFormat string `json:"model_format"`
	ModelFamily string `json:"model_family"`
	ModelType   string `json:"model_type"`
	FileType    string `json:"file_type"`
	RootFS      RootFS `json:"rootfs"`
109
110

	// required by spec
111
112
113
114
115
116
117
118
119
	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
120
func (m *ManifestV2) GetTotalSize() (total int64) {
Patrick Devine's avatar
Patrick Devine committed
121
122
123
	for _, layer := range m.Layers {
		total += layer.Size
	}
Michael Yang's avatar
Michael Yang committed
124

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

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

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

	var manifest *ManifestV2

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

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

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

Patrick Devine's avatar
Patrick Devine committed
153
	return manifest, shaStr, nil
154
155
156
}

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

	model := &Model{
164
165
166
167
168
		Name:      mp.GetFullTagname(),
		ShortName: mp.GetShortTagname(),
		Digest:    digest,
		Template:  "{{ .Prompt }}",
		License:   []string{},
169
170
171
	}

	for _, layer := range manifest.Layers {
Patrick Devine's avatar
Patrick Devine committed
172
		filename, err := GetBlobsPath(layer.Digest)
173
174
175
176
		if err != nil {
			return nil, err
		}

177
178
179
		switch layer.MediaType {
		case "application/vnd.ollama.image.model":
			model.ModelPath = filename
Patrick Devine's avatar
Patrick Devine committed
180
			model.OriginalModel = layer.From
181
		case "application/vnd.ollama.image.embed":
182
183
184
			// 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.")
185
186
		case "application/vnd.ollama.image.adapter":
			model.AdapterPaths = append(model.AdapterPaths, filename)
187
188
189
190
191
192
193
194
195
		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)
196
197
198
			if err != nil {
				return nil, err
			}
199
200

			model.System = string(bts)
201
202
203
204
205
206
207
		case "application/vnd.ollama.image.prompt":
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}

			model.Template = string(bts)
208
		case "application/vnd.ollama.image.params":
Michael Yang's avatar
Michael Yang committed
209
210
211
212
213
			params, err := os.Open(filename)
			if err != nil {
				return nil, err
			}
			defer params.Close()
214

215
			// parse model options parameters into a map so that we can see which fields have been specified explicitly
216
			if err = json.NewDecoder(params).Decode(&model.Options); err != nil {
217
218
				return nil, err
			}
Patrick Devine's avatar
Patrick Devine committed
219
220
221
222
223
224
		case "application/vnd.ollama.image.license":
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}
			model.License = append(model.License, string(bts))
225
226
227
228
229
230
		}
	}

	return model, nil
}

231
232
func filenameWithPath(path, f string) (string, error) {
	// if filePath starts with ~/, replace it with the user's home directory.
Michael Yang's avatar
Michael Yang committed
233
234
	if strings.HasPrefix(f, fmt.Sprintf("~%s", string(os.PathSeparator))) {
		parts := strings.Split(f, string(os.PathSeparator))
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
		home, err := os.UserHomeDir()
		if err != nil {
			return "", fmt.Errorf("failed to open file: %v", err)
		}

		f = filepath.Join(home, filepath.Join(parts[1:]...))
	}

	// if filePath is not an absolute path, make it relative to the modelfile path
	if !filepath.IsAbs(f) {
		f = filepath.Join(filepath.Dir(path), f)
	}

	return f, nil
}

Michael Yang's avatar
Michael Yang committed
251
252
253
254
func realpath(p string) string {
	abspath, err := filepath.Abs(p)
	if err != nil {
		return p
255
256
	}

Michael Yang's avatar
Michael Yang committed
257
	home, err := os.UserHomeDir()
258
	if err != nil {
Michael Yang's avatar
Michael Yang committed
259
		return abspath
260
261
	}

Michael Yang's avatar
Michael Yang committed
262
263
264
265
	if p == "~" {
		return home
	} else if strings.HasPrefix(p, "~/") {
		return filepath.Join(home, p[2:])
266
267
	}

Michael Yang's avatar
Michael Yang committed
268
269
270
271
	return abspath
}

func CreateModel(ctx context.Context, name string, commands []parser.Command, fn func(resp api.ProgressResponse)) error {
272
273
	config := ConfigV2{
		OS:           "linux",
Michael Yang's avatar
Michael Yang committed
274
		Architecture: "amd64",
275
276
	}

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

Michael Yang's avatar
Michael Yang committed
279
	var layers []*LayerReader
Michael Yang's avatar
Michael Yang committed
280

281
	params := make(map[string][]string)
Michael Yang's avatar
Michael Yang committed
282
283
	fromParams := make(map[string]any)

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

288
289
		switch c.Name {
		case "model":
Michael Yang's avatar
Michael Yang committed
290
			bin, err := os.Open(realpath(c.Args))
291
			if err != nil {
Michael Yang's avatar
Michael Yang committed
292
293
294
295
296
297
298
				// 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 {
299
300
301
						return err
					}

Michael Yang's avatar
Michael Yang committed
302
					manifest, _, err = GetManifest(modelpath)
303
304
305
					if err != nil {
						return err
					}
Michael Yang's avatar
Michael Yang committed
306
307
				case err != nil:
					return err
308
				}
309

310
				fn(api.ProgressResponse{Status: "reading model metadata"})
Michael Yang's avatar
Michael Yang committed
311
				fromConfigPath, err := GetBlobsPath(manifest.Config.Digest)
Michael Yang's avatar
Michael Yang committed
312
313
314
315
				if err != nil {
					return err
				}

Michael Yang's avatar
Michael Yang committed
316
				fromConfigFile, err := os.Open(fromConfigPath)
Michael Yang's avatar
Michael Yang committed
317
318
319
				if err != nil {
					return err
				}
Michael Yang's avatar
Michael Yang committed
320
				defer fromConfigFile.Close()
Michael Yang's avatar
Michael Yang committed
321

Michael Yang's avatar
Michael Yang committed
322
323
				var fromConfig ConfigV2
				if err := json.NewDecoder(fromConfigFile).Decode(&fromConfig); err != nil {
Michael Yang's avatar
Michael Yang committed
324
325
326
					return err
				}

Michael Yang's avatar
Michael Yang committed
327
328
329
330
				config.ModelFormat = fromConfig.ModelFormat
				config.ModelFamily = fromConfig.ModelFamily
				config.ModelType = fromConfig.ModelType
				config.FileType = fromConfig.FileType
Michael Yang's avatar
Michael Yang committed
331

Michael Yang's avatar
Michael Yang committed
332
333
334
335
				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
336
337
338
339
						if err != nil {
							return err
						}

Michael Yang's avatar
Michael Yang committed
340
						fromParamsFile, err := os.Open(fromParamsPath)
Michael Yang's avatar
Michael Yang committed
341
342
343
						if err != nil {
							return err
						}
Michael Yang's avatar
Michael Yang committed
344
						defer fromParamsFile.Close()
Michael Yang's avatar
Michael Yang committed
345

Michael Yang's avatar
Michael Yang committed
346
						if err := json.NewDecoder(fromParamsFile).Decode(&fromParams); err != nil {
Michael Yang's avatar
Michael Yang committed
347
348
349
350
							return err
						}
					}

Michael Yang's avatar
Michael Yang committed
351
					layer, err := GetLayerWithBufferFromLayer(layer)
352
353
354
					if err != nil {
						return err
					}
Michael Yang's avatar
Michael Yang committed
355
356
357

					layer.From = modelpath.GetShortTagname()
					layers = append(layers, layer)
358
				}
Michael Yang's avatar
Michael Yang committed
359
360
361

				deleteMap[manifest.Config.Digest] = struct{}{}
				continue
362
			}
Michael Yang's avatar
Michael Yang committed
363
			defer bin.Close()
364

Michael Yang's avatar
Michael Yang committed
365
366
			fn(api.ProgressResponse{Status: "creating model layer"})
			ggml, err := llm.DecodeGGML(bin)
Michael Yang's avatar
Michael Yang committed
367
368
			if err != nil {
				return err
369
370
			}

Michael Yang's avatar
Michael Yang committed
371
372
373
374
375
376
377
378
379
380
			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
			}
381

Michael Yang's avatar
Michael Yang committed
382
383
384
385
386
			layer.MediaType = mediatype
			layers = append(layers, layer)
		case "adapter":
			fn(api.ProgressResponse{Status: "creating adapter layer"})
			bin, err := os.Open(realpath(c.Args))
387
			if err != nil {
Michael Yang's avatar
Michael Yang committed
388
				return err
389
			}
Michael Yang's avatar
Michael Yang committed
390
			defer bin.Close()
391

Michael Yang's avatar
Michael Yang committed
392
			layer, err := CreateLayer(bin)
393
			if err != nil {
Michael Yang's avatar
Michael Yang committed
394
				return err
395
			}
Bruce MacDonald's avatar
Bruce MacDonald committed
396

Michael Yang's avatar
Michael Yang committed
397
398
399
400
401
402
			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
403
404
405
406
407
			layer, err := CreateLayer(strings.NewReader(c.Args))
			if err != nil {
				return err
			}

408
			if layer.Size > 0 {
Michael Yang's avatar
Michael Yang committed
409
				layer.MediaType = mediatype
410
411
				layers = append(layers, layer)
			}
Michael Yang's avatar
Michael Yang committed
412
413
414
415
416
		case "template", "system":
			fn(api.ProgressResponse{Status: fmt.Sprintf("creating %s layer", c.Name)})

			// remove duplicates layers
			layers = removeLayerFromLayers(layers, mediatype)
417

418
			layer, err := CreateLayer(strings.NewReader(c.Args))
419
			if err != nil {
420
				return err
421
			}
422

423
			if layer.Size > 0 {
Michael Yang's avatar
Michael Yang committed
424
				layer.MediaType = mediatype
425
426
				layers = append(layers, layer)
			}
427
		default:
428
			params[c.Name] = append(params[c.Name], c.Args)
429
430
431
		}
	}

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

435
		formattedParams, err := formatParams(params)
436
		if err != nil {
Michael Yang's avatar
Michael Yang committed
437
			return err
438
		}
439

Michael Yang's avatar
Michael Yang committed
440
		for k, v := range fromParams {
Michael Yang's avatar
Michael Yang committed
441
442
443
444
445
			if _, ok := formattedParams[k]; !ok {
				formattedParams[k] = v
			}
		}

Michael Yang's avatar
Michael Yang committed
446
		if config.ModelType == "65B" {
Michael Yang's avatar
Michael Yang committed
447
			if gqa, ok := formattedParams["gqa"].(int); ok && gqa == 8 {
Michael Yang's avatar
Michael Yang committed
448
449
450
451
				config.ModelType = "70B"
			}
		}

Michael Yang's avatar
Michael Yang committed
452
453
		var b bytes.Buffer
		if err := json.NewEncoder(&b).Encode(formattedParams); err != nil {
454
455
456
			return err
		}

Michael Yang's avatar
Michael Yang committed
457
458
		fn(api.ProgressResponse{Status: "creating config layer"})
		layer, err := CreateLayer(bytes.NewReader(b.Bytes()))
459
		if err != nil {
Michael Yang's avatar
Michael Yang committed
460
			return err
461
		}
Michael Yang's avatar
Michael Yang committed
462
463
464

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

467
468
469
470
471
	digests, err := getLayerDigests(layers)
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
472
	configLayer, err := createConfigLayer(config, digests)
473
474
475
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
476
477
478

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

Michael Yang's avatar
Michael Yang committed
480
	if err := SaveLayers(layers, fn, false); err != nil {
481
482
483
		return err
	}

Michael Yang's avatar
Michael Yang committed
484
485
486
487
488
489
	var contentLayers []*Layer
	for _, layer := range layers {
		contentLayers = append(contentLayers, &layer.Layer)
		delete(deleteMap, layer.Digest)
	}

490
	fn(api.ProgressResponse{Status: "writing manifest"})
Michael Yang's avatar
Michael Yang committed
491
	if err := CreateManifest(name, configLayer, contentLayers); err != nil {
492
493
494
		return err
	}

Michael Yang's avatar
Michael Yang committed
495
496
	if noprune := os.Getenv("OLLAMA_NOPRUNE"); noprune == "" {
		if err := deleteUnusedLayers(nil, deleteMap, false); err != nil {
497
498
499
500
			return err
		}
	}

501
	fn(api.ProgressResponse{Status: "success"})
502
503
504
	return nil
}

Michael Yang's avatar
Michael Yang committed
505
func removeLayerFromLayers(layers []*LayerReader, mediaType string) []*LayerReader {
Michael Yang's avatar
Michael Yang committed
506
507
508
	return slices.DeleteFunc(layers, func(layer *LayerReader) bool {
		return layer.MediaType == mediaType
	})
509
510
}

511
func SaveLayers(layers []*LayerReader, fn func(resp api.ProgressResponse), force bool) error {
512
513
	// Write each of the layers to disk
	for _, layer := range layers {
Patrick Devine's avatar
Patrick Devine committed
514
		fp, err := GetBlobsPath(layer.Digest)
515
516
517
		if err != nil {
			return err
		}
518
519

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

523
524
525
526
527
528
529
			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
530
			if _, err = io.Copy(out, layer.Reader); err != nil {
531
532
				return err
			}
Michael Yang's avatar
Michael Yang committed
533

534
		} else {
535
			fn(api.ProgressResponse{Status: fmt.Sprintf("using already created layer %s", layer.Digest)})
536
537
538
539
540
541
		}
	}

	return nil
}

Michael Yang's avatar
Michael Yang committed
542
func CreateManifest(name string, cfg *LayerReader, layers []*Layer) error {
543
	mp := ParseModelPath(name)
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
	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
	}

560
	fp, err := mp.GetManifestPath()
561
562
563
	if err != nil {
		return err
	}
564
565
566
	if err := os.MkdirAll(filepath.Dir(fp), 0o755); err != nil {
		return err
	}
567
	return os.WriteFile(fp, manifestJSON, 0o644)
568
569
}

Michael Yang's avatar
Michael Yang committed
570
func GetLayerWithBufferFromLayer(layer *Layer) (*LayerReader, error) {
Patrick Devine's avatar
Patrick Devine committed
571
	fp, err := GetBlobsPath(layer.Digest)
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
	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
}

590
591
// formatParams converts specified parameter options to their correct types
func formatParams(params map[string][]string) (map[string]interface{}, error) {
592
593
594
	opts := api.Options{}
	valueOpts := reflect.ValueOf(&opts).Elem() // names of the fields in the options struct
	typeOpts := reflect.TypeOf(opts)           // types of the fields in the options struct
Michael Yang's avatar
Michael Yang committed
595

596
	// build map of json struct tags to their types
Michael Yang's avatar
Michael Yang committed
597
598
599
600
601
602
603
604
	jsonOpts := make(map[string]reflect.StructField)
	for _, field := range reflect.VisibleFields(typeOpts) {
		jsonTag := strings.Split(field.Tag.Get("json"), ",")[0]
		if jsonTag != "" {
			jsonOpts[jsonTag] = field
		}
	}

605
	out := make(map[string]interface{})
Michael Yang's avatar
Michael Yang committed
606
	// iterate params and set values based on json struct tags
607
	for key, vals := range params {
Michael Yang's avatar
Michael Yang committed
608
609
610
611
612
		if opt, ok := jsonOpts[key]; ok {
			field := valueOpts.FieldByName(opt.Name)
			if field.IsValid() && field.CanSet() {
				switch field.Kind() {
				case reflect.Float32:
613
					floatVal, err := strconv.ParseFloat(vals[0], 32)
Michael Yang's avatar
Michael Yang committed
614
					if err != nil {
615
						return nil, fmt.Errorf("invalid float value %s", vals)
Michael Yang's avatar
Michael Yang committed
616
617
					}

Michael Yang's avatar
Michael Yang committed
618
					out[key] = float32(floatVal)
Michael Yang's avatar
Michael Yang committed
619
				case reflect.Int:
Michael Yang's avatar
Michael Yang committed
620
					intVal, err := strconv.ParseInt(vals[0], 10, 64)
Michael Yang's avatar
Michael Yang committed
621
					if err != nil {
622
						return nil, fmt.Errorf("invalid int value %s", vals)
Michael Yang's avatar
Michael Yang committed
623
624
					}

625
					out[key] = intVal
Michael Yang's avatar
Michael Yang committed
626
				case reflect.Bool:
627
					boolVal, err := strconv.ParseBool(vals[0])
Michael Yang's avatar
Michael Yang committed
628
					if err != nil {
629
						return nil, fmt.Errorf("invalid bool value %s", vals)
Michael Yang's avatar
Michael Yang committed
630
631
					}

632
					out[key] = boolVal
Michael Yang's avatar
Michael Yang committed
633
				case reflect.String:
634
					out[key] = vals[0]
635
				case reflect.Slice:
636
637
					// TODO: only string slices are supported right now
					out[key] = vals
Michael Yang's avatar
Michael Yang committed
638
639
640
641
642
643
644
				default:
					return nil, fmt.Errorf("unknown type %s for %s", field.Kind(), key)
				}
			}
		}
	}

645
	return out, nil
646
647
}

Michael Yang's avatar
Michael Yang committed
648
func getLayerDigests(layers []*LayerReader) ([]string, error) {
649
650
651
652
653
654
655
656
657
658
659
	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
660
661
func CreateLayer(f io.ReadSeeker) (*LayerReader, error) {
	digest, size := GetSHA256Digest(f)
662
	f.Seek(0, io.SeekStart)
663

Michael Yang's avatar
Michael Yang committed
664
	layer := &LayerReader{
665
666
667
668
669
		Layer: Layer{
			MediaType: "application/vnd.docker.image.rootfs.diff.tar",
			Digest:    digest,
			Size:      size,
		},
Michael Yang's avatar
Michael Yang committed
670
		Reader: f,
671
672
673
674
675
	}

	return layer, nil
}

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

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

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

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

	return nil
}

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

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

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

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

		// 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)
740
		return nil
Michael Yang's avatar
Michael Yang committed
741
742
743
	}

	if err := filepath.Walk(fp, walkFunc); err != nil {
Michael Yang's avatar
Michael Yang committed
744
745
		return err
	}
746
747

	// only delete the files which are still in the deleteMap
Michael Yang's avatar
Michael Yang committed
748
749
750
751
752
753
754
755
756
	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)
757
758
				continue
			}
Michael Yang's avatar
Michael Yang committed
759
760
		} else {
			log.Printf("wanted to remove: %s", fp)
761
762
763
		}
	}

764
765
766
767
	return nil
}

func PruneLayers() error {
Michael Yang's avatar
Michael Yang committed
768
	deleteMap := make(map[string]struct{})
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
	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
785
786
787
		if strings.HasPrefix(name, "sha256:") {
			deleteMap[name] = struct{}{}
		}
788
789
790
791
792
793
794
795
796
797
798
799
800
801
	}

	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
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
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
}

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

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

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

853
	fp, err := mp.GetManifestPath()
854
855
856
857
858
859
860
861
862
863
864
865
	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
866
func ShowModelfile(model *Model) (string, error) {
Michael Yang's avatar
Michael Yang committed
867
	var mt struct {
Patrick Devine's avatar
Patrick Devine committed
868
		*Model
Michael Yang's avatar
Michael Yang committed
869
		From       string
Michael Yang's avatar
Michael Yang committed
870
		Parameters map[string][]any
Patrick Devine's avatar
Patrick Devine committed
871
872
	}

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

Michael Yang's avatar
Michael Yang committed
880
		mt.Parameters[k] = []any{v}
Patrick Devine's avatar
Patrick Devine committed
881
882
	}

Michael Yang's avatar
Michael Yang committed
883
884
	mt.Model = model
	mt.From = model.ModelPath
Patrick Devine's avatar
Patrick Devine committed
885

Michael Yang's avatar
Michael Yang committed
886
	if model.OriginalModel != "" {
Daniel Reis's avatar
Daniel Reis committed
887
		mt.From = model.OriginalModel
Patrick Devine's avatar
Patrick Devine committed
888
889
890
891
892
893
894
895
	}

	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 }}"""
896
897

{{- if .System }}
Patrick Devine's avatar
Patrick Devine committed
898
SYSTEM """{{ .System }}"""
899
{{- end }}
900
901
902
903

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

Michael Yang's avatar
Michael Yang committed
905
906
907
908
{{- range $k, $v := .Parameters }}
{{- range $parameter := $v }}
PARAMETER {{ $k }} {{ printf "%#v" $parameter }}
{{- end }}
Michael Yang's avatar
Michael Yang committed
909
{{- end }}`
Patrick Devine's avatar
Patrick Devine committed
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926

	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
}

927
func PushModel(ctx context.Context, name string, regOpts *RegistryOptions, fn func(api.ProgressResponse)) error {
928
	mp := ParseModelPath(name)
929
930
	fn(api.ProgressResponse{Status: "retrieving manifest"})

931
932
933
934
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
		return fmt.Errorf("insecure protocol http")
	}

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

	var layers []*Layer
Jeffrey Morgan's avatar
Jeffrey Morgan committed
942
	layers = append(layers, manifest.Layers...)
943
944
945
	layers = append(layers, &manifest.Config)

	for _, layer := range layers {
Michael Yang's avatar
Michael Yang committed
946
		if err := uploadBlob(ctx, mp, layer, regOpts, fn); err != nil {
947
948
949
			log.Printf("error uploading blob: %v", err)
			return err
		}
950
951
	}

952
	fn(api.ProgressResponse{Status: "pushing manifest"})
Michael Yang's avatar
Michael Yang committed
953
954
	requestURL := mp.BaseURL()
	requestURL = requestURL.JoinPath("v2", mp.GetNamespaceRepository(), "manifests", mp.Tag)
955
956
957
958
959
960

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

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

969
	fn(api.ProgressResponse{Status: "success"})
970
971
972
973

	return nil
}

974
func PullModel(ctx context.Context, name string, regOpts *RegistryOptions, fn func(api.ProgressResponse)) error {
975
976
	mp := ParseModelPath(name)

977
978
979
980
981
	var manifest *ManifestV2
	var err error
	var noprune string

	// build deleteMap to prune unused layers
Michael Yang's avatar
Michael Yang committed
982
	deleteMap := make(map[string]struct{})
983
984
985
986
987
988
989
990
991

	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
992
				deleteMap[l.Digest] = struct{}{}
993
			}
Michael Yang's avatar
Michael Yang committed
994
			deleteMap[manifest.Config.Digest] = struct{}{}
995
996
997
		}
	}

998
999
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
		return fmt.Errorf("insecure protocol http")
1000
	}
1001

1002
	fn(api.ProgressResponse{Status: "pulling manifest"})
1003

1004
	manifest, err = pullModelManifest(ctx, mp, regOpts)
1005
	if err != nil {
1006
		return fmt.Errorf("pull model manifest: %s", err)
1007
1008
1009
	}

	var layers []*Layer
Bruce MacDonald's avatar
Bruce MacDonald committed
1010
	layers = append(layers, manifest.Layers...)
1011
1012
1013
	layers = append(layers, &manifest.Config)

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

Michael Yang's avatar
Michael Yang committed
1028
1029
1030
	fn(api.ProgressResponse{Status: "verifying sha256 digest"})
	for _, layer := range layers {
		if err := verifyBlob(layer.Digest); err != nil {
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
			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
1042
1043
1044
1045
			return err
		}
	}

1046
	fn(api.ProgressResponse{Status: "writing manifest"})
1047

1048
	manifestJSON, err := json.Marshal(manifest)
1049
1050
1051
1052
	if err != nil {
		return err
	}

1053
	fp, err := mp.GetManifestPath()
1054
1055
1056
	if err != nil {
		return err
	}
1057
1058
1059
	if err := os.MkdirAll(filepath.Dir(fp), 0o755); err != nil {
		return err
	}
1060

Bruce MacDonald's avatar
Bruce MacDonald committed
1061
	err = os.WriteFile(fp, manifestJSON, 0o644)
1062
1063
1064
1065
1066
	if err != nil {
		log.Printf("couldn't write to %s", fp)
		return err
	}

1067
1068
1069
1070
1071
1072
1073
1074
	if noprune == "" {
		fn(api.ProgressResponse{Status: "removing any unused layers"})
		err = deleteUnusedLayers(nil, deleteMap, false)
		if err != nil {
			return err
		}
	}

1075
	fn(api.ProgressResponse{Status: "success"})
1076
1077
1078
1079

	return nil
}

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

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

1099
1100
1101
1102
func createConfigLayer(config ConfigV2, layers []string) (*LayerReader, error) {
	config.RootFS = RootFS{
		Type:    "layers",
		DiffIDs: layers,
1103
1104
1105
1106
1107
1108
1109
	}

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

1110
	digest, size := GetSHA256Digest(bytes.NewBuffer(configJSON))
1111

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

Michael Yang's avatar
Michael Yang committed
1131
	return fmt.Sprintf("sha256:%x", h.Sum(nil)), n
1132
1133
}

Michael Yang's avatar
Michael Yang committed
1134
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
1135
	for try := 0; try < maxRetries; try++ {
Michael Yang's avatar
Michael Yang committed
1136
		resp, err := makeRequest(ctx, method, requestURL, headers, body, regOpts)
Michael Yang's avatar
Michael Yang committed
1137
1138
1139
1140
1141
		if err != nil {
			log.Printf("couldn't start upload: %v", err)
			return nil, err
		}

Michael Yang's avatar
Michael Yang committed
1142
1143
		switch {
		case resp.StatusCode == http.StatusUnauthorized:
Michael Yang's avatar
Michael Yang committed
1144
1145
			auth := resp.Header.Get("www-authenticate")
			authRedir := ParseAuthRedirectString(auth)
Michael Yang's avatar
Michael Yang committed
1146
			token, err := getAuthToken(ctx, authRedir)
Michael Yang's avatar
Michael Yang committed
1147
1148
1149
1150
1151
1152
			if err != nil {
				return nil, err
			}

			regOpts.Token = token
			if body != nil {
Michael Yang's avatar
Michael Yang committed
1153
				body.Seek(0, io.SeekStart)
Michael Yang's avatar
Michael Yang committed
1154
1155
1156
			}

			continue
Michael Yang's avatar
Michael Yang committed
1157
1158
		case resp.StatusCode == http.StatusNotFound:
			return nil, os.ErrNotExist
Michael Yang's avatar
Michael Yang committed
1159
		case resp.StatusCode >= http.StatusBadRequest:
Michael Yang's avatar
Michael Yang committed
1160
1161
1162
1163
1164
1165
			body, 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, body)
Michael Yang's avatar
Michael Yang committed
1166
1167
		default:
			return resp, nil
Michael Yang's avatar
Michael Yang committed
1168
1169
1170
		}
	}

Michael Yang's avatar
Michael Yang committed
1171
	return nil, errMaxRetriesExceeded
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
}