...

Source file src/github.com/docker/cli/opts/port.go

Documentation: github.com/docker/cli/opts

     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  // PortOpt represents a port config in swarm mode.
    24  type PortOpt struct {
    25  	ports []swarm.PortConfig
    26  }
    27  
    28  // Set a new port value
    29  //
    30  //nolint:gocyclo
    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  			// TODO(thaJeztah): these options should not be case-insensitive.
    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  		// 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  		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  				// TODO Name: ?
   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