nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: compose-spec/compose-go
Pull request #482 (2023-11-06 07:31:56)

unit bytes can be set as plain int
This fix a regression as mem limit can be set either as byte unit or plain integer

fix https://github.com/docker/compose/issues/11160


Patch:
diff --git a/types/bytes.go b/types/bytes.go
index 4c873cd..1b2cd41 100644
--- a/types/bytes.go
+++ b/types/bytes.go
@@ -36,7 +36,13 @@ func (u UnitBytes) MarshalJSON() ([]byte, error) {
 }
 
 func (u *UnitBytes) DecodeMapstructure(value interface{}) error {
-	v, err := units.RAMInBytes(fmt.Sprint(value))
-	*u = UnitBytes(v)
-	return err
+	switch v := value.(type) {
+	case int:
+		*u = UnitBytes(v)
+	case string:
+		b, err := units.RAMInBytes(fmt.Sprint(value))
+		*u = UnitBytes(b)
+		return err
+	}
+	return nil
 }


Test patch:
diff --git a/loader/loader_test.go b/loader/loader_test.go
index 8a6f502..597679b 100644
--- a/loader/loader_test.go
+++ b/loader/loader_test.go
@@ -2807,3 +2807,19 @@ networks:
 	})
 	assert.ErrorContains(t, err, "service redis declares mutually exclusive `network_mode` and `networks`")
 }
+
+func TestLoadUnitBytes(t *testing.T) {
+	project, err := loadYAML(`
+name: load-unit-bytes
+services:
+  test1:
+    image: test
+    memswap_limit: -1
+  test2:
+    image: test
+    memswap_limit: 640kb
+`)
+	assert.NilError(t, err)
+	assert.Equal(t, project.Services[0].MemSwapLimit, types.UnitBytes(-1))
+	assert.Equal(t, project.Services[1].MemSwapLimit, types.UnitBytes(640*1024))
+}
diff --git a/loader/paths_test.go b/loader/paths_test.go
index fdcdda5..6d6a8e2 100644
--- a/loader/paths_test.go
+++ b/loader/paths_test.go
@@ -100,7 +100,7 @@ services:
 			"image":    "docker-image://foo",
 			"oci":      "oci-layout://foo",
 			"abs_path": abs,
-			"github":   "github.com/compose-spec/compose-go/v2",
+			"github":   "github.com/compose-spec/compose-go",
 			"rel_path": filepath.Join(wd, "testdata"),
 		},
 	}
Repository: compose-spec/compose-go
Pull request #494 (2023-11-20 16:55:12)

[backport v1] Add a network-scoped mac_address field
Related to:

- https://github.com/compose-spec/compose-spec/pull/435


(cherry picked from commit 0282c0c0958ae996d1c3079c25dde09093906426)


Patch:
diff --git a/loader/full-example.yml b/loader/full-example.yml
index 24d9545..6ef27a9 100644
--- a/loader/full-example.yml
+++ b/loader/full-example.yml
@@ -235,6 +235,7 @@ services:
       other-network:
         ipv4_address: 172.16.238.10
         ipv6_address: 2001:3984:3989::10
+        mac_address: 02:42:72:98:65:08
       other-other-network:
 
     pid: "host"
diff --git a/schema/compose-spec.json b/schema/compose-spec.json
index 81dacbc..0469c9e 100644
--- a/schema/compose-spec.json
+++ b/schema/compose-spec.json
@@ -294,6 +294,7 @@
                         "ipv4_address": {"type": "string"},
                         "ipv6_address": {"type": "string"},
                         "link_local_ips": {"$ref": "#/definitions/list_of_strings"},
+                        "mac_address": {"type": "string"},
                         "priority": {"type": "number"}
                       },
                       "additionalProperties": false,
diff --git a/types/types.go b/types/types.go
index 1a1ef93..d84be3f 100644
--- a/types/types.go
+++ b/types/types.go
@@ -611,6 +611,7 @@ type ServiceNetworkConfig struct {
 	Ipv4Address  string   `yaml:"ipv4_address,omitempty" json:"ipv4_address,omitempty"`
 	Ipv6Address  string   `yaml:"ipv6_address,omitempty" json:"ipv6_address,omitempty"`
 	LinkLocalIPs []string `yaml:"link_local_ips,omitempty" json:"link_local_ips,omitempty"`
+	MacAddress   string   `yaml:"mac_address,omitempty" json:"mac_address,omitempty"`
 
 	Extensions Extensions `yaml:"#extensions,inline" json:"-"`
 }


Test patch:
diff --git a/loader/full-struct_test.go b/loader/full-struct_test.go
index 43500ff..a48a585 100644
--- a/loader/full-struct_test.go
+++ b/loader/full-struct_test.go
@@ -233,6 +233,7 @@ func services(workingDir, homeDir string) []types.ServiceConfig {
 				"other-network": {
 					Ipv4Address: "172.16.238.10",
 					Ipv6Address: "2001:3984:3989::10",
+					MacAddress:  "02:42:72:98:65:08",
 				},
 				"other-other-network": nil,
 			},
@@ -775,6 +776,7 @@ services:
       other-network:
         ipv4_address: 172.16.238.10
         ipv6_address: 2001:3984:3989::10
+        mac_address: 02:42:72:98:65:08
       other-other-network: null
       some-network:
         aliases:
@@ -1392,7 +1394,8 @@ func fullExampleJSON(workingDir, homeDir string) string {
       "networks": {
         "other-network": {
           "ipv4_address": "172.16.238.10",
-          "ipv6_address": "2001:3984:3989::10"
+          "ipv6_address": "2001:3984:3989::10",
+          "mac_address": "02:42:72:98:65:08"
         },
         "other-other-network": null,
         "some-network": {
Repository: compose-spec/compose-go
Pull request #511 (2023-12-18 09:37:08)

restore support for escaped quoted char
closes https://github.com/docker/compose/issues/11276


Patch:
diff --git a/dotenv/fixtures/quoted.env b/dotenv/fixtures/quoted.env
index 2f05d19..a3afd51 100644
--- a/dotenv/fixtures/quoted.env
+++ b/dotenv/fixtures/quoted.env
@@ -11,4 +11,5 @@ OPTION_J = 'first line
 second line
 third line
 and so on'
+OPTION_K='Let\'s go!'
 OPTION_Z = "last value"
diff --git a/dotenv/parser.go b/dotenv/parser.go
index 11b6d02..861cd95 100644
--- a/dotenv/parser.go
+++ b/dotenv/parser.go
@@ -159,27 +159,34 @@ func (p *parser) extractVarValue(src string, envMap map[string]string, lookupFn
 
 	previousCharIsEscape := false
 	// lookup quoted string terminator
+	var chars []byte
 	for i := 1; i < len(src); i++ {
-		if src[i] == '\n' {
+		char := src[i]
+		if char == '\n' {
 			p.line++
 		}
-		if char := src[i]; char != quote {
+		if char != quote {
 			if !previousCharIsEscape && char == '\\' {
 				previousCharIsEscape = true
-			} else {
+				continue
+			}
+			if previousCharIsEscape {
 				previousCharIsEscape = false
+				chars = append(chars, '\\')
 			}
+			chars = append(chars, char)
 			continue
 		}
 
 		// skip escaped quote symbol (\" or \', depends on quote)
 		if previousCharIsEscape {
 			previousCharIsEscape = false
+			chars = append(chars, char)
 			continue
 		}
 
 		// trim quotes
-		value := string(src[1:i])
+		value := string(chars)
 		if quote == prefixDoubleQuote {
 			// expand standard shell escape sequences & then interpolate
 			// variables on the result


Test patch:
diff --git a/dotenv/godotenv_test.go b/dotenv/godotenv_test.go
index 1b60f71..4f6588e 100644
--- a/dotenv/godotenv_test.go
+++ b/dotenv/godotenv_test.go
@@ -171,6 +171,7 @@ func TestLoadQuotedEnv(t *testing.T) {
 second line
 third line
 and so on`,
+		"OPTION_K": "Let's go!",
 		"OPTION_Z": "last value",
 	}
Repository: compose-spec/compose-go
Pull request #528 (2024-01-15 18:45:37)

Fix merge between two numbers
when converting to environment string we were assuming it was always a string.

Fixes: https://github.com/docker/compose/issues/11353


Patch:
diff --git a/override/merge.go b/override/merge.go
index e7ef0e2..0628382 100644
--- a/override/merge.go
+++ b/override/merge.go
@@ -122,7 +122,7 @@ func convertIntoSequence(value any) []any {
 			if v == nil {
 				seq[i] = k
 			} else {
-				seq[i] = fmt.Sprintf("%s=%s", k, v)
+				seq[i] = fmt.Sprintf("%s=%v", k, v)
 			}
 			i++
 		}


Test patch:
diff --git a/override/merge_environment_test.go b/override/merge_environment_test.go
index 67f25df..ee2a2d1 100644
--- a/override/merge_environment_test.go
+++ b/override/merge_environment_test.go
@@ -93,3 +93,22 @@ services:
       - QIX=ZOT
 `)
 }
+
+func Test_mergeYamlEnvironmentNumber(t *testing.T) {
+	assertMergeYaml(t, `
+services:
+  test:
+    environment:
+      FOO: 1
+`, `
+services:
+  test:
+    environment:
+      FOO: 3
+`, `
+services:
+  test:
+    environment:
+      - FOO=3
+`)
+}
Repository: compose-spec/compose-go
Pull request #529 (2024-01-16 16:57:37)

Add port indexer to be sure a service port declaration is unique
Added a portIndexer checking that for a service the same port declaration is present only once, especially when merging multiple compose configuration


Patch:
diff --git a/override/uncity.go b/override/uncity.go
index 85e52b1..d9e60f6 100644
--- a/override/uncity.go
+++ b/override/uncity.go
@@ -36,6 +36,7 @@ func init() {
 	unique["services.*.expose"] = exposeIndexer
 	unique["services.*.secrets"] = mountIndexer("/run/secrets")
 	unique["services.*.configs"] = mountIndexer("")
+	unique["services.*.ports"] = portIndexer
 }
 
 // EnforceUnicity removes redefinition of elements declared in a sequence
@@ -136,3 +137,34 @@ func mountIndexer(defaultPath string) indexer {
 		}
 	}
 }
+
+func portIndexer(y any, p tree.Path) (string, error) {
+	switch value := y.(type) {
+	case int:
+		return strconv.Itoa(value), nil
+	case map[string]any:
+		target, ok := value["target"].(int)
+		if !ok {
+			return "", fmt.Errorf("service ports %s is missing a target port", p)
+		}
+		published, ok := value["published"].(string)
+		if !ok {
+			// try to parse it as an int
+			if pub, ok := value["published"].(int); ok {
+				published = fmt.Sprintf("%d", pub)
+			}
+		}
+		host, ok := value["host_ip"].(string)
+		if !ok {
+			host = "0.0.0.0"
+		}
+		protocol, ok := value["protocol"].(string)
+		if !ok {
+			protocol = "tcp"
+		}
+		return fmt.Sprintf("%s:%s:%d/%s", host, published, target, protocol), nil
+	case string:
+		return value, nil
+	}
+	return "", nil
+}


Test patch:
diff --git a/override/uncity_test.go b/override/uncity_test.go
index 3ad46f1..239a1eb 100644
--- a/override/uncity_test.go
+++ b/override/uncity_test.go
@@ -66,6 +66,72 @@ services:
 `)
 }
 
+func Test_PortsShortUnicity(t *testing.T) {
+	assertUnicity(t, `
+services:
+  test:
+    image: foo
+    ports:
+      - "9080:80"
+      - "9081:81"
+      - "9080:80"
+      - "5000"
+      - "6060:6060/udp"
+      - "9080:6060/udp"
+`, `
+services:
+  test:
+    image: foo
+    ports:
+      - "9080:80"
+      - "9081:81"
+      - "5000"
+      - "6060:6060/udp"
+      - "9080:6060/udp"
+`)
+}
+
+func Test_PortsLongtUnicity(t *testing.T) {
+	assertUnicity(t, `
+services:
+  test:
+    image: foo
+    ports:
+      - target: 80
+        host_ip: 127.0.0.1
+        published: "8080"
+        protocol: tcp
+        mode: host
+      - target: 81
+        published: "8080"
+        protocol: tcp
+      - target: 80
+        host_ip: 127.0.0.2
+        published: "8080"
+        protocol: tcp
+      - target: 81
+        published: "8080"
+        protocol: tcp
+`, `
+services:
+  test:
+    image: foo
+    ports:
+      - target: 80
+        host_ip: 127.0.0.1
+        published: "8080"
+        protocol: tcp
+        mode: host
+      - target: 81
+        published: "8080"
+        protocol: tcp
+      - target: 80
+        host_ip: 127.0.0.2
+        published: "8080"
+        protocol: tcp
+`)
+}
+
 func assertUnicity(t *testing.T, before string, expected string) {
 	got, err := EnforceUnicity(unmarshall(t, before))
 	assert.NilError(t, err)
Repository: compose-spec/compose-go
Pull request #533 (2024-01-18 15:02:17)

enforce unicity as this is checked by validation

closes https://github.com/docker/compose/issues/11371


Patch:
diff --git a/loader/loader.go b/loader/loader.go
index 05c51ba..3514b72 100644
--- a/loader/loader.go
+++ b/loader/loader.go
@@ -326,6 +326,14 @@ func loadYamlModel(ctx context.Context, config types.ConfigDetails, opts *Option
 			}
 
 			dict, err = override.Merge(dict, cfg)
+			if err != nil {
+				return err
+			}
+
+			dict, err = override.EnforceUnicity(dict)
+			if err != nil {
+				return err
+			}
 
 			if !opts.SkipValidation {
 				if err := schema.Validate(dict); err != nil {
@@ -360,11 +368,6 @@ func loadYamlModel(ctx context.Context, config types.ConfigDetails, opts *Option
 		}
 	}
 
-	dict, err = override.EnforceUnicity(dict)
-	if err != nil {
-		return nil, err
-	}
-
 	dict, err = transform.Canonical(dict)
 	if err != nil {
 		return nil, err
diff --git a/override/uncity.go b/override/uncity.go
index d9e60f6..75594a8 100644
--- a/override/uncity.go
+++ b/override/uncity.go
@@ -62,7 +62,7 @@ func enforceUnicity(value any, p tree.Path) (any, error) {
 	case []any:
 		for pattern, indexer := range unique {
 			if p.Matches(pattern) {
-				var seq []any
+				seq := []any{}
 				keys := map[string]int{}
 				for i, entry := range v {
 					key, err := indexer(entry, p.Next(fmt.Sprintf("[%d]", i)))


Test patch:
diff --git a/loader/override_test.go b/loader/override_test.go
index 34e5f07..035fb29 100644
--- a/loader/override_test.go
+++ b/loader/override_test.go
@@ -165,3 +165,35 @@ services:
 	})
 	assert.NilError(t, err)
 }
+
+func TestOverrideVolume(t *testing.T) {
+	yaml := `
+name: test-override-volume
+services:
+  test:
+    image: test
+    volumes:
+      - ./src:/src
+`
+
+	override := `
+services:
+  test:
+    volumes:
+      - ./src:/src
+`
+	p, err := LoadWithContext(context.Background(), types.ConfigDetails{
+		ConfigFiles: []types.ConfigFile{
+			{
+				Filename: "base",
+				Content:  []byte(yaml),
+			},
+			{
+				Filename: "override",
+				Content:  []byte(override),
+			},
+		},
+	})
+	assert.NilError(t, err)
+	assert.Equal(t, len(p.Services["test"].Volumes), 1)
+}
Repository: compose-spec/compose-go
Pull request #535 (2024-01-19 12:38:07)

Fix ulimit parsing
canonical transformation must keep `ulimit` as an int or struct to match json schema (validated after a merge)
converting `int` to internal `single` field must take place during the decode phase, as this is specific to go-structs design


fixes https://github.com/docker/compose/issues/11353#issuecomment-1899835614


Patch:
diff --git a/transform/ulimits.go b/transform/ulimits.go
index 80a997a..9171a47 100644
--- a/transform/ulimits.go
+++ b/transform/ulimits.go
@@ -26,9 +26,7 @@ func transformUlimits(data any, p tree.Path) (any, error) {
 	case map[string]any:
 		return v, nil
 	case int:
-		return map[string]any{
-			"single": v,
-		}, nil
+		return v, nil
 	default:
 		return data, errors.Errorf("%s: invalid type %T for external", p, v)
 	}
diff --git a/types/types.go b/types/types.go
index 72cfc81..1f61206 100644
--- a/types/types.go
+++ b/types/types.go
@@ -603,6 +603,31 @@ type UlimitsConfig struct {
 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
 }
 
+func (u *UlimitsConfig) DecodeMapstructure(value interface{}) error {
+	switch v := value.(type) {
+	case *UlimitsConfig:
+		// this call to DecodeMapstructure is triggered after initial value conversion as we use a map[string]*UlimitsConfig
+		return nil
+	case int:
+		u.Single = v
+		u.Soft = 0
+		u.Hard = 0
+	case map[string]any:
+		u.Single = 0
+		soft, ok := v["soft"]
+		if ok {
+			u.Soft = soft.(int)
+		}
+		hard, ok := v["hard"]
+		if ok {
+			u.Hard = hard.(int)
+		}
+	default:
+		return fmt.Errorf("unexpected value type %T for ulimit", value)
+	}
+	return nil
+}
+
 // MarshalYAML makes UlimitsConfig implement yaml.Marshaller
 func (u *UlimitsConfig) MarshalYAML() (interface{}, error) {
 	if u.Single != 0 {


Test patch:
diff --git a/loader/extends_test.go b/loader/extends_test.go
index 79a5aad..fa57ec9 100644
--- a/loader/extends_test.go
+++ b/loader/extends_test.go
@@ -90,3 +90,28 @@ services:
 	assert.NilError(t, err)
 	assert.Equal(t, p.Services["test"].Ports[0].Target, uint32(8000))
 }
+
+func TestExtendsUlimits(t *testing.T) {
+	yaml := `
+name: test-extends
+services:
+  test:
+    extends:
+      file: testdata/extends/base.yaml
+      service: withUlimits
+`
+	abs, err := filepath.Abs(".")
+	assert.NilError(t, err)
+
+	p, err := LoadWithContext(context.Background(), types.ConfigDetails{
+		ConfigFiles: []types.ConfigFile{
+			{
+				Content:  []byte(yaml),
+				Filename: "(inline)",
+			},
+		},
+		WorkingDir: abs,
+	})
+	assert.NilError(t, err)
+	assert.Equal(t, p.Services["test"].Ulimits["nproc"].Single, 65535)
+}
diff --git a/loader/testdata/extends/base.yaml b/loader/testdata/extends/base.yaml
index 416d405..a194db1 100644
--- a/loader/testdata/extends/base.yaml
+++ b/loader/testdata/extends/base.yaml
@@ -7,4 +7,13 @@ services:
 
   with-port:
     ports:
-      - 8000:8000
\ No newline at end of file
+      - 8000:8000
+
+  withUlimits:
+    image: test
+    ulimits:
+      nproc: 65535
+      nofile:
+        soft: 20000
+        hard: 40000
+
Repository: compose-spec/compose-go
Pull request #537 (2024-01-19 14:13:41)

don't assume attribute type to prevent int vs uint32 type mismatch
when parsed from a "short syntax" string, target port is modeled as a `uint32` while it would be a plain `int` if defined with long yaml syntax.
By not using type cast, we can be more flexible with actual type being used to produce the port index string.

closes https://github.com/docker/compose/issues/11378


Patch:
diff --git a/override/uncity.go b/override/uncity.go
index 75594a8..d549843 100644
--- a/override/uncity.go
+++ b/override/uncity.go
@@ -143,22 +143,22 @@ func portIndexer(y any, p tree.Path) (string, error) {
 	case int:
 		return strconv.Itoa(value), nil
 	case map[string]any:
-		target, ok := value["target"].(int)
+		target, ok := value["target"]
 		if !ok {
 			return "", fmt.Errorf("service ports %s is missing a target port", p)
 		}
-		published, ok := value["published"].(string)
+		published, ok := value["published"]
 		if !ok {
 			// try to parse it as an int
-			if pub, ok := value["published"].(int); ok {
+			if pub, ok := value["published"]; ok {
 				published = fmt.Sprintf("%d", pub)
 			}
 		}
-		host, ok := value["host_ip"].(string)
+		host, ok := value["host_ip"]
 		if !ok {
 			host = "0.0.0.0"
 		}
-		protocol, ok := value["protocol"].(string)
+		protocol, ok := value["protocol"]
 		if !ok {
 			protocol = "tcp"
 		}


Test patch:
diff --git a/loader/extends_test.go b/loader/extends_test.go
index 6fa9060..79a5aad 100644
--- a/loader/extends_test.go
+++ b/loader/extends_test.go
@@ -64,3 +64,29 @@ services:
 	assert.Equal(t, p.Services["test2"].Hostname, "test2")
 	assert.Equal(t, p.Services["test3"].Hostname, "test3")
 }
+
+func TestExtendsPort(t *testing.T) {
+	yaml := `
+name: test-extends-port
+services:
+  test:
+    image: test
+    extends: 
+      file: testdata/extends/base.yaml
+      service: with-port
+`
+	abs, err := filepath.Abs(".")
+	assert.NilError(t, err)
+
+	p, err := LoadWithContext(context.Background(), types.ConfigDetails{
+		ConfigFiles: []types.ConfigFile{
+			{
+				Content:  []byte(yaml),
+				Filename: "(inline)",
+			},
+		},
+		WorkingDir: abs,
+	})
+	assert.NilError(t, err)
+	assert.Equal(t, p.Services["test"].Ports[0].Target, uint32(8000))
+}
diff --git a/loader/testdata/extends/base.yaml b/loader/testdata/extends/base.yaml
index 8cc2f4a..416d405 100644
--- a/loader/testdata/extends/base.yaml
+++ b/loader/testdata/extends/base.yaml
@@ -5,3 +5,6 @@ services:
   another:
     extends: base
 
+  with-port:
+    ports:
+      - 8000:8000
\ No newline at end of file
Repository: compose-spec/compose-go
Pull request #545 (2024-01-23 15:17:50)

add go binding for storage_opt
`storage_opt` is already defined by compose-spec schema but we didn't had binding set in go structs for it

see https://github.com/docker/compose/issues/11395

ndeloof: @jhrotko compose file v2 vs v3 is obsolete, compose-specification merged those into a single (version-less) file format with all legacy attributes supported

Patch:
diff --git a/loader/full-example.yml b/loader/full-example.yml
index 494e16d..e489bbe 100644
--- a/loader/full-example.yml
+++ b/loader/full-example.yml
@@ -273,7 +273,8 @@ services:
     stop_grace_period: 20s
 
     stop_signal: SIGUSR1
-
+    storage_opt:
+      size: "20G"
     sysctls:
       net.core.somaxconn: 1024
       net.ipv4.tcp_syncookies: 0
diff --git a/types/types.go b/types/types.go
index 1f61206..1c5cca7 100644
--- a/types/types.go
+++ b/types/types.go
@@ -120,6 +120,7 @@ type ServiceConfig struct {
 	StdinOpen       bool                             `yaml:"stdin_open,omitempty" json:"stdin_open,omitempty"`
 	StopGracePeriod *Duration                        `yaml:"stop_grace_period,omitempty" json:"stop_grace_period,omitempty"`
 	StopSignal      string                           `yaml:"stop_signal,omitempty" json:"stop_signal,omitempty"`
+	StorageOpt      map[string]string                `yaml:"storage_opt,omitempty" json:"storage_opt,omitempty"`
 	Sysctls         Mapping                          `yaml:"sysctls,omitempty" json:"sysctls,omitempty"`
 	Tmpfs           StringList                       `yaml:"tmpfs,omitempty" json:"tmpfs,omitempty"`
 	Tty             bool                             `yaml:"tty,omitempty" json:"tty,omitempty"`


Test patch:
diff --git a/loader/full-struct_test.go b/loader/full-struct_test.go
index bbbd911..a05aabf 100644
--- a/loader/full-struct_test.go
+++ b/loader/full-struct_test.go
@@ -411,6 +411,7 @@ func services(workingDir, homeDir string) types.Services {
 			},
 			StdinOpen:       true,
 			StopSignal:      "SIGUSR1",
+			StorageOpt:      map[string]string{"size": "20G"},
 			StopGracePeriod: durationPtr(20 * time.Second),
 			Sysctls: map[string]string{
 				"net.core.somaxconn":      "1024",
@@ -895,6 +896,8 @@ services:
     stdin_open: true
     stop_grace_period: 20s
     stop_signal: SIGUSR1
+    storage_opt:
+      size: 20G
     sysctls:
       net.core.somaxconn: "1024"
       net.ipv4.tcp_syncookies: "0"
@@ -1558,6 +1561,9 @@ func fullExampleJSON(workingDir, homeDir string) string {
       "stdin_open": true,
       "stop_grace_period": "20s",
       "stop_signal": "SIGUSR1",
+      "storage_opt": {
+        "size": "20G"
+      },
       "sysctls": {
         "net.core.somaxconn": "1024",
         "net.ipv4.tcp_syncookies": "0"
Repository: compose-spec/compose-go
Pull request #551 (2024-01-25 16:27:40)

Fix extensions for DevelopConfig
Before adding a extension inside `develop` it would not save `x-` as extensions

```
services:
  foo:
    develop:
      x-dev: dev
```

This PR aims to fix this issue. 
Also added more cases to `loader_test.go`

jhrotko: > LGTM! (just make sure to squash the 2 commits before merging/on merge)

done 

Patch:
diff --git a/types/develop.go b/types/develop.go
index 5fc1071..110591d 100644
--- a/types/develop.go
+++ b/types/develop.go
@@ -18,6 +18,8 @@ package types
 
 type DevelopConfig struct {
 	Watch []Trigger `json:"watch,omitempty"`
+
+	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
 }
 
 type WatchAction string


Test patch:
diff --git a/loader/loader_test.go b/loader/loader_test.go
index 031789a..04671ee 100644
--- a/loader/loader_test.go
+++ b/loader/loader_test.go
@@ -305,18 +305,40 @@ func TestLoadFromFile(t *testing.T) {
 func TestLoadExtensions(t *testing.T) {
 	actual, err := loadYAML(`
 name: load-extensions
+x-project: project
+
+configs:
+  x-config-name:
+    external: true
+    x-config-ext: config
 services:
   foo:
     image: busybox
-    x-foo: bar`)
+    x-foo: bar
+    healthcheck:
+      x-healthcheck: health
+    develop:
+      x-dev: dev`)
 	assert.NilError(t, err)
+	assert.Check(t, is.DeepEqual(types.Extensions{
+		"x-project": "project",
+	}, actual.Extensions))
 	assert.Check(t, is.Len(actual.Services, 1))
 	service := actual.Services["foo"]
 	assert.Check(t, is.Equal("busybox", service.Image))
-	extras := types.Extensions{
+
+	assert.Check(t, is.DeepEqual(types.Extensions{
 		"x-foo": "bar",
-	}
-	assert.Check(t, is.DeepEqual(extras, service.Extensions))
+	}, service.Extensions))
+	assert.Check(t, is.DeepEqual(types.Extensions{
+		"x-config-ext": "config",
+	}, actual.Configs["x-config-name"].Extensions))
+	assert.Check(t, is.DeepEqual(types.Extensions{
+		"x-dev": "dev",
+	}, service.Develop.Extensions))
+	assert.Check(t, is.DeepEqual(types.Extensions{
+		"x-healthcheck": "health",
+	}, service.HealthCheck.Extensions))
 }
 
 func TestLoadExtends(t *testing.T) {