Repository: compose-spec/compose-go
Pull request #786 (2025-05-12 06:00:36)

provider.options can be an array
This allows to pass:
```yaml
services:
  cat:
    provider:
      type: schrodinger
      options:
        health:
           - dead
           - alive
```


Patch:
diff --git a/schema/compose-spec.json b/schema/compose-spec.json
index 9aed303..7160af9 100644
--- a/schema/compose-spec.json
+++ b/schema/compose-spec.json
@@ -401,7 +401,17 @@
               "type": "object",
               "description": "Provider-specific options.",
               "patternProperties": {
-                "^.+$": {"type": ["string", "number", "null"]}
+                "^.+$": {"oneOf": [
+                  { "type": ["string", "number", "boolean"] },
+                  { "type": "array", "items": {"type": ["string", "number", "boolean"]}}
+                ]}
+              }
+            },
+            "configs": {
+              "type": "object",
+              "description": "Config files to pass to the provider.",
+              "patternProperties": {
+                "^.+$": {"type": ["string"]}
               }
             }
           },
diff --git a/types/derived.gen.go b/types/derived.gen.go
index 4e3278f..eea7601 100644
--- a/types/derived.gen.go
+++ b/types/derived.gen.go
@@ -1229,8 +1229,8 @@ func deriveDeepCopy_12(dst, src []DeviceMapping) {
 func deriveDeepCopy_13(dst, src *ServiceProviderConfig) {
 	dst.Type = src.Type
 	if src.Options != nil {
-		dst.Options = make(map[string]string, len(src.Options))
-		deriveDeepCopy_4(dst.Options, src.Options)
+		dst.Options = make(map[string][]string, len(src.Options))
+		deriveDeepCopy_15(dst.Options, src.Options)
 	} else {
 		dst.Options = nil
 	}
diff --git a/types/options.go b/types/options.go
index c603d05..9aadb89 100644
--- a/types/options.go
+++ b/types/options.go
@@ -40,3 +40,27 @@ func (d *Options) DecodeMapstructure(value interface{}) error {
 	}
 	return nil
 }
+
+// MultiOptions allow option to be repeated
+type MultiOptions map[string][]string
+
+func (d *MultiOptions) DecodeMapstructure(value interface{}) error {
+	switch v := value.(type) {
+	case map[string]interface{}:
+		m := make(map[string][]string)
+		for key, e := range v {
+			switch e := e.(type) {
+			case []interface{}:
+				for _, v := range e {
+					m[key] = append(m[key], fmt.Sprint(v))
+				}
+			default:
+				m[key] = append(m[key], fmt.Sprint(e))
+			}
+		}
+		*d = m
+	default:
+		return fmt.Errorf("invalid type %T for options", value)
+	}
+	return nil
+}
diff --git a/types/types.go b/types/types.go
index 61d1615..2317a91 100644
--- a/types/types.go
+++ b/types/types.go
@@ -143,9 +143,9 @@ type ServiceConfig struct {
 }
 
 type ServiceProviderConfig struct {
-	Type       string     `yaml:"type,omitempty" json:"driver,omitempty"`
-	Options    Options    `yaml:"options,omitempty" json:"options,omitempty"`
-	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
+	Type       string       `yaml:"type,omitempty" json:"driver,omitempty"`
+	Options    MultiOptions `yaml:"options,omitempty" json:"options,omitempty"`
+	Extensions Extensions   `yaml:"#extensions,inline,omitempty" json:"-"`
 }
 
 // MarshalYAML makes ServiceConfig implement yaml.Marshaller


Test patch:
diff --git a/loader/loader_test.go b/loader/loader_test.go
index 3af6aec..bd9766c 100644
--- a/loader/loader_test.go
+++ b/loader/loader_test.go
@@ -3771,11 +3771,25 @@ services:
       type: foo
       options:
         bar: zot
+        strings:
+          - foo
+          - bar
+        numbers:
+          - 12
+          - 34
+        booleans:
+          - true
+          - false
 `)
 	assert.NilError(t, err)
 	assert.DeepEqual(t, p.Services["test"].Provider, &types.ServiceProviderConfig{
-		Type:    "foo",
-		Options: map[string]string{"bar": "zot"},
+		Type: "foo",
+		Options: types.MultiOptions{
+			"bar":      []string{"zot"},
+			"strings":  []string{"foo", "bar"},
+			"numbers":  []string{"12", "34"},
+			"booleans": []string{"true", "false"},
+		},
 	})
 }