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
24 type PortOpt struct {
25 ports []swarm.PortConfig
26 }
27
28
29
30
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
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
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
123 func (p *PortOpt) Type() string {
124 return "port"
125 }
126
127
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
138 func (p *PortOpt) Value() []swarm.PortConfig {
139 return p.ports
140 }
141
142
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
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 }
172
View as plain text