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