Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -507,11 +507,13 @@ Options:
| Flag | Description |
| --- | --- |
| -f, --file stringArray | Build definition file
| --load | Shorthand for --set=*.output=type=docker
| --no-cache | Do not use cache when building the image
| --print | Print the options without building
| --progress string | Set type of progress output (auto, plain, tty). Use plain to show container output (default "auto")
| --pull | Always attempt to pull a newer version of the image
| --set stringArray | Override target value (eg: target.key=value)
| --push | Shorthand for --set=*.output=type=registry
| --set stringArray | Override target value (eg: targetpattern.key=value)

#### `-f, --file FILE`

Expand Down Expand Up @@ -554,14 +556,16 @@ Same as `build --progress`. Set type of progress output (auto, plain, tty). Use

Same as `build --pull`.

#### `--set target.key[.subkey]=value`
#### `--set targetpattern.key[.subkey]=value`

Override target configurations from command line.
Override target configurations from command line. The pattern matching syntax is defined in https://golang.org/pkg/path/#Match.

Example:
```
docker buildx bake --set target.args.mybuildarg=value
docker buildx bake --set target.platform=linux/arm64
docker buildx bake --set foo*.args.mybuildarg=value # overrides build arg for all targets starting with 'foo'
docker buildx bake --set *.platform=linux/arm64 # overrides platform for all targets
```

#### File definition
Expand Down
124 changes: 74 additions & 50 deletions bake/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,26 @@ func mergeConfig(c1, c2 Config) Config {
return c1
}

func (c Config) expandTargets(pattern string) ([]string, error) {
if _, ok := c.Target[pattern]; ok {
return []string{pattern}, nil
}
var names []string
for name := range c.Target {
ok, err := path.Match(pattern, name)
if err != nil {
return nil, errors.Wrapf(err, "could not match targets with '%s'", pattern)
}
if ok {
names = append(names, name)
}
}
if len(names) == 0 {
return nil, errors.Errorf("could not find any target matching '%s'", pattern)
}
return names, nil
}

func (c Config) newOverrides(v []string) (map[string]Target, error) {
m := map[string]Target{}
for _, v := range v {
Expand All @@ -116,65 +136,69 @@ func (c Config) newOverrides(v []string) (map[string]Target, error) {
return nil, errors.Errorf("invalid override key %s, expected target.name", parts[0])
}

name := keys[0]
pattern := keys[0]
if len(parts) != 2 && keys[1] != "args" {
return nil, errors.Errorf("invalid override %s, expected target.name=value", v)
}

t := m[name]
if _, ok := c.Target[name]; !ok {
return nil, errors.Errorf("unknown target %s", name)
names, err := c.expandTargets(pattern)
if err != nil {
return nil, err
}

switch keys[1] {
case "context":
t.Context = &parts[1]
case "dockerfile":
t.Dockerfile = &parts[1]
case "args":
if len(keys) != 3 {
return nil, errors.Errorf("invalid key %s, args requires name", parts[0])
}
if t.Args == nil {
t.Args = map[string]string{}
}
if len(parts) < 2 {
v, ok := os.LookupEnv(keys[2])
if ok {
t.Args[keys[2]] = v
for _, name := range names {
t := m[name]

switch keys[1] {
case "context":
t.Context = &parts[1]
case "dockerfile":
t.Dockerfile = &parts[1]
case "args":
if len(keys) != 3 {
return nil, errors.Errorf("invalid key %s, args requires name", parts[0])
}
} else {
t.Args[keys[2]] = parts[1]
}
case "labels":
if len(keys) != 3 {
return nil, errors.Errorf("invalid key %s, lanels requires name", parts[0])
}
if t.Labels == nil {
t.Labels = map[string]string{}
if t.Args == nil {
t.Args = map[string]string{}
}
if len(parts) < 2 {
v, ok := os.LookupEnv(keys[2])
if ok {
t.Args[keys[2]] = v
}
} else {
t.Args[keys[2]] = parts[1]
}
case "labels":
if len(keys) != 3 {
return nil, errors.Errorf("invalid key %s, lanels requires name", parts[0])
}
if t.Labels == nil {
t.Labels = map[string]string{}
}
t.Labels[keys[2]] = parts[1]
case "tags":
t.Tags = append(t.Tags, parts[1])
case "cache-from":
t.CacheFrom = append(t.CacheFrom, parts[1])
case "cache-to":
t.CacheTo = append(t.CacheTo, parts[1])
case "target":
s := parts[1]
t.Target = &s
case "secrets":
t.Secrets = append(t.Secrets, parts[1])
case "ssh":
t.SSH = append(t.SSH, parts[1])
case "platform":
t.Platforms = append(t.Platforms, parts[1])
case "output":
t.Outputs = append(t.Outputs, parts[1])
default:
return nil, errors.Errorf("unknown key: %s", keys[1])
}
t.Labels[keys[2]] = parts[1]
case "tags":
t.Tags = append(t.Tags, parts[1])
case "cache-from":
t.CacheFrom = append(t.CacheFrom, parts[1])
case "cache-to":
t.CacheTo = append(t.CacheTo, parts[1])
case "target":
s := parts[1]
t.Target = &s
case "secrets":
t.Secrets = append(t.Secrets, parts[1])
case "ssh":
t.SSH = append(t.SSH, parts[1])
case "platform":
t.Platforms = append(t.Platforms, parts[1])
case "output":
t.Outputs = append(t.Outputs, parts[1])
default:
return nil, errors.Errorf("unknown key: %s", keys[1])
m[name] = t
}
m[name] = t
}
return m, nil
}
Expand Down
76 changes: 68 additions & 8 deletions bake/bake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ func TestReadTargets(t *testing.T) {

fp := filepath.Join(tmpdir, "config.hcl")
err = ioutil.WriteFile(fp, []byte(`
target "dep" {
target "webDEP" {
args {
VAR_INHERITED = "dep"
VAR_BOTH = "dep"
VAR_INHERITED = "webDEP"
VAR_BOTH = "webDEP"
}
}

Expand All @@ -30,7 +30,7 @@ target "webapp" {
args {
VAR_BOTH = "webapp"
}
inherits = ["dep"]
inherits = ["webDEP"]
}`), 0600)
require.NoError(t, err)

Expand All @@ -43,13 +43,13 @@ target "webapp" {

require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile)
require.Equal(t, ".", *m["webapp"].Context)
require.Equal(t, "dep", m["webapp"].Args["VAR_INHERITED"])
require.Equal(t, "webDEP", m["webapp"].Args["VAR_INHERITED"])
})

t.Run("InvalidTargetOverrides", func(t *testing.T) {
_, err := ReadTargets(ctx, []string{fp}, []string{"webapp"}, []string{"nosuchtarget.context=foo"})
require.NotNil(t, err)
require.Equal(t, err.Error(), "unknown target nosuchtarget")
require.Equal(t, err.Error(), "could not find any target matching 'nosuchtarget'")
})

t.Run("ArgsOverrides", func(t *testing.T) {
Expand Down Expand Up @@ -87,8 +87,8 @@ target "webapp" {
// building leaf but overriding parent fields
t.Run("parent", func(t *testing.T) {
m, err := ReadTargets(ctx, []string{fp}, []string{"webapp"}, []string{
"dep.args.VAR_INHERITED=override",
"dep.args.VAR_BOTH=override",
"webDEP.args.VAR_INHERITED=override",
"webDEP.args.VAR_BOTH=override",
})
require.NoError(t, err)
require.Equal(t, m["webapp"].Args["VAR_INHERITED"], "override")
Expand All @@ -106,6 +106,66 @@ target "webapp" {
require.Equal(t, "foo", *m["webapp"].Context)
})

t.Run("PatternOverride", func(t *testing.T) {
// same check for two cases
multiTargetCheck := func(t *testing.T, m map[string]Target, err error) {
require.NoError(t, err)
require.Equal(t, 2, len(m))
require.Equal(t, "foo", *m["webapp"].Dockerfile)
require.Equal(t, "webDEP", m["webapp"].Args["VAR_INHERITED"])
require.Equal(t, "foo", *m["webDEP"].Dockerfile)
require.Equal(t, "webDEP", m["webDEP"].Args["VAR_INHERITED"])
}

cases := []struct {
name string
targets []string
overrides []string
check func(*testing.T, map[string]Target, error)
}{
{
name: "multi target single pattern",
targets: []string{"webapp", "webDEP"},
overrides: []string{"web*.dockerfile=foo"},
check: multiTargetCheck,
},
{
name: "multi target multi pattern",
targets: []string{"webapp", "webDEP"},
overrides: []string{"web*.dockerfile=foo", "*.args.VAR_BOTH=bar"},
check: multiTargetCheck,
},
{
name: "single target",
targets: []string{"webapp"},
overrides: []string{"web*.dockerfile=foo"},
check: func(t *testing.T, m map[string]Target, err error) {
require.NoError(t, err)
require.Equal(t, 1, len(m))
require.Equal(t, "foo", *m["webapp"].Dockerfile)
require.Equal(t, "webDEP", m["webapp"].Args["VAR_INHERITED"])
},
},
{
name: "nomatch",
targets: []string{"webapp"},
overrides: []string{"nomatch*.dockerfile=foo"},
check: func(t *testing.T, m map[string]Target, err error) {
// NOTE: I am unsure whether failing to match should always error out
// instead of simply skipping that override.
// Let's enforce the error and we can relax it later if users complain.
require.NotNil(t, err)
require.Equal(t, err.Error(), "could not find any target matching 'nomatch*'")
},
},
}
for _, test := range cases {
t.Run(test.name, func(t *testing.T) {
m, err := ReadTargets(ctx, []string{fp}, test.targets, test.overrides)
test.check(t, m, err)
})
}
})
}

func TestReadTargetsCompose(t *testing.T) {
Expand Down
16 changes: 14 additions & 2 deletions commands/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,17 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) error {
targets = []string{"default"}
}

m, err := bake.ReadTargets(ctx, in.files, targets, in.overrides)
overrides := in.overrides
if in.exportPush {
if in.exportLoad {
return errors.Errorf("push and load may not be set together at the moment")
}
overrides = append(overrides, "*.output=type=registry")
} else if in.exportLoad {
overrides = append(overrides, "*.output=type=docker")
}

m, err := bake.ReadTargets(ctx, in.files, targets, overrides)
if err != nil {
return err
}
Expand Down Expand Up @@ -99,7 +109,9 @@ func bakeCmd(dockerCli command.Cli) *cobra.Command {

flags.StringArrayVarP(&options.files, "file", "f", []string{}, "Build definition file")
flags.BoolVar(&options.printOnly, "print", false, "Print the options without building")
flags.StringArrayVar(&options.overrides, "set", nil, "Override target value (eg: target.key=value)")
flags.StringArrayVar(&options.overrides, "set", nil, "Override target value (eg: targetpattern.key=value)")
flags.BoolVar(&options.exportPush, "push", false, "Shorthand for --set=*.output=type=registry")
flags.BoolVar(&options.exportLoad, "load", false, "Shorthand for --set=*.output=type=docker")

commonFlags(&options.commonOptions, flags)

Expand Down
11 changes: 5 additions & 6 deletions commands/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ type buildOptions struct {
extraHosts []string
networkMode string

exportPush bool
exportLoad bool

// unimplemented
squash bool
quiet bool
Expand All @@ -65,9 +62,11 @@ type buildOptions struct {
}

type commonOptions struct {
noCache bool
progress string
pull bool
noCache bool
progress string
pull bool
exportPush bool
exportLoad bool
}

func runBuild(dockerCli command.Cli, in buildOptions) error {
Expand Down