mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-05-29 17:05:46 +08:00
Merge pull request #164 from tiborvass/multitarget-set
bake: allow pattern matching for target names in --set
This commit is contained in:
commit
0e1f0e3c73
10
README.md
10
README.md
@ -507,11 +507,13 @@ Options:
|
|||||||
| Flag | Description |
|
| Flag | Description |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| -f, --file stringArray | Build definition file
|
| -f, --file stringArray | Build definition file
|
||||||
|
| --load | Shorthand for --set=*.output=type=docker
|
||||||
| --no-cache | Do not use cache when building the image
|
| --no-cache | Do not use cache when building the image
|
||||||
| --print | Print the options without building
|
| --print | Print the options without building
|
||||||
| --progress string | Set type of progress output (auto, plain, tty). Use plain to show container output (default "auto")
|
| --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
|
| --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`
|
#### `-f, --file FILE`
|
||||||
|
|
||||||
@ -554,14 +556,16 @@ Same as `build --progress`. Set type of progress output (auto, plain, tty). Use
|
|||||||
|
|
||||||
Same as `build --pull`.
|
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:
|
Example:
|
||||||
```
|
```
|
||||||
docker buildx bake --set target.args.mybuildarg=value
|
docker buildx bake --set target.args.mybuildarg=value
|
||||||
docker buildx bake --set target.platform=linux/arm64
|
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
|
#### File definition
|
||||||
|
124
bake/bake.go
124
bake/bake.go
@ -106,6 +106,26 @@ func mergeConfig(c1, c2 Config) Config {
|
|||||||
return c1
|
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) {
|
func (c Config) newOverrides(v []string) (map[string]Target, error) {
|
||||||
m := map[string]Target{}
|
m := map[string]Target{}
|
||||||
for _, v := range v {
|
for _, v := range v {
|
||||||
@ -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])
|
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" {
|
if len(parts) != 2 && keys[1] != "args" {
|
||||||
return nil, errors.Errorf("invalid override %s, expected target.name=value", v)
|
return nil, errors.Errorf("invalid override %s, expected target.name=value", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
t := m[name]
|
names, err := c.expandTargets(pattern)
|
||||||
if _, ok := c.Target[name]; !ok {
|
if err != nil {
|
||||||
return nil, errors.Errorf("unknown target %s", name)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch keys[1] {
|
for _, name := range names {
|
||||||
case "context":
|
t := m[name]
|
||||||
t.Context = &parts[1]
|
|
||||||
case "dockerfile":
|
switch keys[1] {
|
||||||
t.Dockerfile = &parts[1]
|
case "context":
|
||||||
case "args":
|
t.Context = &parts[1]
|
||||||
if len(keys) != 3 {
|
case "dockerfile":
|
||||||
return nil, errors.Errorf("invalid key %s, args requires name", parts[0])
|
t.Dockerfile = &parts[1]
|
||||||
}
|
case "args":
|
||||||
if t.Args == nil {
|
if len(keys) != 3 {
|
||||||
t.Args = map[string]string{}
|
return nil, errors.Errorf("invalid key %s, args requires name", parts[0])
|
||||||
}
|
|
||||||
if len(parts) < 2 {
|
|
||||||
v, ok := os.LookupEnv(keys[2])
|
|
||||||
if ok {
|
|
||||||
t.Args[keys[2]] = v
|
|
||||||
}
|
}
|
||||||
} else {
|
if t.Args == nil {
|
||||||
t.Args[keys[2]] = parts[1]
|
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])
|
||||||
}
|
}
|
||||||
case "labels":
|
m[name] = t
|
||||||
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])
|
|
||||||
}
|
}
|
||||||
m[name] = t
|
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,10 @@ func TestReadTargets(t *testing.T) {
|
|||||||
|
|
||||||
fp := filepath.Join(tmpdir, "config.hcl")
|
fp := filepath.Join(tmpdir, "config.hcl")
|
||||||
err = ioutil.WriteFile(fp, []byte(`
|
err = ioutil.WriteFile(fp, []byte(`
|
||||||
target "dep" {
|
target "webDEP" {
|
||||||
args {
|
args {
|
||||||
VAR_INHERITED = "dep"
|
VAR_INHERITED = "webDEP"
|
||||||
VAR_BOTH = "dep"
|
VAR_BOTH = "webDEP"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ target "webapp" {
|
|||||||
args {
|
args {
|
||||||
VAR_BOTH = "webapp"
|
VAR_BOTH = "webapp"
|
||||||
}
|
}
|
||||||
inherits = ["dep"]
|
inherits = ["webDEP"]
|
||||||
}`), 0600)
|
}`), 0600)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -43,13 +43,13 @@ target "webapp" {
|
|||||||
|
|
||||||
require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile)
|
require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile)
|
||||||
require.Equal(t, ".", *m["webapp"].Context)
|
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) {
|
t.Run("InvalidTargetOverrides", func(t *testing.T) {
|
||||||
_, err := ReadTargets(ctx, []string{fp}, []string{"webapp"}, []string{"nosuchtarget.context=foo"})
|
_, err := ReadTargets(ctx, []string{fp}, []string{"webapp"}, []string{"nosuchtarget.context=foo"})
|
||||||
require.NotNil(t, err)
|
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) {
|
t.Run("ArgsOverrides", func(t *testing.T) {
|
||||||
@ -87,8 +87,8 @@ target "webapp" {
|
|||||||
// building leaf but overriding parent fields
|
// building leaf but overriding parent fields
|
||||||
t.Run("parent", func(t *testing.T) {
|
t.Run("parent", func(t *testing.T) {
|
||||||
m, err := ReadTargets(ctx, []string{fp}, []string{"webapp"}, []string{
|
m, err := ReadTargets(ctx, []string{fp}, []string{"webapp"}, []string{
|
||||||
"dep.args.VAR_INHERITED=override",
|
"webDEP.args.VAR_INHERITED=override",
|
||||||
"dep.args.VAR_BOTH=override",
|
"webDEP.args.VAR_BOTH=override",
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, m["webapp"].Args["VAR_INHERITED"], "override")
|
require.Equal(t, m["webapp"].Args["VAR_INHERITED"], "override")
|
||||||
@ -106,6 +106,66 @@ target "webapp" {
|
|||||||
require.Equal(t, "foo", *m["webapp"].Context)
|
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) {
|
func TestReadTargetsCompose(t *testing.T) {
|
||||||
|
@ -37,7 +37,17 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) error {
|
|||||||
targets = []string{"default"}
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -99,7 +109,9 @@ func bakeCmd(dockerCli command.Cli) *cobra.Command {
|
|||||||
|
|
||||||
flags.StringArrayVarP(&options.files, "file", "f", []string{}, "Build definition file")
|
flags.StringArrayVarP(&options.files, "file", "f", []string{}, "Build definition file")
|
||||||
flags.BoolVar(&options.printOnly, "print", false, "Print the options without building")
|
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)
|
commonFlags(&options.commonOptions, flags)
|
||||||
|
|
||||||
|
@ -38,9 +38,6 @@ type buildOptions struct {
|
|||||||
extraHosts []string
|
extraHosts []string
|
||||||
networkMode string
|
networkMode string
|
||||||
|
|
||||||
exportPush bool
|
|
||||||
exportLoad bool
|
|
||||||
|
|
||||||
// unimplemented
|
// unimplemented
|
||||||
squash bool
|
squash bool
|
||||||
quiet bool
|
quiet bool
|
||||||
@ -65,9 +62,11 @@ type buildOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type commonOptions struct {
|
type commonOptions struct {
|
||||||
noCache bool
|
noCache bool
|
||||||
progress string
|
progress string
|
||||||
pull bool
|
pull bool
|
||||||
|
exportPush bool
|
||||||
|
exportLoad bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func runBuild(dockerCli command.Cli, in buildOptions) error {
|
func runBuild(dockerCli command.Cli, in buildOptions) error {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user