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