Repository: compose-spec/compose-go
Pull request #230 (2022-02-21 09:46:34)

published can either be a port(int) or range (string)
This updated compose schema and adds support for https://github.com/compose-spec/compose-spec/pull/222

I haven't found a global way for mapstructure to convert int into a string (to match type in json schema declared as `["string", "integer"]`)


Patch:
diff --git a/compatibility/services.go b/compatibility/services.go
index 68b65e0..d28f07f 100644
--- a/compatibility/services.go
+++ b/compatibility/services.go
@@ -550,8 +550,8 @@ func (c *AllowList) CheckPortsTarget(p *types.ServicePortConfig) {
 }
 
 func (c *AllowList) CheckPortsPublished(p *types.ServicePortConfig) {
-	if !c.supported("services.ports.published") && p.Published != 0 {
-		p.Published = 0
+	if !c.supported("services.ports.published") && len(p.Published) != 0 {
+		p.Published = ""
 		c.Unsupported("services.ports.published")
 	}
 }
diff --git a/loader/interpolate.go b/loader/interpolate.go
index e7ff6f9..97a19f5 100644
--- a/loader/interpolate.go
+++ b/loader/interpolate.go
@@ -52,7 +52,6 @@ var interpolateTypeCastMapping = map[interp.Path]interp.Cast{
 	servicePath("oom_score_adj"):                                     toInt64,
 	servicePath("pids_limit"):                                        toInt64,
 	servicePath("ports", interp.PathMatchList, "target"):             toInt,
-	servicePath("ports", interp.PathMatchList, "published"):          toInt,
 	servicePath("privileged"):                                        toBoolean,
 	servicePath("read_only"):                                         toBoolean,
 	servicePath("scale"):                                             toInt,
diff --git a/loader/loader.go b/loader/loader.go
index 4754419..1c0394f 100644
--- a/loader/loader.go
+++ b/loader/loader.go
@@ -24,6 +24,7 @@ import (
 	"path/filepath"
 	"reflect"
 	"sort"
+	"strconv"
 	"strings"
 	"time"
 
@@ -813,6 +814,10 @@ var transformServicePort TransformerFunc = func(data interface{}) (interface{},
 					ports = append(ports, v)
 				}
 			case map[string]interface{}:
+				published := value["published"]
+				if v, ok := published.(int); ok {
+					value["published"] = strconv.Itoa(v)
+				}
 				ports = append(ports, groupXFieldsIntoExtensions(value))
 			default:
 				return data, errors.Errorf("invalid type %T for port", value)
diff --git a/loader/merge.go b/loader/merge.go
index b01c868..3af10a1 100644
--- a/loader/merge.go
+++ b/loader/merge.go
@@ -154,7 +154,7 @@ func toServicePortConfigsMap(s interface{}) (map[interface{}]interface{}, error)
 	m := map[interface{}]interface{}{}
 	type port struct {
 		target    uint32
-		published uint32
+		published string
 		ip        string
 		protocol  string
 	}
diff --git a/schema/compose-spec.json b/schema/compose-spec.json
index 91bdb34..bf23cf4 100644
--- a/schema/compose-spec.json
+++ b/schema/compose-spec.json
@@ -8,7 +8,12 @@
   "properties": {
     "version": {
       "type": "string",
-      "description": "Version of the Compose specification used. Tools not implementing required version MUST reject the configuration file."
+      "description": "declared for backward compatibility, ignored."
+    },
+
+    "name": {
+      "type": "string",
+      "description": "define the Compose project name, until user defines one explicitly."
     },
 
     "services": {
@@ -318,7 +323,7 @@
                   "mode": {"type": "string"},
                   "host_ip": {"type": "string"},
                   "target": {"type": "integer"},
-                  "published": {"type": "integer"},
+                  "published": {"type": ["string", "integer"]},
                   "protocol": {"type": "string"}
                 },
                 "additionalProperties": false,
@@ -410,7 +415,8 @@
                     "type": "object",
                     "properties": {
                       "propagation": {"type": "string"},
-                      "create_host_path": {"type": "boolean"}
+                      "create_host_path": {"type": "boolean"},
+                      "selinux": {"type": "string", "enum": ["z", "Z"]}
                     },
                     "additionalProperties": false,
                     "patternProperties": {"^x-": {}}
@@ -519,7 +525,8 @@
               "type": "object",
               "properties": {
                 "cpus": {"type": ["number", "string"]},
-                "memory": {"type": "string"}
+                "memory": {"type": "string"},
+                "pids": {"type": "integer"}
               },
               "additionalProperties": false,
               "patternProperties": {"^x-": {}}
diff --git a/types/project.go b/types/project.go
index 6de8e61..dc208ed 100644
--- a/types/project.go
+++ b/types/project.go
@@ -29,7 +29,7 @@ import (
 
 // Project is the result of loading a set of compose files
 type Project struct {
-	Name         string            `yaml:"-" json:"-"`
+	Name         string            `yaml:"name,omitempty" json:"name,omitempty"`
 	WorkingDir   string            `yaml:"-" json:"-"`
 	Services     Services          `json:"services"`
 	Networks     Networks          `yaml:",omitempty" json:"networks,omitempty"`
diff --git a/types/types.go b/types/types.go
index 978b760..8a323ee 100644
--- a/types/types.go
+++ b/types/types.go
@@ -495,6 +495,7 @@ type Resource struct {
 	// TODO: types to convert from units and ratios
 	NanoCPUs         string            `mapstructure:"cpus" yaml:"cpus,omitempty" json:"cpus,omitempty"`
 	MemoryBytes      UnitBytes         `mapstructure:"memory" yaml:"memory,omitempty" json:"memory,omitempty"`
+	PIds             int64             `mapstructure:"pids" yaml:"pids,omitempty" json:"pids,omitempty"`
 	Devices          []DeviceRequest   `mapstructure:"devices" yaml:"devices,omitempty" json:"devices,omitempty"`
 	GenericResources []GenericResource `mapstructure:"generic_resources" yaml:"generic_resources,omitempty" json:"generic_resources,omitempty"`
 
@@ -581,7 +582,7 @@ type ServicePortConfig struct {
 	Mode      string `yaml:",omitempty" json:"mode,omitempty"`
 	HostIP    string `mapstructure:"host_ip" yaml:"host_ip,omitempty" json:"host_ip,omitempty"`
 	Target    uint32 `yaml:",omitempty" json:"target,omitempty"`
-	Published uint32 `yaml:",omitempty" json:"published,omitempty"`
+	Published string `yaml:",omitempty" json:"published,omitempty"`
 	Protocol  string `yaml:",omitempty" json:"protocol,omitempty"`
 
 	Extensions map[string]interface{} `yaml:",inline" json:"-"`
@@ -615,21 +616,13 @@ func ParsePortConfig(value string) ([]ServicePortConfig, error) {
 func convertPortToPortConfig(port nat.Port, portBindings map[nat.Port][]nat.PortBinding) ([]ServicePortConfig, error) {
 	var portConfigs []ServicePortConfig
 	for _, binding := range portBindings[port] {
-		startHostPort, endHostPort, err := nat.ParsePortRange(binding.HostPort)
-
-		if err != nil && binding.HostPort != "" {
-			return nil, fmt.Errorf("invalid hostport binding (%s) for port (%s)", binding.HostPort, port.Port())
-		}
-
-		for i := startHostPort; i <= endHostPort; i++ {
-			portConfigs = append(portConfigs, ServicePortConfig{
-				HostIP:    binding.HostIP,
-				Protocol:  strings.ToLower(port.Proto()),
-				Target:    uint32(port.Int()),
-				Published: uint32(i),
-				Mode:      "ingress",
-			})
-		}
+		portConfigs = append(portConfigs, ServicePortConfig{
+			HostIP:    binding.HostIP,
+			Protocol:  strings.ToLower(port.Proto()),
+			Target:    uint32(port.Int()),
+			Published: binding.HostPort,
+			Mode:      "ingress",
+		})
 	}
 	return portConfigs, nil
 }
@@ -657,11 +650,16 @@ const (
 	VolumeTypeTmpfs = "tmpfs"
 	// VolumeTypeNamedPipe is the type for mounting Windows named pipes
 	VolumeTypeNamedPipe = "npipe"
+
+	// SElinuxShared share the volume content
+	SElinuxShared = "z"
+	// SElinuxUnshared label content as private unshared
+	SElinuxUnshared = "Z"
 )
 
 // ServiceVolumeBind are options for a service volume of type bind
 type ServiceVolumeBind struct {
-	SELinux        string `yaml:",omitempty" json:"selinux,omitempty"`
+	SELinux        string `mapstructure:"selinux" yaml:",omitempty" json:"selinux,omitempty"`
 	Propagation    string `yaml:",omitempty" json:"propagation,omitempty"`
 	CreateHostPath bool   `mapstructure:"create_host_path" yaml:"create_host_path,omitempty" json:"create_host_path,omitempty"`
 


Test patch:
diff --git a/cli/options_test.go b/cli/options_test.go
index a4e330e..3fca750 100644
--- a/cli/options_test.go
+++ b/cli/options_test.go
@@ -196,7 +196,7 @@ func TestProjectWithDotEnv(t *testing.T) {
 	assert.NilError(t, err)
 	service, err := p.GetService("simple")
 	assert.NilError(t, err)
-	assert.Equal(t, service.Ports[0].Published, uint32(8000))
+	assert.Equal(t, service.Ports[0].Published, "8000")
 }
 
 func TestProjectWithDiscardEnvFile(t *testing.T) {
diff --git a/loader/full-struct_test.go b/loader/full-struct_test.go
index e167e8f..25419a2 100644
--- a/loader/full-struct_test.go
+++ b/loader/full-struct_test.go
@@ -249,27 +249,27 @@ func services(workingDir, homeDir string) []types.ServiceConfig {
 				{
 					Mode:      "ingress",
 					Target:    8000,
-					Published: 8000,
+					Published: "8000",
 					Protocol:  "tcp",
 				},
 				//"9090-9091:8080-8081",
 				{
 					Mode:      "ingress",
 					Target:    8080,
-					Published: 9090,
+					Published: "9090",
 					Protocol:  "tcp",
 				},
 				{
 					Mode:      "ingress",
 					Target:    8081,
-					Published: 9091,
+					Published: "9091",
 					Protocol:  "tcp",
 				},
 				//"49100:22",
 				{
 					Mode:      "ingress",
 					Target:    22,
-					Published: 49100,
+					Published: "49100",
 					Protocol:  "tcp",
 				},
 				//"127.0.0.1:8001:8001",
@@ -277,7 +277,7 @@ func services(workingDir, homeDir string) []types.ServiceConfig {
 					Mode:      "ingress",
 					HostIP:    "127.0.0.1",
 					Target:    8001,
-					Published: 8001,
+					Published: "8001",
 					Protocol:  "tcp",
 				},
 				//"127.0.0.1:5000-5010:5000-5010",
@@ -285,77 +285,77 @@ func services(workingDir, homeDir string) []types.ServiceConfig {
 					Mode:      "ingress",
 					HostIP:    "127.0.0.1",
 					Target:    5000,
-					Published: 5000,
+					Published: "5000",
 					Protocol:  "tcp",
 				},
 				{
 					Mode:      "ingress",
 					HostIP:    "127.0.0.1",
 					Target:    5001,
-					Published: 5001,
+					Published: "5001",
 					Protocol:  "tcp",
 				},
 				{
 					Mode:      "ingress",
 					HostIP:    "127.0.0.1",
 					Target:    5002,
-					Published: 5002,
+					Published: "5002",
 					Protocol:  "tcp",
 				},
 				{
 					Mode:      "ingress",
 					HostIP:    "127.0.0.1",
 					Target:    5003,
-					Published: 5003,
+					Published: "5003",
 					Protocol:  "tcp",
 				},
 				{
 					Mode:      "ingress",
 					HostIP:    "127.0.0.1",
 					Target:    5004,
-					Published: 5004,
+					Published: "5004",
 					Protocol:  "tcp",
 				},
 				{
 					Mode:      "ingress",
 					HostIP:    "127.0.0.1",
 					Target:    5005,
-					Published: 5005,
+					Published: "5005",
 					Protocol:  "tcp",
 				},
 				{
 					Mode:      "ingress",
 					HostIP:    "127.0.0.1",
 					Target:    5006,
-					Published: 5006,
+					Published: "5006",
 					Protocol:  "tcp",
 				},
 				{
 					Mode:      "ingress",
 					HostIP:    "127.0.0.1",
 					Target:    5007,
-					Published: 5007,
+					Published: "5007",
 					Protocol:  "tcp",
 				},
 				{
 					Mode:      "ingress",
 					HostIP:    "127.0.0.1",
 					Target:    5008,
-					Published: 5008,
+					Published: "5008",
 					Protocol:  "tcp",
 				},
 				{
 					Mode:      "ingress",
 					HostIP:    "127.0.0.1",
 					Target:    5009,
-					Published: 5009,
+					Published: "5009",
 					Protocol:  "tcp",
 				},
 				{
 					Mode:      "ingress",
 					HostIP:    "127.0.0.1",
 					Target:    5010,
-					Published: 5010,
+					Published: "5010",
 					Protocol:  "tcp",
 				},
 			},
@@ -734,79 +734,79 @@ func fullExampleYAML(workingDir, homeDir string) string {
       protocol: tcp
     - mode: ingress
       target: 8000
-      published: 8000
+      published: "8000"
       protocol: tcp
     - mode: ingress
       target: 8080
-      published: 9090
+      published: "9090"
       protocol: tcp
     - mode: ingress
       target: 8081
-      published: 9091
+      published: "9091"
       protocol: tcp
     - mode: ingress
       target: 22
-      published: 49100
+      published: "49100"
       protocol: tcp
     - mode: ingress
       host_ip: 127.0.0.1
       target: 8001
-      published: 8001
+      published: "8001"
       protocol: tcp
     - mode: ingress
       host_ip: 127.0.0.1
       target: 5000
-      published: 5000
+      published: "5000"
       protocol: tcp
     - mode: ingress
       host_ip: 127.0.0.1
       target: 5001
-      published: 5001
+      published: "5001"
       protocol: tcp
     - mode: ingress
       host_ip: 127.0.0.1
       target: 5002
-      published: 5002
+      published: "5002"
       protocol: tcp
     - mode: ingress
       host_ip: 127.0.0.1
       target: 5003
-      published: 5003
+      published: "5003"
       protocol: tcp
     - mode: ingress
       host_ip: 127.0.0.1
       target: 5004
-      published: 5004
+      published: "5004"
       protocol: tcp
     - mode: ingress
       host_ip: 127.0.0.1
       target: 5005
-      published: 5005
+      published: "5005"
       protocol: tcp
     - mode: ingress
       host_ip: 127.0.0.1
       target: 5006
-      published: 5006
+      published: "5006"
       protocol: tcp
     - mode: ingress
       host_ip: 127.0.0.1
       target: 5007
-      published: 5007
+      published: "5007"
       protocol: tcp
     - mode: ingress
       host_ip: 127.0.0.1
       target: 5008
-      published: 5008
+      published: "5008"
       protocol: tcp
     - mode: ingress
       host_ip: 127.0.0.1
       target: 5009
-      published: 5009
+      published: "5009"
       protocol: tcp
     - mode: ingress
       host_ip: 127.0.0.1
       target: 5010
-      published: 5010
+      published: "5010"
       protocol: tcp
     privileged: true
     read_only: true
@@ -1314,109 +1314,109 @@ func fullExampleJSON(workingDir, homeDir string) string {
         {
           "mode": "ingress",
           "target": 8000,
-          "published": 8000,
+          "published": "8000",
           "protocol": "tcp"
         },
         {
           "mode": "ingress",
           "target": 8080,
-          "published": 9090,
+          "published": "9090",
           "protocol": "tcp"
         },
         {
           "mode": "ingress",
           "target": 8081,
-          "published": 9091,
+          "published": "9091",
           "protocol": "tcp"
         },
         {
           "mode": "ingress",
           "target": 22,
-          "published": 49100,
+          "published": "49100",
           "protocol": "tcp"
         },
         {
           "mode": "ingress",
           "host_ip": "127.0.0.1",
           "target": 8001,
-          "published": 8001,
+          "published": "8001",
           "protocol": "tcp"
         },
         {
           "mode": "ingress",
           "host_ip": "127.0.0.1",
           "target": 5000,
-          "published": 5000,
+          "published": "5000",
           "protocol": "tcp"
         },
         {
           "mode": "ingress",
           "host_ip": "127.0.0.1",
           "target": 5001,
-          "published": 5001,
+          "published": "5001",
           "protocol": "tcp"
         },
         {
           "mode": "ingress",
           "host_ip": "127.0.0.1",
           "target": 5002,
-          "published": 5002,
+          "published": "5002",
           "protocol": "tcp"
         },
         {
           "mode": "ingress",
           "host_ip": "127.0.0.1",
           "target": 5003,
-          "published": 5003,
+          "published": "5003",
           "protocol": "tcp"
         },
         {
           "mode": "ingress",
           "host_ip": "127.0.0.1",
           "target": 5004,
-          "published": 5004,
+          "published": "5004",
           "protocol": "tcp"
         },
         {
           "mode": "ingress",
           "host_ip": "127.0.0.1",
           "target": 5005,
-          "published": 5005,
+          "published": "5005",
           "protocol": "tcp"
         },
         {
           "mode": "ingress",
           "host_ip": "127.0.0.1",
           "target": 5006,
-          "published": 5006,
+          "published": "5006",
           "protocol": "tcp"
         },
         {
           "mode": "ingress",
           "host_ip": "127.0.0.1",
           "target": 5007,
-          "published": 5007,
+          "published": "5007",
           "protocol": "tcp"
         },
         {
           "mode": "ingress",
           "host_ip": "127.0.0.1",
           "target": 5008,
-          "published": 5008,
+          "published": "5008",
           "protocol": "tcp"
         },
         {
           "mode": "ingress",
           "host_ip": "127.0.0.1",
           "target": 5009,
-          "published": 5009,
+          "published": "5009",
           "protocol": "tcp"
         },
         {
           "mode": "ingress",
           "host_ip": "127.0.0.1",
           "target": 5010,
-          "published": 5010,
+          "published": "5010",
           "protocol": "tcp"
         }
       ],
diff --git a/loader/loader_test.go b/loader/loader_test.go
index 9c97f1b..3e3dd61 100644
--- a/loader/loader_test.go
+++ b/loader/loader_test.go
@@ -133,60 +133,59 @@ var samplePortsConfig = []types.ServicePortConfig{
 	{
 		Mode:      "ingress",
 		Target:    8080,
-		Published: 80,
+		Published: "80",
 		Protocol:  "tcp",
 	},
 	{
 		Mode:      "ingress",
 		Target:    8081,
-		Published: 81,
+		Published: "81",
 		Protocol:  "tcp",
 	},
 	{
 		Mode:      "ingress",
 		Target:    8082,
-		Published: 82,
+		Published: "82",
 		Protocol:  "tcp",
 	},
 	{
 		Mode:      "ingress",
 		Target:    8090,
-		Published: 90,
+		Published: "90",
 		Protocol:  "udp",
 	},
 	{
 		Mode:      "ingress",
 		Target:    8091,
-		Published: 91,
+		Published: "91",
 		Protocol:  "udp",
 	},
 	{
 		Mode:      "ingress",
 		Target:    8092,
-		Published: 92,
+		Published: "92",
 		Protocol:  "udp",
 	},
 	{
 		Mode:      "ingress",
 		Target:    8500,
-		Published: 85,
+		Published: "85",
 		Protocol:  "tcp",
 	},
 	{
-		Mode:      "ingress",
-		Target:    8600,
-		Published: 0,
-		Protocol:  "tcp",
+		Mode:     "ingress",
+		Target:   8600,
+		Protocol: "tcp",
 	},
 	{
 		Target:    53,
-		Published: 10053,
+		Published: "10053",
 		Protocol:  "udp",
 	},
 	{
 		Mode:      "host",
 		Target:    22,
-		Published: 10022,
+		Published: "10022",
 	},
 }
 
@@ -592,7 +591,7 @@ services:
       - $theint
       - "34567"
       - target: $theint
-        published: $theint
+        published: "$theint"
         x-foo-bar: true
     ulimits:
       nproc: $theint
@@ -690,7 +689,7 @@ networks:
 				Ports: []types.ServicePortConfig{
 					{Target: 555, Mode: "ingress", Protocol: "tcp"},
 					{Target: 34567, Mode: "ingress", Protocol: "tcp"},
-					{Target: 555, Published: 555, Extensions: map[string]interface{}{"x-foo-bar": true}},
+					{Target: 555, Published: "555", Extensions: map[string]interface{}{"x-foo-bar": true}},
 				},
 				Ulimits: map[string]*types.UlimitsConfig{
 					"nproc":  {Single: 555},
diff --git a/loader/merge_test.go b/loader/merge_test.go
index b96fd33..79a1241 100644
--- a/loader/merge_test.go
+++ b/loader/merge_test.go
@@ -253,7 +253,7 @@ func TestLoadMultipleServicePorts(t *testing.T) {
 			expected: []types.ServicePortConfig{
 				{
 					Mode:      "ingress",
-					Published: 8080,
+					Published: "8080",
 					Target:    80,
 					Protocol:  "tcp",
 				},
@@ -274,13 +274,13 @@ func TestLoadMultipleServicePorts(t *testing.T) {
 			expected: []types.ServicePortConfig{
 				{
 					Mode:      "ingress",
-					Published: 8080,
+					Published: "8080",
 					Target:    80,
 					Protocol:  "tcp",
 				},
 				{
 					Mode:      "ingress",
-					Published: 8081,
+					Published: "8081",
 					Target:    80,
 					Protocol:  "tcp",
 				},
@@ -301,13 +301,13 @@ func TestLoadMultipleServicePorts(t *testing.T) {
 			expected: []types.ServicePortConfig{
 				{
 					Mode:      "ingress",
-					Published: 8080,
+					Published: "8080",
 					Target:    80,
 					Protocol:  "tcp",
 				},
 				{
 					Mode:      "ingress",
-					Published: 8080,
+					Published: "8080",
 					Target:    80,
 					Protocol:  "udp",
 				},
@@ -324,16 +324,14 @@ func TestLoadMultipleServicePorts(t *testing.T) {
 			portOverride: map[string]interface{}{},
 			expected: []types.ServicePortConfig{
 				{
-					Mode:      "ingress",
-					Published: 0,
-					Target:    5000,
-					Protocol:  "tcp",
+					Mode:     "ingress",
+					Target:   5000,
+					Protocol: "tcp",
 				},
 				{
-					Mode:      "ingress",
-					Published: 0,
-					Target:    6000,
-					Protocol:  "tcp",
+					Mode:     "ingress",
+					Target:   6000,
+					Protocol: "tcp",
 				},
 			},
 		},
@@ -969,17 +967,17 @@ func TestLoadMultipleConfigs(t *testing.T) {
 					{
 						Mode:      "ingress",
 						Target:    80,
-						Published: 8080,
+						Published: "8080",
 						Protocol:  "tcp",
 					},
 					{
 						Target:    81,
-						Published: 8080,
+						Published: "8080",
 					},
 					{
 						Mode:      "ingress",
 						Target:    90,
-						Published: 9090,
+						Published: "9090",
 						Protocol:  "tcp",
 					},
 				},
diff --git a/loader/normalize_test.go b/loader/normalize_test.go
index a2e3f68..c32a78e 100644
--- a/loader/normalize_test.go
+++ b/loader/normalize_test.go
@@ -60,7 +60,8 @@ func TestNormalizeNetworkNames(t *testing.T) {
 		},
 	}
 
-	expected := `services:
+	expected := `name: myProject
+services:
   foo:
     build:
       context: ./testdata
@@ -105,7 +106,8 @@ func TestNormalizeResolvePathsBuildContextPaths(t *testing.T) {
 		},
 	}
 
-	expected := fmt.Sprintf(`services:
+	expected := fmt.Sprintf(`name: myProject
+services:
   foo:
     build:
       context: %s
diff --git a/types/types_test.go b/types/types_test.go
index fe757c2..4db455d 100644
--- a/types/types_test.go
+++ b/types/types_test.go
@@ -45,7 +45,18 @@ func TestParsePortConfig(t *testing.T) {
 				{
 					Protocol:  "tcp",
 					Target:    8080,
-					Published: 80,
+					Published: "80",
+					Mode:      "ingress",
+				},
+			},
+		},
+		{
+			value: "80-90:8080",
+			expected: []ServicePortConfig{
+				{
+					Protocol:  "tcp",
+					Target:    8080,
+					Published: "80-90",
 					Mode:      "ingress",
 				},
 			},
@@ -56,7 +67,7 @@ func TestParsePortConfig(t *testing.T) {
 				{
 					Protocol:  "tcp",
 					Target:    80,
-					Published: 8080,
+					Published: "8080",
 					Mode:      "ingress",
 				},
 			},
@@ -67,7 +78,7 @@ func TestParsePortConfig(t *testing.T) {
 				{
 					Protocol:  "udp",
 					Target:    8080,
-					Published: 80,
+					Published: "80",
 					Mode:      "ingress",
 				},
 			},
@@ -78,13 +89,13 @@ func TestParsePortConfig(t *testing.T) {
 				{
 					Protocol:  "tcp",
 					Target:    8080,
-					Published: 80,
+					Published: "80",
 					Mode:      "ingress",
 				},
 				{
 					Protocol:  "tcp",
 					Target:    8081,
-					Published: 81,
+					Published: "81",
 					Mode:      "ingress",
 				},
 			},
@@ -95,19 +106,19 @@ func TestParsePortConfig(t *testing.T) {
 				{
 					Protocol:  "udp",
 					Target:    8080,
-					Published: 80,
+					Published: "80",
 					Mode:      "ingress",
 				},
 				{
 					Protocol:  "udp",
 					Target:    8081,
-					Published: 81,
+					Published: "81",
 					Mode:      "ingress",
 				},
 				{
 					Protocol:  "udp",
 					Target:    8082,
-					Published: 82,
+					Published: "82",
 					Mode:      "ingress",
 				},
 			},
@@ -118,19 +129,7 @@ func TestParsePortConfig(t *testing.T) {
 				{
 					Protocol:  "udp",
 					Target:    8080,
-					Published: 80,
-					Mode:      "ingress",
-				},
-				{
-					Protocol:  "udp",
-					Target:    8080,
-					Published: 81,
-					Mode:      "ingress",
-				},
-				{
-					Protocol:  "udp",
-					Target:    8080,
-					Published: 82,
+					Published: "80-82",
 					Mode:      "ingress",
 				},
 			},
@@ -141,7 +140,7 @@ func TestParsePortConfig(t *testing.T) {
 				{
 					Protocol:  "tcp",
 					Target:    8080,
-					Published: 80,
+					Published: "80",
 					Mode:      "ingress",
 				},
 			},
@@ -173,7 +172,7 @@ func TestParsePortConfig(t *testing.T) {
 					HostIP:    "1.1.1.1",
 					Protocol:  "tcp",
 					Target:    80,
-					Published: 80,
+					Published: "80",
 					Mode:      "ingress",
 				},
 			},