images.go 30.3 KB
Newer Older
1
2
3
package server

import (
4
	"archive/zip"
5
	"bytes"
6
	"context"
7
	"crypto/sha256"
8
	"encoding/base64"
Patrick Devine's avatar
Patrick Devine committed
9
	"encoding/hex"
10
11
12
13
	"encoding/json"
	"errors"
	"fmt"
	"io"
14
	"io/fs"
15
	"log"
16
	"log/slog"
17
	"net/http"
Michael Yang's avatar
Michael Yang committed
18
	"net/url"
19
20
	"os"
	"path/filepath"
Michael Yang's avatar
Michael Yang committed
21
	"runtime"
Michael Yang's avatar
Michael Yang committed
22
	"strconv"
23
24
	"strings"

Michael Yang's avatar
Michael Yang committed
25
26
	"golang.org/x/exp/slices"

27
	"github.com/ollama/ollama/api"
28
	"github.com/ollama/ollama/auth"
29
	"github.com/ollama/ollama/convert"
Michael Yang's avatar
Michael Yang committed
30
	"github.com/ollama/ollama/format"
31
32
	"github.com/ollama/ollama/llm"
	"github.com/ollama/ollama/parser"
33
	"github.com/ollama/ollama/types/errtypes"
Michael Yang's avatar
Michael Yang committed
34
	"github.com/ollama/ollama/types/model"
35
	"github.com/ollama/ollama/version"
36
37
)

Michael Yang's avatar
Michael Yang committed
38
39
40
41
42
43
44
type registryOptions struct {
	Insecure bool
	Username string
	Password string
	Token    string
}

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

62
63
64
65
func (m *Model) IsEmbedding() bool {
	return slices.Contains(m.Config.ModelFamilies, "bert") || slices.Contains(m.Config.ModelFamilies, "nomic-bert")
}

66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
func (m *Model) Commands() (cmds []parser.Command) {
	cmds = append(cmds, parser.Command{Name: "model", Args: m.ModelPath})

	if m.Template != "" {
		cmds = append(cmds, parser.Command{Name: "template", Args: m.Template})
	}

	if m.System != "" {
		cmds = append(cmds, parser.Command{Name: "system", Args: m.System})
	}

	for _, adapter := range m.AdapterPaths {
		cmds = append(cmds, parser.Command{Name: "adapter", Args: adapter})
	}

	for _, projector := range m.ProjectorPaths {
		cmds = append(cmds, parser.Command{Name: "projector", Args: projector})
	}

	for k, v := range m.Options {
		switch v := v.(type) {
		case []any:
			for _, s := range v {
				cmds = append(cmds, parser.Command{Name: k, Args: fmt.Sprintf("%v", s)})
			}
		default:
			cmds = append(cmds, parser.Command{Name: k, Args: fmt.Sprintf("%v", v)})
		}
	}

	for _, license := range m.License {
		cmds = append(cmds, parser.Command{Name: "license", Args: license})
	}

	for _, msg := range m.Messages {
		cmds = append(cmds, parser.Command{Name: "message", Args: fmt.Sprintf("%s %s", msg.Role, msg.Content)})
	}

	return cmds

}

108
109
110
type Message struct {
	Role    string `json:"role"`
	Content string `json:"content"`
111
112
113
114
115
}

type ManifestV2 struct {
	SchemaVersion int      `json:"schemaVersion"`
	MediaType     string   `json:"mediaType"`
Michael Yang's avatar
Michael Yang committed
116
	Config        *Layer   `json:"config"`
117
118
119
120
	Layers        []*Layer `json:"layers"`
}

type ConfigV2 struct {
121
122
123
124
125
126
	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"`

127
	// required by spec
128
129
	Architecture string `json:"architecture"`
	OS           string `json:"os"`
130
	RootFS       RootFS `json:"rootfs"`
131
132
}

Michael Yang's avatar
Michael Yang committed
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
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
	}
}

163
164
165
166
167
type RootFS struct {
	Type    string   `json:"type"`
	DiffIDs []string `json:"diff_ids"`
}

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

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

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

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

	var manifest *ManifestV2

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

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

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

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

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

	model := &Model{
212
213
214
215
216
		Name:      mp.GetFullTagname(),
		ShortName: mp.GetShortTagname(),
		Digest:    digest,
		Template:  "{{ .Prompt }}",
		License:   []string{},
Patrick Devine's avatar
Patrick Devine committed
217
		Size:      manifest.GetTotalSize(),
218
219
	}

220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
	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
	}

235
	for _, layer := range manifest.Layers {
Patrick Devine's avatar
Patrick Devine committed
236
		filename, err := GetBlobsPath(layer.Digest)
237
238
239
240
		if err != nil {
			return nil, err
		}

241
242
243
		switch layer.MediaType {
		case "application/vnd.ollama.image.model":
			model.ModelPath = filename
244
			model.ParentModel = layer.From
245
		case "application/vnd.ollama.image.embed":
246
247
			// Deprecated in versions  > 0.1.2
			// TODO: remove this warning in a future version
248
			slog.Info("WARNING: model contains embeddings, but embeddings in modelfiles have been deprecated and will be ignored.")
249
250
		case "application/vnd.ollama.image.adapter":
			model.AdapterPaths = append(model.AdapterPaths, filename)
Michael Yang's avatar
Michael Yang committed
251
252
		case "application/vnd.ollama.image.projector":
			model.ProjectorPaths = append(model.ProjectorPaths, filename)
253
254
255
256
257
258
259
260
261
		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)
262
263
264
			if err != nil {
				return nil, err
			}
265
266

			model.System = string(bts)
267
268
269
270
271
272
273
		case "application/vnd.ollama.image.prompt":
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}

			model.Template = string(bts)
274
		case "application/vnd.ollama.image.params":
Michael Yang's avatar
Michael Yang committed
275
276
277
278
279
			params, err := os.Open(filename)
			if err != nil {
				return nil, err
			}
			defer params.Close()
280

281
			// parse model options parameters into a map so that we can see which fields have been specified explicitly
282
			if err = json.NewDecoder(params).Decode(&model.Options); err != nil {
283
284
				return nil, err
			}
285
286
287
288
289
290
291
292
293
294
		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
295
296
297
298
299
300
		case "application/vnd.ollama.image.license":
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}
			model.License = append(model.License, string(bts))
301
302
303
304
305
306
		}
	}

	return model, nil
}

307
308
func realpath(mfDir, from string) string {
	abspath, err := filepath.Abs(from)
Michael Yang's avatar
Michael Yang committed
309
	if err != nil {
310
		return from
311
312
	}

Michael Yang's avatar
Michael Yang committed
313
	home, err := os.UserHomeDir()
314
	if err != nil {
Michael Yang's avatar
Michael Yang committed
315
		return abspath
316
317
	}

318
	if from == "~" {
Michael Yang's avatar
Michael Yang committed
319
		return home
320
321
322
323
324
325
326
	} 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)
327
328
	}

Michael Yang's avatar
Michael Yang committed
329
330
331
	return abspath
}

Michael Yang's avatar
Michael Yang committed
332
func CreateModel(ctx context.Context, name, modelFileDir, quantization string, commands []parser.Command, fn func(resp api.ProgressResponse)) error {
333
334
335
336
337
338
339
	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{}{}
		}
	}

340
341
	config := ConfigV2{
		OS:           "linux",
Michael Yang's avatar
Michael Yang committed
342
		Architecture: "amd64",
Michael Yang's avatar
Michael Yang committed
343
344
345
		RootFS: RootFS{
			Type: "layers",
		},
346
347
	}

Michael Yang's avatar
Michael Yang committed
348
	var layers Layers
349
	messages := []string{}
Michael Yang's avatar
Michael Yang committed
350

351
	params := make(map[string][]string)
Michael Yang's avatar
Michael Yang committed
352
353
	fromParams := make(map[string]any)

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

357
358
		switch c.Name {
		case "model":
Michael Yang's avatar
Michael Yang committed
359
360
361
362
363
364
365
366
367
			if strings.HasPrefix(c.Args, "@") {
				blobPath, err := GetBlobsPath(strings.TrimPrefix(c.Args, "@"))
				if err != nil {
					return err
				}

				c.Args = blobPath
			}

368
369
			pathName := realpath(modelFileDir, c.Args)

370
			ggufName, err := convertModel(name, pathName, fn)
371
			if err != nil {
372
				var pathErr *fs.PathError
373
374
375
				switch {
				case errors.Is(err, zip.ErrFormat):
					// it's not a safetensor archive
376
377
				case errors.As(err, &pathErr):
					// it's not a file on disk, could be a model reference
378
379
380
381
382
383
384
385
				default:
					return err
				}
			}

			if ggufName != "" {
				pathName = ggufName
				defer os.RemoveAll(ggufName)
Michael Yang's avatar
Michael Yang committed
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405

				if quantization != "" {
					quantization = strings.ToUpper(quantization)
					fn(api.ProgressResponse{Status: fmt.Sprintf("quantizing %s model to %s", "F16", quantization)})
					tempfile, err := os.CreateTemp(filepath.Dir(ggufName), quantization)
					if err != nil {
						return err
					}
					defer os.RemoveAll(tempfile.Name())

					if err := llm.Quantize(ggufName, tempfile.Name(), quantization); err != nil {
						return err
					}

					if err := tempfile.Close(); err != nil {
						return err
					}

					pathName = tempfile.Name()
				}
406
407
408
			}

			bin, err := os.Open(pathName)
409
			if err != nil {
Michael Yang's avatar
Michael Yang committed
410
411
412
413
414
415
				// 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
416
					if err := PullModel(ctx, c.Args, &registryOptions{}, fn); err != nil {
417
418
419
						return err
					}

Michael Yang's avatar
Michael Yang committed
420
					manifest, _, err = GetManifest(modelpath)
421
422
423
					if err != nil {
						return err
					}
Michael Yang's avatar
Michael Yang committed
424
425
				case err != nil:
					return err
426
				}
427

428
				fn(api.ProgressResponse{Status: "reading model metadata"})
Michael Yang's avatar
Michael Yang committed
429
				fromConfigPath, err := GetBlobsPath(manifest.Config.Digest)
Michael Yang's avatar
Michael Yang committed
430
431
432
433
				if err != nil {
					return err
				}

Michael Yang's avatar
Michael Yang committed
434
				fromConfigFile, err := os.Open(fromConfigPath)
Michael Yang's avatar
Michael Yang committed
435
436
437
				if err != nil {
					return err
				}
Michael Yang's avatar
Michael Yang committed
438
				defer fromConfigFile.Close()
Michael Yang's avatar
Michael Yang committed
439

Michael Yang's avatar
Michael Yang committed
440
441
				var fromConfig ConfigV2
				if err := json.NewDecoder(fromConfigFile).Decode(&fromConfig); err != nil {
Michael Yang's avatar
Michael Yang committed
442
443
444
					return err
				}

Bruce MacDonald's avatar
Bruce MacDonald committed
445
446
447
448
449
				// 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
450
451
452
453
				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
454

Michael Yang's avatar
Michael Yang committed
455
456
457
458
				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
459
460
461
462
						if err != nil {
							return err
						}

Michael Yang's avatar
Michael Yang committed
463
						fromParamsFile, err := os.Open(fromParamsPath)
Michael Yang's avatar
Michael Yang committed
464
465
466
						if err != nil {
							return err
						}
Michael Yang's avatar
Michael Yang committed
467
						defer fromParamsFile.Close()
Michael Yang's avatar
Michael Yang committed
468

Michael Yang's avatar
Michael Yang committed
469
						if err := json.NewDecoder(fromParamsFile).Decode(&fromParams); err != nil {
Michael Yang's avatar
Michael Yang committed
470
471
472
473
							return err
						}
					}

Michael Yang's avatar
Michael Yang committed
474
					layer, err := NewLayerFromLayer(layer.Digest, layer.MediaType, modelpath.GetShortTagname())
475
476
477
					if err != nil {
						return err
					}
Michael Yang's avatar
Michael Yang committed
478

Michael Yang's avatar
Michael Yang committed
479
					layers.Add(layer)
480
				}
Michael Yang's avatar
Michael Yang committed
481
482
483

				deleteMap[manifest.Config.Digest] = struct{}{}
				continue
484
			}
Michael Yang's avatar
Michael Yang committed
485
			defer bin.Close()
486

487
488
489
			var offset int64
			for {
				fn(api.ProgressResponse{Status: "creating model layer"})
490
491
492
				if _, err := bin.Seek(offset, io.SeekStart); err != nil {
					return err
				}
493

Michael Yang's avatar
Michael Yang committed
494
495
496
497
498
499
500
				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
501
				}
Michael Yang's avatar
Michael Yang committed
502

Michael Yang's avatar
Michael Yang committed
503
				config.SetModelFormat(ggml.Name())
Michael Yang's avatar
Michael Yang committed
504
505
506
				config.SetModelFamily(ggml.KV().Architecture())
				config.SetModelType(format.HumanNumber(ggml.KV().ParameterCount()))
				config.SetFileType(ggml.KV().FileType())
507

508
				mediatype := mediatype
Michael Yang's avatar
Michael Yang committed
509
				if ggml.KV().Architecture() == "clip" {
510
511
					mediatype = "application/vnd.ollama.image.projector"
				}
512

Michael Yang's avatar
Michael Yang committed
513
				sr := io.NewSectionReader(bin, offset, size)
514
515
516
517
518
519
520
				layer, err := NewLayer(sr, mediatype)
				if err != nil {
					return err
				}

				layers.Add(layer)

Michael Yang's avatar
Michael Yang committed
521
				offset += size
522
			}
Michael Yang's avatar
Michael Yang committed
523
		case "adapter":
524
525
526
527
528
529
530
531
			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
532

Michael Yang's avatar
Michael Yang committed
533
			fn(api.ProgressResponse{Status: "creating adapter layer"})
534
			bin, err := os.Open(realpath(modelFileDir, c.Args))
535
			if err != nil {
Michael Yang's avatar
Michael Yang committed
536
				return err
537
			}
Michael Yang's avatar
Michael Yang committed
538
			defer bin.Close()
539

Michael Yang's avatar
Michael Yang committed
540
			_, size, err := llm.DecodeGGML(bin)
Michael Yang's avatar
Michael Yang committed
541
542
543
544
			if err != nil {
				return err
			}

Michael Yang's avatar
Michael Yang committed
545
			sr := io.NewSectionReader(bin, 0, size)
Michael Yang's avatar
Michael Yang committed
546
			layer, err := NewLayer(sr, mediatype)
547
			if err != nil {
Michael Yang's avatar
Michael Yang committed
548
				return err
549
			}
Bruce MacDonald's avatar
Bruce MacDonald committed
550

Michael Yang's avatar
Michael Yang committed
551
			layers.Add(layer)
Michael Yang's avatar
Michael Yang committed
552
553
		case "license":
			fn(api.ProgressResponse{Status: "creating license layer"})
Michael Yang's avatar
Michael Yang committed
554
555
556

			bin := strings.NewReader(c.Args)
			layer, err := NewLayer(bin, mediatype)
Bruce MacDonald's avatar
Bruce MacDonald committed
557
558
559
560
			if err != nil {
				return err
			}

Michael Yang's avatar
Michael Yang committed
561
			layers.Add(layer)
Michael Yang's avatar
Michael Yang committed
562
563
564
		case "template", "system":
			fn(api.ProgressResponse{Status: fmt.Sprintf("creating %s layer", c.Name)})

Michael Yang's avatar
Michael Yang committed
565
566
			bin := strings.NewReader(c.Args)
			layer, err := NewLayer(bin, mediatype)
567
			if err != nil {
568
				return err
569
			}
570

Michael Yang's avatar
Michael Yang committed
571
			layers.Replace(layer)
572
573
		case "message":
			messages = append(messages, c.Args)
574
		default:
575
			params[c.Name] = append(params[c.Name], c.Args)
576
577
578
		}
	}

579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
	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
603
	if len(params) > 0 {
Michael Yang's avatar
Michael Yang committed
604
		fn(api.ProgressResponse{Status: "creating parameters layer"})
Michael Yang's avatar
Michael Yang committed
605

606
		formattedParams, err := api.FormatParams(params)
607
		if err != nil {
Michael Yang's avatar
Michael Yang committed
608
			return err
609
		}
610

Michael Yang's avatar
Michael Yang committed
611
		for k, v := range fromParams {
Michael Yang's avatar
Michael Yang committed
612
613
614
615
616
			if _, ok := formattedParams[k]; !ok {
				formattedParams[k] = v
			}
		}

Michael Yang's avatar
Michael Yang committed
617
618
		var b bytes.Buffer
		if err := json.NewEncoder(&b).Encode(formattedParams); err != nil {
619
620
621
			return err
		}

Michael Yang's avatar
Michael Yang committed
622
		fn(api.ProgressResponse{Status: "creating config layer"})
Michael Yang's avatar
Michael Yang committed
623
		layer, err := NewLayer(&b, "application/vnd.ollama.image.params")
624
		if err != nil {
Michael Yang's avatar
Michael Yang committed
625
			return err
626
		}
Michael Yang's avatar
Michael Yang committed
627

Michael Yang's avatar
Michael Yang committed
628
		layers.Replace(layer)
629
630
	}

Michael Yang's avatar
Michael Yang committed
631
632
633
	digests := make([]string, len(layers.items))
	for i, layer := range layers.items {
		digests[i] = layer.Digest
634
635
	}

Michael Yang's avatar
Michael Yang committed
636
	config.RootFS.DiffIDs = digests
Michael Yang's avatar
Michael Yang committed
637

Michael Yang's avatar
Michael Yang committed
638
639
	var b bytes.Buffer
	if err := json.NewEncoder(&b).Encode(config); err != nil {
640
641
642
		return err
	}

Michael Yang's avatar
Michael Yang committed
643
644
	configLayer, err := NewLayer(&b, "application/vnd.docker.container.image.v1+json")
	if err != nil {
645
646
647
		return err
	}

Michael Yang's avatar
Michael Yang committed
648
	delete(deleteMap, configLayer.Digest)
649

Michael Yang's avatar
Michael Yang committed
650
651
	for _, layer := range append(layers.items, configLayer) {
		committed, err := layer.Commit()
652
653
654
		if err != nil {
			return err
		}
655

Michael Yang's avatar
Michael Yang committed
656
657
658
		status := "writing layer"
		if !committed {
			status = "using already created layer"
659
660
		}

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

Michael Yang's avatar
Michael Yang committed
663
		delete(deleteMap, layer.Digest)
664
665
	}

Michael Yang's avatar
Michael Yang committed
666
667
	fn(api.ProgressResponse{Status: "writing manifest"})
	if err := WriteManifest(name, configLayer, layers.items); err != nil {
668
669
		return err
	}
670

Michael Yang's avatar
Michael Yang committed
671
672
673
	if noprune := os.Getenv("OLLAMA_NOPRUNE"); noprune == "" {
		if err := deleteUnusedLayers(nil, deleteMap, false); err != nil {
			return err
674
675
676
		}
	}

Michael Yang's avatar
Michael Yang committed
677
678
	fn(api.ProgressResponse{Status: "success"})
	return nil
679
680
}

681
func convertModel(name, path string, fn func(resp api.ProgressResponse)) (string, error) {
682
	r, err := zip.OpenReader(path)
683
684
685
686
687
688
689
690
691
692
693
	if err != nil {
		return "", err
	}
	defer r.Close()

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

694
	fn(api.ProgressResponse{Status: "unpacking model metadata"})
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
	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()
	}

716
	mf, err := convert.GetModelFormat(tempDir)
717
718
719
720
	if err != nil {
		return "", err
	}

721
	params, err := mf.GetParams(tempDir)
722
723
	if err != nil {
		return "", err
724
725
	}

726
727
728
729
730
731
	mArch, err := mf.GetModelArch(name, tempDir, params)
	if err != nil {
		return "", err
	}

	fn(api.ProgressResponse{Status: "processing tensors"})
732
	if err := mArch.GetTensors(); err != nil {
733
734
735
		return "", err
	}

736
	if err := mArch.LoadVocab(); err != nil {
737
738
739
		return "", err
	}

740
	fn(api.ProgressResponse{Status: "converting model"})
741
	path, err = mArch.WriteGGUF()
742
743
744
745
	if err != nil {
		return "", err
	}

746
	return path, nil
747
748
}

Michael Yang's avatar
Michael Yang committed
749
func CopyModel(src, dst model.Name) error {
750
751
752
753
754
755
756
	if !dst.IsFullyQualified() {
		return model.Unqualified(dst)
	}
	if !src.IsFullyQualified() {
		return model.Unqualified(src)
	}

757
758
759
760
	if src.Filepath() == dst.Filepath() {
		return nil
	}

Michael Yang's avatar
Michael Yang committed
761
	manifests, err := GetManifestPath()
762
763
764
765
	if err != nil {
		return err
	}

766
	dstpath := filepath.Join(manifests, dst.Filepath())
Michael Yang's avatar
Michael Yang committed
767
	if err := os.MkdirAll(filepath.Dir(dstpath), 0o755); err != nil {
768
769
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
770

771
	srcpath := filepath.Join(manifests, src.Filepath())
Michael Yang's avatar
Michael Yang committed
772
	srcfile, err := os.Open(srcpath)
Patrick Devine's avatar
Patrick Devine committed
773
774
775
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
776
	defer srcfile.Close()
Patrick Devine's avatar
Patrick Devine committed
777

Michael Yang's avatar
Michael Yang committed
778
	dstfile, err := os.Create(dstpath)
Patrick Devine's avatar
Patrick Devine committed
779
780
781
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
782
	defer dstfile.Close()
Patrick Devine's avatar
Patrick Devine committed
783

Michael Yang's avatar
Michael Yang committed
784
785
	_, err = io.Copy(dstfile, srcfile)
	return err
Patrick Devine's avatar
Patrick Devine committed
786
787
}

Michael Yang's avatar
Michael Yang committed
788
func deleteUnusedLayers(skipModelPath *ModelPath, deleteMap map[string]struct{}, dryRun bool) error {
789
790
791
792
	fp, err := GetManifestPath()
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
793
794
795
796

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

Michael Yang's avatar
Michael Yang committed
799
800
801
802
		dir, file := filepath.Split(path)
		dir = strings.Trim(strings.TrimPrefix(dir, fp), string(os.PathSeparator))
		tag := strings.Join([]string{dir, file}, ":")
		fmp := ParseModelPath(tag)
803

Michael Yang's avatar
Michael Yang committed
804
		// skip the manifest we're trying to delete
805
		if skipModelPath != nil && skipModelPath.GetFullTagname() == fmp.GetFullTagname() {
Michael Yang's avatar
Michael Yang committed
806
			return nil
807
		}
Michael Yang's avatar
Michael Yang committed
808
809
810
811

		// 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
812
			// nolint: nilerr
Michael Yang's avatar
Michael Yang committed
813
814
815
816
817
818
819
820
			return nil
		}

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

		delete(deleteMap, manifest.Config.Digest)
821
		return nil
Michael Yang's avatar
Michael Yang committed
822
823
824
	}

	if err := filepath.Walk(fp, walkFunc); err != nil {
Michael Yang's avatar
Michael Yang committed
825
826
		return err
	}
827
828

	// only delete the files which are still in the deleteMap
Michael Yang's avatar
Michael Yang committed
829
830
831
	for k := range deleteMap {
		fp, err := GetBlobsPath(k)
		if err != nil {
832
			slog.Info(fmt.Sprintf("couldn't get file path for '%s': %v", k, err))
Michael Yang's avatar
Michael Yang committed
833
834
835
836
			continue
		}
		if !dryRun {
			if err := os.Remove(fp); err != nil {
837
				slog.Info(fmt.Sprintf("couldn't remove file '%s': %v", fp, err))
838
839
				continue
			}
Michael Yang's avatar
Michael Yang committed
840
		} else {
841
			slog.Info(fmt.Sprintf("wanted to remove: %s", fp))
842
843
844
		}
	}

845
846
847
848
	return nil
}

func PruneLayers() error {
Michael Yang's avatar
Michael Yang committed
849
	deleteMap := make(map[string]struct{})
850
851
852
853
854
855
856
	p, err := GetBlobsPath("")
	if err != nil {
		return err
	}

	blobs, err := os.ReadDir(p)
	if err != nil {
857
		slog.Info(fmt.Sprintf("couldn't read dir '%s': %v", p, err))
858
859
860
861
862
		return err
	}

	for _, blob := range blobs {
		name := blob.Name()
863
		name = strings.ReplaceAll(name, "-", ":")
Michael Yang's avatar
Michael Yang committed
864
865
866
		if strings.HasPrefix(name, "sha256:") {
			deleteMap[name] = struct{}{}
		}
867
868
	}

869
	slog.Info(fmt.Sprintf("total blobs: %d", len(deleteMap)))
870
871
872
873
874
875

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

876
	slog.Info(fmt.Sprintf("total unused blobs removed: %d", len(deleteMap)))
877
878
879
880

	return nil
}

Michael Yang's avatar
Michael Yang committed
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
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
}

914
915
916
917
918
919
920
func DeleteModel(name string) error {
	mp := ParseModelPath(name)
	manifest, _, err := GetManifest(mp)
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
921
	deleteMap := make(map[string]struct{})
922
	for _, layer := range manifest.Layers {
Michael Yang's avatar
Michael Yang committed
923
		deleteMap[layer.Digest] = struct{}{}
924
	}
Michael Yang's avatar
Michael Yang committed
925
	deleteMap[manifest.Config.Digest] = struct{}{}
926
927
928
929
930
931

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

932
	fp, err := mp.GetManifestPath()
933
934
935
936
937
	if err != nil {
		return err
	}
	err = os.Remove(fp)
	if err != nil {
938
		slog.Info(fmt.Sprintf("couldn't remove manifest file '%s': %v", fp, err))
939
940
941
942
943
944
		return err
	}

	return nil
}

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

949
950
951
952
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
		return fmt.Errorf("insecure protocol http")
	}

Patrick Devine's avatar
Patrick Devine committed
953
	manifest, _, err := GetManifest(mp)
954
	if err != nil {
955
		fn(api.ProgressResponse{Status: "couldn't retrieve manifest"})
956
957
958
959
		return err
	}

	var layers []*Layer
Jeffrey Morgan's avatar
Jeffrey Morgan committed
960
	layers = append(layers, manifest.Layers...)
Michael Yang's avatar
Michael Yang committed
961
	layers = append(layers, manifest.Config)
962
963

	for _, layer := range layers {
Michael Yang's avatar
Michael Yang committed
964
		if err := uploadBlob(ctx, mp, layer, regOpts, fn); err != nil {
965
			slog.Info(fmt.Sprintf("error uploading blob: %v", err))
966
967
			return err
		}
968
969
	}

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

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

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

987
	fn(api.ProgressResponse{Status: "success"})
988
989
990
991

	return nil
}

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

995
996
997
998
999
	var manifest *ManifestV2
	var err error
	var noprune string

	// build deleteMap to prune unused layers
Michael Yang's avatar
Michael Yang committed
1000
	deleteMap := make(map[string]struct{})
1001
1002
1003
1004
1005
1006
1007
1008
1009

	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
1010
				deleteMap[l.Digest] = struct{}{}
1011
			}
Michael Yang's avatar
Michael Yang committed
1012
			deleteMap[manifest.Config.Digest] = struct{}{}
1013
1014
1015
		}
	}

1016
1017
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
		return fmt.Errorf("insecure protocol http")
1018
	}
1019

1020
	fn(api.ProgressResponse{Status: "pulling manifest"})
1021

1022
	manifest, err = pullModelManifest(ctx, mp, regOpts)
1023
	if err != nil {
1024
		return fmt.Errorf("pull model manifest: %s", err)
1025
1026
1027
	}

	var layers []*Layer
Bruce MacDonald's avatar
Bruce MacDonald committed
1028
	layers = append(layers, manifest.Layers...)
Michael Yang's avatar
Michael Yang committed
1029
	layers = append(layers, manifest.Config)
1030
1031

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

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

1064
	fn(api.ProgressResponse{Status: "writing manifest"})
1065

1066
	manifestJSON, err := json.Marshal(manifest)
1067
1068
1069
1070
	if err != nil {
		return err
	}

1071
	fp, err := mp.GetManifestPath()
1072
1073
1074
	if err != nil {
		return err
	}
1075
1076
1077
	if err := os.MkdirAll(filepath.Dir(fp), 0o755); err != nil {
		return err
	}
1078

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

1085
1086
1087
1088
1089
1090
1091
1092
	if noprune == "" {
		fn(api.ProgressResponse{Status: "removing any unused layers"})
		err = deleteUnusedLayers(nil, deleteMap, false)
		if err != nil {
			return err
		}
	}

1093
	fn(api.ProgressResponse{Status: "success"})
1094
1095
1096
1097

	return nil
}

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

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

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

1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
var errUnauthorized = fmt.Errorf("unauthorized: access denied")

// getTokenSubject returns the subject of a JWT token, it does not validate the token
func getTokenSubject(token string) string {
	parts := strings.Split(token, ".")
	if len(parts) != 3 {
		slog.Error("jwt token does not contain 3 parts")
		return ""
	}

	payload := parts[1]
	payloadBytes, err := base64.RawURLEncoding.DecodeString(payload)
	if err != nil {
		slog.Error(fmt.Sprintf("failed to decode jwt payload: %v", err))
		return ""
	}

	var payloadMap map[string]interface{}
	if err := json.Unmarshal(payloadBytes, &payloadMap); err != nil {
		slog.Error(fmt.Sprintf("failed to unmarshal payload JSON: %v", err))
		return ""
	}

	sub, ok := payloadMap["sub"]
	if !ok {
		slog.Error("jwt does not contain 'sub' field")
		return ""
	}

	return fmt.Sprintf("%s", sub)
}
1159

Michael Yang's avatar
Michael Yang committed
1160
func makeRequestWithRetry(ctx context.Context, method string, requestURL *url.URL, headers http.Header, body io.ReadSeeker, regOpts *registryOptions) (*http.Response, error) {
1161
	anonymous := true // access will default to anonymous if no user is found associated with the public key
Michael Yang's avatar
Michael Yang committed
1162
	for i := 0; i < 2; i++ {
Michael Yang's avatar
Michael Yang committed
1163
		resp, err := makeRequest(ctx, method, requestURL, headers, body, regOpts)
Michael Yang's avatar
Michael Yang committed
1164
		if err != nil {
Michael Yang's avatar
Michael Yang committed
1165
			if !errors.Is(err, context.Canceled) {
1166
				slog.Info(fmt.Sprintf("request failed: %v", err))
Michael Yang's avatar
Michael Yang committed
1167
1168
			}

Michael Yang's avatar
Michael Yang committed
1169
1170
			return nil, err
		}
Michael Yang's avatar
Michael Yang committed
1171
1172
1173
1174

		switch {
		case resp.StatusCode == http.StatusUnauthorized:
			// Handle authentication error with one retry
Michael Yang's avatar
Michael Yang committed
1175
1176
			challenge := parseRegistryChallenge(resp.Header.Get("www-authenticate"))
			token, err := getAuthorizationToken(ctx, challenge)
Michael Yang's avatar
Michael Yang committed
1177
1178
1179
			if err != nil {
				return nil, err
			}
1180
			anonymous = getTokenSubject(token) == "anonymous"
Michael Yang's avatar
Michael Yang committed
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
			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
1198
1199
1200
		}
	}

1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
	if anonymous {
		// no user is associated with the public key, and the request requires non-anonymous access
		pubKey, nestedErr := auth.GetPublicKey()
		if nestedErr != nil {
			slog.Error(fmt.Sprintf("couldn't get public key: %v", nestedErr))
			return nil, errUnauthorized
		}
		return nil, &errtypes.UnknownOllamaKey{Key: pubKey}
	}
	// user is associated with the public key, but is not authorized to make the request
Michael Yang's avatar
Michael Yang committed
1211
	return nil, errUnauthorized
Michael Yang's avatar
Michael Yang committed
1212
1213
}

Michael Yang's avatar
Michael Yang committed
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
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
1247
	resp, err := http.DefaultClient.Do(req)
Michael Yang's avatar
Michael Yang committed
1248
1249
1250
1251
1252
1253
1254
	if err != nil {
		return nil, err
	}

	return resp, nil
}

Patrick Devine's avatar
Patrick Devine committed
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
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
1278
func parseRegistryChallenge(authStr string) registryChallenge {
Patrick Devine's avatar
Patrick Devine committed
1279
1280
	authStr = strings.TrimPrefix(authStr, "Bearer ")

Michael Yang's avatar
Michael Yang committed
1281
	return registryChallenge{
Patrick Devine's avatar
Patrick Devine committed
1282
1283
1284
1285
1286
1287
		Realm:   getValue(authStr, "realm"),
		Service: getValue(authStr, "service"),
		Scope:   getValue(authStr, "scope"),
	}
}

1288
var errDigestMismatch = errors.New("digest mismatch, file must be downloaded again")
1289

Michael Yang's avatar
Michael Yang committed
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
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 {
1304
		return fmt.Errorf("%w: want %s, got %s", errDigestMismatch, digest, fileDigest)
Michael Yang's avatar
Michael Yang committed
1305
1306
1307
1308
	}

	return nil
}