images.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  1. package server
  2. import (
  3. "bytes"
  4. "crypto/sha256"
  5. "encoding/hex"
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "io/ioutil"
  11. "log"
  12. "net/http"
  13. "os"
  14. "path"
  15. "path/filepath"
  16. "reflect"
  17. "strconv"
  18. "strings"
  19. "github.com/jmorganca/ollama/api"
  20. "github.com/jmorganca/ollama/parser"
  21. )
  22. type Model struct {
  23. Name string `json:"name"`
  24. ModelPath string
  25. Prompt string
  26. Options api.Options
  27. }
  28. type ManifestV2 struct {
  29. SchemaVersion int `json:"schemaVersion"`
  30. MediaType string `json:"mediaType"`
  31. Config Layer `json:"config"`
  32. Layers []*Layer `json:"layers"`
  33. }
  34. type Layer struct {
  35. MediaType string `json:"mediaType"`
  36. Digest string `json:"digest"`
  37. Size int `json:"size"`
  38. }
  39. type LayerWithBuffer struct {
  40. Layer
  41. Buffer *bytes.Buffer
  42. }
  43. type ConfigV2 struct {
  44. Architecture string `json:"architecture"`
  45. OS string `json:"os"`
  46. RootFS RootFS `json:"rootfs"`
  47. }
  48. type RootFS struct {
  49. Type string `json:"type"`
  50. DiffIDs []string `json:"diff_ids"`
  51. }
  52. func GetManifest(mp ModelPath) (*ManifestV2, error) {
  53. fp, err := mp.GetManifestPath(false)
  54. if err != nil {
  55. return nil, err
  56. }
  57. if _, err = os.Stat(fp); err != nil && !errors.Is(err, os.ErrNotExist) {
  58. return nil, fmt.Errorf("couldn't find model '%s'", mp.GetShortTagname())
  59. }
  60. var manifest *ManifestV2
  61. f, err := os.Open(fp)
  62. if err != nil {
  63. return nil, fmt.Errorf("couldn't open file '%s'", fp)
  64. }
  65. decoder := json.NewDecoder(f)
  66. err = decoder.Decode(&manifest)
  67. if err != nil {
  68. return nil, err
  69. }
  70. return manifest, nil
  71. }
  72. func GetModel(name string) (*Model, error) {
  73. mp := ParseModelPath(name)
  74. manifest, err := GetManifest(mp)
  75. if err != nil {
  76. return nil, err
  77. }
  78. model := &Model{
  79. Name: mp.GetFullTagname(),
  80. }
  81. for _, layer := range manifest.Layers {
  82. filename, err := GetBlobsPath(layer.Digest)
  83. if err != nil {
  84. return nil, err
  85. }
  86. switch layer.MediaType {
  87. case "application/vnd.ollama.image.model":
  88. model.ModelPath = filename
  89. case "application/vnd.ollama.image.prompt":
  90. data, err := os.ReadFile(filename)
  91. if err != nil {
  92. return nil, err
  93. }
  94. model.Prompt = string(data)
  95. case "application/vnd.ollama.image.params":
  96. params, err := os.Open(filename)
  97. if err != nil {
  98. return nil, err
  99. }
  100. defer params.Close()
  101. var opts api.Options
  102. if err = json.NewDecoder(params).Decode(&opts); err != nil {
  103. return nil, err
  104. }
  105. model.Options = opts
  106. }
  107. }
  108. return model, nil
  109. }
  110. func getAbsPath(fp string) (string, error) {
  111. if strings.HasPrefix(fp, "~/") {
  112. parts := strings.Split(fp, "/")
  113. home, err := os.UserHomeDir()
  114. if err != nil {
  115. return "", err
  116. }
  117. fp = filepath.Join(home, filepath.Join(parts[1:]...))
  118. }
  119. return os.ExpandEnv(fp), nil
  120. }
  121. func CreateModel(name string, mf io.Reader, fn func(status string)) error {
  122. fn("parsing modelfile")
  123. commands, err := parser.Parse(mf)
  124. if err != nil {
  125. fn(fmt.Sprintf("error: %v", err))
  126. return err
  127. }
  128. var layers []*LayerWithBuffer
  129. params := make(map[string]string)
  130. for _, c := range commands {
  131. log.Printf("[%s] - %s\n", c.Name, c.Arg)
  132. switch c.Name {
  133. case "model":
  134. fn("looking for model")
  135. mf, err := GetManifest(ParseModelPath(c.Arg))
  136. if err != nil {
  137. // if we couldn't read the manifest, try getting the bin file
  138. fp, err := getAbsPath(c.Arg)
  139. if err != nil {
  140. fn("error determing path. exiting.")
  141. return err
  142. }
  143. fn("creating model layer")
  144. file, err := os.Open(fp)
  145. if err != nil {
  146. fn(fmt.Sprintf("couldn't find model '%s'", c.Arg))
  147. return fmt.Errorf("failed to open file: %v", err)
  148. }
  149. defer file.Close()
  150. l, err := CreateLayer(file)
  151. if err != nil {
  152. fn(fmt.Sprintf("couldn't create model layer: %v", err))
  153. return fmt.Errorf("failed to create layer: %v", err)
  154. }
  155. l.MediaType = "application/vnd.ollama.image.model"
  156. layers = append(layers, l)
  157. } else {
  158. log.Printf("manifest = %#v", mf)
  159. for _, l := range mf.Layers {
  160. newLayer, err := GetLayerWithBufferFromLayer(l)
  161. if err != nil {
  162. fn(fmt.Sprintf("couldn't read layer: %v", err))
  163. return err
  164. }
  165. layers = append(layers, newLayer)
  166. }
  167. }
  168. case "prompt":
  169. fn("creating prompt layer")
  170. // remove the prompt layer if one exists
  171. layers = removeLayerFromLayers(layers, "application/vnd.ollama.image.prompt")
  172. prompt := strings.NewReader(c.Arg)
  173. l, err := CreateLayer(prompt)
  174. if err != nil {
  175. fn(fmt.Sprintf("couldn't create prompt layer: %v", err))
  176. return fmt.Errorf("failed to create layer: %v", err)
  177. }
  178. l.MediaType = "application/vnd.ollama.image.prompt"
  179. layers = append(layers, l)
  180. default:
  181. params[c.Name] = c.Arg
  182. }
  183. }
  184. // Create a single layer for the parameters
  185. if len(params) > 0 {
  186. fn("creating parameter layer")
  187. layers = removeLayerFromLayers(layers, "application/vnd.ollama.image.params")
  188. paramData, err := paramsToReader(params)
  189. if err != nil {
  190. return fmt.Errorf("couldn't create params json: %v", err)
  191. }
  192. l, err := CreateLayer(paramData)
  193. if err != nil {
  194. return fmt.Errorf("failed to create layer: %v", err)
  195. }
  196. l.MediaType = "application/vnd.ollama.image.params"
  197. layers = append(layers, l)
  198. }
  199. digests, err := getLayerDigests(layers)
  200. if err != nil {
  201. return err
  202. }
  203. var manifestLayers []*Layer
  204. for _, l := range layers {
  205. manifestLayers = append(manifestLayers, &l.Layer)
  206. }
  207. // Create a layer for the config object
  208. fn("creating config layer")
  209. cfg, err := createConfigLayer(digests)
  210. if err != nil {
  211. return err
  212. }
  213. layers = append(layers, cfg)
  214. err = SaveLayers(layers, fn, false)
  215. if err != nil {
  216. fn(fmt.Sprintf("error saving layers: %v", err))
  217. return err
  218. }
  219. // Create the manifest
  220. fn("writing manifest")
  221. err = CreateManifest(name, cfg, manifestLayers)
  222. if err != nil {
  223. fn(fmt.Sprintf("error creating manifest: %v", err))
  224. return err
  225. }
  226. fn("success")
  227. return nil
  228. }
  229. func removeLayerFromLayers(layers []*LayerWithBuffer, mediaType string) []*LayerWithBuffer {
  230. j := 0
  231. for _, l := range layers {
  232. if l.MediaType != mediaType {
  233. layers[j] = l
  234. j++
  235. }
  236. }
  237. return layers[:j]
  238. }
  239. func SaveLayers(layers []*LayerWithBuffer, fn func(status string), force bool) error {
  240. // Write each of the layers to disk
  241. for _, layer := range layers {
  242. fp, err := GetBlobsPath(layer.Digest)
  243. if err != nil {
  244. return err
  245. }
  246. _, err = os.Stat(fp)
  247. if os.IsNotExist(err) || force {
  248. fn(fmt.Sprintf("writing layer %s", layer.Digest))
  249. out, err := os.Create(fp)
  250. if err != nil {
  251. log.Printf("couldn't create %s", fp)
  252. return err
  253. }
  254. defer out.Close()
  255. _, err = io.Copy(out, layer.Buffer)
  256. if err != nil {
  257. return err
  258. }
  259. } else {
  260. fn(fmt.Sprintf("using already created layer %s", layer.Digest))
  261. }
  262. }
  263. return nil
  264. }
  265. func CreateManifest(name string, cfg *LayerWithBuffer, layers []*Layer) error {
  266. mp := ParseModelPath(name)
  267. manifest := ManifestV2{
  268. SchemaVersion: 2,
  269. MediaType: "application/vnd.docker.distribution.manifest.v2+json",
  270. Config: Layer{
  271. MediaType: cfg.MediaType,
  272. Size: cfg.Size,
  273. Digest: cfg.Digest,
  274. },
  275. Layers: layers,
  276. }
  277. manifestJSON, err := json.Marshal(manifest)
  278. if err != nil {
  279. return err
  280. }
  281. fp, err := mp.GetManifestPath(true)
  282. if err != nil {
  283. return err
  284. }
  285. return os.WriteFile(fp, manifestJSON, 0o644)
  286. }
  287. func GetLayerWithBufferFromLayer(layer *Layer) (*LayerWithBuffer, error) {
  288. fp, err := GetBlobsPath(layer.Digest)
  289. if err != nil {
  290. return nil, err
  291. }
  292. file, err := os.Open(fp)
  293. if err != nil {
  294. return nil, fmt.Errorf("could not open blob: %w", err)
  295. }
  296. defer file.Close()
  297. newLayer, err := CreateLayer(file)
  298. if err != nil {
  299. return nil, err
  300. }
  301. newLayer.MediaType = layer.MediaType
  302. return newLayer, nil
  303. }
  304. func paramsToReader(params map[string]string) (io.Reader, error) {
  305. opts := api.DefaultOptions()
  306. typeOpts := reflect.TypeOf(opts)
  307. // build map of json struct tags
  308. jsonOpts := make(map[string]reflect.StructField)
  309. for _, field := range reflect.VisibleFields(typeOpts) {
  310. jsonTag := strings.Split(field.Tag.Get("json"), ",")[0]
  311. if jsonTag != "" {
  312. jsonOpts[jsonTag] = field
  313. }
  314. }
  315. valueOpts := reflect.ValueOf(&opts).Elem()
  316. // iterate params and set values based on json struct tags
  317. for key, val := range params {
  318. if opt, ok := jsonOpts[key]; ok {
  319. field := valueOpts.FieldByName(opt.Name)
  320. if field.IsValid() && field.CanSet() {
  321. switch field.Kind() {
  322. case reflect.Float32:
  323. floatVal, err := strconv.ParseFloat(val, 32)
  324. if err != nil {
  325. return nil, fmt.Errorf("invalid float value %s", val)
  326. }
  327. field.SetFloat(floatVal)
  328. case reflect.Int:
  329. intVal, err := strconv.ParseInt(val, 10, 0)
  330. if err != nil {
  331. return nil, fmt.Errorf("invalid int value %s", val)
  332. }
  333. field.SetInt(intVal)
  334. case reflect.Bool:
  335. boolVal, err := strconv.ParseBool(val)
  336. if err != nil {
  337. return nil, fmt.Errorf("invalid bool value %s", val)
  338. }
  339. field.SetBool(boolVal)
  340. case reflect.String:
  341. field.SetString(val)
  342. default:
  343. return nil, fmt.Errorf("unknown type %s for %s", field.Kind(), key)
  344. }
  345. }
  346. }
  347. }
  348. bts, err := json.Marshal(opts)
  349. if err != nil {
  350. return nil, err
  351. }
  352. return bytes.NewReader(bts), nil
  353. }
  354. func getLayerDigests(layers []*LayerWithBuffer) ([]string, error) {
  355. var digests []string
  356. for _, l := range layers {
  357. if l.Digest == "" {
  358. return nil, fmt.Errorf("layer is missing a digest")
  359. }
  360. digests = append(digests, l.Digest)
  361. }
  362. return digests, nil
  363. }
  364. // CreateLayer creates a Layer object from a given file
  365. func CreateLayer(f io.Reader) (*LayerWithBuffer, error) {
  366. buf := new(bytes.Buffer)
  367. _, err := io.Copy(buf, f)
  368. if err != nil {
  369. return nil, err
  370. }
  371. digest, size := GetSHA256Digest(buf)
  372. layer := &LayerWithBuffer{
  373. Layer: Layer{
  374. MediaType: "application/vnd.docker.image.rootfs.diff.tar",
  375. Digest: digest,
  376. Size: size,
  377. },
  378. Buffer: buf,
  379. }
  380. return layer, nil
  381. }
  382. func PushModel(name, username, password string, fn func(status, digest string, Total, Completed int, Percent float64)) error {
  383. mp := ParseModelPath(name)
  384. fn("retrieving manifest", "", 0, 0, 0)
  385. manifest, err := GetManifest(mp)
  386. if err != nil {
  387. fn("couldn't retrieve manifest", "", 0, 0, 0)
  388. return err
  389. }
  390. var layers []*Layer
  391. var total int
  392. var completed int
  393. for _, layer := range manifest.Layers {
  394. layers = append(layers, layer)
  395. total += layer.Size
  396. }
  397. layers = append(layers, &manifest.Config)
  398. total += manifest.Config.Size
  399. for _, layer := range layers {
  400. exists, err := checkBlobExistence(mp, layer.Digest, username, password)
  401. if err != nil {
  402. return err
  403. }
  404. if exists {
  405. completed += layer.Size
  406. fn("using existing layer", layer.Digest, total, completed, float64(completed)/float64(total))
  407. continue
  408. }
  409. fn("starting upload", layer.Digest, total, completed, float64(completed)/float64(total))
  410. location, err := startUpload(mp, username, password)
  411. if err != nil {
  412. log.Printf("couldn't start upload: %v", err)
  413. return err
  414. }
  415. err = uploadBlob(location, layer, username, password)
  416. if err != nil {
  417. log.Printf("error uploading blob: %v", err)
  418. return err
  419. }
  420. completed += layer.Size
  421. fn("upload complete", layer.Digest, total, completed, float64(completed)/float64(total))
  422. }
  423. fn("pushing manifest", "", total, completed, float64(completed/total))
  424. url := fmt.Sprintf("%s://%s/v2/%s/manifests/%s", mp.ProtocolScheme, mp.Registry, mp.GetNamespaceRepository(), mp.Tag)
  425. headers := map[string]string{
  426. "Content-Type": "application/vnd.docker.distribution.manifest.v2+json",
  427. }
  428. manifestJSON, err := json.Marshal(manifest)
  429. if err != nil {
  430. return err
  431. }
  432. resp, err := makeRequest("PUT", url, headers, bytes.NewReader(manifestJSON), username, password)
  433. if err != nil {
  434. return err
  435. }
  436. defer resp.Body.Close()
  437. // Check for success: For a successful upload, the Docker registry will respond with a 201 Created
  438. if resp.StatusCode != http.StatusCreated {
  439. body, _ := io.ReadAll(resp.Body)
  440. return fmt.Errorf("registry responded with code %d: %v", resp.StatusCode, string(body))
  441. }
  442. fn("success", "", total, completed, 1.0)
  443. return nil
  444. }
  445. func PullModel(name, username, password string, fn func(status, digest string, Total, Completed int, Percent float64)) error {
  446. mp := ParseModelPath(name)
  447. fn("pulling manifest", "", 0, 0, 0)
  448. manifest, err := pullModelManifest(mp, username, password)
  449. if err != nil {
  450. return fmt.Errorf("pull model manifest: %q", err)
  451. }
  452. var layers []*Layer
  453. var total int
  454. var completed int
  455. for _, layer := range manifest.Layers {
  456. layers = append(layers, layer)
  457. total += layer.Size
  458. }
  459. layers = append(layers, &manifest.Config)
  460. total += manifest.Config.Size
  461. for _, layer := range layers {
  462. fn("starting download", layer.Digest, total, completed, float64(completed)/float64(total))
  463. if err := downloadBlob(mp, layer.Digest, username, password, fn); err != nil {
  464. fn(fmt.Sprintf("error downloading: %v", err), layer.Digest, 0, 0, 0)
  465. return err
  466. }
  467. completed += layer.Size
  468. fn("download complete", layer.Digest, total, completed, float64(completed)/float64(total))
  469. }
  470. fn("writing manifest", "", total, completed, 1.0)
  471. manifestJSON, err := json.Marshal(manifest)
  472. if err != nil {
  473. return err
  474. }
  475. fp, err := mp.GetManifestPath(true)
  476. if err != nil {
  477. return err
  478. }
  479. err = os.WriteFile(fp, manifestJSON, 0644)
  480. if err != nil {
  481. log.Printf("couldn't write to %s", fp)
  482. return err
  483. }
  484. fn("success", "", total, completed, 1.0)
  485. return nil
  486. }
  487. func pullModelManifest(mp ModelPath, username, password string) (*ManifestV2, error) {
  488. url := fmt.Sprintf("%s://%s/v2/%s/manifests/%s", mp.ProtocolScheme, mp.Registry, mp.GetNamespaceRepository(), mp.Tag)
  489. headers := map[string]string{
  490. "Accept": "application/vnd.docker.distribution.manifest.v2+json",
  491. }
  492. resp, err := makeRequest("GET", url, headers, nil, username, password)
  493. if err != nil {
  494. log.Printf("couldn't get manifest: %v", err)
  495. return nil, err
  496. }
  497. defer resp.Body.Close()
  498. // Check for success: For a successful upload, the Docker registry will respond with a 201 Created
  499. if resp.StatusCode != http.StatusOK {
  500. body, _ := io.ReadAll(resp.Body)
  501. return nil, fmt.Errorf("registry responded with code %d: %s", resp.StatusCode, body)
  502. }
  503. var m *ManifestV2
  504. if err := json.NewDecoder(resp.Body).Decode(&m); err != nil {
  505. return nil, err
  506. }
  507. return m, err
  508. }
  509. func createConfigLayer(layers []string) (*LayerWithBuffer, error) {
  510. // TODO change architecture and OS
  511. config := ConfigV2{
  512. Architecture: "arm64",
  513. OS: "linux",
  514. RootFS: RootFS{
  515. Type: "layers",
  516. DiffIDs: layers,
  517. },
  518. }
  519. configJSON, err := json.Marshal(config)
  520. if err != nil {
  521. return nil, err
  522. }
  523. buf := bytes.NewBuffer(configJSON)
  524. digest, size := GetSHA256Digest(buf)
  525. layer := &LayerWithBuffer{
  526. Layer: Layer{
  527. MediaType: "application/vnd.docker.container.image.v1+json",
  528. Digest: digest,
  529. Size: size,
  530. },
  531. Buffer: buf,
  532. }
  533. return layer, nil
  534. }
  535. // GetSHA256Digest returns the SHA256 hash of a given buffer and returns it, and the size of buffer
  536. func GetSHA256Digest(data *bytes.Buffer) (string, int) {
  537. layerBytes := data.Bytes()
  538. hash := sha256.Sum256(layerBytes)
  539. return "sha256:" + hex.EncodeToString(hash[:]), len(layerBytes)
  540. }
  541. func startUpload(mp ModelPath, username string, password string) (string, error) {
  542. url := fmt.Sprintf("%s://%s/v2/%s/blobs/uploads/", mp.ProtocolScheme, mp.Registry, mp.GetNamespaceRepository())
  543. resp, err := makeRequest("POST", url, nil, nil, username, password)
  544. if err != nil {
  545. log.Printf("couldn't start upload: %v", err)
  546. return "", err
  547. }
  548. defer resp.Body.Close()
  549. // Check for success
  550. if resp.StatusCode != http.StatusAccepted {
  551. body, _ := io.ReadAll(resp.Body)
  552. return "", fmt.Errorf("registry responded with code %d: %s", resp.StatusCode, body)
  553. }
  554. // Extract UUID location from header
  555. location := resp.Header.Get("Location")
  556. if location == "" {
  557. return "", fmt.Errorf("location header is missing in response")
  558. }
  559. return location, nil
  560. }
  561. // Function to check if a blob already exists in the Docker registry
  562. func checkBlobExistence(mp ModelPath, digest string, username string, password string) (bool, error) {
  563. url := fmt.Sprintf("%s://%s/v2/%s/blobs/%s", mp.ProtocolScheme, mp.Registry, mp.GetNamespaceRepository(), digest)
  564. resp, err := makeRequest("HEAD", url, nil, nil, username, password)
  565. if err != nil {
  566. log.Printf("couldn't check for blob: %v", err)
  567. return false, err
  568. }
  569. defer resp.Body.Close()
  570. // Check for success: If the blob exists, the Docker registry will respond with a 200 OK
  571. return resp.StatusCode == http.StatusOK, nil
  572. }
  573. func uploadBlob(location string, layer *Layer, username string, password string) error {
  574. // Create URL
  575. url := fmt.Sprintf("%s&digest=%s", location, layer.Digest)
  576. headers := make(map[string]string)
  577. headers["Content-Length"] = fmt.Sprintf("%d", layer.Size)
  578. headers["Content-Type"] = "application/octet-stream"
  579. // TODO change from monolithic uploads to chunked uploads
  580. // TODO allow resumability
  581. // TODO allow canceling uploads via DELETE
  582. // TODO allow cross repo blob mount
  583. fp, err := GetBlobsPath(layer.Digest)
  584. if err != nil {
  585. return err
  586. }
  587. f, err := os.Open(fp)
  588. if err != nil {
  589. return err
  590. }
  591. resp, err := makeRequest("PUT", url, headers, f, username, password)
  592. if err != nil {
  593. log.Printf("couldn't upload blob: %v", err)
  594. return err
  595. }
  596. defer resp.Body.Close()
  597. // Check for success: For a successful upload, the Docker registry will respond with a 201 Created
  598. if resp.StatusCode != http.StatusCreated {
  599. body, _ := io.ReadAll(resp.Body)
  600. return fmt.Errorf("registry responded with code %d: %v", resp.StatusCode, string(body))
  601. }
  602. return nil
  603. }
  604. func downloadBlob(mp ModelPath, digest string, username, password string, fn func(status, digest string, Total, Completed int, Percent float64)) error {
  605. fp, err := GetBlobsPath(digest)
  606. if err != nil {
  607. return err
  608. }
  609. _, err = os.Stat(fp)
  610. if !os.IsNotExist(err) {
  611. // we already have the file, so return
  612. log.Printf("already have %s\n", digest)
  613. return nil
  614. }
  615. var size int64
  616. fi, err := os.Stat(fp + "-partial")
  617. switch {
  618. case errors.Is(err, os.ErrNotExist):
  619. // noop, file doesn't exist so create it
  620. case err != nil:
  621. return fmt.Errorf("stat: %w", err)
  622. default:
  623. size = fi.Size()
  624. }
  625. url := fmt.Sprintf("%s://%s/v2/%s/blobs/%s", mp.ProtocolScheme, mp.Registry, mp.GetNamespaceRepository(), digest)
  626. headers := map[string]string{
  627. "Range": fmt.Sprintf("bytes=%d-", size),
  628. }
  629. resp, err := makeRequest("GET", url, headers, nil, username, password)
  630. if err != nil {
  631. log.Printf("couldn't download blob: %v", err)
  632. return err
  633. }
  634. defer resp.Body.Close()
  635. if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
  636. body, _ := ioutil.ReadAll(resp.Body)
  637. return fmt.Errorf("registry responded with code %d: %v", resp.StatusCode, string(body))
  638. }
  639. err = os.MkdirAll(path.Dir(fp), 0o700)
  640. if err != nil {
  641. return fmt.Errorf("make blobs directory: %w", err)
  642. }
  643. out, err := os.OpenFile(fp+"-partial", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
  644. if err != nil {
  645. panic(err)
  646. }
  647. defer out.Close()
  648. remaining, _ := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
  649. completed := size
  650. total := remaining + completed
  651. for {
  652. fn(fmt.Sprintf("Downloading %s", digest), digest, int(total), int(completed), float64(completed)/float64(total))
  653. if completed >= total {
  654. if err := os.Rename(fp+"-partial", fp); err != nil {
  655. fn(fmt.Sprintf("error renaming file: %v", err), digest, int(total), int(completed), 1)
  656. return err
  657. }
  658. break
  659. }
  660. n, err := io.CopyN(out, resp.Body, 8192)
  661. if err != nil && !errors.Is(err, io.EOF) {
  662. return err
  663. }
  664. completed += n
  665. }
  666. log.Printf("success getting %s\n", digest)
  667. return nil
  668. }
  669. func makeRequest(method, url string, headers map[string]string, body io.Reader, username, password string) (*http.Response, error) {
  670. req, err := http.NewRequest(method, url, body)
  671. if err != nil {
  672. return nil, err
  673. }
  674. for k, v := range headers {
  675. req.Header.Set(k, v)
  676. }
  677. // TODO: better auth
  678. if username != "" && password != "" {
  679. req.SetBasicAuth(username, password)
  680. }
  681. client := &http.Client{
  682. CheckRedirect: func(req *http.Request, via []*http.Request) error {
  683. if len(via) >= 10 {
  684. return fmt.Errorf("too many redirects")
  685. }
  686. log.Printf("redirected to: %s\n", req.URL)
  687. return nil
  688. },
  689. }
  690. resp, err := client.Do(req)
  691. if err != nil {
  692. return nil, err
  693. }
  694. return resp, nil
  695. }