Skip to content

Commit

Permalink
Merge pull request #434 from milas/extends-file-build-context
Browse files Browse the repository at this point in the history
loader: consistently resolve `build.context` path
  • Loading branch information
glours authored Jul 18, 2023
2 parents 4c1837e + 2c14f1c commit 95ac1be
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 24 deletions.
2 changes: 1 addition & 1 deletion loader/full-example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ services:
- bar
labels: [FOO=BAR]
additional_contexts:
foo: /bar
foo: ./bar
secrets:
- secret1
- source: secret2
Expand Down
21 changes: 15 additions & 6 deletions loader/full-struct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ func services(workingDir, homeDir string) []types.ServiceConfig {
"com.example.foo": "bar",
},
Build: &types.BuildConfig{
Context: "./dir",
Context: filepath.Join(workingDir, "dir"),
Dockerfile: "Dockerfile",
Args: map[string]*string{"foo": strPtr("bar")},
SSH: []types.SSHKey{{ID: "default", Path: ""}},
Target: "foo",
Network: "foo",
CacheFrom: []string{"foo", "bar"},
AdditionalContexts: types.Mapping{"foo": "/bar"},
AdditionalContexts: types.Mapping{"foo": filepath.Join(workingDir, "bar")},
Labels: map[string]string{"FOO": "BAR"},
Secrets: []types.ServiceSecretConfig{
{
Expand Down Expand Up @@ -439,6 +439,7 @@ func services(workingDir, homeDir string) []types.ServiceConfig {
{
Name: "bar",
Build: &types.BuildConfig{
Context: workingDir,
DockerfileInline: "FROM alpine\nRUN echo \"hello\" > /world.txt\n",
},
Environment: types.MappingWithEquals{},
Expand Down Expand Up @@ -599,14 +600,15 @@ func fullExampleYAML(workingDir, homeDir string) string {
services:
bar:
build:
context: %s
dockerfile_inline: |
FROM alpine
RUN echo "hello" > /world.txt
foo:
annotations:
com.example.foo: bar
build:
context: ./dir
context: %s
dockerfile: Dockerfile
args:
foo: bar
Expand All @@ -618,7 +620,7 @@ services:
- foo
- bar
additional_contexts:
foo: /bar
foo: %s
network: foo
target: foo
secrets:
Expand Down Expand Up @@ -1039,6 +1041,9 @@ x-nested:
bar: baz
foo: bar
`,
workingDir,
filepath.Join(workingDir, "dir"),
filepath.Join(workingDir, "bar"),
filepath.Join(workingDir, "example1.env"),
filepath.Join(workingDir, "example2.env"),
workingDir,
Expand Down Expand Up @@ -1150,6 +1155,7 @@ func fullExampleJSON(workingDir, homeDir string) string {
"services": {
"bar": {
"build": {
"context": "%s",
"dockerfile_inline": "FROM alpine\nRUN echo \"hello\" \u003e /world.txt\n"
},
"command": null,
Expand All @@ -1160,7 +1166,7 @@ func fullExampleJSON(workingDir, homeDir string) string {
"com.example.foo": "bar"
},
"build": {
"context": "./dir",
"context": "%s",
"dockerfile": "Dockerfile",
"args": {
"foo": "bar"
Expand All @@ -1176,7 +1182,7 @@ func fullExampleJSON(workingDir, homeDir string) string {
"bar"
],
"additional_contexts": {
"foo": "/bar"
"foo": "%s"
},
"network": "foo",
"target": "foo",
Expand Down Expand Up @@ -1686,6 +1692,9 @@ func fullExampleJSON(workingDir, homeDir string) string {
toPath(workingDir, "config_data"),
toPath(homeDir, "config_data"),
toPath(workingDir, "secret_data"),
toPath(workingDir),
toPath(workingDir, "dir"),
toPath(workingDir, "bar"),
toPath(workingDir, "example1.env"),
toPath(workingDir, "example2.env"),
toPath(workingDir),
Expand Down
61 changes: 61 additions & 0 deletions loader/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,67 @@ services:
assert.DeepEqual(t, service.Command, types.ShellCommand{"/bin/ash", "-c", "rm -rf /tmp/might-not-exist"})
}

func TestLoadExtendsMultipleFiles(t *testing.T) {
if testing.Short() {
t.Skip("Test creates real files on disk")
}

tmpdir := t.TempDir()

aDir := filepath.Join(tmpdir, "a")
assert.NilError(t, os.Mkdir(aDir, 0o700))
aYAML := `
services:
a:
build: .
`
assert.NilError(t, os.WriteFile(filepath.Join(tmpdir, "a", "compose.yaml"), []byte(aYAML), 0o600))

bDir := filepath.Join(tmpdir, "b")
assert.NilError(t, os.Mkdir(bDir, 0o700))
bYAML := `
services:
b:
build:
target: fake
`
assert.NilError(t, os.WriteFile(filepath.Join(tmpdir, "b", "compose.yaml"), []byte(bYAML), 0o600))

rootYAML := `
services:
a:
extends:
file: ./a/compose.yaml
service: a
b:
extends:
file: ./b/compose.yaml
service: b
`
assert.NilError(t, os.WriteFile(filepath.Join(tmpdir, "compose.yaml"), []byte(rootYAML), 0o600))

actual, err := Load(types.ConfigDetails{
WorkingDir: tmpdir,
ConfigFiles: []types.ConfigFile{{
Filename: filepath.Join(tmpdir, "compose.yaml"),
}},
Environment: nil,
}, func(options *Options) {
options.SkipNormalization = true
options.SkipConsistencyCheck = true
})
assert.NilError(t, err)
assert.Assert(t, is.Len(actual.Services, 2))

svcA, err := actual.GetService("a")
assert.NilError(t, err)
assert.Equal(t, svcA.Build.Context, aDir)

svcB, err := actual.GetService("b")
assert.NilError(t, err)
assert.Equal(t, svcB.Build.Context, bDir)
}

func TestLoadCredentialSpec(t *testing.T) {
actual, err := loadYAML(`
name: load-credential-spec
Expand Down
22 changes: 10 additions & 12 deletions loader/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,9 @@ func ResolveRelativePaths(project *types.Project) error {
}

func ResolveServiceRelativePaths(workingDir string, s *types.ServiceConfig) {
if s.Build != nil && s.Build.Context != "" && !isRemoteContext(s.Build.Context) {
// Build context might be a remote http/git context. Unfortunately supported "remote"
// syntax is highly ambiguous in moby/moby and not defined by compose-spec,
// so let's assume runtime will check
localContext := absPath(workingDir, s.Build.Context)
if _, err := os.Stat(localContext); err == nil {
s.Build.Context = localContext
if s.Build != nil {
if !isRemoteContext(s.Build.Context) {
s.Build.Context = absPath(workingDir, s.Build.Context)
}
for name, path := range s.Build.AdditionalContexts {
if strings.Contains(path, "://") { // `docker-image://` or any builder specific context type
Expand All @@ -83,10 +79,7 @@ func ResolveServiceRelativePaths(workingDir string, s *types.ServiceConfig) {
if isRemoteContext(path) {
continue
}
path = absPath(workingDir, path)
if _, err := os.Stat(path); err == nil {
s.Build.AdditionalContexts[name] = path
}
s.Build.AdditionalContexts[name] = absPath(workingDir, path)
}
}
for j, f := range s.EnvFile {
Expand Down Expand Up @@ -127,8 +120,13 @@ func absComposeFiles(composeFiles []string) ([]string, error) {
return composeFiles, nil
}

// isRemoteContext returns true if the value is a Git reference or HTTP(S) URL.
//
// Any other value is assumed to be a local filesystem path and returns false.
//
// See: https://github.com/moby/buildkit/blob/18fc875d9bfd6e065cd8211abc639434ba65aa56/frontend/dockerui/context.go#L76-L79
func isRemoteContext(maybeURL string) bool {
for _, prefix := range []string{"https://", "http://", "git://", "github.com/", "git@"} {
for _, prefix := range []string{"https://", "http://", "git://", "ssh://", "github.com/", "git@"} {
if strings.HasPrefix(maybeURL, prefix) {
return true
}
Expand Down
5 changes: 3 additions & 2 deletions loader/paths_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ func TestResolveBuildContextPaths(t *testing.T) {

func TestResolveAdditionalContexts(t *testing.T) {
wd, _ := filepath.Abs(".")
absSubdir := filepath.Join(wd, "dir")
project := types.Project{
Name: "myProject",
WorkingDir: wd,
Expand All @@ -96,7 +97,7 @@ func TestResolveAdditionalContexts(t *testing.T) {
AdditionalContexts: map[string]string{
"image": "docker-image://foo",
"oci": "oci-layout://foo",
"abs_path": "/tmp",
"abs_path": absSubdir,
"github": "github.com/compose-spec/compose-go",
"rel_path": "./testdata",
},
Expand All @@ -117,7 +118,7 @@ func TestResolveAdditionalContexts(t *testing.T) {
AdditionalContexts: map[string]string{
"image": "docker-image://foo",
"oci": "oci-layout://foo",
"abs_path": "/tmp",
"abs_path": absSubdir,
"github": "github.com/compose-spec/compose-go",
"rel_path": filepath.Join(wd, "testdata"),
},
Expand Down
4 changes: 2 additions & 2 deletions loader/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import (
is "gotest.tools/v3/assert/cmp"
)

func TestMarshallProject(t *testing.T) {
func TestMarshalProject(t *testing.T) {
workingDir, err := os.Getwd()
assert.NilError(t, err)
homeDir, err := os.UserHomeDir()
Expand All @@ -45,7 +45,7 @@ func TestMarshallProject(t *testing.T) {
assert.NilError(t, err)
}

func TestJSONMarshallProject(t *testing.T) {
func TestJSONMarshalProject(t *testing.T) {
workingDir, err := os.Getwd()
assert.NilError(t, err)
homeDir, err := os.UserHomeDir()
Expand Down
5 changes: 4 additions & 1 deletion loader/with-version-struct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package loader

import (
"path/filepath"

"github.com/compose-spec/compose-go/types"
)

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

func withVersionServices() []types.ServiceConfig {
buildCtx, _ := filepath.Abs("./Dockerfile")
return []types.ServiceConfig{
{
Name: "web",

Build: &types.BuildConfig{
Context: "./Dockerfile",
Context: buildCtx,
},
Environment: types.MappingWithEquals{},
Networks: map[string]*types.ServiceNetworkConfig{
Expand Down

0 comments on commit 95ac1be

Please sign in to comment.