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