github.com/docker/docker-ce@v17.12.1-ce-rc2+incompatible/components/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  )
    13  
    14  const (
    15  	portOptTargetPort    = "target"
    16  	portOptPublishedPort = "published"
    17  	portOptProtocol      = "protocol"
    18  	portOptMode          = "mode"
    19  )
    20  
    21  // PortOpt represents a port config in swarm mode.
    22  type PortOpt struct {
    23  	ports []swarm.PortConfig
    24  }
    25  
    26  // Set a new port value
    27  // nolint: gocyclo
    28  func (p *PortOpt) Set(value string) error {
    29  	longSyntax, err := regexp.MatchString(`\w+=\w+(,\w+=\w+)*`, value)
    30  	if err != nil {
    31  		return err
    32  	}
    33  	if longSyntax {
    34  		csvReader := csv.NewReader(strings.NewReader(value))
    35  		fields, err := csvReader.Read()
    36  		if err != nil {
    37  			return err
    38  		}
    39  
    40  		pConfig := swarm.PortConfig{}
    41  		for _, field := range fields {
    42  			parts := strings.SplitN(field, "=", 2)
    43  			if len(parts) != 2 {
    44  				return fmt.Errorf("invalid field %s", field)
    45  			}
    46  
    47  			key := strings.ToLower(parts[0])
    48  			value := strings.ToLower(parts[1])
    49  
    50  			switch key {
    51  			case portOptProtocol:
    52  				if value != string(swarm.PortConfigProtocolTCP) && value != string(swarm.PortConfigProtocolUDP) {
    53  					return fmt.Errorf("invalid protocol value %s", value)
    54  				}
    55  
    56  				pConfig.Protocol = swarm.PortConfigProtocol(value)
    57  			case portOptMode:
    58  				if value != string(swarm.PortConfigPublishModeIngress) && value != string(swarm.PortConfigPublishModeHost) {
    59  					return fmt.Errorf("invalid publish mode value %s", value)
    60  				}
    61  
    62  				pConfig.PublishMode = swarm.PortConfigPublishMode(value)
    63  			case portOptTargetPort:
    64  				tPort, err := strconv.ParseUint(value, 10, 16)
    65  				if err != nil {
    66  					return err
    67  				}
    68  
    69  				pConfig.TargetPort = uint32(tPort)
    70  			case portOptPublishedPort:
    71  				pPort, err := strconv.ParseUint(value, 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  		hostPort, err := strconv.ParseUint(binding.HostPort, 10, 16)
   151  		if err != nil && binding.HostPort != "" {
   152  			return nil, fmt.Errorf("invalid hostport binding (%s) for port (%s)", binding.HostPort, port.Port())
   153  		}
   154  		ports = append(ports, swarm.PortConfig{
   155  			//TODO Name: ?
   156  			Protocol:      swarm.PortConfigProtocol(strings.ToLower(port.Proto())),
   157  			TargetPort:    uint32(port.Int()),
   158  			PublishedPort: uint32(hostPort),
   159  			PublishMode:   swarm.PortConfigPublishModeIngress,
   160  		})
   161  	}
   162  	return ports, nil
   163  }