Skip to content

Commit

Permalink
bake: use controller to build
Browse files Browse the repository at this point in the history
With this change we are now passing a list of controller options
to run a build and a map of responses and result context is returned
as bake can handle a list of targets.

Signed-off-by: CrazyMax <[email protected]>
  • Loading branch information
crazy-max committed Jun 1, 2023
1 parent 1af68cb commit 0ce15c3
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 66 deletions.
11 changes: 3 additions & 8 deletions bake/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
composecli "github.com/compose-spec/compose-go/cli"
"github.com/docker/buildx/bake/hclparser"
"github.com/docker/buildx/build"
cbuild "github.com/docker/buildx/controller/build"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/docker/buildx/util/buildflags"
hcl "github.com/hashicorp/hcl/v2"
Expand Down Expand Up @@ -908,18 +907,14 @@ func (t *Target) GetName(ectx *hcl.EvalContext, block *hcl.Block, loadDeps func(
return value.AsString(), nil
}

func TargetsToBuildOpt(m map[string]*Target, inp *Input) (map[string]build.Options, error) {
m2 := make(map[string]build.Options, len(m))
func TargetsToControllerOptions(m map[string]*Target, inp *Input) (map[string]*controllerapi.BuildOptions, error) {
m2 := make(map[string]*controllerapi.BuildOptions, len(m))
for k, v := range m {
opts, err := toControllerOpt(v, inp)
if err != nil {
return nil, err
}
bo, err := cbuild.ToBuildOpts(*opts, nil)
if err != nil {
return nil, err
}
m2[k] = *bo
m2[k] = opts
}
return m2, nil
}
Expand Down
44 changes: 29 additions & 15 deletions bake/bake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"
"testing"

"github.com/docker/buildx/controller/pb"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -390,7 +391,7 @@ func TestHCLCwdPrefix(t *testing.T) {
_, ok := m["app"]
require.True(t, ok)

_, err = TargetsToBuildOpt(m, &Input{})
_, err = TargetsToControllerOptions(m, &Input{})
require.NoError(t, err)

require.Equal(t, "test", *m["app"].Dockerfile)
Expand Down Expand Up @@ -421,7 +422,7 @@ func TestOverrideMerge(t *testing.T) {
_, ok := m["app"]
require.True(t, ok)

_, err = TargetsToBuildOpt(m, &Input{})
_, err = TargetsToControllerOptions(m, &Input{})
require.NoError(t, err)

require.Equal(t, []string{"linux/arm", "linux/ppc64le"}, m["app"].Platforms)
Expand Down Expand Up @@ -456,7 +457,7 @@ func TestReadContexts(t *testing.T) {
_, ok := m["app"]
require.True(t, ok)

bo, err := TargetsToBuildOpt(m, &Input{})
bo, err := TargetsToControllerOptions(m, &Input{})
require.NoError(t, err)

ctxs := bo["app"].Inputs.NamedContexts
Expand All @@ -472,7 +473,7 @@ func TestReadContexts(t *testing.T) {
_, ok = m["app"]
require.True(t, ok)

bo, err = TargetsToBuildOpt(m, &Input{})
bo, err = TargetsToControllerOptions(m, &Input{})
require.NoError(t, err)

ctxs = bo["app"].Inputs.NamedContexts
Expand All @@ -490,7 +491,7 @@ func TestReadContexts(t *testing.T) {
_, ok = m["app"]
require.True(t, ok)

bo, err = TargetsToBuildOpt(m, &Input{})
bo, err = TargetsToControllerOptions(m, &Input{})
require.NoError(t, err)

ctxs = bo["app"].Inputs.NamedContexts
Expand Down Expand Up @@ -1325,7 +1326,7 @@ func TestHCLNullVars(t *testing.T) {
_, ok := m["default"]
require.True(t, ok)

_, err = TargetsToBuildOpt(m, &Input{})
_, err = TargetsToControllerOptions(m, &Input{})
require.NoError(t, err)
require.Equal(t, map[string]*string{"bar": ptrstr("baz")}, m["default"].Args)
require.Equal(t, map[string]*string{"com.docker.app.baz": ptrstr("foo")}, m["default"].Labels)
Expand Down Expand Up @@ -1360,7 +1361,7 @@ func TestJSONNullVars(t *testing.T) {
_, ok := m["default"]
require.True(t, ok)

_, err = TargetsToBuildOpt(m, &Input{})
_, err = TargetsToControllerOptions(m, &Input{})
require.NoError(t, err)
require.Equal(t, map[string]*string{"bar": ptrstr("baz")}, m["default"].Args)
}
Expand Down Expand Up @@ -1432,21 +1433,34 @@ func TestAttestDuplicates(t *testing.T) {
require.Equal(t, []string{"type=sbom,foo=bar", "type=provenance,mode=max"}, m["default"].Attest)
require.NoError(t, err)

opts, err := TargetsToBuildOpt(m, &Input{})
opts, err := TargetsToControllerOptions(m, &Input{})
require.NoError(t, err)
require.Equal(t, map[string]*string{
"sbom": ptrstr("type=sbom,foo=bar"),
"provenance": ptrstr("type=provenance,mode=max"),
require.Equal(t, []*pb.Attest{
{
Type: "sbom",
Attrs: "type=sbom,foo=bar",
},
{
Type: "provenance",
Attrs: "type=provenance,mode=max",
},
}, opts["default"].Attests)

m, _, err = ReadTargets(ctx, []File{fp}, []string{"default"}, []string{"*.attest=type=sbom,disabled=true"}, nil)
require.Equal(t, []string{"type=sbom,disabled=true", "type=provenance,mode=max"}, m["default"].Attest)
require.NoError(t, err)

opts, err = TargetsToBuildOpt(m, &Input{})
opts, err = TargetsToControllerOptions(m, &Input{})
require.NoError(t, err)
require.Equal(t, map[string]*string{
"sbom": nil,
"provenance": ptrstr("type=provenance,mode=max"),
require.Equal(t, []*pb.Attest{
{
Type: "sbom",
Disabled: true,
Attrs: "type=sbom,disabled=true",
},
{
Type: "provenance",
Attrs: "type=provenance,mode=max",
},
}, opts["default"].Attests)
}
4 changes: 2 additions & 2 deletions build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ func Build(ctx context.Context, nodes []builder.Node, opt map[string]Options, do
return BuildWithResultHandler(ctx, nodes, opt, docker, configDir, w, nil)
}

func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[string]Options, docker *dockerutil.Client, configDir string, w progress.Writer, resultHandleFunc func(driverIndex int, rCtx *ResultContext)) (resp map[string]*client.SolveResponse, err error) {
func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[string]Options, docker *dockerutil.Client, configDir string, w progress.Writer, resultHandleFunc func(target string, rCtx *ResultContext)) (resp map[string]*client.SolveResponse, err error) {
if len(nodes) == 0 {
return nil, errors.Errorf("driver required for build")
}
Expand Down Expand Up @@ -932,7 +932,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
if resultHandleFunc != nil {
resultCtx, err := NewResultContext(ctx, cc, so, res)
if err == nil {
resultHandleFunc(dp.driverIndex, resultCtx)
resultHandleFunc(k, resultCtx)
} else {
logrus.Warnf("failed to record result: %s", err)
}
Expand Down
34 changes: 16 additions & 18 deletions commands/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import (
"github.com/docker/buildx/bake"
"github.com/docker/buildx/build"
"github.com/docker/buildx/builder"
cbuild "github.com/docker/buildx/controller/build"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/docker/buildx/util/buildflags"
"github.com/docker/buildx/util/cobrautil/completion"
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/dockerutil"
"github.com/docker/buildx/util/progress"
"github.com/docker/buildx/util/tracing"
"github.com/docker/cli/cli/command"
Expand All @@ -29,10 +29,8 @@ type bakeOptions struct {
sbom string
provenance string

builder string
metadataFile string
exportPush bool
exportLoad bool
controllerapi.CommonOptions
//control.ControlOptions
}

func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags commonFlags) (err error) {
Expand Down Expand Up @@ -67,12 +65,12 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com
}

overrides := in.overrides
if in.exportPush {
if in.exportLoad {
if in.ExportPush {
if in.ExportLoad {
return errors.Errorf("push and load may not be set together at the moment")
}
overrides = append(overrides, "*.push=true")
} else if in.exportLoad {
} else if in.ExportLoad {
overrides = append(overrides, "*.output=type=docker")
}
if cFlags.noCache != nil {
Expand Down Expand Up @@ -100,7 +98,7 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com
// instance only needed for reading remote bake files or building
if url != "" || !in.printOnly {
b, err := builder.New(dockerCli,
builder.WithName(in.builder),
builder.WithName(in.Builder),
builder.WithContextPathHash(contextPathHash),
)
if err != nil {
Expand Down Expand Up @@ -166,7 +164,7 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com
}

// this function can update target context string from the input so call before printOnly check
bo, err := bake.TargetsToBuildOpt(tgts, inp)
opts, err := bake.TargetsToControllerOptions(tgts, inp)
if err != nil {
return err
}
Expand All @@ -191,17 +189,17 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com
return nil
}

resp, err := build.Build(ctx, nodes, bo, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), printer)
resp, _, err := cbuild.RunBuild(ctx, dockerCli, in.Builder, opts, os.Stdin, printer, false)
if err != nil {
return wrapBuildError(err, true)
}

if len(in.metadataFile) > 0 {
if len(in.MetadataFile) > 0 {
dt := make(map[string]interface{})
for t, r := range resp {
dt[t] = decodeExporterResponse(r.ExporterResponse)
}
if err := writeMetadataFile(in.metadataFile, dt); err != nil {
if err := writeMetadataFile(in.MetadataFile, dt); err != nil {
return err
}
}
Expand All @@ -225,8 +223,8 @@ func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
if !cmd.Flags().Lookup("pull").Changed {
cFlags.pull = nil
}
options.builder = rootOpts.builder
options.metadataFile = cFlags.metadataFile
options.Builder = rootOpts.builder
options.MetadataFile = cFlags.metadataFile
// Other common flags (noCache, pull and progress) are processed in runBake function.
return runBake(dockerCli, args, options, cFlags)
},
Expand All @@ -236,9 +234,9 @@ func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
flags := cmd.Flags()

flags.StringArrayVarP(&options.files, "file", "f", []string{}, "Build definition file")
flags.BoolVar(&options.exportLoad, "load", false, `Shorthand for "--set=*.output=type=docker"`)
flags.BoolVar(&options.ExportLoad, "load", false, `Shorthand for "--set=*.output=type=docker"`)
flags.BoolVar(&options.printOnly, "print", false, "Print the options without building")
flags.BoolVar(&options.exportPush, "push", false, `Shorthand for "--set=*.output=type=registry"`)
flags.BoolVar(&options.ExportPush, "push", false, `Shorthand for "--set=*.output=type=registry"`)
flags.StringVar(&options.sbom, "sbom", "", `Shorthand for "--set=*.attest=type=sbom"`)
flags.StringVar(&options.provenance, "provenance", "", `Shorthand for "--set=*.attest=type=provenance"`)
flags.StringArrayVar(&options.overrides, "set", nil, `Override target value (e.g., "targetpattern.key=value")`)
Expand Down
7 changes: 5 additions & 2 deletions commands/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,11 @@ func getImageID(resp map[string]string) string {
}

func runBasicBuild(ctx context.Context, dockerCli command.Cli, opts *controllerapi.BuildOptions, options buildOptions, printer *progress.Printer) (*client.SolveResponse, error) {
resp, _, err := cbuild.RunBuild(ctx, dockerCli, *opts, os.Stdin, printer, false)
return resp, err
resp, _, err := cbuild.RunBuild(ctx, dockerCli, options.Builder, map[string]*controllerapi.BuildOptions{cbuild.DefaultTargetName: opts}, os.Stdin, printer, false)
if err != nil {
return nil, err
}
return resp[cbuild.DefaultTargetName], err
}

func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *controllerapi.BuildOptions, options buildOptions, printer *progress.Printer) (*client.SolveResponse, error) {
Expand Down
58 changes: 41 additions & 17 deletions controller/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,54 @@ import (
"github.com/moby/buildkit/session/auth/authprovider"
"github.com/moby/buildkit/util/grpcerrors"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc/codes"
)

const defaultTargetName = "default"
const DefaultTargetName = "default"

// RunBuild runs the specified build and returns the result.
//
// NOTE: When an error happens during the build and this function acquires the debuggable *build.ResultContext,
// this function returns it in addition to the error (i.e. it does "return nil, res, err"). The caller can
// inspect the result and debug the cause of that error.
func RunBuild(ctx context.Context, dockerCli command.Cli, in controllerapi.BuildOptions, inStream io.Reader, progress progress.Writer, generateResult bool) (*client.SolveResponse, *build.ResultContext, error) {
opts, err := ToBuildOpts(in, inStream)
if err != nil {
func RunBuild(ctx context.Context, dockerCli command.Cli, builderName string, in map[string]*controllerapi.BuildOptions, inStream io.Reader, progress progress.Writer, generateResult bool) (map[string]*client.SolveResponse, map[string]*build.ResultContext, error) {
var err error

opts := make(map[string]build.Options, len(in))
optsMu := sync.Mutex{}
eg, _ := errgroup.WithContext(ctx)
for t, o := range in {
func(t string, o *controllerapi.BuildOptions) {
eg.Go(func() error {
opt, err := ToBuildOpts(o, inStream)
if err != nil {
return err
}
optsMu.Lock()
opts[t] = *opt
optsMu.Unlock()
return nil
})
}(t, o)
}
if err := eg.Wait(); err != nil {
return nil, nil, err
}

// key string used for kubernetes "sticky" mode
contextPathHash, err := filepath.Abs(in.Inputs.ContextPath)
if err != nil {
contextPathHash = in.Inputs.ContextPath
contextPathHash, _ := os.Getwd()
if len(in) == 1 {
for _, o := range in {
contextPathHash, err = filepath.Abs(o.Inputs.ContextPath)
if err != nil {
contextPathHash = o.Inputs.ContextPath
}
}
}

b, err := builder.New(dockerCli,
builder.WithName(in.Opts.Builder),
builder.WithName(builderName),
builder.WithContextPathHash(contextPathHash),
)
if err != nil {
Expand All @@ -64,15 +88,15 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in controllerapi.Build
return nil, nil, err
}

resp, res, err := buildTargets(ctx, dockerCli, b.NodeGroup, nodes, map[string]build.Options{defaultTargetName: *opts}, progress, generateResult)
resp, res, err := buildTargets(ctx, dockerCli, b.NodeGroup, nodes, opts, progress, generateResult)
err = wrapBuildError(err, false)
if err != nil {
return nil, nil, err
}
return resp, res, nil
}

func ToBuildOpts(in controllerapi.BuildOptions, inStream io.Reader) (*build.Options, error) {
func ToBuildOpts(in *controllerapi.BuildOptions, inStream io.Reader) (*build.Options, error) {
if in.Opts.NoCache && len(in.NoCacheFilter) > 0 {
return nil, errors.Errorf("--no-cache and --no-cache-filter cannot currently be used together")
}
Expand Down Expand Up @@ -216,27 +240,27 @@ func ToBuildOpts(in controllerapi.BuildOptions, inStream io.Reader) (*build.Opti
// NOTE: When an error happens during the build and this function acquires the debuggable *build.ResultContext,
// this function returns it in addition to the error (i.e. it does "return nil, res, err"). The caller can
// inspect the result and debug the cause of that error.
func buildTargets(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup, nodes []builder.Node, opts map[string]build.Options, progress progress.Writer, generateResult bool) (*client.SolveResponse, *build.ResultContext, error) {
var res *build.ResultContext
func buildTargets(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup, nodes []builder.Node, opts map[string]build.Options, progress progress.Writer, generateResult bool) (map[string]*client.SolveResponse, map[string]*build.ResultContext, error) {
var res map[string]*build.ResultContext
var resp map[string]*client.SolveResponse
var err error
if generateResult {
var mu sync.Mutex
var idx int
resp, err = build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), progress, func(driverIndex int, gotRes *build.ResultContext) {
resp, err = build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), progress, func(target string, gotRes *build.ResultContext) {
mu.Lock()
defer mu.Unlock()
if res == nil || driverIndex < idx {
idx, res = driverIndex, gotRes
if res == nil {
res = make(map[string]*build.ResultContext)
}
res[target] = gotRes
})
} else {
resp, err = build.Build(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), progress)
}
if err != nil {
return nil, res, err
}
return resp[defaultTargetName], res, err
return resp, res, err
}

func wrapBuildError(err error, bake bool) error {
Expand Down
Loading

0 comments on commit 0ce15c3

Please sign in to comment.