writer.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. package mobyexporter
  2. import (
  3. "context"
  4. "encoding/json"
  5. "time"
  6. "github.com/containerd/containerd/platforms"
  7. "github.com/containerd/log"
  8. "github.com/moby/buildkit/cache"
  9. "github.com/moby/buildkit/util/progress"
  10. "github.com/moby/buildkit/util/system"
  11. "github.com/opencontainers/go-digest"
  12. ocispec "github.com/opencontainers/image-spec/specs-go/v1"
  13. "github.com/pkg/errors"
  14. )
  15. func emptyImageConfig() ([]byte, error) {
  16. pl := platforms.Normalize(platforms.DefaultSpec())
  17. img := ocispec.Image{}
  18. img.Architecture = pl.Architecture
  19. img.OS = pl.OS
  20. img.Variant = pl.Variant
  21. img.RootFS.Type = "layers"
  22. img.Config.WorkingDir = "/"
  23. img.Config.Env = []string{"PATH=" + system.DefaultPathEnv(pl.OS)}
  24. dt, err := json.Marshal(img)
  25. return dt, errors.Wrap(err, "failed to create empty image config")
  26. }
  27. func parseHistoryFromConfig(dt []byte) ([]ocispec.History, error) {
  28. var config struct {
  29. History []ocispec.History
  30. }
  31. if err := json.Unmarshal(dt, &config); err != nil {
  32. return nil, errors.Wrap(err, "failed to unmarshal history from config")
  33. }
  34. return config.History, nil
  35. }
  36. func patchImageConfig(dt []byte, dps []digest.Digest, history []ocispec.History, cache []byte) ([]byte, error) {
  37. m := map[string]json.RawMessage{}
  38. if err := json.Unmarshal(dt, &m); err != nil {
  39. return nil, errors.Wrap(err, "failed to parse image config for patch")
  40. }
  41. var rootFS ocispec.RootFS
  42. rootFS.Type = "layers"
  43. rootFS.DiffIDs = append(rootFS.DiffIDs, dps...)
  44. dt, err := json.Marshal(rootFS)
  45. if err != nil {
  46. return nil, errors.Wrap(err, "failed to marshal rootfs")
  47. }
  48. m["rootfs"] = dt
  49. dt, err = json.Marshal(history)
  50. if err != nil {
  51. return nil, errors.Wrap(err, "failed to marshal history")
  52. }
  53. m["history"] = dt
  54. if _, ok := m["created"]; !ok {
  55. var tm *time.Time
  56. for _, h := range history {
  57. if h.Created != nil {
  58. tm = h.Created
  59. }
  60. }
  61. dt, err = json.Marshal(&tm)
  62. if err != nil {
  63. return nil, errors.Wrap(err, "failed to marshal creation time")
  64. }
  65. m["created"] = dt
  66. }
  67. if cache != nil {
  68. dt, err := json.Marshal(cache)
  69. if err != nil {
  70. return nil, err
  71. }
  72. m["moby.buildkit.cache.v0"] = dt
  73. }
  74. dt, err = json.Marshal(m)
  75. return dt, errors.Wrap(err, "failed to marshal config after patch")
  76. }
  77. func normalizeLayersAndHistory(diffs []digest.Digest, history []ocispec.History, ref cache.ImmutableRef) ([]digest.Digest, []ocispec.History) {
  78. refMeta := getRefMetadata(ref, len(diffs))
  79. var historyLayers int
  80. for _, h := range history {
  81. if !h.EmptyLayer {
  82. historyLayers++
  83. }
  84. }
  85. if historyLayers > len(diffs) {
  86. // this case shouldn't happen but if it does force set history layers empty
  87. // from the bottom
  88. log.G(context.TODO()).Warn("invalid image config with unaccounted layers")
  89. historyCopy := make([]ocispec.History, 0, len(history))
  90. var l int
  91. for _, h := range history {
  92. if l >= len(diffs) {
  93. h.EmptyLayer = true
  94. }
  95. if !h.EmptyLayer {
  96. l++
  97. }
  98. historyCopy = append(historyCopy, h)
  99. }
  100. history = historyCopy
  101. }
  102. if len(diffs) > historyLayers {
  103. // some history items are missing. add them based on the ref metadata
  104. for _, md := range refMeta[historyLayers:] {
  105. history = append(history, ocispec.History{
  106. Created: md.createdAt,
  107. CreatedBy: md.description,
  108. Comment: "buildkit.exporter.image.v0",
  109. })
  110. }
  111. }
  112. var layerIndex int
  113. for i, h := range history {
  114. if !h.EmptyLayer {
  115. if h.Created == nil {
  116. h.Created = refMeta[layerIndex].createdAt
  117. }
  118. layerIndex++
  119. }
  120. history[i] = h
  121. }
  122. // Find the first new layer time. Otherwise, the history item for a first
  123. // metadata command would be the creation time of a base image layer.
  124. // If there is no such then the last layer with timestamp.
  125. var created *time.Time
  126. var noCreatedTime bool
  127. for _, h := range history {
  128. if h.Created != nil {
  129. created = h.Created
  130. if noCreatedTime {
  131. break
  132. }
  133. } else {
  134. noCreatedTime = true
  135. }
  136. }
  137. // Fill in created times for all history items to be either the first new
  138. // layer time or the previous layer.
  139. noCreatedTime = false
  140. for i, h := range history {
  141. if h.Created != nil {
  142. if noCreatedTime {
  143. created = h.Created
  144. }
  145. } else {
  146. noCreatedTime = true
  147. h.Created = created
  148. }
  149. history[i] = h
  150. }
  151. return diffs, history
  152. }
  153. type refMetadata struct {
  154. description string
  155. createdAt *time.Time
  156. }
  157. func getRefMetadata(ref cache.ImmutableRef, limit int) []refMetadata {
  158. if ref == nil {
  159. return make([]refMetadata, limit)
  160. }
  161. layerChain := ref.LayerChain()
  162. defer layerChain.Release(context.TODO())
  163. if limit < len(layerChain) {
  164. layerChain = layerChain[len(layerChain)-limit:]
  165. }
  166. metas := make([]refMetadata, len(layerChain))
  167. for i, layer := range layerChain {
  168. meta := &metas[i]
  169. if description := layer.GetDescription(); description != "" {
  170. meta.description = description
  171. } else {
  172. meta.description = "created by buildkit" // shouldn't be shown but don't fail build
  173. }
  174. createdAt := layer.GetCreatedAt()
  175. meta.createdAt = &createdAt
  176. }
  177. return metas
  178. }
  179. func oneOffProgress(ctx context.Context, id string) func(err error) error {
  180. pw, _, _ := progress.NewFromContext(ctx)
  181. now := time.Now()
  182. st := progress.Status{
  183. Started: &now,
  184. }
  185. _ = pw.Write(id, st)
  186. return func(err error) error {
  187. // TODO: set error on status
  188. now := time.Now()
  189. st.Completed = &now
  190. _ = pw.Write(id, st)
  191. _ = pw.Close()
  192. return err
  193. }
  194. }