2016-11-10 15:13:26 -05:00
|
|
|
package opts
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/csv"
|
|
|
|
"fmt"
|
2016-12-08 16:32:10 -05:00
|
|
|
"regexp"
|
2016-11-10 15:13:26 -05:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/docker/docker/api/types/swarm"
|
2016-12-08 16:32:10 -05:00
|
|
|
"github.com/docker/go-connections/nat"
|
2016-11-10 15:13:26 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
portOptTargetPort = "target"
|
|
|
|
portOptPublishedPort = "published"
|
|
|
|
portOptProtocol = "protocol"
|
|
|
|
portOptMode = "mode"
|
|
|
|
)
|
|
|
|
|
2016-11-18 18:57:11 -05:00
|
|
|
// PortOpt represents a port config in swarm mode.
|
2016-11-10 15:13:26 -05:00
|
|
|
type PortOpt struct {
|
|
|
|
ports []swarm.PortConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set a new port value
|
|
|
|
func (p *PortOpt) Set(value string) error {
|
2016-12-08 16:32:10 -05:00
|
|
|
longSyntax, err := regexp.MatchString(`\w+=\w+(,\w+=\w+)*`, value)
|
2016-11-10 15:13:26 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-12-08 16:32:10 -05:00
|
|
|
if longSyntax {
|
|
|
|
csvReader := csv.NewReader(strings.NewReader(value))
|
|
|
|
fields, err := csvReader.Read()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2016-11-10 15:13:26 -05:00
|
|
|
}
|
|
|
|
|
2016-12-08 16:32:10 -05:00
|
|
|
pConfig := swarm.PortConfig{}
|
|
|
|
for _, field := range fields {
|
|
|
|
parts := strings.SplitN(field, "=", 2)
|
|
|
|
if len(parts) != 2 {
|
|
|
|
return fmt.Errorf("invalid field %s", field)
|
2016-11-10 15:13:26 -05:00
|
|
|
}
|
|
|
|
|
2016-12-08 16:32:10 -05:00
|
|
|
key := strings.ToLower(parts[0])
|
|
|
|
value := strings.ToLower(parts[1])
|
|
|
|
|
|
|
|
switch key {
|
|
|
|
case portOptProtocol:
|
|
|
|
if value != string(swarm.PortConfigProtocolTCP) && value != string(swarm.PortConfigProtocolUDP) {
|
|
|
|
return fmt.Errorf("invalid protocol value %s", value)
|
|
|
|
}
|
|
|
|
|
|
|
|
pConfig.Protocol = swarm.PortConfigProtocol(value)
|
|
|
|
case portOptMode:
|
|
|
|
if value != string(swarm.PortConfigPublishModeIngress) && value != string(swarm.PortConfigPublishModeHost) {
|
|
|
|
return fmt.Errorf("invalid publish mode value %s", value)
|
|
|
|
}
|
|
|
|
|
|
|
|
pConfig.PublishMode = swarm.PortConfigPublishMode(value)
|
|
|
|
case portOptTargetPort:
|
|
|
|
tPort, err := strconv.ParseUint(value, 10, 16)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
pConfig.TargetPort = uint32(tPort)
|
|
|
|
case portOptPublishedPort:
|
|
|
|
pPort, err := strconv.ParseUint(value, 10, 16)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
pConfig.PublishedPort = uint32(pPort)
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("invalid field key %s", key)
|
2016-11-10 15:13:26 -05:00
|
|
|
}
|
2016-12-08 16:32:10 -05:00
|
|
|
}
|
2016-11-10 15:13:26 -05:00
|
|
|
|
2016-12-08 16:32:10 -05:00
|
|
|
if pConfig.TargetPort == 0 {
|
|
|
|
return fmt.Errorf("missing mandatory field %q", portOptTargetPort)
|
|
|
|
}
|
2016-11-10 15:13:26 -05:00
|
|
|
|
2016-12-09 15:17:57 -05:00
|
|
|
if pConfig.PublishMode == "" {
|
|
|
|
pConfig.PublishMode = swarm.PortConfigPublishModeIngress
|
|
|
|
}
|
|
|
|
|
|
|
|
if pConfig.Protocol == "" {
|
|
|
|
pConfig.Protocol = swarm.PortConfigProtocolTCP
|
|
|
|
}
|
|
|
|
|
2016-12-08 16:32:10 -05:00
|
|
|
p.ports = append(p.ports, pConfig)
|
|
|
|
} else {
|
|
|
|
// short syntax
|
|
|
|
portConfigs := []swarm.PortConfig{}
|
|
|
|
// We can ignore errors because the format was already validated by ValidatePort
|
|
|
|
ports, portBindings, _ := nat.ParsePortSpecs([]string{value})
|
2016-11-10 15:13:26 -05:00
|
|
|
|
2016-12-08 16:32:10 -05:00
|
|
|
for port := range ports {
|
2017-01-12 12:01:29 -05:00
|
|
|
portConfig, err := ConvertPortToPortConfig(port, portBindings)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
portConfigs = append(portConfigs, portConfig...)
|
2016-11-10 15:13:26 -05:00
|
|
|
}
|
2016-12-08 16:32:10 -05:00
|
|
|
p.ports = append(p.ports, portConfigs...)
|
2016-11-10 15:13:26 -05:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Type returns the type of this option
|
|
|
|
func (p *PortOpt) Type() string {
|
|
|
|
return "port"
|
|
|
|
}
|
|
|
|
|
|
|
|
// String returns a string repr of this option
|
|
|
|
func (p *PortOpt) String() string {
|
|
|
|
ports := []string{}
|
|
|
|
for _, port := range p.ports {
|
|
|
|
repr := fmt.Sprintf("%v:%v/%s/%s", port.PublishedPort, port.TargetPort, port.Protocol, port.PublishMode)
|
|
|
|
ports = append(ports, repr)
|
|
|
|
}
|
|
|
|
return strings.Join(ports, ", ")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Value returns the ports
|
|
|
|
func (p *PortOpt) Value() []swarm.PortConfig {
|
|
|
|
return p.ports
|
|
|
|
}
|
2016-12-08 16:32:10 -05:00
|
|
|
|
|
|
|
// ConvertPortToPortConfig converts ports to the swarm type
|
|
|
|
func ConvertPortToPortConfig(
|
|
|
|
port nat.Port,
|
|
|
|
portBindings map[nat.Port][]nat.PortBinding,
|
2017-01-12 12:01:29 -05:00
|
|
|
) ([]swarm.PortConfig, error) {
|
2016-12-08 16:32:10 -05:00
|
|
|
ports := []swarm.PortConfig{}
|
|
|
|
|
|
|
|
for _, binding := range portBindings[port] {
|
2017-01-12 12:01:29 -05:00
|
|
|
hostPort, err := strconv.ParseUint(binding.HostPort, 10, 16)
|
|
|
|
if err != nil && binding.HostPort != "" {
|
|
|
|
return nil, fmt.Errorf("invalid hostport binding (%s) for port (%s)", binding.HostPort, port.Port())
|
|
|
|
}
|
2016-12-08 16:32:10 -05:00
|
|
|
ports = append(ports, swarm.PortConfig{
|
|
|
|
//TODO Name: ?
|
|
|
|
Protocol: swarm.PortConfigProtocol(strings.ToLower(port.Proto())),
|
|
|
|
TargetPort: uint32(port.Int()),
|
|
|
|
PublishedPort: uint32(hostPort),
|
2016-12-09 15:17:57 -05:00
|
|
|
PublishMode: swarm.PortConfigPublishModeIngress,
|
2016-12-08 16:32:10 -05:00
|
|
|
})
|
|
|
|
}
|
2017-01-12 12:01:29 -05:00
|
|
|
return ports, nil
|
2016-12-08 16:32:10 -05:00
|
|
|
}
|