github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/builder/builder-next/exporter/export.go (about) 1 package containerimage 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "strconv" 8 "strings" 9 10 distref "github.com/docker/distribution/reference" 11 "github.com/docker/docker/image" 12 "github.com/docker/docker/layer" 13 "github.com/docker/docker/reference" 14 "github.com/moby/buildkit/exporter" 15 "github.com/moby/buildkit/exporter/containerimage/exptypes" 16 "github.com/moby/buildkit/util/compression" 17 "github.com/opencontainers/go-digest" 18 "github.com/pkg/errors" 19 ) 20 21 const ( 22 keyImageName = "name" 23 keyBuildInfo = "buildinfo" 24 keyBuildInfoAttrs = "buildinfo-attrs" 25 ) 26 27 // Differ can make a moby layer from a snapshot 28 type Differ interface { 29 EnsureLayer(ctx context.Context, key string) ([]layer.DiffID, error) 30 } 31 32 // Opt defines a struct for creating new exporter 33 type Opt struct { 34 ImageStore image.Store 35 ReferenceStore reference.Store 36 Differ Differ 37 } 38 39 type imageExporter struct { 40 opt Opt 41 } 42 43 // New creates a new moby imagestore exporter 44 func New(opt Opt) (exporter.Exporter, error) { 45 im := &imageExporter{opt: opt} 46 return im, nil 47 } 48 49 func (e *imageExporter) Resolve(ctx context.Context, opt map[string]string) (exporter.ExporterInstance, error) { 50 i := &imageExporterInstance{ 51 imageExporter: e, 52 buildInfo: true, 53 } 54 for k, v := range opt { 55 switch k { 56 case keyImageName: 57 for _, v := range strings.Split(v, ",") { 58 ref, err := distref.ParseNormalizedNamed(v) 59 if err != nil { 60 return nil, err 61 } 62 i.targetNames = append(i.targetNames, ref) 63 } 64 case keyBuildInfo: 65 if v == "" { 66 i.buildInfo = true 67 continue 68 } 69 b, err := strconv.ParseBool(v) 70 if err != nil { 71 return nil, errors.Wrapf(err, "non-bool value specified for %s", k) 72 } 73 i.buildInfo = b 74 case keyBuildInfoAttrs: 75 if v == "" { 76 i.buildInfoAttrs = false 77 continue 78 } 79 b, err := strconv.ParseBool(v) 80 if err != nil { 81 return nil, errors.Wrapf(err, "non-bool value specified for %s", k) 82 } 83 i.buildInfoAttrs = b 84 default: 85 if i.meta == nil { 86 i.meta = make(map[string][]byte) 87 } 88 i.meta[k] = []byte(v) 89 } 90 } 91 return i, nil 92 } 93 94 type imageExporterInstance struct { 95 *imageExporter 96 targetNames []distref.Named 97 meta map[string][]byte 98 buildInfo bool 99 buildInfoAttrs bool 100 } 101 102 func (e *imageExporterInstance) Name() string { 103 return "exporting to image" 104 } 105 106 func (e *imageExporterInstance) Config() exporter.Config { 107 return exporter.Config{ 108 Compression: compression.Config{ 109 Type: compression.Default, 110 }, 111 } 112 } 113 114 func (e *imageExporterInstance) Export(ctx context.Context, inp exporter.Source, sessionID string) (map[string]string, error) { 115 if len(inp.Refs) > 1 { 116 return nil, fmt.Errorf("exporting multiple references to image store is currently unsupported") 117 } 118 119 ref := inp.Ref 120 if ref != nil && len(inp.Refs) == 1 { 121 return nil, fmt.Errorf("invalid exporter input: Ref and Refs are mutually exclusive") 122 } 123 124 // only one loop 125 for _, v := range inp.Refs { 126 ref = v 127 } 128 129 var config []byte 130 var buildInfo []byte 131 switch len(inp.Refs) { 132 case 0: 133 config = inp.Metadata[exptypes.ExporterImageConfigKey] 134 if v, ok := inp.Metadata[exptypes.ExporterBuildInfo]; ok { 135 buildInfo = v 136 } 137 case 1: 138 platformsBytes, ok := inp.Metadata[exptypes.ExporterPlatformsKey] 139 if !ok { 140 return nil, fmt.Errorf("cannot export image, missing platforms mapping") 141 } 142 var p exptypes.Platforms 143 if err := json.Unmarshal(platformsBytes, &p); err != nil { 144 return nil, errors.Wrapf(err, "failed to parse platforms passed to exporter") 145 } 146 if len(p.Platforms) != len(inp.Refs) { 147 return nil, errors.Errorf("number of platforms does not match references %d %d", len(p.Platforms), len(inp.Refs)) 148 } 149 config = inp.Metadata[fmt.Sprintf("%s/%s", exptypes.ExporterImageConfigKey, p.Platforms[0].ID)] 150 if v, ok := inp.Metadata[fmt.Sprintf("%s/%s", exptypes.ExporterBuildInfo, p.Platforms[0].ID)]; ok { 151 buildInfo = v 152 } 153 } 154 155 var diffs []digest.Digest 156 if ref != nil { 157 layersDone := oneOffProgress(ctx, "exporting layers") 158 159 if err := ref.Finalize(ctx); err != nil { 160 return nil, layersDone(err) 161 } 162 163 if err := ref.Extract(ctx, nil); err != nil { 164 return nil, err 165 } 166 167 diffIDs, err := e.opt.Differ.EnsureLayer(ctx, ref.ID()) 168 if err != nil { 169 return nil, layersDone(err) 170 } 171 172 diffs = make([]digest.Digest, len(diffIDs)) 173 for i := range diffIDs { 174 diffs[i] = digest.Digest(diffIDs[i]) 175 } 176 177 _ = layersDone(nil) 178 } 179 180 if len(config) == 0 { 181 var err error 182 config, err = emptyImageConfig() 183 if err != nil { 184 return nil, err 185 } 186 } 187 188 history, err := parseHistoryFromConfig(config) 189 if err != nil { 190 return nil, err 191 } 192 193 diffs, history = normalizeLayersAndHistory(diffs, history, ref) 194 195 config, err = patchImageConfig(config, diffs, history, inp.Metadata[exptypes.ExporterInlineCache], buildInfo) 196 if err != nil { 197 return nil, err 198 } 199 200 configDigest := digest.FromBytes(config) 201 202 configDone := oneOffProgress(ctx, fmt.Sprintf("writing image %s", configDigest)) 203 id, err := e.opt.ImageStore.Create(config) 204 if err != nil { 205 return nil, configDone(err) 206 } 207 _ = configDone(nil) 208 209 if e.opt.ReferenceStore != nil { 210 for _, targetName := range e.targetNames { 211 tagDone := oneOffProgress(ctx, "naming to "+targetName.String()) 212 if err := e.opt.ReferenceStore.AddTag(targetName, digest.Digest(id), true); err != nil { 213 return nil, tagDone(err) 214 } 215 _ = tagDone(nil) 216 } 217 } 218 219 return map[string]string{ 220 exptypes.ExporterImageConfigDigestKey: configDigest.String(), 221 exptypes.ExporterImageDigestKey: id.String(), 222 }, nil 223 }