diff --git a/bake/compose.go b/bake/compose.go index 025dd810..48c71dbe 100644 --- a/bake/compose.go +++ b/bake/compose.go @@ -80,13 +80,15 @@ func ParseCompose(dt []byte) (*Config, error) { return val, ok })), CacheFrom: s.Build.CacheFrom, - // TODO: add platforms + } + if err = t.composeExtTarget(s.Build.Extensions); err != nil { + return nil, err } if s.Build.Target != "" { target := s.Build.Target t.Target = &target } - if s.Image != "" { + if len(t.Tags) == 0 && s.Image != "" { t.Tags = []string{s.Image} } c.Targets = append(c.Targets, t) @@ -111,3 +113,82 @@ func flatten(in compose.MappingWithEquals) compose.Mapping { } return out } + +// composeExtTarget converts Compose build extension x-bake to bake Target +// https://github.com/compose-spec/compose-spec/blob/master/spec.md#extension +func (t *Target) composeExtTarget(exts map[string]interface{}) error { + if ext, ok := exts["x-bake"]; ok { + for key, val := range ext.(map[string]interface{}) { + switch key { + case "tags": + if res, k := val.(string); k { + t.Tags = append(t.Tags, res) + } else { + for _, res := range val.([]interface{}) { + t.Tags = append(t.Tags, res.(string)) + } + } + case "cache-from": + t.CacheFrom = []string{} // Needed to override the main field + if res, k := val.(string); k { + t.CacheFrom = append(t.CacheFrom, res) + } else { + for _, res := range val.([]interface{}) { + t.CacheFrom = append(t.CacheFrom, res.(string)) + } + } + case "cache-to": + if res, k := val.(string); k { + t.CacheTo = append(t.CacheTo, res) + } else { + for _, res := range val.([]interface{}) { + t.CacheTo = append(t.CacheTo, res.(string)) + } + } + case "secret": + if res, k := val.(string); k { + t.Secrets = append(t.Secrets, res) + } else { + for _, res := range val.([]interface{}) { + t.Secrets = append(t.Secrets, res.(string)) + } + } + case "ssh": + if res, k := val.(string); k { + t.SSH = append(t.SSH, res) + } else { + for _, res := range val.([]interface{}) { + t.SSH = append(t.SSH, res.(string)) + } + } + case "platforms": + if res, k := val.(string); k { + t.Platforms = append(t.Platforms, res) + } else { + for _, res := range val.([]interface{}) { + t.Platforms = append(t.Platforms, res.(string)) + } + } + case "output": + if res, k := val.(string); k { + t.Outputs = append(t.Outputs, res) + } else { + for _, res := range val.([]interface{}) { + t.Outputs = append(t.Outputs, res.(string)) + } + } + case "pull": + if res, ok := val.(bool); ok { + t.Pull = &res + } + case "no-cache": + if res, ok := val.(bool); ok { + t.NoCache = &res + } + default: + return fmt.Errorf("compose file invalid: unkwown %s field for x-bake", key) + } + } + } + return nil +} diff --git a/bake/compose_test.go b/bake/compose_test.go index 076b9248..daa5f661 100644 --- a/bake/compose_test.go +++ b/bake/compose_test.go @@ -214,3 +214,70 @@ networks: _, err := ParseCompose(dt) require.NoError(t, err) } + +func TestComposeExt(t *testing.T) { + var dt = []byte(` +services: + addon: + image: ct-addon:bar + build: + context: . + dockerfile: ./Dockerfile + cache_from: + - user/app:cache + args: + CT_ECR: foo + CT_TAG: bar + x-bake: + tags: + - ct-addon:foo + - ct-addon:alp + platforms: + - linux/amd64 + - linux/arm64 + cache-from: + - type=local,src=path/to/cache + cache-to: local,dest=path/to/cache + pull: true + + aws: + image: ct-fake-aws:bar + build: + dockerfile: ./aws.Dockerfile + args: + CT_ECR: foo + CT_TAG: bar + x-bake: + secret: + - id=mysecret,src=/local/secret + - id=mysecret2,src=/local/secret2 + ssh: default + platforms: linux/arm64 + output: type=docker + no-cache: true +`) + + c, err := ParseCompose(dt) + require.NoError(t, err) + require.Equal(t, 2, len(c.Targets)) + sort.Slice(c.Targets, func(i, j int) bool { + return c.Targets[i].Name < c.Targets[j].Name + }) + require.Equal(t, c.Targets[0].Args, map[string]string{"CT_ECR": "foo", "CT_TAG": "bar"}) + require.Equal(t, c.Targets[0].Tags, []string{"ct-addon:foo", "ct-addon:alp"}) + require.Equal(t, c.Targets[0].Platforms, []string{"linux/amd64", "linux/arm64"}) + require.Equal(t, c.Targets[0].CacheFrom, []string{"type=local,src=path/to/cache"}) + require.Equal(t, c.Targets[0].CacheTo, []string{"local,dest=path/to/cache"}) + require.Equal(t, c.Targets[0].Pull, newBool(true)) + require.Equal(t, c.Targets[1].Tags, []string{"ct-fake-aws:bar"}) + require.Equal(t, c.Targets[1].Secrets, []string{"id=mysecret,src=/local/secret", "id=mysecret2,src=/local/secret2"}) + require.Equal(t, c.Targets[1].SSH, []string{"default"}) + require.Equal(t, c.Targets[1].Platforms, []string{"linux/arm64"}) + require.Equal(t, c.Targets[1].Outputs, []string{"type=docker"}) + require.Equal(t, c.Targets[1].NoCache, newBool(true)) +} + +func newBool(val bool) *bool { + b := val + return &b +} diff --git a/docs/reference/buildx_bake.md b/docs/reference/buildx_bake.md index 3121f9bf..72267790 100644 --- a/docs/reference/buildx_bake.md +++ b/docs/reference/buildx_bake.md @@ -756,3 +756,108 @@ $ docker buildx bake --print app } } ``` + +### Extension field with Compose + +[Special extension](https://github.com/compose-spec/compose-spec/blob/master/spec.md#extension) +field `x-bake` can be used in your compose file to evaluate fields that are not +(yet) available in the [build definition](https://github.com/compose-spec/compose-spec/blob/master/build.md#build-definition). + +```yaml +# docker-compose.yml +services: + addon: + image: ct-addon:bar + build: + context: . + dockerfile: ./Dockerfile + args: + CT_ECR: foo + CT_TAG: bar + x-bake: + tags: + - ct-addon:foo + - ct-addon:alp + platforms: + - linux/amd64 + - linux/arm64 + cache-from: + - user/app:cache + - type=local,src=path/to/cache + cache-to: type=local,dest=path/to/cache + pull: true + + aws: + image: ct-fake-aws:bar + build: + dockerfile: ./aws.Dockerfile + args: + CT_ECR: foo + CT_TAG: bar + x-bake: + secret: + - id=mysecret,src=./secret + - id=mysecret2,src=./secret2 + platforms: linux/arm64 + output: type=docker + no-cache: true +``` + +```console +$ docker buildx bake --print +{ + "target": { + "addon": { + "context": ".", + "dockerfile": "./Dockerfile", + "args": { + "CT_ECR": "foo", + "CT_TAG": "bar" + }, + "tags": [ + "ct-addon:foo", + "ct-addon:alp" + ], + "cache-from": [ + "user/app:cache", + "type=local,src=path/to/cache" + ], + "cache-to": [ + "type=local,dest=path/to/cache" + ], + "platforms": [ + "linux/amd64", + "linux/arm64" + ], + "pull": true + }, + "aws": { + "context": ".", + "dockerfile": "./aws.Dockerfile", + "args": { + "CT_ECR": "foo", + "CT_TAG": "bar" + }, + "tags": [ + "ct-fake-aws:bar" + ], + "secret": [ + "id=mysecret,src=./secret", + "id=mysecret2,src=./secret2" + ], + "platforms": [ + "linux/arm64" + ], + "output": [ + "type=docker" + ], + "no-cache": true + } + } +} +``` + +Complete list of valid fields for `x-bake`: + +`tags`, `cache-from`, `cache-to`, `secret`, `ssh`, `platforms`, `output`, +`pull`, `no-cache`