dockerfile: support exporting multiple platforms
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>docker-18.09
parent
d70d816dee
commit
33f4382b69
|
@ -0,0 +1,15 @@
|
||||||
|
package exptypes
|
||||||
|
|
||||||
|
import specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
|
||||||
|
const ExporterImageConfigKey = "containerimage.config"
|
||||||
|
const ExporterPlatformsKey = "refs.platforms"
|
||||||
|
|
||||||
|
type Platforms struct {
|
||||||
|
Platforms []Platform
|
||||||
|
}
|
||||||
|
|
||||||
|
type Platform struct {
|
||||||
|
ID string
|
||||||
|
Platform specs.Platform
|
||||||
|
}
|
|
@ -5,12 +5,15 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containerd/containerd/platforms"
|
"github.com/containerd/containerd/platforms"
|
||||||
"github.com/docker/docker/builder/dockerignore"
|
"github.com/docker/docker/builder/dockerignore"
|
||||||
"github.com/moby/buildkit/client/llb"
|
"github.com/moby/buildkit/client/llb"
|
||||||
|
"github.com/moby/buildkit/exporter/containerimage/exptypes"
|
||||||
"github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb"
|
"github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb"
|
||||||
"github.com/moby/buildkit/frontend/gateway/client"
|
"github.com/moby/buildkit/frontend/gateway/client"
|
||||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
@ -24,13 +27,13 @@ const (
|
||||||
keyTarget = "target"
|
keyTarget = "target"
|
||||||
keyFilename = "filename"
|
keyFilename = "filename"
|
||||||
keyCacheFrom = "cache-from"
|
keyCacheFrom = "cache-from"
|
||||||
exporterImageConfig = "containerimage.config"
|
|
||||||
defaultDockerfileName = "Dockerfile"
|
defaultDockerfileName = "Dockerfile"
|
||||||
dockerignoreFilename = ".dockerignore"
|
dockerignoreFilename = ".dockerignore"
|
||||||
buildArgPrefix = "build-arg:"
|
buildArgPrefix = "build-arg:"
|
||||||
labelPrefix = "label:"
|
labelPrefix = "label:"
|
||||||
keyNoCache = "no-cache"
|
keyNoCache = "no-cache"
|
||||||
keyTargetPlatform = "platform"
|
keyTargetPlatform = "platform"
|
||||||
|
keyMultiPlatform = "multi-platform"
|
||||||
)
|
)
|
||||||
|
|
||||||
var httpPrefix = regexp.MustCompile("^https?://")
|
var httpPrefix = regexp.MustCompile("^https?://")
|
||||||
|
@ -45,12 +48,12 @@ func Build(ctx context.Context, c client.Client) (*client.Result, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buildPlatforms := []specs.Platform{defaultBuildPlatform}
|
buildPlatforms := []specs.Platform{defaultBuildPlatform}
|
||||||
targetPlatform := platforms.DefaultSpec()
|
targetPlatforms := []*specs.Platform{nil}
|
||||||
if v := opts[keyTargetPlatform]; v != "" {
|
if v := opts[keyTargetPlatform]; v != "" {
|
||||||
var err error
|
var err error
|
||||||
targetPlatform, err = platforms.Parse(v)
|
targetPlatforms, err = parsePlatforms(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "failed to parse target platform %s", v)
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,47 +200,107 @@ func Build(ctx context.Context, c client.Client) (*client.Result, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
st, img, err := dockerfile2llb.Dockerfile2LLB(ctx, dtDockerfile, dockerfile2llb.ConvertOpt{
|
exportMap := len(targetPlatforms) > 1
|
||||||
Target: opts[keyTarget],
|
|
||||||
MetaResolver: c,
|
|
||||||
BuildArgs: filter(opts, buildArgPrefix),
|
|
||||||
Labels: filter(opts, labelPrefix),
|
|
||||||
SessionID: c.BuildOpts().SessionID,
|
|
||||||
BuildContext: buildContext,
|
|
||||||
Excludes: excludes,
|
|
||||||
IgnoreCache: ignoreCache,
|
|
||||||
TargetPlatform: &targetPlatform,
|
|
||||||
BuildPlatforms: buildPlatforms,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
if v := opts[keyMultiPlatform]; v != "" {
|
||||||
return nil, errors.Wrapf(err, "failed to create LLB definition")
|
b, err := strconv.ParseBool(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Errorf("invalid boolean value %s", v)
|
||||||
|
}
|
||||||
|
if !b && exportMap {
|
||||||
|
return nil, errors.Errorf("returning multiple target plaforms is not allowed")
|
||||||
|
}
|
||||||
|
exportMap = b
|
||||||
}
|
}
|
||||||
|
|
||||||
def, err = st.Marshal()
|
expPlatforms := &exptypes.Platforms{
|
||||||
if err != nil {
|
Platforms: make([]exptypes.Platform, len(targetPlatforms)),
|
||||||
return nil, errors.Wrapf(err, "failed to marshal LLB definition")
|
}
|
||||||
|
res := client.NewResult()
|
||||||
|
|
||||||
|
eg, ctx = errgroup.WithContext(ctx)
|
||||||
|
|
||||||
|
for i, tp := range targetPlatforms {
|
||||||
|
func(i int, tp *specs.Platform) {
|
||||||
|
eg.Go(func() error {
|
||||||
|
st, img, err := dockerfile2llb.Dockerfile2LLB(ctx, dtDockerfile, dockerfile2llb.ConvertOpt{
|
||||||
|
Target: opts[keyTarget],
|
||||||
|
MetaResolver: c,
|
||||||
|
BuildArgs: filter(opts, buildArgPrefix),
|
||||||
|
Labels: filter(opts, labelPrefix),
|
||||||
|
SessionID: c.BuildOpts().SessionID,
|
||||||
|
BuildContext: buildContext,
|
||||||
|
Excludes: excludes,
|
||||||
|
IgnoreCache: ignoreCache,
|
||||||
|
TargetPlatform: tp,
|
||||||
|
BuildPlatforms: buildPlatforms,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to create LLB definition")
|
||||||
|
}
|
||||||
|
|
||||||
|
def, err := st.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to marshal LLB definition")
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := json.Marshal(img)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to marshal image config")
|
||||||
|
}
|
||||||
|
|
||||||
|
var cacheFrom []string
|
||||||
|
if cacheFromStr := opts[keyCacheFrom]; cacheFromStr != "" {
|
||||||
|
cacheFrom = strings.Split(cacheFromStr, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := c.Solve(ctx, client.SolveRequest{
|
||||||
|
Definition: def.ToPB(),
|
||||||
|
ImportCacheRefs: cacheFrom,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ref, err := r.SingleRef()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exportMap {
|
||||||
|
res.AddMeta(exptypes.ExporterImageConfigKey, config)
|
||||||
|
res.SetRef(ref)
|
||||||
|
} else {
|
||||||
|
p := platforms.DefaultSpec()
|
||||||
|
if tp != nil {
|
||||||
|
p = *tp
|
||||||
|
}
|
||||||
|
|
||||||
|
k := platforms.Format(p)
|
||||||
|
res.AddMeta(fmt.Sprintf("%s/%s", exptypes.ExporterImageConfigKey, k), config)
|
||||||
|
res.AddRef(k, ref)
|
||||||
|
expPlatforms.Platforms[i] = exptypes.Platform{
|
||||||
|
ID: k,
|
||||||
|
Platform: p,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}(i, tp)
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := json.Marshal(img)
|
if err := eg.Wait(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "failed to marshal image config")
|
|
||||||
}
|
|
||||||
|
|
||||||
var cacheFrom []string
|
|
||||||
if cacheFromStr := opts[keyCacheFrom]; cacheFromStr != "" {
|
|
||||||
cacheFrom = strings.Split(cacheFromStr, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := c.Solve(ctx, client.SolveRequest{
|
|
||||||
Definition: def.ToPB(),
|
|
||||||
ImportCacheRefs: cacheFrom,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res.AddMeta(exporterImageConfig, config)
|
if exportMap {
|
||||||
|
dt, err := json.Marshal(expPlatforms)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res.AddMeta(exptypes.ExporterPlatformsKey, dt)
|
||||||
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
@ -308,3 +371,16 @@ func isArchive(header []byte) bool {
|
||||||
_, err := r.Next()
|
_, err := r.Next()
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parsePlatforms(v string) ([]*specs.Platform, error) {
|
||||||
|
var pp []*specs.Platform
|
||||||
|
for _, v := range strings.Split(v, ",") {
|
||||||
|
p, err := platforms.Parse(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to parse target platform %s", v)
|
||||||
|
}
|
||||||
|
p = platforms.Normalize(p)
|
||||||
|
pp = append(pp, &p)
|
||||||
|
}
|
||||||
|
return pp, nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue