Skip to content

Commit f6d2c5e

Browse files
authored
Merge pull request #3292 from crazy-max/compose-filtered-spec
bake: ignore unrelated fields when parsing compose files
2 parents 661a159 + c124b14 commit f6d2c5e

File tree

15 files changed

+673
-467
lines changed

15 files changed

+673
-467
lines changed

bake/compose.go

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/compose-spec/compose-go/v2/consts"
1212
"github.com/compose-spec/compose-go/v2/dotenv"
1313
"github.com/compose-spec/compose-go/v2/loader"
14+
composeschema "github.com/compose-spec/compose-go/v2/schema"
1415
composetypes "github.com/compose-spec/compose-go/v2/types"
1516
"github.com/docker/buildx/util/buildflags"
1617
dockeropts "github.com/docker/cli/opts"
@@ -35,21 +36,7 @@ func ParseComposeFiles(fs []File) (*Config, error) {
3536
}
3637

3738
func ParseCompose(cfgs []composetypes.ConfigFile, envs map[string]string) (*Config, error) {
38-
if envs == nil {
39-
envs = make(map[string]string)
40-
}
41-
cfg, err := loader.LoadWithContext(context.Background(), composetypes.ConfigDetails{
42-
ConfigFiles: cfgs,
43-
Environment: envs,
44-
}, func(options *loader.Options) {
45-
projectName := "bake"
46-
if v, ok := envs[consts.ComposeProjectName]; ok && v != "" {
47-
projectName = v
48-
}
49-
options.SetProjectName(projectName, false)
50-
options.SkipNormalization = true
51-
options.Profiles = []string{"*"}
52-
})
39+
cfg, err := loadComposeFiles(cfgs, envs)
5340
if err != nil {
5441
return nil, err
5542
}
@@ -208,6 +195,67 @@ func ParseCompose(cfgs []composetypes.ConfigFile, envs map[string]string) (*Conf
208195
return &c, nil
209196
}
210197

198+
func loadComposeFiles(cfgs []composetypes.ConfigFile, envs map[string]string, options ...func(*loader.Options)) (*composetypes.Project, error) {
199+
if envs == nil {
200+
envs = make(map[string]string)
201+
}
202+
203+
cfgDetails := composetypes.ConfigDetails{
204+
ConfigFiles: cfgs,
205+
Environment: envs,
206+
}
207+
208+
raw, err := loader.LoadModelWithContext(context.Background(), cfgDetails, append([]func(*loader.Options){func(opts *loader.Options) {
209+
projectName := "bake"
210+
if v, ok := envs[consts.ComposeProjectName]; ok && v != "" {
211+
projectName = v
212+
}
213+
opts.SetProjectName(projectName, false)
214+
opts.SkipNormalization = true
215+
opts.SkipValidation = true
216+
}}, options...)...)
217+
if err != nil {
218+
return nil, err
219+
}
220+
221+
filtered := make(map[string]any)
222+
for _, key := range []string{"services", "secrets"} {
223+
if key == "services" {
224+
if services, ok := raw["services"].(map[string]any); ok {
225+
filteredServices := make(map[string]any)
226+
for svcName, svc := range services {
227+
if svc == nil {
228+
filteredServices[svcName] = map[string]any{}
229+
} else if svcMap, ok := svc.(map[string]any); ok {
230+
filteredService := make(map[string]any)
231+
for _, svcField := range []string{"image", "build", "environment", "env_file"} {
232+
if val, ok := svcMap[svcField]; ok {
233+
filteredService[svcField] = val
234+
}
235+
}
236+
filteredServices[svcName] = filteredService
237+
}
238+
}
239+
filtered["services"] = filteredServices
240+
}
241+
} else if v, ok := raw[key]; ok {
242+
filtered[key] = v
243+
}
244+
}
245+
246+
if err := composeschema.Validate(filtered); err != nil {
247+
return nil, err
248+
}
249+
250+
return loader.ModelToProject(filtered, loader.ToOptions(&cfgDetails, append([]func(*loader.Options){func(options *loader.Options) {
251+
options.SkipNormalization = true
252+
options.Profiles = []string{"*"}
253+
}}, options...)), composetypes.ConfigDetails{
254+
ConfigFiles: cfgs,
255+
Environment: envs,
256+
})
257+
}
258+
211259
func validateComposeFile(dt []byte, fn string) (bool, error) {
212260
envs, err := composeEnv()
213261
if err != nil {
@@ -225,16 +273,7 @@ func validateComposeFile(dt []byte, fn string) (bool, error) {
225273
}
226274

227275
func validateCompose(dt []byte, envs map[string]string) error {
228-
_, err := loader.LoadWithContext(context.Background(), composetypes.ConfigDetails{
229-
ConfigFiles: []composetypes.ConfigFile{
230-
{
231-
Content: dt,
232-
},
233-
},
234-
Environment: envs,
235-
}, func(options *loader.Options) {
236-
options.SetProjectName("bake", false)
237-
options.SkipNormalization = true
276+
_, err := loadComposeFiles([]composetypes.ConfigFile{{Content: dt}}, envs, func(options *loader.Options) {
238277
// consistency is checked later in ParseCompose to ensure multiple
239278
// compose files can be merged together
240279
options.SkipConsistencyCheck = true

bake/compose_test.go

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ services:
192192

193193
_, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
194194
require.Error(t, err)
195+
require.ErrorContains(t, err, `has neither an image nor a build context specified`)
195196
}
196197

197198
func TestAdvancedNetwork(t *testing.T) {
@@ -610,7 +611,6 @@ func TestValidateComposeFile(t *testing.T) {
610611
fn string
611612
dt []byte
612613
isCompose bool
613-
wantErr bool
614614
}{
615615
{
616616
name: "empty service",
@@ -620,7 +620,6 @@ services:
620620
foo:
621621
`),
622622
isCompose: true,
623-
wantErr: true,
624623
},
625624
{
626625
name: "build",
@@ -631,7 +630,6 @@ services:
631630
build: .
632631
`),
633632
isCompose: true,
634-
wantErr: false,
635633
},
636634
{
637635
name: "image",
@@ -642,7 +640,6 @@ services:
642640
image: nginx
643641
`),
644642
isCompose: true,
645-
wantErr: false,
646643
},
647644
{
648645
name: "unknown ext",
@@ -653,7 +650,6 @@ services:
653650
image: nginx
654651
`),
655652
isCompose: true,
656-
wantErr: false,
657653
},
658654
{
659655
name: "hcl",
@@ -664,18 +660,13 @@ target "default" {
664660
}
665661
`),
666662
isCompose: false,
667-
wantErr: false,
668663
},
669664
}
670665
for _, tt := range cases {
671666
t.Run(tt.name, func(t *testing.T) {
672667
isCompose, err := validateComposeFile(tt.dt, tt.fn)
673668
assert.Equal(t, tt.isCompose, isCompose)
674-
if tt.wantErr {
675-
require.Error(t, err)
676-
} else {
677-
require.NoError(t, err)
678-
}
669+
require.NoError(t, err)
679670
})
680671
}
681672
}
@@ -888,6 +879,40 @@ services:
888879
require.Equal(t, map[string]*string{"TEST_VALUE": ptrstr("abc"), "FOO_VALUE": ptrstr("abc")}, c.Targets[0].Args)
889880
}
890881

882+
func TestUnknownField(t *testing.T) {
883+
tmpdir := t.TempDir()
884+
dt := []byte(`
885+
services:
886+
webapp:
887+
bar: baz
888+
build:
889+
context: .
890+
891+
foo:
892+
- bar.baz
893+
`)
894+
895+
chdir(t, tmpdir)
896+
_, err := ParseComposeFiles([]File{{Name: "compose.yml", Data: dt}})
897+
require.NoError(t, err)
898+
}
899+
900+
func TestUnknownBuildField(t *testing.T) {
901+
tmpdir := t.TempDir()
902+
dt := []byte(`
903+
services:
904+
webapp:
905+
build:
906+
context: .
907+
foo: bar
908+
`)
909+
910+
chdir(t, tmpdir)
911+
_, err := ParseComposeFiles([]File{{Name: "compose.yml", Data: dt}})
912+
require.Error(t, err)
913+
require.ErrorContains(t, err, `additional properties 'foo' not allowed`)
914+
}
915+
891916
// chdir changes the current working directory to the named directory,
892917
// and then restore the original working directory at the end of the test.
893918
func chdir(t *testing.T, dir string) {

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
github.com/Masterminds/semver/v3 v3.2.1
77
github.com/Microsoft/go-winio v0.6.2
88
github.com/aws/aws-sdk-go-v2/config v1.27.27
9-
github.com/compose-spec/compose-go/v2 v2.7.1
9+
github.com/compose-spec/compose-go/v2 v2.7.2-0.20250703132301-891fce532a51 // main
1010
github.com/containerd/console v1.0.5
1111
github.com/containerd/containerd/v2 v2.1.1
1212
github.com/containerd/continuity v0.4.5
@@ -105,7 +105,7 @@ require (
105105
github.com/go-openapi/jsonpointer v0.21.0 // indirect
106106
github.com/go-openapi/jsonreference v0.20.2 // indirect
107107
github.com/go-openapi/swag v0.23.0 // indirect
108-
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
108+
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
109109
github.com/gogo/protobuf v1.3.2 // indirect
110110
github.com/golang/protobuf v1.5.4 // indirect
111111
github.com/google/gnostic-models v0.6.8 // indirect

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
6262
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
6363
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
6464
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
65-
github.com/compose-spec/compose-go/v2 v2.7.1 h1:EUIbuaD0R/J1KA+FbJMNbcS9+jt/CVudbp5iHqUllSs=
66-
github.com/compose-spec/compose-go/v2 v2.7.1/go.mod h1:TmjkIB9W73fwVxkYY+u2uhMbMUakjiif79DlYgXsyvU=
65+
github.com/compose-spec/compose-go/v2 v2.7.2-0.20250703132301-891fce532a51 h1:AjI75N9METifYMZK7eNt8XIgY9Sryv+1w3XDA7X2vZQ=
66+
github.com/compose-spec/compose-go/v2 v2.7.2-0.20250703132301-891fce532a51/go.mod h1:Zow/3eYNOnl2T4qLGZEizf8d/ht1qfy09G7WGOSzGOY=
6767
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
6868
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
6969
github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc=
@@ -159,8 +159,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
159159
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
160160
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
161161
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
162-
github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc=
163-
github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
162+
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
163+
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
164164
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
165165
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
166166
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=

vendor/github.com/compose-spec/compose-go/v2/loader/loader.go

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/compose-spec/compose-go/v2/loader/validate.go

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/go-viper/mapstructure/v2/.editorconfig

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/go-viper/mapstructure/v2/.golangci.yaml

Lines changed: 31 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)