export.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. package mobyexporter
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "strings"
  7. distref "github.com/distribution/reference"
  8. "github.com/docker/docker/image"
  9. "github.com/docker/docker/layer"
  10. "github.com/moby/buildkit/exporter"
  11. "github.com/moby/buildkit/exporter/containerimage/exptypes"
  12. "github.com/opencontainers/go-digest"
  13. "github.com/pkg/errors"
  14. )
  15. const (
  16. keyImageName = "name"
  17. )
  18. // Differ can make a moby layer from a snapshot
  19. type Differ interface {
  20. EnsureLayer(ctx context.Context, key string) ([]layer.DiffID, error)
  21. }
  22. type ImageTagger interface {
  23. TagImage(ctx context.Context, imageID image.ID, newTag distref.Named) error
  24. }
  25. // Opt defines a struct for creating new exporter
  26. type Opt struct {
  27. ImageStore image.Store
  28. Differ Differ
  29. ImageTagger ImageTagger
  30. }
  31. type imageExporter struct {
  32. opt Opt
  33. }
  34. // New creates a new moby imagestore exporter
  35. func New(opt Opt) (exporter.Exporter, error) {
  36. im := &imageExporter{opt: opt}
  37. return im, nil
  38. }
  39. func (e *imageExporter) Resolve(ctx context.Context, opt map[string]string) (exporter.ExporterInstance, error) {
  40. i := &imageExporterInstance{
  41. imageExporter: e,
  42. }
  43. for k, v := range opt {
  44. switch k {
  45. case keyImageName:
  46. for _, v := range strings.Split(v, ",") {
  47. ref, err := distref.ParseNormalizedNamed(v)
  48. if err != nil {
  49. return nil, err
  50. }
  51. i.targetNames = append(i.targetNames, ref)
  52. }
  53. default:
  54. if i.meta == nil {
  55. i.meta = make(map[string][]byte)
  56. }
  57. i.meta[k] = []byte(v)
  58. }
  59. }
  60. return i, nil
  61. }
  62. type imageExporterInstance struct {
  63. *imageExporter
  64. targetNames []distref.Named
  65. meta map[string][]byte
  66. }
  67. func (e *imageExporterInstance) Name() string {
  68. return "exporting to image"
  69. }
  70. func (e *imageExporterInstance) Config() *exporter.Config {
  71. return exporter.NewConfig()
  72. }
  73. func (e *imageExporterInstance) Export(ctx context.Context, inp *exporter.Source, sessionID string) (map[string]string, exporter.DescriptorReference, error) {
  74. if len(inp.Refs) > 1 {
  75. return nil, nil, fmt.Errorf("exporting multiple references to image store is currently unsupported")
  76. }
  77. ref := inp.Ref
  78. if ref != nil && len(inp.Refs) == 1 {
  79. return nil, nil, fmt.Errorf("invalid exporter input: Ref and Refs are mutually exclusive")
  80. }
  81. // only one loop
  82. for _, v := range inp.Refs {
  83. ref = v
  84. }
  85. var config []byte
  86. switch len(inp.Refs) {
  87. case 0:
  88. config = inp.Metadata[exptypes.ExporterImageConfigKey]
  89. case 1:
  90. platformsBytes, ok := inp.Metadata[exptypes.ExporterPlatformsKey]
  91. if !ok {
  92. return nil, nil, fmt.Errorf("cannot export image, missing platforms mapping")
  93. }
  94. var p exptypes.Platforms
  95. if err := json.Unmarshal(platformsBytes, &p); err != nil {
  96. return nil, nil, errors.Wrapf(err, "failed to parse platforms passed to exporter")
  97. }
  98. if len(p.Platforms) != len(inp.Refs) {
  99. return nil, nil, errors.Errorf("number of platforms does not match references %d %d", len(p.Platforms), len(inp.Refs))
  100. }
  101. config = inp.Metadata[fmt.Sprintf("%s/%s", exptypes.ExporterImageConfigKey, p.Platforms[0].ID)]
  102. }
  103. var diffs []digest.Digest
  104. if ref != nil {
  105. layersDone := oneOffProgress(ctx, "exporting layers")
  106. if err := ref.Finalize(ctx); err != nil {
  107. return nil, nil, layersDone(err)
  108. }
  109. if err := ref.Extract(ctx, nil); err != nil {
  110. return nil, nil, err
  111. }
  112. diffIDs, err := e.opt.Differ.EnsureLayer(ctx, ref.ID())
  113. if err != nil {
  114. return nil, nil, layersDone(err)
  115. }
  116. diffs = make([]digest.Digest, len(diffIDs))
  117. for i := range diffIDs {
  118. diffs[i] = digest.Digest(diffIDs[i])
  119. }
  120. _ = layersDone(nil)
  121. }
  122. if len(config) == 0 {
  123. var err error
  124. config, err = emptyImageConfig()
  125. if err != nil {
  126. return nil, nil, err
  127. }
  128. }
  129. history, err := parseHistoryFromConfig(config)
  130. if err != nil {
  131. return nil, nil, err
  132. }
  133. diffs, history = normalizeLayersAndHistory(diffs, history, ref)
  134. config, err = patchImageConfig(config, diffs, history, inp.Metadata[exptypes.ExporterInlineCache])
  135. if err != nil {
  136. return nil, nil, err
  137. }
  138. configDigest := digest.FromBytes(config)
  139. configDone := oneOffProgress(ctx, fmt.Sprintf("writing image %s", configDigest))
  140. id, err := e.opt.ImageStore.Create(config)
  141. if err != nil {
  142. return nil, nil, configDone(err)
  143. }
  144. _ = configDone(nil)
  145. if e.opt.ImageTagger != nil {
  146. for _, targetName := range e.targetNames {
  147. tagDone := oneOffProgress(ctx, "naming to "+targetName.String())
  148. if err := e.opt.ImageTagger.TagImage(ctx, image.ID(digest.Digest(id)), targetName); err != nil {
  149. return nil, nil, tagDone(err)
  150. }
  151. _ = tagDone(nil)
  152. }
  153. }
  154. return map[string]string{
  155. exptypes.ExporterImageConfigDigestKey: configDigest.String(),
  156. exptypes.ExporterImageDigestKey: id.String(),
  157. }, nil, nil
  158. }