mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-28 12:03:33 +00:00
add validation for PortSpec
This commit is contained in:
+41
-1
@@ -33,6 +33,46 @@ type PortSpec struct {
|
|||||||
Mode string
|
Mode string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *PortSpec) Validate() error {
|
||||||
|
if p.ContainerPort == 0 {
|
||||||
|
return fmt.Errorf("container port must be non-zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch p.Protocol {
|
||||||
|
case ProtocolHTTP, ProtocolHTTPS, ProtocolTCP, ProtocolUDP:
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid protocol: '%s'", p.Protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch p.Mode {
|
||||||
|
case PortModeIngress:
|
||||||
|
if p.HostIP.IsValid() {
|
||||||
|
return fmt.Errorf("host IP cannot be specified in %s mode", PortModeIngress)
|
||||||
|
}
|
||||||
|
if p.Hostname != "" && p.Protocol != ProtocolHTTP && p.Protocol != ProtocolHTTPS {
|
||||||
|
return fmt.Errorf("hostname is only valid with '%s' or '%s' protocols", ProtocolHTTP, ProtocolHTTPS)
|
||||||
|
}
|
||||||
|
if p.Hostname == "" && (p.Protocol == ProtocolHTTP || p.Protocol == ProtocolHTTPS) {
|
||||||
|
return fmt.Errorf("hostname is required with '%s' or '%s' protocols", ProtocolHTTP, ProtocolHTTPS)
|
||||||
|
}
|
||||||
|
case PortModeHost:
|
||||||
|
if p.PublishedPort == 0 {
|
||||||
|
return fmt.Errorf("published port is required in %s mode", PortModeHost)
|
||||||
|
}
|
||||||
|
if p.Protocol != ProtocolTCP && p.Protocol != ProtocolUDP {
|
||||||
|
return fmt.Errorf("unsupported protocol '%s' in %s mode, only '%s' and '%s' are supported",
|
||||||
|
p.Protocol, PortModeHost, ProtocolTCP, ProtocolUDP)
|
||||||
|
}
|
||||||
|
if p.Hostname != "" {
|
||||||
|
return fmt.Errorf("hostname cannot be specified in %s mode", PortModeHost)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid mode: '%s'", p.Mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func ParsePortSpec(port string) (PortSpec, error) {
|
func ParsePortSpec(port string) (PortSpec, error) {
|
||||||
spec := PortSpec{
|
spec := PortSpec{
|
||||||
Protocol: ProtocolTCP, // Default protocol.
|
Protocol: ProtocolTCP, // Default protocol.
|
||||||
@@ -147,7 +187,7 @@ func ParsePortSpec(port string) (PortSpec, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return spec, nil
|
return spec, spec.Validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// splitPortParts splits a port specification [hostname|host_ip:][published_port:]container_port into its parts.
|
// splitPortParts splits a port specification [hostname|host_ip:][published_port:]container_port into its parts.
|
||||||
|
|||||||
+271
-60
@@ -26,15 +26,6 @@ func TestParsePortSpec(t *testing.T) {
|
|||||||
Mode: PortModeIngress,
|
Mode: PortModeIngress,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "container port zero",
|
|
||||||
port: "0",
|
|
||||||
expected: PortSpec{
|
|
||||||
ContainerPort: 0,
|
|
||||||
Protocol: ProtocolTCP,
|
|
||||||
Mode: PortModeIngress,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "published and container port",
|
name: "published and container port",
|
||||||
port: "8000:8080",
|
port: "8000:8080",
|
||||||
@@ -82,34 +73,6 @@ func TestParsePortSpec(t *testing.T) {
|
|||||||
Mode: PortModeIngress,
|
Mode: PortModeIngress,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "http protocol",
|
|
||||||
port: "8080/http",
|
|
||||||
expected: PortSpec{
|
|
||||||
ContainerPort: 8080,
|
|
||||||
Protocol: ProtocolHTTP,
|
|
||||||
Mode: PortModeIngress,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "https protocol",
|
|
||||||
port: "8080/https",
|
|
||||||
expected: PortSpec{
|
|
||||||
ContainerPort: 8080,
|
|
||||||
Protocol: ProtocolHTTPS,
|
|
||||||
Mode: PortModeIngress,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "published port https",
|
|
||||||
port: "8000:8080/https",
|
|
||||||
expected: PortSpec{
|
|
||||||
PublishedPort: 8000,
|
|
||||||
ContainerPort: 8080,
|
|
||||||
Protocol: ProtocolHTTPS,
|
|
||||||
Mode: PortModeIngress,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "hostname and container port",
|
name: "hostname and container port",
|
||||||
port: "app.example.com:8080",
|
port: "app.example.com:8080",
|
||||||
@@ -154,24 +117,6 @@ func TestParsePortSpec(t *testing.T) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Host mode.
|
// Host mode.
|
||||||
{
|
|
||||||
name: "host mode",
|
|
||||||
port: "8080@host",
|
|
||||||
expected: PortSpec{
|
|
||||||
ContainerPort: 8080,
|
|
||||||
Protocol: ProtocolTCP,
|
|
||||||
Mode: PortModeHost,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "host mode with protocol",
|
|
||||||
port: "8080/udp@host",
|
|
||||||
expected: PortSpec{
|
|
||||||
ContainerPort: 8080,
|
|
||||||
Protocol: ProtocolUDP,
|
|
||||||
Mode: PortModeHost,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "host mode published with protocol",
|
name: "host mode published with protocol",
|
||||||
port: "80:8080/udp@host",
|
port: "80:8080/udp@host",
|
||||||
@@ -227,6 +172,11 @@ func TestParsePortSpec(t *testing.T) {
|
|||||||
port: "invalid",
|
port: "invalid",
|
||||||
wantErr: "invalid container port",
|
wantErr: "invalid container port",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "container port zero",
|
||||||
|
port: "0",
|
||||||
|
wantErr: "container port must be non-zero",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "out of range container port",
|
name: "out of range container port",
|
||||||
port: "100500",
|
port: "100500",
|
||||||
@@ -267,6 +217,37 @@ func TestParsePortSpec(t *testing.T) {
|
|||||||
port: "test:invalid:8080",
|
port: "test:invalid:8080",
|
||||||
wantErr: "invalid published port",
|
wantErr: "invalid published port",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "missing hostname with http",
|
||||||
|
port: "8080/http",
|
||||||
|
wantErr: "hostname is required",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing hostname with http",
|
||||||
|
port: "8080/https",
|
||||||
|
wantErr: "hostname is required",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing hostname with published https port",
|
||||||
|
port: "8000:8080/https",
|
||||||
|
wantErr: "hostname is required",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hostname with tcp protocol",
|
||||||
|
port: "app.example.com:8080/tcp",
|
||||||
|
wantErr: "hostname is only valid with 'http' or 'https' protocols",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "missing published port in host mode",
|
||||||
|
port: "8080@host",
|
||||||
|
wantErr: "published port is required in host mode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing published port with protocol in host mode",
|
||||||
|
port: "8080/udp@host",
|
||||||
|
wantErr: "published port is required in host mode",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "invalid host IPv4",
|
name: "invalid host IPv4",
|
||||||
port: "300.0.0.1:80:8080@host",
|
port: "300.0.0.1:80:8080@host",
|
||||||
@@ -287,6 +268,16 @@ func TestParsePortSpec(t *testing.T) {
|
|||||||
port: "2001:db8::1234:5678:80:8080@host",
|
port: "2001:db8::1234:5678:80:8080@host",
|
||||||
wantErr: "invalid host IP",
|
wantErr: "invalid host IP",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "http in host mode",
|
||||||
|
port: "80:8080/http@host",
|
||||||
|
wantErr: "unsupported protocol 'http' in host mode, only 'tcp' and 'udp' are supported",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "https in host mode",
|
||||||
|
port: "80:8080/https@host",
|
||||||
|
wantErr: "unsupported protocol 'https' in host mode, only 'tcp' and 'udp' are supported",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "hostname in host mode",
|
name: "hostname in host mode",
|
||||||
port: "app.example.com:8080@host",
|
port: "app.example.com:8080@host",
|
||||||
@@ -297,11 +288,6 @@ func TestParsePortSpec(t *testing.T) {
|
|||||||
port: "app.example.com:invalid:8080@host",
|
port: "app.example.com:invalid:8080@host",
|
||||||
wantErr: "invalid published port",
|
wantErr: "invalid published port",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "hostname with tcp protocol",
|
|
||||||
port: "app.example.com:8080/tcp",
|
|
||||||
wantErr: "hostname is only valid with 'http' or 'https' protocols",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -320,3 +306,228 @@ func TestParsePortSpec(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPortSpec_Validate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
spec PortSpec
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
// Valid ingress mode.
|
||||||
|
{
|
||||||
|
name: "ingress mode tcp",
|
||||||
|
spec: PortSpec{
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolTCP,
|
||||||
|
Mode: PortModeIngress,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ingress mode with published tcp port",
|
||||||
|
spec: PortSpec{
|
||||||
|
PublishedPort: 80,
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolTCP,
|
||||||
|
Mode: PortModeIngress,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ingress mode udp",
|
||||||
|
spec: PortSpec{
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolUDP,
|
||||||
|
Mode: PortModeIngress,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ingress mode with published udp port",
|
||||||
|
spec: PortSpec{
|
||||||
|
PublishedPort: 80,
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolUDP,
|
||||||
|
Mode: PortModeIngress,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ingress mode with hostname and http",
|
||||||
|
spec: PortSpec{
|
||||||
|
Hostname: "app.example.com",
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolHTTP,
|
||||||
|
Mode: PortModeIngress,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ingress mode with hostname and https",
|
||||||
|
spec: PortSpec{
|
||||||
|
Hostname: "app.example.com",
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolHTTPS,
|
||||||
|
Mode: PortModeIngress,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ingress mode with hostname and published port",
|
||||||
|
spec: PortSpec{
|
||||||
|
Hostname: "app.example.com",
|
||||||
|
PublishedPort: 6443,
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolHTTPS,
|
||||||
|
Mode: PortModeIngress,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Valid host mode.
|
||||||
|
{
|
||||||
|
name: "host mode",
|
||||||
|
spec: PortSpec{
|
||||||
|
PublishedPort: 80,
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolTCP,
|
||||||
|
Mode: PortModeHost,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "host mode with IPv4",
|
||||||
|
spec: PortSpec{
|
||||||
|
HostIP: netip.MustParseAddr("127.0.0.1"),
|
||||||
|
PublishedPort: 80,
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolTCP,
|
||||||
|
Mode: PortModeHost,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "host mode with IPv6",
|
||||||
|
spec: PortSpec{
|
||||||
|
HostIP: netip.MustParseAddr("2001:db8::1234:5678"),
|
||||||
|
PublishedPort: 80,
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolUDP,
|
||||||
|
Mode: PortModeHost,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Error cases.
|
||||||
|
{
|
||||||
|
name: "missing container port",
|
||||||
|
spec: PortSpec{
|
||||||
|
Protocol: ProtocolTCP,
|
||||||
|
Mode: PortModeIngress,
|
||||||
|
},
|
||||||
|
wantErr: "container port must be non-zero",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid protocol",
|
||||||
|
spec: PortSpec{
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: "invalid",
|
||||||
|
Mode: PortModeIngress,
|
||||||
|
},
|
||||||
|
wantErr: "invalid protocol: 'invalid'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid mode",
|
||||||
|
spec: PortSpec{
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolTCP,
|
||||||
|
Mode: "invalid",
|
||||||
|
},
|
||||||
|
wantErr: "invalid mode: 'invalid'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hostname with non-http protocol",
|
||||||
|
spec: PortSpec{
|
||||||
|
Hostname: "app.example.com",
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolTCP,
|
||||||
|
Mode: PortModeIngress,
|
||||||
|
},
|
||||||
|
wantErr: "hostname is only valid with 'http' or 'https' protocols",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing hostname with http",
|
||||||
|
spec: PortSpec{
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolHTTP,
|
||||||
|
Mode: PortModeIngress,
|
||||||
|
},
|
||||||
|
wantErr: "hostname is required with 'http' or 'https' protocols",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing hostname with https",
|
||||||
|
spec: PortSpec{
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolHTTPS,
|
||||||
|
Mode: PortModeIngress,
|
||||||
|
},
|
||||||
|
wantErr: "hostname is required with 'http' or 'https' protocols",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "host IP in ingress mode",
|
||||||
|
spec: PortSpec{
|
||||||
|
HostIP: netip.MustParseAddr("127.0.0.1"),
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolTCP,
|
||||||
|
Mode: PortModeIngress,
|
||||||
|
},
|
||||||
|
wantErr: "host IP cannot be specified in ingress mode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero published port in host mode",
|
||||||
|
spec: PortSpec{
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolTCP,
|
||||||
|
Mode: PortModeHost,
|
||||||
|
},
|
||||||
|
wantErr: "published port is required in host mode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hostname in host mode",
|
||||||
|
spec: PortSpec{
|
||||||
|
Hostname: "app.example.com",
|
||||||
|
PublishedPort: 80,
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolTCP,
|
||||||
|
Mode: PortModeHost,
|
||||||
|
},
|
||||||
|
wantErr: "hostname cannot be specified in host mode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "http in host mode",
|
||||||
|
spec: PortSpec{
|
||||||
|
PublishedPort: 80,
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolHTTP,
|
||||||
|
Mode: PortModeHost,
|
||||||
|
},
|
||||||
|
wantErr: "unsupported protocol 'http' in host mode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "https in host mode",
|
||||||
|
spec: PortSpec{
|
||||||
|
PublishedPort: 80,
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolHTTPS,
|
||||||
|
Mode: PortModeHost,
|
||||||
|
},
|
||||||
|
wantErr: "unsupported protocol 'https' in host mode",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
err := tt.spec.Validate()
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.Error(t, err, tt.wantErr)
|
||||||
|
assert.Contains(t, err.Error(), tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user