github.1git.de/docker/cli@v26.1.3+incompatible/opts/port.go (about) 1 package opts 2 3 import ( 4 "encoding/csv" 5 "fmt" 6 "net" 7 "regexp" 8 "strconv" 9 "strings" 10 11 "github.com/docker/docker/api/types/swarm" 12 "github.com/docker/go-connections/nat" 13 "github.com/sirupsen/logrus" 14 ) 15 16 const ( 17 portOptTargetPort = "target" 18 portOptPublishedPort = "published" 19 portOptProtocol = "protocol" 20 portOptMode = "mode" 21 ) 22 23 // PortOpt represents a port config in swarm mode. 24 type PortOpt struct { 25 ports []swarm.PortConfig 26 } 27 28 // Set a new port value 29 // 30 //nolint:gocyclo 31 func (p *PortOpt) Set(value string) error { 32 longSyntax, err := regexp.MatchString(`\w+=\w+(,\w+=\w+)*`, value) 33 if err != nil { 34 return err 35 } 36 if longSyntax { 37 csvReader := csv.NewReader(strings.NewReader(value)) 38 fields, err := csvReader.Read() 39 if err != nil { 40 return err 41 } 42 43 pConfig := swarm.PortConfig{} 44 for _, field := range fields { 45 // TODO(thaJeztah): these options should not be case-insensitive. 46 key, val, ok := strings.Cut(strings.ToLower(field), "=") 47 if !ok || key == "" { 48 return fmt.Errorf("invalid field %s", field) 49 } 50 switch key { 51 case portOptProtocol: 52 if val != string(swarm.PortConfigProtocolTCP) && val != string(swarm.PortConfigProtocolUDP) && val != string(swarm.PortConfigProtocolSCTP) { 53 return fmt.Errorf("invalid protocol value %s", val) 54 } 55 56 pConfig.Protocol = swarm.PortConfigProtocol(val) 57 case portOptMode: 58 if val != string(swarm.PortConfigPublishModeIngress) && val != string(swarm.PortConfigPublishModeHost) { 59 return fmt.Errorf("invalid publish mode value %s", val) 60 } 61 62 pConfig.PublishMode = swarm.PortConfigPublishMode(val) 63 case portOptTargetPort: 64 tPort, err := strconv.ParseUint(val, 10, 16) 65 if err != nil { 66 return err 67 } 68 69 pConfig.TargetPort = uint32(tPort) 70 case portOptPublishedPort: 71 pPort, err := strconv.ParseUint(val, 10, 16) 72 if err != nil { 73 return err 74 } 75 76 pConfig.PublishedPort = uint32(pPort) 77 default: 78 return fmt.Errorf("invalid field key %s", key) 79 } 80 } 81 82 if pConfig.TargetPort == 0 { 83 return fmt.Errorf("missing mandatory field %q", portOptTargetPort) 84 } 85 86 if pConfig.PublishMode == "" { 87 pConfig.PublishMode = swarm.PortConfigPublishModeIngress 88 } 89 90 if pConfig.Protocol == "" { 91 pConfig.Protocol = swarm.PortConfigProtocolTCP 92 } 93 94 p.ports = append(p.ports, pConfig) 95 } else { 96 // short syntax 97 portConfigs := []swarm.PortConfig{} 98 ports, portBindingMap, err := nat.ParsePortSpecs([]string{value}) 99 if err != nil { 100 return err 101 } 102 for _, portBindings := range portBindingMap { 103 for _, portBinding := range portBindings { 104 if portBinding.HostIP != "" { 105 return fmt.Errorf("hostip is not supported") 106 } 107 } 108 } 109 110 for port := range ports { 111 portConfig, err := ConvertPortToPortConfig(port, portBindingMap) 112 if err != nil { 113 return err 114 } 115 portConfigs = append(portConfigs, portConfig...) 116 } 117 p.ports = append(p.ports, portConfigs...) 118 } 119 return nil 120 } 121 122 // Type returns the type of this option 123 func (p *PortOpt) Type() string { 124 return "port" 125 } 126 127 // String returns a string repr of this option 128 func (p *PortOpt) String() string { 129 ports := []string{} 130 for _, port := range p.ports { 131 repr := fmt.Sprintf("%v:%v/%s/%s", port.PublishedPort, port.TargetPort, port.Protocol, port.PublishMode) 132 ports = append(ports, repr) 133 } 134 return strings.Join(ports, ", ") 135 } 136 137 // Value returns the ports 138 func (p *PortOpt) Value() []swarm.PortConfig { 139 return p.ports 140 } 141 142 // ConvertPortToPortConfig converts ports to the swarm type 143 func ConvertPortToPortConfig( 144 port nat.Port, 145 portBindings map[nat.Port][]nat.PortBinding, 146 ) ([]swarm.PortConfig, error) { 147 ports := []swarm.PortConfig{} 148 149 for _, binding := range portBindings[port] { 150 if p := net.ParseIP(binding.HostIP); p != nil && !p.IsUnspecified() { 151 logrus.Warnf("ignoring IP-address (%s:%s) service will listen on '0.0.0.0'", net.JoinHostPort(binding.HostIP, binding.HostPort), port) 152 } 153 154 startHostPort, endHostPort, err := nat.ParsePortRange(binding.HostPort) 155 156 if err != nil && binding.HostPort != "" { 157 return nil, fmt.Errorf("invalid hostport binding (%s) for port (%s)", binding.HostPort, port.Port()) 158 } 159 160 for i := startHostPort; i <= endHostPort; i++ { 161 ports = append(ports, swarm.PortConfig{ 162 // TODO Name: ? 163 Protocol: swarm.PortConfigProtocol(strings.ToLower(port.Proto())), 164 TargetPort: uint32(port.Int()), 165 PublishedPort: uint32(i), 166 PublishMode: swarm.PortConfigPublishModeIngress, 167 }) 168 } 169 } 170 return ports, nil 171 }