Repository: compose-spec/compose-go
Pull request #685 (2024-09-24 12:55:45)

make extra_hosts values uniques
Issue occurred when you have an `extra_hosts` value defined in the main config file and you have an `extends` of the service with the exact same name without any additional `extra_hosts` config

Fixes https://github.com/docker/compose/issues/12155


Patch:
diff --git a/override/merge.go b/override/merge.go
index 8a468dd..697dbc7 100644
--- a/override/merge.go
+++ b/override/merge.go
@@ -47,7 +47,7 @@ func init() {
 	mergeSpecials["services.*.build"] = mergeBuild
 	mergeSpecials["services.*.build.args"] = mergeToSequence
 	mergeSpecials["services.*.build.additional_contexts"] = mergeToSequence
-	mergeSpecials["services.*.build.extra_hosts"] = mergeToSequence
+	mergeSpecials["services.*.build.extra_hosts"] = mergeExtraHosts
 	mergeSpecials["services.*.build.labels"] = mergeToSequence
 	mergeSpecials["services.*.command"] = override
 	mergeSpecials["services.*.depends_on"] = mergeDependsOn
@@ -58,7 +58,7 @@ func init() {
 	mergeSpecials["services.*.entrypoint"] = override
 	mergeSpecials["services.*.env_file"] = mergeToSequence
 	mergeSpecials["services.*.environment"] = mergeToSequence
-	mergeSpecials["services.*.extra_hosts"] = mergeToSequence
+	mergeSpecials["services.*.extra_hosts"] = mergeExtraHosts
 	mergeSpecials["services.*.healthcheck.test"] = override
 	mergeSpecials["services.*.labels"] = mergeToSequence
 	mergeSpecials["services.*.logging"] = mergeLogging
@@ -163,6 +163,22 @@ func mergeNetworks(c any, o any, path tree.Path) (any, error) {
 	return mergeMappings(right, left, path)
 }
 
+func mergeExtraHosts(c any, o any, _ tree.Path) (any, error) {
+	right := convertIntoSequence(c)
+	left := convertIntoSequence(o)
+	// Rewrite content of left slice to remove duplicate elements
+	i := 0
+	for _, v := range left {
+		if !slices.Contains(right, v) {
+			left[i] = v
+			i++
+		}
+	}
+	// keep only not duplicated elements from left slice
+	left = left[:i]
+	return append(right, left...), nil
+}
+
 func mergeToSequence(c any, o any, _ tree.Path) (any, error) {
 	right := convertIntoSequence(c)
 	left := convertIntoSequence(o)
@@ -172,15 +188,21 @@ func mergeToSequence(c any, o any, _ tree.Path) (any, error) {
 func convertIntoSequence(value any) []any {
 	switch v := value.(type) {
 	case map[string]any:
-		seq := make([]any, len(v))
-		i := 0
-		for k, v := range v {
-			if v == nil {
-				seq[i] = k
+		var seq []any
+		for k, val := range v {
+			if val == nil {
+				seq = append(seq, k)
 			} else {
-				seq[i] = fmt.Sprintf("%s=%v", k, v)
+				switch vl := val.(type) {
+				// if val is an array we need to add the key with each value one by one
+				case []any:
+					for _, vlv := range vl {
+						seq = append(seq, fmt.Sprintf("%s=%v", k, vlv))
+					}
+				default:
+					seq = append(seq, fmt.Sprintf("%s=%v", k, val))
+				}
 			}
-			i++
 		}
 		slices.SortFunc(seq, func(a, b any) int {
 			return cmp.Compare(a.(string), b.(string))
diff --git a/schema/compose-spec.json b/schema/compose-spec.json
index c7963f6..a95b941 100644
--- a/schema/compose-spec.json
+++ b/schema/compose-spec.json
@@ -878,7 +878,8 @@
           "patternProperties": {
             ".+": {
               "type": ["string", "array"]
-            }
+            },
+            "uniqueItems": false
           },
           "additionalProperties": false
         },


Test patch:
diff --git a/override/merge_extra_hosts_test.go b/override/merge_extra_hosts_test.go
index d5cc859..dc801b0 100644
--- a/override/merge_extra_hosts_test.go
+++ b/override/merge_extra_hosts_test.go
@@ -32,6 +32,8 @@ services:
   test:
     extra_hosts:
       - localhost=127.0.0.1
+      - example.com=1.2.3.4
+      - example.com=4.3.2.1
 `, `
 services:
   test:
@@ -39,6 +41,7 @@ services:
     extra_hosts:
       - example.com=1.2.3.4
       - localhost=127.0.0.1
+      - example.com=4.3.2.1
 `)
 }
 
@@ -54,12 +57,14 @@ services:
   test:
     extra_hosts:
       "localhost": "127.0.0.1"
+      "example.com": ["1.2.3.4", "4.3.2.1"]
 `, `
 services:
   test:
     image: foo
     extra_hosts:
       - example.com=1.2.3.4
+      - example.com=4.3.2.1
       - localhost=127.0.0.1
 `)
 }
@@ -76,6 +81,8 @@ services:
   test:
     extra_hosts:
       - localhost=127.0.0.1
+      - example.com=1.2.3.4
+      - example.com=4.3.2.1
 `, `
 services:
   test:
@@ -83,5 +90,6 @@ services:
     extra_hosts:
       - example.com=1.2.3.4
       - localhost=127.0.0.1
+      - example.com=4.3.2.1
 `)
 }