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