mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-28 12:03:33 +00:00
add String() method for PortSpec to convert to publish format
This commit is contained in:
@@ -3,6 +3,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -10,27 +11,48 @@ const (
|
|||||||
LabelServiceID = "uncloud.service.id"
|
LabelServiceID = "uncloud.service.id"
|
||||||
LabelServiceName = "uncloud.service.name"
|
LabelServiceName = "uncloud.service.name"
|
||||||
LabelServiceMode = "uncloud.service.mode"
|
LabelServiceMode = "uncloud.service.mode"
|
||||||
|
LabelServicePorts = "uncloud.service.ports"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Container struct {
|
type Container struct {
|
||||||
types.Container
|
types.Container
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceID returns the service ID that the container is part of.
|
// ServiceID returns the ID of the service this container belongs to.
|
||||||
func (c *Container) ServiceID() string {
|
func (c *Container) ServiceID() string {
|
||||||
return c.Labels[LabelServiceID]
|
return c.Labels[LabelServiceID]
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceName returns the service name that the container is part of.
|
// ServiceName returns the name of the service this container belongs to.
|
||||||
func (c *Container) ServiceName() string {
|
func (c *Container) ServiceName() string {
|
||||||
return c.Labels[LabelServiceName]
|
return c.Labels[LabelServiceName]
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceMode returns the replication mode of the service that the container is part of.
|
// ServiceMode returns the replication mode of the service this container belongs to.
|
||||||
func (c *Container) ServiceMode() string {
|
func (c *Container) ServiceMode() string {
|
||||||
return c.Labels[LabelServiceMode]
|
return c.Labels[LabelServiceMode]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServicePorts returns the ports this container publishes as part of its service.
|
||||||
|
func (c *Container) ServicePorts() ([]PortSpec, error) {
|
||||||
|
encoded, ok := c.Labels[LabelServicePorts]
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
publishPorts := strings.Split(encoded, ",")
|
||||||
|
ports := make([]PortSpec, len(publishPorts))
|
||||||
|
for i, p := range publishPorts {
|
||||||
|
port, err := ParsePortSpec(strings.TrimSpace(p))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ports[i] = port
|
||||||
|
}
|
||||||
|
|
||||||
|
return ports, nil
|
||||||
|
}
|
||||||
|
|
||||||
// runningStatusRegex matches the status string of a running container.
|
// runningStatusRegex matches the status string of a running container.
|
||||||
// - "Up 3 minutes (healthy)" -> groups: ["Up 3 minutes (healthy)", "healthy"]
|
// - "Up 3 minutes (healthy)" -> groups: ["Up 3 minutes (healthy)", "healthy"]
|
||||||
// - "Up 5 seconds" -> groups: ["Up 5 seconds", ""]
|
// - "Up 5 seconds" -> groups: ["Up 5 seconds", ""]
|
||||||
|
|||||||
+44
-1
@@ -39,6 +39,8 @@ func (p *PortSpec) Validate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch p.Protocol {
|
switch p.Protocol {
|
||||||
|
case "":
|
||||||
|
return fmt.Errorf("protocol must be specified")
|
||||||
case ProtocolHTTP, ProtocolHTTPS, ProtocolTCP, ProtocolUDP:
|
case ProtocolHTTP, ProtocolHTTPS, ProtocolTCP, ProtocolUDP:
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid protocol '%s', supported protocols: '%s', '%s', '%s', '%s'",
|
return fmt.Errorf("invalid protocol '%s', supported protocols: '%s', '%s', '%s', '%s'",
|
||||||
@@ -46,7 +48,9 @@ func (p *PortSpec) Validate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch p.Mode {
|
switch p.Mode {
|
||||||
case "", PortModeIngress: // Default mode is ingress if not specified.
|
case "":
|
||||||
|
return fmt.Errorf("mode must be specified")
|
||||||
|
case PortModeIngress:
|
||||||
if p.HostIP.IsValid() {
|
if p.HostIP.IsValid() {
|
||||||
return fmt.Errorf("host IP cannot be specified in %s mode", PortModeIngress)
|
return fmt.Errorf("host IP cannot be specified in %s mode", PortModeIngress)
|
||||||
}
|
}
|
||||||
@@ -79,6 +83,45 @@ func (p *PortSpec) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns the port specification in the -p/--publish flag format.
|
||||||
|
// Format:
|
||||||
|
// [hostname:][load_balancer_port:]container_port/protocol for ingress mode (default) or
|
||||||
|
// [host_ip:]:host_port:container_port/protocol@host for host mode.
|
||||||
|
func (p *PortSpec) String() (string, error) {
|
||||||
|
if err := p.Validate(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts []string
|
||||||
|
|
||||||
|
switch p.Mode {
|
||||||
|
case "", PortModeIngress: // [hostname:][load_balancer_port:]container_port/protocol
|
||||||
|
if p.Hostname != "" {
|
||||||
|
parts = append(parts, p.Hostname)
|
||||||
|
}
|
||||||
|
if p.PublishedPort != 0 {
|
||||||
|
parts = append(parts, fmt.Sprint(p.PublishedPort))
|
||||||
|
}
|
||||||
|
parts = append(parts, fmt.Sprint(p.ContainerPort))
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s/%s", strings.Join(parts, ":"), p.Protocol), nil
|
||||||
|
case PortModeHost: // [host_ip:]:host_port:container_port/protocol@host
|
||||||
|
if p.HostIP.IsValid() {
|
||||||
|
if p.HostIP.Is6() {
|
||||||
|
parts = append(parts, fmt.Sprintf("[%s]", p.HostIP))
|
||||||
|
} else {
|
||||||
|
parts = append(parts, p.HostIP.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parts = append(parts, fmt.Sprint(p.PublishedPort))
|
||||||
|
parts = append(parts, fmt.Sprint(p.ContainerPort))
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s/%s@host", strings.Join(parts, ":"), p.Protocol), nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("not implemented for mode: '%s'", p.Mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ParsePortSpec(port string) (PortSpec, error) {
|
func ParsePortSpec(port string) (PortSpec, error) {
|
||||||
spec := PortSpec{
|
spec := PortSpec{
|
||||||
Protocol: ProtocolTCP, // Default protocol.
|
Protocol: ProtocolTCP, // Default protocol.
|
||||||
|
|||||||
@@ -7,6 +7,391 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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 tcp",
|
||||||
|
spec: PortSpec{
|
||||||
|
PublishedPort: 80,
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolUDP,
|
||||||
|
Mode: PortModeHost,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "host mode udp",
|
||||||
|
spec: PortSpec{
|
||||||
|
PublishedPort: 80,
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolUDP,
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
wantErr: "container port must be non-zero",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid protocol",
|
||||||
|
spec: PortSpec{
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: "invalid",
|
||||||
|
},
|
||||||
|
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: "invalid hostname",
|
||||||
|
spec: PortSpec{
|
||||||
|
Hostname: "app",
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolHTTPS,
|
||||||
|
Mode: PortModeIngress,
|
||||||
|
},
|
||||||
|
wantErr: "invalid hostname 'app': must be a valid domain name containing at least one dot",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPortSpec_String(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
spec PortSpec
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
// Ingress mode.
|
||||||
|
{
|
||||||
|
name: "container port only",
|
||||||
|
spec: PortSpec{
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolTCP,
|
||||||
|
Mode: PortModeIngress,
|
||||||
|
},
|
||||||
|
expected: "8080/tcp",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "container port udp",
|
||||||
|
spec: PortSpec{
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolUDP,
|
||||||
|
Mode: PortModeIngress,
|
||||||
|
},
|
||||||
|
expected: "8080/udp",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "published and container port",
|
||||||
|
spec: PortSpec{
|
||||||
|
ContainerPort: 8080,
|
||||||
|
PublishedPort: 80,
|
||||||
|
Protocol: ProtocolTCP,
|
||||||
|
Mode: PortModeIngress,
|
||||||
|
},
|
||||||
|
expected: "80:8080/tcp",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hostname and container port https",
|
||||||
|
spec: PortSpec{
|
||||||
|
Hostname: "app.example.com",
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolHTTPS,
|
||||||
|
Mode: PortModeIngress,
|
||||||
|
},
|
||||||
|
expected: "app.example.com:8080/https",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hostname and container port http",
|
||||||
|
spec: PortSpec{
|
||||||
|
Hostname: "app.example.com",
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolHTTP,
|
||||||
|
Mode: PortModeIngress,
|
||||||
|
},
|
||||||
|
expected: "app.example.com:8080/http",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hostname and published and container port https",
|
||||||
|
spec: PortSpec{
|
||||||
|
Hostname: "app.example.com",
|
||||||
|
PublishedPort: 6443,
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolHTTPS,
|
||||||
|
Mode: PortModeIngress,
|
||||||
|
},
|
||||||
|
expected: "app.example.com:6443:8080/https",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hostname and published and container port http",
|
||||||
|
spec: PortSpec{
|
||||||
|
Hostname: "app.example.com",
|
||||||
|
PublishedPort: 6443,
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolHTTP,
|
||||||
|
Mode: PortModeIngress,
|
||||||
|
},
|
||||||
|
expected: "app.example.com:6443:8080/http",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Host mode.
|
||||||
|
{
|
||||||
|
name: "host mode tcp",
|
||||||
|
spec: PortSpec{
|
||||||
|
PublishedPort: 80,
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolTCP,
|
||||||
|
Mode: PortModeHost,
|
||||||
|
},
|
||||||
|
expected: "80:8080/tcp@host",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "host mode udp",
|
||||||
|
spec: PortSpec{
|
||||||
|
PublishedPort: 80,
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolUDP,
|
||||||
|
Mode: PortModeHost,
|
||||||
|
},
|
||||||
|
expected: "80:8080/udp@host",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "host mode with IPv4 tcp",
|
||||||
|
spec: PortSpec{
|
||||||
|
HostIP: netip.MustParseAddr("127.0.0.1"),
|
||||||
|
PublishedPort: 80,
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolTCP,
|
||||||
|
Mode: PortModeHost,
|
||||||
|
},
|
||||||
|
expected: "127.0.0.1:80:8080/tcp@host",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "host mode with IPv4 udp",
|
||||||
|
spec: PortSpec{
|
||||||
|
HostIP: netip.MustParseAddr("127.0.0.1"),
|
||||||
|
PublishedPort: 80,
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolUDP,
|
||||||
|
Mode: PortModeHost,
|
||||||
|
},
|
||||||
|
expected: "127.0.0.1:80:8080/udp@host",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "host mode with IPv6",
|
||||||
|
spec: PortSpec{
|
||||||
|
HostIP: netip.MustParseAddr("2001:db8::1234:5678"),
|
||||||
|
PublishedPort: 80,
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: ProtocolTCP,
|
||||||
|
Mode: PortModeHost,
|
||||||
|
},
|
||||||
|
expected: "[2001:db8::1234:5678]:80:8080/tcp@host",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := tt.spec.String()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParsePortSpec(t *testing.T) {
|
func TestParsePortSpec(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -311,232 +696,3 @@ 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 tcp",
|
|
||||||
spec: PortSpec{
|
|
||||||
ContainerPort: 8080,
|
|
||||||
Protocol: ProtocolTCP,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ingress mode with published tcp port",
|
|
||||||
spec: PortSpec{
|
|
||||||
PublishedPort: 80,
|
|
||||||
ContainerPort: 8080,
|
|
||||||
Protocol: ProtocolTCP,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ingress mode udp",
|
|
||||||
spec: PortSpec{
|
|
||||||
ContainerPort: 8080,
|
|
||||||
Protocol: ProtocolUDP,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ingress mode with published udp port",
|
|
||||||
spec: PortSpec{
|
|
||||||
PublishedPort: 80,
|
|
||||||
ContainerPort: 8080,
|
|
||||||
Protocol: ProtocolUDP,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ingress mode with hostname and http",
|
|
||||||
spec: PortSpec{
|
|
||||||
Hostname: "app.example.com",
|
|
||||||
ContainerPort: 8080,
|
|
||||||
Protocol: ProtocolHTTP,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ingress mode with hostname and https",
|
|
||||||
spec: PortSpec{
|
|
||||||
Hostname: "app.example.com",
|
|
||||||
ContainerPort: 8080,
|
|
||||||
Protocol: ProtocolHTTPS,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ingress mode with hostname and published port",
|
|
||||||
spec: PortSpec{
|
|
||||||
Hostname: "app.example.com",
|
|
||||||
PublishedPort: 6443,
|
|
||||||
ContainerPort: 8080,
|
|
||||||
Protocol: ProtocolHTTPS,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
},
|
|
||||||
wantErr: "container port must be non-zero",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid protocol",
|
|
||||||
spec: PortSpec{
|
|
||||||
ContainerPort: 8080,
|
|
||||||
Protocol: "invalid",
|
|
||||||
},
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
wantErr: "hostname is only valid with 'http' or 'https' protocols",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid hostname",
|
|
||||||
spec: PortSpec{
|
|
||||||
Hostname: "app",
|
|
||||||
ContainerPort: 8080,
|
|
||||||
Protocol: ProtocolHTTPS,
|
|
||||||
},
|
|
||||||
wantErr: "invalid hostname 'app': must be a valid domain name containing at least one dot",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "missing hostname with http",
|
|
||||||
spec: PortSpec{
|
|
||||||
ContainerPort: 8080,
|
|
||||||
Protocol: ProtocolHTTP,
|
|
||||||
},
|
|
||||||
wantErr: "hostname is required with 'http' or 'https' protocols",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "missing hostname with https",
|
|
||||||
spec: PortSpec{
|
|
||||||
ContainerPort: 8080,
|
|
||||||
Protocol: ProtocolHTTPS,
|
|
||||||
},
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
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