Skip to content

Commit

Permalink
Add buildx build --opt (EXPERIMENTAL)
Browse files Browse the repository at this point in the history
This allows opt-in to new features of BuildKit that are not officially
exposed to buildx yet.

Signed-off-by: Akihiro Suda <[email protected]>
  • Loading branch information
AkihiroSuda committed Feb 20, 2024
1 parent 714b85a commit be04adc
Show file tree
Hide file tree
Showing 11 changed files with 216 additions and 124 deletions.
20 changes: 18 additions & 2 deletions bake/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -554,9 +554,9 @@ func (c Config) newOverrides(v []string) (map[string]map[string]Override, error)
o.Value = v
}
fallthrough
case "contexts":
case "contexts", "opts":
if len(keys) != 3 {
return nil, errors.Errorf("invalid key %s, contexts requires name", parts[0])
return nil, errors.Errorf("invalid key %s, %s requires name", parts[0], keys[1])
}
fallthrough
default:
Expand Down Expand Up @@ -699,6 +699,7 @@ type Target struct {
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional" cty:"no-cache"`
NetworkMode *string `json:"-" hcl:"-" cty:"-"`
NoCacheFilter []string `json:"no-cache-filter,omitempty" hcl:"no-cache-filter,optional" cty:"no-cache-filter"`
Opts map[string]string `json:"opts,omitempty" hcl:"opts,optional" cty:"opts"`
// IMPORTANT: if you add more fields here, do not forget to update newOverrides and docs/bake-reference.md.

// linked is a private field to mark a target used as a linked one
Expand Down Expand Up @@ -809,6 +810,12 @@ func (t *Target) Merge(t2 *Target) {
if t2.NoCacheFilter != nil { // merge
t.NoCacheFilter = append(t.NoCacheFilter, t2.NoCacheFilter...)
}
for k, v := range t2.Opts {
if t.Opts == nil {
t.Opts = map[string]string{}
}
t.Opts[k] = v
}
t.Inherits = append(t.Inherits, t2.Inherits...)
}

Expand Down Expand Up @@ -893,6 +900,14 @@ func (t *Target) AddOverrides(overrides map[string]Override) error {
}
}
}
case "opts":
if len(keys) != 2 {
return errors.Errorf("opts require name")
}
if t.Opts == nil {
t.Opts = map[string]string{}
}
t.Opts[keys[1]] = value
default:
return errors.Errorf("unknown key: %s", keys[0])
}
Expand Down Expand Up @@ -1244,6 +1259,7 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
Pull: pull,
NetworkMode: networkMode,
Linked: t.linked,
RawOpts: t.Opts,
}

platforms, err := platformutil.Parse(t.Platforms)
Expand Down
12 changes: 12 additions & 0 deletions build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ type Options struct {
PrintFunc *PrintFunc
SourcePolicy *spb.Policy
GroupRef string

RawOpts map[string]string
}

type PrintFunc struct {
Expand Down Expand Up @@ -226,6 +228,16 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt Op
AllowedEntitlements: opt.Allow,
SourcePolicy: opt.SourcePolicy,
}
if opt.RawOpts != nil {
defer func() {
for k, v := range opt.RawOpts {
if oldV, ok := so.FrontendAttrs[k]; ok {
logrus.Debugf("Overriding frontend opt %s=%q with %q", k, oldV, v)
}
so.FrontendAttrs[k] = v
}
}()
}

if so.Ref == "" {
so.Ref = identity.NewID()
Expand Down
11 changes: 10 additions & 1 deletion commands/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ type buildOptions struct {
control.ControlOptions

invokeConfig *invokeConfig

rawOpts []string
}

func (o *buildOptions) toControllerOptions() (*controllerapi.BuildOptions, error) {
Expand All @@ -120,6 +122,11 @@ func (o *buildOptions) toControllerOptions() (*controllerapi.BuildOptions, error
return nil, err
}

rawOpts, err := listToMap(o.rawOpts, true)
if err != nil {
return nil, err
}

opts := controllerapi.BuildOptions{
Allow: o.allow,
Annotations: o.annotations,
Expand All @@ -141,6 +148,7 @@ func (o *buildOptions) toControllerOptions() (*controllerapi.BuildOptions, error
Pull: o.pull,
ExportPush: o.exportPush,
ExportLoad: o.exportLoad,
RawOpts: rawOpts,
}

// TODO: extract env var parsing to a method easily usable by library consumers
Expand Down Expand Up @@ -590,7 +598,8 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions, debugConfig *debug.D

if isExperimental() {
flags.StringVar(&options.printFunc, "print", "", "Print result of information request (e.g., outline, targets)")
cobrautil.MarkFlagsExperimental(flags, "print")
flags.StringArrayVar(&options.rawOpts, "opt", []string{}, "Set BuildKit frontend option directly")
cobrautil.MarkFlagsExperimental(flags, "print", "opt")
}

flags.BoolVar(&options.exportPush, "push", false, `Shorthand for "--output=type=registry"`)
Expand Down
1 change: 1 addition & 0 deletions controller/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in controllerapi.Build
Target: in.Target,
Ulimits: controllerUlimitOpt2DockerUlimit(in.Ulimits),
GroupRef: in.GroupRef,
RawOpts: in.RawOpts,
}

platforms, err := platformutil.Parse(in.Platforms)
Expand Down
252 changes: 131 additions & 121 deletions controller/pb/controller.pb.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions controller/pb/controller.proto
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ message BuildOptions {
string Ref = 29;
string GroupRef = 30;
repeated string Annotations = 31;
map<string, string> RawOpts = 32;
}

message ExportEntry {
Expand Down
6 changes: 6 additions & 0 deletions docs/bake-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ The following table shows the complete list of attributes that you can assign to
| [`name`](#targetname) | String | Override the target name when using a matrix. |
| [`no-cache-filter`](#targetno-cache-filter) | List | Disable build cache for specific stages |
| [`no-cache`](#targetno-cache) | Boolean | Disable build cache completely |
| [`opts`](#targetopts) | Map | Set BuildKit frontend option directly |
| [`output`](#targetoutput) | List | Output destinations |
| [`platforms`](#targetplatforms) | List | Target platforms |
| [`pull`](#targetpull) | Boolean | Always pull images |
Expand Down Expand Up @@ -769,6 +770,11 @@ target "default" {
}
```

### `target.opts`

This is the same as the `--opt` flag for `buildctl build`.
This attribute is experimental.

### `target.output`

Configuration for exporting the build output.
Expand Down
1 change: 1 addition & 0 deletions docs/reference/buildx_build.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Start a build
| `--network` | `string` | `default` | Set the networking mode for the `RUN` instructions during build |
| `--no-cache` | | | Do not use cache when building the image |
| [`--no-cache-filter`](#no-cache-filter) | `stringArray` | | Do not cache specified stages |
| `--opt` | `stringArray` | | Set BuildKit frontend option directly (EXPERIMENTAL) |
| [`-o`](#output), [`--output`](#output) | `stringArray` | | Output destination (format: `type=local,dest=path`) |
| [`--platform`](#platform) | `stringArray` | | Set target platform for build |
| `--print` | `string` | | Print result of information request (e.g., outline, targets) (EXPERIMENTAL) |
Expand Down
1 change: 1 addition & 0 deletions docs/reference/buildx_debug_build.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Start a build
| `--network` | `string` | `default` | Set the networking mode for the `RUN` instructions during build |
| `--no-cache` | | | Do not use cache when building the image |
| `--no-cache-filter` | `stringArray` | | Do not cache specified stages |
| `--opt` | `stringArray` | | Set BuildKit frontend option directly (EXPERIMENTAL) |
| `-o`, `--output` | `stringArray` | | Output destination (format: `type=local,dest=path`) |
| `--platform` | `stringArray` | | Set target platform for build |
| `--print` | `string` | | Print result of information request (e.g., outline, targets) (EXPERIMENTAL) |
Expand Down
31 changes: 31 additions & 0 deletions tests/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ var buildTests = []func(t *testing.T, sb integration.Sandbox){
testBuildCacheExportNotSupported,
testBuildOCIExportNotSupported,
testBuildMultiPlatformNotSupported,
testBuildOpt,
testDockerHostGateway,
}

Expand Down Expand Up @@ -417,6 +418,36 @@ func testBuildMultiPlatformNotSupported(t *testing.T, sb integration.Sandbox) {
require.Contains(t, string(out), "Multi-platform build is not supported")
}

func testBuildOpt(t *testing.T, sb integration.Sandbox) {
dir := createTestProject(t)

registry, err := sb.NewRegistry()
if errors.Is(err, integration.ErrRequirements) {
t.Skip(err.Error())
}
require.NoError(t, err)
target := registry + "/buildx/registry:latest"

labels := []string{
"--label", "label0=foo",
"--opt", "label:label0=bar", // higher precedence
}
out, err := buildCmd(sb, withExperimental(), withArgs(labels...), withArgs(fmt.Sprintf("--output=type=image,name=%s,push=true", target), dir))
require.NoError(t, err, string(out))

desc, provider, err := contentutil.ProviderFromRef(target)
require.NoError(t, err)
imgs, err := testutil.ReadImages(sb.Context(), provider, desc)
require.NoError(t, err)

pk := platforms.Format(platforms.Normalize(platforms.DefaultSpec()))
img := imgs.Find(pk)
require.NotNil(t, img)

require.NotNil(t, img.Manifest)
assert.Equal(t, "bar", img.Img.Config.Labels["label0"])
}

func testDockerHostGateway(t *testing.T, sb integration.Sandbox) {
dockerfile := []byte(`
FROM busybox
Expand Down
4 changes: 4 additions & 0 deletions tests/integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ func withEnv(env ...string) cmdOpt {
}
}

func withExperimental() cmdOpt {
return withEnv("BUILDX_EXPERIMENTAL=1")
}

func withArgs(args ...string) cmdOpt {
return func(cmd *exec.Cmd) {
cmd.Args = append(cmd.Args, args...)
Expand Down

0 comments on commit be04adc

Please sign in to comment.