Skip to content

Commit 95ac1be

Browse files
authored
Merge pull request #434 from milas/extends-file-build-context
loader: consistently resolve `build.context` path
2 parents 4c1837e + 2c14f1c commit 95ac1be

File tree

7 files changed

+96
-24
lines changed

7 files changed

+96
-24
lines changed

loader/full-example.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ services:
2424
- bar
2525
labels: [FOO=BAR]
2626
additional_contexts:
27-
foo: /bar
27+
foo: ./bar
2828
secrets:
2929
- secret1
3030
- source: secret2

loader/full-struct_test.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,14 @@ func services(workingDir, homeDir string) []types.ServiceConfig {
5353
"com.example.foo": "bar",
5454
},
5555
Build: &types.BuildConfig{
56-
Context: "./dir",
56+
Context: filepath.Join(workingDir, "dir"),
5757
Dockerfile: "Dockerfile",
5858
Args: map[string]*string{"foo": strPtr("bar")},
5959
SSH: []types.SSHKey{{ID: "default", Path: ""}},
6060
Target: "foo",
6161
Network: "foo",
6262
CacheFrom: []string{"foo", "bar"},
63-
AdditionalContexts: types.Mapping{"foo": "/bar"},
63+
AdditionalContexts: types.Mapping{"foo": filepath.Join(workingDir, "bar")},
6464
Labels: map[string]string{"FOO": "BAR"},
6565
Secrets: []types.ServiceSecretConfig{
6666
{
@@ -439,6 +439,7 @@ func services(workingDir, homeDir string) []types.ServiceConfig {
439439
{
440440
Name: "bar",
441441
Build: &types.BuildConfig{
442+
Context: workingDir,
442443
DockerfileInline: "FROM alpine\nRUN echo \"hello\" > /world.txt\n",
443444
},
444445
Environment: types.MappingWithEquals{},
@@ -599,14 +600,15 @@ func fullExampleYAML(workingDir, homeDir string) string {
599600
services:
600601
bar:
601602
build:
603+
context: %s
602604
dockerfile_inline: |
603605
FROM alpine
604606
RUN echo "hello" > /world.txt
605607
foo:
606608
annotations:
607609
com.example.foo: bar
608610
build:
609-
context: ./dir
611+
context: %s
610612
dockerfile: Dockerfile
611613
args:
612614
foo: bar
@@ -618,7 +620,7 @@ services:
618620
- foo
619621
- bar
620622
additional_contexts:
621-
foo: /bar
623+
foo: %s
622624
network: foo
623625
target: foo
624626
secrets:
@@ -1039,6 +1041,9 @@ x-nested:
10391041
bar: baz
10401042
foo: bar
10411043
`,
1044+
workingDir,
1045+
filepath.Join(workingDir, "dir"),
1046+
filepath.Join(workingDir, "bar"),
10421047
filepath.Join(workingDir, "example1.env"),
10431048
filepath.Join(workingDir, "example2.env"),
10441049
workingDir,
@@ -1150,6 +1155,7 @@ func fullExampleJSON(workingDir, homeDir string) string {
11501155
"services": {
11511156
"bar": {
11521157
"build": {
1158+
"context": "%s",
11531159
"dockerfile_inline": "FROM alpine\nRUN echo \"hello\" \u003e /world.txt\n"
11541160
},
11551161
"command": null,
@@ -1160,7 +1166,7 @@ func fullExampleJSON(workingDir, homeDir string) string {
11601166
"com.example.foo": "bar"
11611167
},
11621168
"build": {
1163-
"context": "./dir",
1169+
"context": "%s",
11641170
"dockerfile": "Dockerfile",
11651171
"args": {
11661172
"foo": "bar"
@@ -1176,7 +1182,7 @@ func fullExampleJSON(workingDir, homeDir string) string {
11761182
"bar"
11771183
],
11781184
"additional_contexts": {
1179-
"foo": "/bar"
1185+
"foo": "%s"
11801186
},
11811187
"network": "foo",
11821188
"target": "foo",
@@ -1686,6 +1692,9 @@ func fullExampleJSON(workingDir, homeDir string) string {
16861692
toPath(workingDir, "config_data"),
16871693
toPath(homeDir, "config_data"),
16881694
toPath(workingDir, "secret_data"),
1695+
toPath(workingDir),
1696+
toPath(workingDir, "dir"),
1697+
toPath(workingDir, "bar"),
16891698
toPath(workingDir, "example1.env"),
16901699
toPath(workingDir, "example2.env"),
16911700
toPath(workingDir),

loader/loader_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,67 @@ services:
360360
assert.DeepEqual(t, service.Command, types.ShellCommand{"/bin/ash", "-c", "rm -rf /tmp/might-not-exist"})
361361
}
362362

363+
func TestLoadExtendsMultipleFiles(t *testing.T) {
364+
if testing.Short() {
365+
t.Skip("Test creates real files on disk")
366+
}
367+
368+
tmpdir := t.TempDir()
369+
370+
aDir := filepath.Join(tmpdir, "a")
371+
assert.NilError(t, os.Mkdir(aDir, 0o700))
372+
aYAML := `
373+
services:
374+
a:
375+
build: .
376+
`
377+
assert.NilError(t, os.WriteFile(filepath.Join(tmpdir, "a", "compose.yaml"), []byte(aYAML), 0o600))
378+
379+
bDir := filepath.Join(tmpdir, "b")
380+
assert.NilError(t, os.Mkdir(bDir, 0o700))
381+
bYAML := `
382+
services:
383+
b:
384+
build:
385+
target: fake
386+
`
387+
assert.NilError(t, os.WriteFile(filepath.Join(tmpdir, "b", "compose.yaml"), []byte(bYAML), 0o600))
388+
389+
rootYAML := `
390+
services:
391+
a:
392+
extends:
393+
file: ./a/compose.yaml
394+
service: a
395+
b:
396+
extends:
397+
file: ./b/compose.yaml
398+
service: b
399+
`
400+
assert.NilError(t, os.WriteFile(filepath.Join(tmpdir, "compose.yaml"), []byte(rootYAML), 0o600))
401+
402+
actual, err := Load(types.ConfigDetails{
403+
WorkingDir: tmpdir,
404+
ConfigFiles: []types.ConfigFile{{
405+
Filename: filepath.Join(tmpdir, "compose.yaml"),
406+
}},
407+
Environment: nil,
408+
}, func(options *Options) {
409+
options.SkipNormalization = true
410+
options.SkipConsistencyCheck = true
411+
})
412+
assert.NilError(t, err)
413+
assert.Assert(t, is.Len(actual.Services, 2))
414+
415+
svcA, err := actual.GetService("a")
416+
assert.NilError(t, err)
417+
assert.Equal(t, svcA.Build.Context, aDir)
418+
419+
svcB, err := actual.GetService("b")
420+
assert.NilError(t, err)
421+
assert.Equal(t, svcB.Build.Context, bDir)
422+
}
423+
363424
func TestLoadCredentialSpec(t *testing.T) {
364425
actual, err := loadYAML(`
365426
name: load-credential-spec

loader/paths.go

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,9 @@ func ResolveRelativePaths(project *types.Project) error {
6868
}
6969

7070
func ResolveServiceRelativePaths(workingDir string, s *types.ServiceConfig) {
71-
if s.Build != nil && s.Build.Context != "" && !isRemoteContext(s.Build.Context) {
72-
// Build context might be a remote http/git context. Unfortunately supported "remote"
73-
// syntax is highly ambiguous in moby/moby and not defined by compose-spec,
74-
// so let's assume runtime will check
75-
localContext := absPath(workingDir, s.Build.Context)
76-
if _, err := os.Stat(localContext); err == nil {
77-
s.Build.Context = localContext
71+
if s.Build != nil {
72+
if !isRemoteContext(s.Build.Context) {
73+
s.Build.Context = absPath(workingDir, s.Build.Context)
7874
}
7975
for name, path := range s.Build.AdditionalContexts {
8076
if strings.Contains(path, "://") { // `docker-image://` or any builder specific context type
@@ -83,10 +79,7 @@ func ResolveServiceRelativePaths(workingDir string, s *types.ServiceConfig) {
8379
if isRemoteContext(path) {
8480
continue
8581
}
86-
path = absPath(workingDir, path)
87-
if _, err := os.Stat(path); err == nil {
88-
s.Build.AdditionalContexts[name] = path
89-
}
82+
s.Build.AdditionalContexts[name] = absPath(workingDir, path)
9083
}
9184
}
9285
for j, f := range s.EnvFile {
@@ -127,8 +120,13 @@ func absComposeFiles(composeFiles []string) ([]string, error) {
127120
return composeFiles, nil
128121
}
129122

123+
// isRemoteContext returns true if the value is a Git reference or HTTP(S) URL.
124+
//
125+
// Any other value is assumed to be a local filesystem path and returns false.
126+
//
127+
// See: https://github.com/moby/buildkit/blob/18fc875d9bfd6e065cd8211abc639434ba65aa56/frontend/dockerui/context.go#L76-L79
130128
func isRemoteContext(maybeURL string) bool {
131-
for _, prefix := range []string{"https://", "http://", "git://", "github.com/", "git@"} {
129+
for _, prefix := range []string{"https://", "http://", "git://", "ssh://", "github.com/", "git@"} {
132130
if strings.HasPrefix(maybeURL, prefix) {
133131
return true
134132
}

loader/paths_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ func TestResolveBuildContextPaths(t *testing.T) {
8484

8585
func TestResolveAdditionalContexts(t *testing.T) {
8686
wd, _ := filepath.Abs(".")
87+
absSubdir := filepath.Join(wd, "dir")
8788
project := types.Project{
8889
Name: "myProject",
8990
WorkingDir: wd,
@@ -96,7 +97,7 @@ func TestResolveAdditionalContexts(t *testing.T) {
9697
AdditionalContexts: map[string]string{
9798
"image": "docker-image://foo",
9899
"oci": "oci-layout://foo",
99-
"abs_path": "/tmp",
100+
"abs_path": absSubdir,
100101
"github": "github.com/compose-spec/compose-go",
101102
"rel_path": "./testdata",
102103
},
@@ -117,7 +118,7 @@ func TestResolveAdditionalContexts(t *testing.T) {
117118
AdditionalContexts: map[string]string{
118119
"image": "docker-image://foo",
119120
"oci": "oci-layout://foo",
120-
"abs_path": "/tmp",
121+
"abs_path": absSubdir,
121122
"github": "github.com/compose-spec/compose-go",
122123
"rel_path": filepath.Join(wd, "testdata"),
123124
},

loader/types_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525
is "gotest.tools/v3/assert/cmp"
2626
)
2727

28-
func TestMarshallProject(t *testing.T) {
28+
func TestMarshalProject(t *testing.T) {
2929
workingDir, err := os.Getwd()
3030
assert.NilError(t, err)
3131
homeDir, err := os.UserHomeDir()
@@ -45,7 +45,7 @@ func TestMarshallProject(t *testing.T) {
4545
assert.NilError(t, err)
4646
}
4747

48-
func TestJSONMarshallProject(t *testing.T) {
48+
func TestJSONMarshalProject(t *testing.T) {
4949
workingDir, err := os.Getwd()
5050
assert.NilError(t, err)
5151
homeDir, err := os.UserHomeDir()

loader/with-version-struct_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package loader
1818

1919
import (
20+
"path/filepath"
21+
2022
"github.com/compose-spec/compose-go/types"
2123
)
2224

@@ -29,12 +31,13 @@ func withVersionExampleConfig() *types.Config {
2931
}
3032

3133
func withVersionServices() []types.ServiceConfig {
34+
buildCtx, _ := filepath.Abs("./Dockerfile")
3235
return []types.ServiceConfig{
3336
{
3437
Name: "web",
3538

3639
Build: &types.BuildConfig{
37-
Context: "./Dockerfile",
40+
Context: buildCtx,
3841
},
3942
Environment: types.MappingWithEquals{},
4043
Networks: map[string]*types.ServiceNetworkConfig{

0 commit comments

Comments
 (0)