github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/builder/builder-next/exporter/mobyexporter/export.go (about) 1 package mobyexporter 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "strings" 8 "time" 9 10 "github.com/containerd/containerd/content" 11 "github.com/containerd/containerd/leases" 12 distref "github.com/distribution/reference" 13 "github.com/docker/docker/image" 14 "github.com/docker/docker/layer" 15 "github.com/moby/buildkit/exporter" 16 "github.com/moby/buildkit/exporter/containerimage" 17 "github.com/moby/buildkit/exporter/containerimage/exptypes" 18 "github.com/opencontainers/go-digest" 19 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 20 "github.com/pkg/errors" 21 ) 22 23 // Differ can make a moby layer from a snapshot 24 type Differ interface { 25 EnsureLayer(ctx context.Context, key string) ([]layer.DiffID, error) 26 } 27 28 type ImageTagger interface { 29 TagImage(ctx context.Context, imageID image.ID, newTag distref.Named) error 30 } 31 32 // Opt defines a struct for creating new exporter 33 type Opt struct { 34 ImageStore image.Store 35 Differ Differ 36 ImageTagger ImageTagger 37 ContentStore content.Store 38 LeaseManager leases.Manager 39 } 40 41 type imageExporter struct { 42 opt Opt 43 } 44 45 // New creates a new moby imagestore exporter 46 func New(opt Opt) (exporter.Exporter, error) { 47 im := &imageExporter{opt: opt} 48 return im, nil 49 } 50 51 func (e *imageExporter) Resolve(ctx context.Context, id int, opt map[string]string) (exporter.ExporterInstance, error) { 52 i := &imageExporterInstance{ 53 imageExporter: e, 54 id: id, 55 } 56 for k, v := range opt { 57 switch exptypes.ImageExporterOptKey(k) { 58 case exptypes.OptKeyName: 59 for _, v := range strings.Split(v, ",") { 60 ref, err := distref.ParseNormalizedNamed(v) 61 if err != nil { 62 return nil, err 63 } 64 i.targetNames = append(i.targetNames, ref) 65 } 66 default: 67 if i.meta == nil { 68 i.meta = make(map[string][]byte) 69 } 70 i.meta[k] = []byte(v) 71 } 72 } 73 return i, nil 74 } 75 76 type imageExporterInstance struct { 77 *imageExporter 78 id int 79 targetNames []distref.Named 80 meta map[string][]byte 81 } 82 83 func (e *imageExporterInstance) ID() int { 84 return e.id 85 } 86 87 func (e *imageExporterInstance) Name() string { 88 return "exporting to image" 89 } 90 91 func (e *imageExporterInstance) Config() *exporter.Config { 92 return exporter.NewConfig() 93 } 94 95 func (e *imageExporterInstance) Export(ctx context.Context, inp *exporter.Source, inlineCache exptypes.InlineCache, sessionID string) (map[string]string, exporter.DescriptorReference, error) { 96 if len(inp.Refs) > 1 { 97 return nil, nil, fmt.Errorf("exporting multiple references to image store is currently unsupported") 98 } 99 100 ref := inp.Ref 101 if ref != nil && len(inp.Refs) == 1 { 102 return nil, nil, fmt.Errorf("invalid exporter input: Ref and Refs are mutually exclusive") 103 } 104 105 // only one loop 106 for _, v := range inp.Refs { 107 ref = v 108 } 109 110 var config []byte 111 switch len(inp.Refs) { 112 case 0: 113 config = inp.Metadata[exptypes.ExporterImageConfigKey] 114 case 1: 115 ps, err := exptypes.ParsePlatforms(inp.Metadata) 116 if err != nil { 117 return nil, nil, fmt.Errorf("cannot export image, failed to parse platforms: %w", err) 118 } 119 if len(ps.Platforms) != len(inp.Refs) { 120 return nil, nil, errors.Errorf("number of platforms does not match references %d %d", len(ps.Platforms), len(inp.Refs)) 121 } 122 config = inp.Metadata[fmt.Sprintf("%s/%s", exptypes.ExporterImageConfigKey, ps.Platforms[0].ID)] 123 } 124 125 var diffs []digest.Digest 126 if ref != nil { 127 layersDone := oneOffProgress(ctx, "exporting layers") 128 129 if err := ref.Finalize(ctx); err != nil { 130 return nil, nil, layersDone(err) 131 } 132 133 if err := ref.Extract(ctx, nil); err != nil { 134 return nil, nil, err 135 } 136 137 diffIDs, err := e.opt.Differ.EnsureLayer(ctx, ref.ID()) 138 if err != nil { 139 return nil, nil, layersDone(err) 140 } 141 142 diffs = make([]digest.Digest, len(diffIDs)) 143 for i := range diffIDs { 144 diffs[i] = digest.Digest(diffIDs[i]) 145 } 146 147 _ = layersDone(nil) 148 } 149 150 if len(config) == 0 { 151 var err error 152 config, err = emptyImageConfig() 153 if err != nil { 154 return nil, nil, err 155 } 156 } 157 158 history, err := parseHistoryFromConfig(config) 159 if err != nil { 160 return nil, nil, err 161 } 162 163 diffs, history = normalizeLayersAndHistory(diffs, history, ref) 164 165 var inlineCacheEntry *exptypes.InlineCacheEntry 166 if inlineCache != nil { 167 inlineCacheResult, err := inlineCache(ctx) 168 if err != nil { 169 return nil, nil, err 170 } 171 if inlineCacheResult != nil { 172 if ref != nil { 173 inlineCacheEntry, _ = inlineCacheResult.FindRef(ref.ID()) 174 } else { 175 inlineCacheEntry = inlineCacheResult.Ref 176 } 177 } 178 } 179 config, err = patchImageConfig(config, diffs, history, inlineCacheEntry) 180 if err != nil { 181 return nil, nil, err 182 } 183 184 configDigest := digest.FromBytes(config) 185 186 configDone := oneOffProgress(ctx, fmt.Sprintf("writing image %s", configDigest)) 187 id, err := e.opt.ImageStore.Create(config) 188 if err != nil { 189 return nil, nil, configDone(err) 190 } 191 _ = configDone(nil) 192 193 var names []string 194 for _, targetName := range e.targetNames { 195 names = append(names, targetName.String()) 196 if e.opt.ImageTagger != nil { 197 tagDone := oneOffProgress(ctx, "naming to "+targetName.String()) 198 if err := e.opt.ImageTagger.TagImage(ctx, image.ID(digest.Digest(id)), targetName); err != nil { 199 return nil, nil, tagDone(err) 200 } 201 _ = tagDone(nil) 202 } 203 } 204 205 resp := map[string]string{ 206 exptypes.ExporterImageConfigDigestKey: configDigest.String(), 207 exptypes.ExporterImageDigestKey: id.String(), 208 } 209 if len(names) > 0 { 210 resp["image.name"] = strings.Join(names, ",") 211 } 212 213 descRef, err := e.newTempReference(ctx, config) 214 if err != nil { 215 return nil, nil, fmt.Errorf("failed to create a temporary descriptor reference: %w", err) 216 } 217 218 return resp, descRef, nil 219 } 220 221 func (e *imageExporterInstance) newTempReference(ctx context.Context, config []byte) (exporter.DescriptorReference, error) { 222 lm := e.opt.LeaseManager 223 224 dgst := digest.FromBytes(config) 225 lease, err := lm.Create(ctx, leases.WithRandomID(), leases.WithExpiration(time.Hour)) 226 if err != nil { 227 return nil, err 228 } 229 230 desc := ocispec.Descriptor{ 231 Digest: dgst, 232 MediaType: "application/vnd.docker.container.image.v1+json", 233 Size: int64(len(config)), 234 } 235 236 if err := content.WriteBlob(ctx, e.opt.ContentStore, desc.Digest.String(), bytes.NewReader(config), desc); err != nil { 237 return nil, fmt.Errorf("failed to save temporary image config: %w", err) 238 } 239 240 return containerimage.NewDescriptorReference(desc, func(ctx context.Context) error { 241 return lm.Delete(ctx, lease) 242 }), nil 243 }