Repository: compose-spec/compose-go
Pull request #501 (2023-11-23 16:14:29)

add support of 'x-' named resources
Add support of `x-` named Config, Network, Secrets, Volumes

Fixes https://github.com/docker/compose/issues/11221


Patch:
diff --git a/loader/loader.go b/loader/loader.go
index a700046..7dbd149 100644
--- a/loader/loader.go
+++ b/loader/loader.go
@@ -901,6 +901,14 @@ func transformUlimits(data interface{}) (interface{}, error) {
 // LoadNetworks produces a NetworkConfig map from a compose file Dict
 // the source Dict is not validated if directly used. Use Load() to enable validation
 func LoadNetworks(source map[string]interface{}) (map[string]types.NetworkConfig, error) {
+	x, ok := source[extensions]
+	if ok {
+		// as a top-level attribute, "network" doesn't support extensions, and a network can be named `x-foo`
+		for k, v := range x.(map[string]interface{}) {
+			source[k] = v
+		}
+		delete(source, extensions)
+	}
 	networks := make(map[string]types.NetworkConfig)
 	err := Transform(source, &networks)
 	if err != nil {
@@ -935,6 +943,16 @@ func externalVolumeError(volume, key string) error {
 // LoadVolumes produces a VolumeConfig map from a compose file Dict
 // the source Dict is not validated if directly used. Use Load() to enable validation
 func LoadVolumes(source map[string]interface{}) (map[string]types.VolumeConfig, error) {
+
+	x, ok := source[extensions]
+	if ok {
+		// as a top-level attribute, "volumes" doesn't support extensions, and a volume can be named `x-foo`
+		for k, v := range x.(map[string]interface{}) {
+			source[k] = v
+		}
+		delete(source, extensions)
+	}
+
 	volumes := make(map[string]types.VolumeConfig)
 	if err := Transform(source, &volumes); err != nil {
 		return volumes, err
@@ -969,6 +987,14 @@ func LoadVolumes(source map[string]interface{}) (map[string]types.VolumeConfig,
 // LoadSecrets produces a SecretConfig map from a compose file Dict
 // the source Dict is not validated if directly used. Use Load() to enable validation
 func LoadSecrets(source map[string]interface{}) (map[string]types.SecretConfig, error) {
+	x, ok := source[extensions]
+	if ok {
+		// as a top-level attribute, "secrets" doesn't support extensions, and a secret can be named `x-foo`
+		for k, v := range x.(map[string]interface{}) {
+			source[k] = v
+		}
+		delete(source, extensions)
+	}
 	secrets := make(map[string]types.SecretConfig)
 	if err := Transform(source, &secrets); err != nil {
 		return secrets, err
@@ -986,6 +1012,14 @@ func LoadSecrets(source map[string]interface{}) (map[string]types.SecretConfig,
 // LoadConfigObjs produces a ConfigObjConfig map from a compose file Dict
 // the source Dict is not validated if directly used. Use Load() to enable validation
 func LoadConfigObjs(source map[string]interface{}) (map[string]types.ConfigObjConfig, error) {
+	x, ok := source[extensions]
+	if ok {
+		// as a top-level attribute, "config" doesn't support extensions, and a config can be named `x-foo`
+		for k, v := range x.(map[string]interface{}) {
+			source[k] = v
+		}
+		delete(source, extensions)
+	}
 	configs := make(map[string]types.ConfigObjConfig)
 	if err := Transform(source, &configs); err != nil {
 		return configs, err


Test patch:
diff --git a/loader/loader_test.go b/loader/loader_test.go
index 9e2039f..b0b13db 100644
--- a/loader/loader_test.go
+++ b/loader/loader_test.go
@@ -2507,16 +2507,31 @@ name: 'test-x-service'
 services:
   x-foo:
     image: busybox
+    volumes:
+      - x-volume:/dev/null
+    configs:
+      - x-config
+    secrets:
+      - x-secret
+    networks:
+      - x-network
+volumes:
+  x-volume:
+secrets:
+  x-secret:
+    external: true
+networks:
+  x-network:
+configs:
+  x-config:
+    external: true
 `)
 	assert.NilError(t, err)
-	assert.DeepEqual(t, p.Services, types.Services{
-		{
-			Name:        "x-foo",
-			Image:       "busybox",
-			Environment: types.MappingWithEquals{},
-			Scale:       1,
-		},
-	})
+	assert.DeepEqual(t, p.ServiceNames(), []string{"x-foo"})
+	assert.DeepEqual(t, p.SecretNames(), []string{"x-secret"})
+	assert.DeepEqual(t, p.VolumeNames(), []string{"x-volume"})
+	assert.DeepEqual(t, p.ConfigNames(), []string{"x-config"})
+	assert.DeepEqual(t, p.NetworkNames(), []string{"x-network"})
 }
 
 func TestLoadWithInclude(t *testing.T) {