Skip to content

Commit c124b14

Browse files
committed
bake: ignore unrelated fields when parsing compose files
Signed-off-by: CrazyMax <[email protected]>
1 parent 07e3226 commit c124b14

File tree

2 files changed

+100
-36
lines changed

2 files changed

+100
-36
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) {

0 commit comments

Comments
 (0)