layer.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. package server
  2. import (
  3. "crypto/sha256"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "log/slog"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. )
  12. type Layer struct {
  13. MediaType string `json:"mediaType"`
  14. Digest string `json:"digest"`
  15. Size int64 `json:"size"`
  16. From string `json:"from,omitempty"`
  17. status string
  18. }
  19. func NewLayer(r io.Reader, mediatype string) (Layer, error) {
  20. blobs, err := GetBlobsPath("")
  21. if err != nil {
  22. return Layer{}, err
  23. }
  24. temp, err := os.CreateTemp(blobs, "sha256-")
  25. if err != nil {
  26. return Layer{}, err
  27. }
  28. defer temp.Close()
  29. defer os.Remove(temp.Name())
  30. sha256sum := sha256.New()
  31. n, err := io.Copy(io.MultiWriter(temp, sha256sum), r)
  32. if err != nil {
  33. return Layer{}, err
  34. }
  35. if err := temp.Close(); err != nil {
  36. return Layer{}, err
  37. }
  38. digest := fmt.Sprintf("sha256:%x", sha256sum.Sum(nil))
  39. blob, err := GetBlobsPath(digest)
  40. if err != nil {
  41. return Layer{}, err
  42. }
  43. status := "using existing layer"
  44. if _, err := os.Stat(blob); err != nil {
  45. status = "creating new layer"
  46. if err := os.Rename(temp.Name(), blob); err != nil {
  47. return Layer{}, err
  48. }
  49. if err := os.Chmod(blob, 0o644); err != nil {
  50. return Layer{}, err
  51. }
  52. }
  53. return Layer{
  54. MediaType: mediatype,
  55. Digest: digest,
  56. Size: n,
  57. status: fmt.Sprintf("%s %s", status, digest),
  58. }, nil
  59. }
  60. func NewLayerFromLayer(digest, mediatype, from string) (Layer, error) {
  61. if digest == "" {
  62. return Layer{}, errors.New("creating new layer from layer with empty digest")
  63. }
  64. blob, err := GetBlobsPath(digest)
  65. if err != nil {
  66. return Layer{}, err
  67. }
  68. fi, err := os.Stat(blob)
  69. if err != nil {
  70. return Layer{}, err
  71. }
  72. return Layer{
  73. MediaType: mediatype,
  74. Digest: digest,
  75. Size: fi.Size(),
  76. From: from,
  77. status: fmt.Sprintf("using existing layer %s", digest),
  78. }, nil
  79. }
  80. func (l *Layer) Open() (io.ReadSeekCloser, error) {
  81. if l.Digest == "" {
  82. return nil, errors.New("opening layer with empty digest")
  83. }
  84. blob, err := GetBlobsPath(l.Digest)
  85. if err != nil {
  86. return nil, err
  87. }
  88. return os.Open(blob)
  89. }
  90. // Prune removes the layer from the filesystem if it is not referenced any manifest.
  91. func (l *Layer) Prune() error {
  92. if l.Digest == "" {
  93. return nil
  94. }
  95. ms, err := Manifests()
  96. if err != nil {
  97. return err
  98. }
  99. for _, m := range ms {
  100. for _, layer := range append(m.Layers, m.Config) {
  101. if layer.Digest == l.Digest {
  102. // something is using this layer
  103. return nil
  104. }
  105. }
  106. }
  107. blob, err := GetBlobsPath(l.Digest)
  108. if err != nil {
  109. return err
  110. }
  111. slog.Debug("pruning layer", "digest", l.Digest)
  112. return os.Remove(blob)
  113. }
  114. func Layers() (map[string]Layer, error) {
  115. blobs, err := GetBlobsPath("")
  116. if err != nil {
  117. return nil, err
  118. }
  119. // TODO(mxyng): use something less brittle
  120. matches, err := filepath.Glob(filepath.Join(blobs, "*"))
  121. if err != nil {
  122. return nil, err
  123. }
  124. layers := make(map[string]Layer)
  125. for _, match := range matches {
  126. rel, err := filepath.Rel(blobs, match)
  127. if err != nil {
  128. slog.Warn("bad filepath", "path", match, "error", err)
  129. continue
  130. }
  131. // TODO(mxyng): this should ideally use model.Digest but
  132. // that's currently incompatible with the manifest digest
  133. digest := strings.Replace(rel, "sha256-", "sha256:", 1)
  134. layer, err := NewLayerFromLayer(digest, "", "")
  135. if err != nil {
  136. slog.Warn("bad blob", "digest", digest, "error", err)
  137. layer = Layer{Digest: rel}
  138. }
  139. layers[digest] = layer
  140. }
  141. return layers, nil
  142. }