images.go 20 KB

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