Repository: compose-spec/compose-go
Pull request #423 (2023-06-28 17:07:36)

loader: restore implicit default of `.` for build context
In the Compose spec, as well as Compose v1.x, the `build.context` field is mandatory in the object form of a `service.build` definition.

However, in compose-go (and thus Compose v2.x), this has never been enforced, and an empty `build.context` field was implicitly set to `.` aka the project directory.

Restore that behavior pending a decision on whether we want to make the spec less restrictive here so that this is acceptable or change `compose-go` to begin emitting a warning and eventually reject this.

Note that the order of normalization and resolving paths has been switched so that normalization occurs first, and then paths can be resolved across all fields consistently. This resulted in a small test update where this was incorrect before - it was loading with path resolution enabled but then asserting it had a relative path for the env file.


Patch:
diff --git a/loader/loader.go b/loader/loader.go
index 5480320..ab7cf54 100644
--- a/loader/loader.go
+++ b/loader/loader.go
@@ -261,6 +261,13 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
 		Extensions:  model.Extensions,
 	}
 
+	if !opts.SkipNormalization {
+		err = Normalize(project)
+		if err != nil {
+			return nil, err
+		}
+	}
+
 	if opts.ResolvePaths {
 		err = ResolveRelativePaths(project)
 		if err != nil {
@@ -277,13 +284,6 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
 		}
 	}
 
-	if !opts.SkipNormalization {
-		err = Normalize(project)
-		if err != nil {
-			return nil, err
-		}
-	}
-
 	if !opts.SkipConsistencyCheck {
 		err = checkConsistency(project)
 		if err != nil {
diff --git a/loader/normalize.go b/loader/normalize.go
index ee11cf4..1fe9fba 100644
--- a/loader/normalize.go
+++ b/loader/normalize.go
@@ -29,6 +29,7 @@ import (
 
 // Normalize compose project by moving deprecated attributes to their canonical position and injecting implicit defaults
 func Normalize(project *types.Project) error {
+	// TODO(milas): this really belongs in ResolveRelativePaths
 	absWorkingDir, err := filepath.Abs(project.WorkingDir)
 	if err != nil {
 		return err
@@ -44,8 +45,7 @@ func Normalize(project *types.Project) error {
 		project.Networks["default"] = types.NetworkConfig{}
 	}
 
-	err = relocateExternalName(project)
-	if err != nil {
+	if err := relocateExternalName(project); err != nil {
 		return err
 	}
 
@@ -65,6 +65,9 @@ func Normalize(project *types.Project) error {
 		}
 
 		if s.Build != nil {
+			if s.Build.Context == "" {
+				s.Build.Context = "."
+			}
 			if s.Build.Dockerfile == "" && s.Build.DockerfileInline == "" {
 				s.Build.Dockerfile = "Dockerfile"
 			}


Test patch:
diff --git a/loader/loader_test.go b/loader/loader_test.go
index 8f2507e..58fded2 100644
--- a/loader/loader_test.go
+++ b/loader/loader_test.go
@@ -1928,7 +1928,8 @@ func TestLoadWithExtends(t *testing.T) {
 
 	extendsDir := filepath.Join("testdata", "subdir")
 
-	expectedEnvFilePath := filepath.Join(extendsDir, "extra.env")
+	expectedEnvFilePath, err := filepath.Abs(filepath.Join(extendsDir, "extra.env"))
+	assert.NilError(t, err)
 
 	expServices := types.Services{
 		{
diff --git a/loader/normalize_test.go b/loader/normalize_test.go
index f5b1af1..e68f13c 100644
--- a/loader/normalize_test.go
+++ b/loader/normalize_test.go
@@ -218,3 +218,17 @@ func TestNormalizeImplicitDependencies(t *testing.T) {
 	assert.NilError(t, err)
 	assert.DeepEqual(t, expected, project.Services[0].DependsOn)
 }
+
+func TestImplicitContextPath(t *testing.T) {
+	project := &types.Project{
+		Name: "myProject",
+		Services: types.Services{
+			types.ServiceConfig{
+				Name:  "test",
+				Build: &types.BuildConfig{},
+			},
+		},
+	}
+	assert.NilError(t, Normalize(project))
+	assert.Equal(t, ".", project.Services[0].Build.Context)
+}