From 7cf5d8e22358a26583f8197c1cdcf31ec2a2eed3 Mon Sep 17 00:00:00 2001 From: Dean Chen <862469039@qq.com> Date: Sat, 31 Oct 2026 06:45:14 +0500 Subject: [PATCH] cli/compose: accept numeric strings for long-form ports compose convert quotes published and target, and stack deploy then rejects the file. Treat those strings as integers. Fixes #1073 Signed-off-by: Dean Chen <862469039@qq.com> --- cli/compose/loader/loader.go | 24 +++++++++++++++ cli/compose/loader/loader_test.go | 29 +++++++++++++++++++ .../schema/data/config_schema_v3.10.json | 4 +-- .../schema/data/config_schema_v3.11.json | 4 +-- .../schema/data/config_schema_v3.12.json | 4 +-- .../schema/data/config_schema_v3.13.json | 4 +-- .../schema/data/config_schema_v3.2.json | 4 +-- .../schema/data/config_schema_v3.3.json | 4 +-- .../schema/data/config_schema_v3.4.json | 4 +-- .../schema/data/config_schema_v3.5.json | 4 +-- .../schema/data/config_schema_v3.6.json | 4 +-- .../schema/data/config_schema_v3.7.json | 4 +-- .../schema/data/config_schema_v3.8.json | 4 +-- .../schema/data/config_schema_v3.9.json | 4 +-- 14 files changed, 77 insertions(+), 24 deletions(-) diff --git a/cli/compose/loader/loader.go b/cli/compose/loader/loader.go index 0a9cb03ae0a5..9eaab48442df 100644 --- a/cli/compose/loader/loader.go +++ b/cli/compose/loader/loader.go @@ -717,6 +717,9 @@ var transformServicePort TransformerFunc = func(data any) (any, error) { } ports = append(ports, v...) case map[string]any: + if err := normalizeLongFormPort(value); err != nil { + return data, err + } ports = append(ports, value) default: return data, fmt.Errorf("invalid type %T for port", value) @@ -728,6 +731,27 @@ var transformServicePort TransformerFunc = func(data any) (any, error) { } } +// normalizeLongFormPort accepts quoted integers for target and published. +// compose convert emits those as strings, and the schema used to reject them. +func normalizeLongFormPort(port map[string]any) error { + for _, field := range []string{"target", "published"} { + v, ok := port[field] + if !ok || v == nil { + continue + } + s, ok := v.(string) + if !ok { + continue + } + u, err := strconv.ParseUint(s, 10, 16) + if err != nil { + return fmt.Errorf("invalid %s port %q", field, s) + } + port[field] = int(u) + } + return nil +} + var transformStringSourceMap TransformerFunc = func(data any) (any, error) { switch value := data.(type) { case string: diff --git a/cli/compose/loader/loader_test.go b/cli/compose/loader/loader_test.go index 41f70c7cb23c..be7d4722e577 100644 --- a/cli/compose/loader/loader_test.go +++ b/cli/compose/loader/loader_test.go @@ -602,6 +602,35 @@ volumes: assert.Check(t, is.Equal(home, config.Volumes["test"].Driver)) } +func TestLoadLongFormStringPorts(t *testing.T) { + config, err := loadYAML(` +version: "3.8" +services: + web: + image: nginx:alpine + ports: + - published: "8080" + target: "80" + protocol: tcp +`) + assert.NilError(t, err) + assert.Assert(t, len(config.Services) == 1) + assert.Check(t, is.DeepEqual(config.Services[0].Ports, []types.ServicePortConfig{ + {Target: 80, Published: 8080, Protocol: "tcp"}, + })) + + _, err = loadYAML(` +version: "3.4" +services: + web: + image: nginx:alpine + ports: + - published: "abc" + target: 80 +`) + assert.ErrorContains(t, err, `invalid published port "abc"`) +} + func TestLoadWithInterpolationCastFull(t *testing.T) { dict, err := ParseYAML([]byte(` version: "3.8" diff --git a/cli/compose/schema/data/config_schema_v3.10.json b/cli/compose/schema/data/config_schema_v3.10.json index 7c032cf54b31..667dbd894d36 100644 --- a/cli/compose/schema/data/config_schema_v3.10.json +++ b/cli/compose/schema/data/config_schema_v3.10.json @@ -222,8 +222,8 @@ "type": "object", "properties": { "mode": {"type": "string"}, - "target": {"type": "integer"}, - "published": {"type": "integer"}, + "target": {"type": ["integer", "string"]}, + "published": {"type": ["integer", "string"]}, "protocol": {"type": "string"} }, "additionalProperties": false diff --git a/cli/compose/schema/data/config_schema_v3.11.json b/cli/compose/schema/data/config_schema_v3.11.json index fb2c9fd84b0c..9e16457d6195 100644 --- a/cli/compose/schema/data/config_schema_v3.11.json +++ b/cli/compose/schema/data/config_schema_v3.11.json @@ -222,8 +222,8 @@ "type": "object", "properties": { "mode": {"type": "string"}, - "target": {"type": "integer"}, - "published": {"type": "integer"}, + "target": {"type": ["integer", "string"]}, + "published": {"type": ["integer", "string"]}, "protocol": {"type": "string"} }, "additionalProperties": false diff --git a/cli/compose/schema/data/config_schema_v3.12.json b/cli/compose/schema/data/config_schema_v3.12.json index 2a548a38163d..e77d2260999c 100644 --- a/cli/compose/schema/data/config_schema_v3.12.json +++ b/cli/compose/schema/data/config_schema_v3.12.json @@ -222,8 +222,8 @@ "type": "object", "properties": { "mode": {"type": "string"}, - "target": {"type": "integer"}, - "published": {"type": "integer"}, + "target": {"type": ["integer", "string"]}, + "published": {"type": ["integer", "string"]}, "protocol": {"type": "string"} }, "additionalProperties": false diff --git a/cli/compose/schema/data/config_schema_v3.13.json b/cli/compose/schema/data/config_schema_v3.13.json index 8daa8892d625..f096fc514627 100644 --- a/cli/compose/schema/data/config_schema_v3.13.json +++ b/cli/compose/schema/data/config_schema_v3.13.json @@ -228,8 +228,8 @@ "type": "object", "properties": { "mode": {"type": "string"}, - "target": {"type": "integer"}, - "published": {"type": "integer"}, + "target": {"type": ["integer", "string"]}, + "published": {"type": ["integer", "string"]}, "protocol": {"type": "string"} }, "additionalProperties": false diff --git a/cli/compose/schema/data/config_schema_v3.2.json b/cli/compose/schema/data/config_schema_v3.2.json index 6e0e0e747da9..88a73a147879 100644 --- a/cli/compose/schema/data/config_schema_v3.2.json +++ b/cli/compose/schema/data/config_schema_v3.2.json @@ -175,8 +175,8 @@ "type": "object", "properties": { "mode": {"type": "string"}, - "target": {"type": "integer"}, - "published": {"type": "integer"}, + "target": {"type": ["integer", "string"]}, + "published": {"type": ["integer", "string"]}, "protocol": {"type": "string"} }, "additionalProperties": false diff --git a/cli/compose/schema/data/config_schema_v3.3.json b/cli/compose/schema/data/config_schema_v3.3.json index 13a58044d843..299f004c4c27 100644 --- a/cli/compose/schema/data/config_schema_v3.3.json +++ b/cli/compose/schema/data/config_schema_v3.3.json @@ -213,8 +213,8 @@ "type": "object", "properties": { "mode": {"type": "string"}, - "target": {"type": "integer"}, - "published": {"type": "integer"}, + "target": {"type": ["integer", "string"]}, + "published": {"type": ["integer", "string"]}, "protocol": {"type": "string"} }, "additionalProperties": false diff --git a/cli/compose/schema/data/config_schema_v3.4.json b/cli/compose/schema/data/config_schema_v3.4.json index 8660c98da42b..57f79fb5178b 100644 --- a/cli/compose/schema/data/config_schema_v3.4.json +++ b/cli/compose/schema/data/config_schema_v3.4.json @@ -216,8 +216,8 @@ "type": "object", "properties": { "mode": {"type": "string"}, - "target": {"type": "integer"}, - "published": {"type": "integer"}, + "target": {"type": ["integer", "string"]}, + "published": {"type": ["integer", "string"]}, "protocol": {"type": "string"} }, "additionalProperties": false diff --git a/cli/compose/schema/data/config_schema_v3.5.json b/cli/compose/schema/data/config_schema_v3.5.json index bf9c56c02e37..ced340cce648 100644 --- a/cli/compose/schema/data/config_schema_v3.5.json +++ b/cli/compose/schema/data/config_schema_v3.5.json @@ -218,8 +218,8 @@ "type": "object", "properties": { "mode": {"type": "string"}, - "target": {"type": "integer"}, - "published": {"type": "integer"}, + "target": {"type": ["integer", "string"]}, + "published": {"type": ["integer", "string"]}, "protocol": {"type": "string"} }, "additionalProperties": false diff --git a/cli/compose/schema/data/config_schema_v3.6.json b/cli/compose/schema/data/config_schema_v3.6.json index cd6a638ceb7f..8734763a6ff3 100644 --- a/cli/compose/schema/data/config_schema_v3.6.json +++ b/cli/compose/schema/data/config_schema_v3.6.json @@ -218,8 +218,8 @@ "type": "object", "properties": { "mode": {"type": "string"}, - "target": {"type": "integer"}, - "published": {"type": "integer"}, + "target": {"type": ["integer", "string"]}, + "published": {"type": ["integer", "string"]}, "protocol": {"type": "string"} }, "additionalProperties": false diff --git a/cli/compose/schema/data/config_schema_v3.7.json b/cli/compose/schema/data/config_schema_v3.7.json index 69d5c52f87f5..98cc55be9f22 100644 --- a/cli/compose/schema/data/config_schema_v3.7.json +++ b/cli/compose/schema/data/config_schema_v3.7.json @@ -219,8 +219,8 @@ "type": "object", "properties": { "mode": {"type": "string"}, - "target": {"type": "integer"}, - "published": {"type": "integer"}, + "target": {"type": ["integer", "string"]}, + "published": {"type": ["integer", "string"]}, "protocol": {"type": "string"} }, "additionalProperties": false diff --git a/cli/compose/schema/data/config_schema_v3.8.json b/cli/compose/schema/data/config_schema_v3.8.json index 059c0bcf76d8..a9c0f6b90312 100644 --- a/cli/compose/schema/data/config_schema_v3.8.json +++ b/cli/compose/schema/data/config_schema_v3.8.json @@ -220,8 +220,8 @@ "type": "object", "properties": { "mode": {"type": "string"}, - "target": {"type": "integer"}, - "published": {"type": "integer"}, + "target": {"type": ["integer", "string"]}, + "published": {"type": ["integer", "string"]}, "protocol": {"type": "string"} }, "additionalProperties": false diff --git a/cli/compose/schema/data/config_schema_v3.9.json b/cli/compose/schema/data/config_schema_v3.9.json index c6f63fda46d3..69dc63057e61 100644 --- a/cli/compose/schema/data/config_schema_v3.9.json +++ b/cli/compose/schema/data/config_schema_v3.9.json @@ -222,8 +222,8 @@ "type": "object", "properties": { "mode": {"type": "string"}, - "target": {"type": "integer"}, - "published": {"type": "integer"}, + "target": {"type": ["integer", "string"]}, + "published": {"type": ["integer", "string"]}, "protocol": {"type": "string"} }, "additionalProperties": false