github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/specgenutil/util.go (about)

     1  package specgenutil
     2  
     3  import (
     4  	"io/ioutil"
     5  	"net"
     6  	"os"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/containers/common/libnetwork/types"
    11  	"github.com/containers/common/pkg/config"
    12  	storageTypes "github.com/containers/storage/types"
    13  	"github.com/pkg/errors"
    14  	"github.com/sirupsen/logrus"
    15  )
    16  
    17  // ReadPodIDFile reads the specified file and returns its content (i.e., first
    18  // line).
    19  func ReadPodIDFile(path string) (string, error) {
    20  	content, err := ioutil.ReadFile(path)
    21  	if err != nil {
    22  		return "", errors.Wrap(err, "error reading pod ID file")
    23  	}
    24  	return strings.Split(string(content), "\n")[0], nil
    25  }
    26  
    27  // ReadPodIDFiles reads the specified files and returns their content (i.e.,
    28  // first line).
    29  func ReadPodIDFiles(files []string) ([]string, error) {
    30  	ids := []string{}
    31  	for _, file := range files {
    32  		id, err := ReadPodIDFile(file)
    33  		if err != nil {
    34  			return nil, err
    35  		}
    36  		ids = append(ids, id)
    37  	}
    38  	return ids, nil
    39  }
    40  
    41  // CreateExpose parses user-provided exposed port definitions and converts them
    42  // into SpecGen format.
    43  // TODO: The SpecGen format should really handle ranges more sanely - we could
    44  // be massively inflating what is sent over the wire with a large range.
    45  func CreateExpose(expose []string) (map[uint16]string, error) {
    46  	toReturn := make(map[uint16]string)
    47  
    48  	for _, e := range expose {
    49  		// Check for protocol
    50  		proto := "tcp"
    51  		splitProto := strings.Split(e, "/")
    52  		if len(splitProto) > 2 {
    53  			return nil, errors.Errorf("invalid expose format - protocol can only be specified once")
    54  		} else if len(splitProto) == 2 {
    55  			proto = splitProto[1]
    56  		}
    57  
    58  		// Check for a range
    59  		start, len, err := parseAndValidateRange(splitProto[0])
    60  		if err != nil {
    61  			return nil, err
    62  		}
    63  
    64  		var index uint16
    65  		for index = 0; index < len; index++ {
    66  			portNum := start + index
    67  			protocols, ok := toReturn[portNum]
    68  			if !ok {
    69  				toReturn[portNum] = proto
    70  			} else {
    71  				newProto := strings.Join(append(strings.Split(protocols, ","), strings.Split(proto, ",")...), ",")
    72  				toReturn[portNum] = newProto
    73  			}
    74  		}
    75  	}
    76  
    77  	return toReturn, nil
    78  }
    79  
    80  // CreatePortBindings iterates ports mappings into SpecGen format.
    81  func CreatePortBindings(ports []string) ([]types.PortMapping, error) {
    82  	// --publish is formatted as follows:
    83  	// [[hostip:]hostport[-endPort]:]containerport[-endPort][/protocol]
    84  	toReturn := make([]types.PortMapping, 0, len(ports))
    85  
    86  	for _, p := range ports {
    87  		var (
    88  			ctrPort                 string
    89  			proto, hostIP, hostPort *string
    90  		)
    91  
    92  		splitProto := strings.Split(p, "/")
    93  		switch len(splitProto) {
    94  		case 1:
    95  			// No protocol was provided
    96  		case 2:
    97  			proto = &(splitProto[1])
    98  		default:
    99  			return nil, errors.Errorf("invalid port format - protocol can only be specified once")
   100  		}
   101  
   102  		remainder := splitProto[0]
   103  		haveV6 := false
   104  
   105  		// Check for an IPv6 address in brackets
   106  		splitV6 := strings.Split(remainder, "]")
   107  		switch len(splitV6) {
   108  		case 1:
   109  			// Do nothing, proceed as before
   110  		case 2:
   111  			// We potentially have an IPv6 address
   112  			haveV6 = true
   113  			if !strings.HasPrefix(splitV6[0], "[") {
   114  				return nil, errors.Errorf("invalid port format - IPv6 addresses must be enclosed by []")
   115  			}
   116  			if !strings.HasPrefix(splitV6[1], ":") {
   117  				return nil, errors.Errorf("invalid port format - IPv6 address must be followed by a colon (':')")
   118  			}
   119  			ipNoPrefix := strings.TrimPrefix(splitV6[0], "[")
   120  			hostIP = &ipNoPrefix
   121  			remainder = strings.TrimPrefix(splitV6[1], ":")
   122  		default:
   123  			return nil, errors.Errorf("invalid port format - at most one IPv6 address can be specified in a --publish")
   124  		}
   125  
   126  		splitPort := strings.Split(remainder, ":")
   127  		switch len(splitPort) {
   128  		case 1:
   129  			if haveV6 {
   130  				return nil, errors.Errorf("invalid port format - must provide host and destination port if specifying an IP")
   131  			}
   132  			ctrPort = splitPort[0]
   133  		case 2:
   134  			hostPort = &(splitPort[0])
   135  			ctrPort = splitPort[1]
   136  		case 3:
   137  			if haveV6 {
   138  				return nil, errors.Errorf("invalid port format - when v6 address specified, must be [ipv6]:hostPort:ctrPort")
   139  			}
   140  			hostIP = &(splitPort[0])
   141  			hostPort = &(splitPort[1])
   142  			ctrPort = splitPort[2]
   143  		default:
   144  			return nil, errors.Errorf("invalid port format - format is [[hostIP:]hostPort:]containerPort")
   145  		}
   146  
   147  		newPort, err := parseSplitPort(hostIP, hostPort, ctrPort, proto)
   148  		if err != nil {
   149  			return nil, err
   150  		}
   151  
   152  		toReturn = append(toReturn, newPort)
   153  	}
   154  
   155  	return toReturn, nil
   156  }
   157  
   158  // parseSplitPort parses individual components of the --publish flag to produce
   159  // a single port mapping in SpecGen format.
   160  func parseSplitPort(hostIP, hostPort *string, ctrPort string, protocol *string) (types.PortMapping, error) {
   161  	newPort := types.PortMapping{}
   162  	if ctrPort == "" {
   163  		return newPort, errors.Errorf("must provide a non-empty container port to publish")
   164  	}
   165  	ctrStart, ctrLen, err := parseAndValidateRange(ctrPort)
   166  	if err != nil {
   167  		return newPort, errors.Wrapf(err, "error parsing container port")
   168  	}
   169  	newPort.ContainerPort = ctrStart
   170  	newPort.Range = ctrLen
   171  
   172  	if protocol != nil {
   173  		if *protocol == "" {
   174  			return newPort, errors.Errorf("must provide a non-empty protocol to publish")
   175  		}
   176  		newPort.Protocol = *protocol
   177  	}
   178  	if hostIP != nil {
   179  		if *hostIP == "" {
   180  			return newPort, errors.Errorf("must provide a non-empty container host IP to publish")
   181  		} else if *hostIP != "0.0.0.0" {
   182  			// If hostIP is 0.0.0.0, leave it unset - CNI treats
   183  			// 0.0.0.0 and empty differently, Docker does not.
   184  			testIP := net.ParseIP(*hostIP)
   185  			if testIP == nil {
   186  				return newPort, errors.Errorf("cannot parse %q as an IP address", *hostIP)
   187  			}
   188  			newPort.HostIP = testIP.String()
   189  		}
   190  	}
   191  	if hostPort != nil {
   192  		if *hostPort == "" {
   193  			// Set 0 as a placeholder. The server side of Specgen
   194  			// will find a random, open, unused port to use.
   195  			newPort.HostPort = 0
   196  		} else {
   197  			hostStart, hostLen, err := parseAndValidateRange(*hostPort)
   198  			if err != nil {
   199  				return newPort, errors.Wrapf(err, "error parsing host port")
   200  			}
   201  			if hostLen != ctrLen {
   202  				return newPort, errors.Errorf("host and container port ranges have different lengths: %d vs %d", hostLen, ctrLen)
   203  			}
   204  			newPort.HostPort = hostStart
   205  		}
   206  	}
   207  
   208  	hport := newPort.HostPort
   209  	logrus.Debugf("Adding port mapping from %d to %d length %d protocol %q", hport, newPort.ContainerPort, newPort.Range, newPort.Protocol)
   210  
   211  	return newPort, nil
   212  }
   213  
   214  // Parse and validate a port range.
   215  // Returns start port, length of range, error.
   216  func parseAndValidateRange(portRange string) (uint16, uint16, error) {
   217  	splitRange := strings.Split(portRange, "-")
   218  	if len(splitRange) > 2 {
   219  		return 0, 0, errors.Errorf("invalid port format - port ranges are formatted as startPort-stopPort")
   220  	}
   221  
   222  	if splitRange[0] == "" {
   223  		return 0, 0, errors.Errorf("port numbers cannot be negative")
   224  	}
   225  
   226  	startPort, err := parseAndValidatePort(splitRange[0])
   227  	if err != nil {
   228  		return 0, 0, err
   229  	}
   230  
   231  	var rangeLen uint16 = 1
   232  	if len(splitRange) == 2 {
   233  		if splitRange[1] == "" {
   234  			return 0, 0, errors.Errorf("must provide ending number for port range")
   235  		}
   236  		endPort, err := parseAndValidatePort(splitRange[1])
   237  		if err != nil {
   238  			return 0, 0, err
   239  		}
   240  		if endPort <= startPort {
   241  			return 0, 0, errors.Errorf("the end port of a range must be higher than the start port - %d is not higher than %d", endPort, startPort)
   242  		}
   243  		// Our range is the total number of ports
   244  		// involved, so we need to add 1 (8080:8081 is
   245  		// 2 ports, for example, not 1)
   246  		rangeLen = endPort - startPort + 1
   247  	}
   248  
   249  	return startPort, rangeLen, nil
   250  }
   251  
   252  // Turn a single string into a valid U16 port.
   253  func parseAndValidatePort(port string) (uint16, error) {
   254  	num, err := strconv.Atoi(port)
   255  	if err != nil {
   256  		return 0, errors.Wrapf(err, "invalid port number")
   257  	}
   258  	if num < 1 || num > 65535 {
   259  		return 0, errors.Errorf("port numbers must be between 1 and 65535 (inclusive), got %d", num)
   260  	}
   261  	return uint16(num), nil
   262  }
   263  
   264  func CreateExitCommandArgs(storageConfig storageTypes.StoreOptions, config *config.Config, syslog, rm, exec bool) ([]string, error) {
   265  	// We need a cleanup process for containers in the current model.
   266  	// But we can't assume that the caller is Podman - it could be another
   267  	// user of the API.
   268  	// As such, provide a way to specify a path to Podman, so we can
   269  	// still invoke a cleanup process.
   270  
   271  	podmanPath, err := os.Executable()
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  
   276  	command := []string{podmanPath,
   277  		"--root", storageConfig.GraphRoot,
   278  		"--runroot", storageConfig.RunRoot,
   279  		"--log-level", logrus.GetLevel().String(),
   280  		"--cgroup-manager", config.Engine.CgroupManager,
   281  		"--tmpdir", config.Engine.TmpDir,
   282  		"--network-config-dir", config.Network.NetworkConfigDir,
   283  		"--network-backend", config.Network.NetworkBackend,
   284  		"--volumepath", config.Engine.VolumePath,
   285  	}
   286  	if config.Engine.OCIRuntime != "" {
   287  		command = append(command, []string{"--runtime", config.Engine.OCIRuntime}...)
   288  	}
   289  	if storageConfig.GraphDriverName != "" {
   290  		command = append(command, []string{"--storage-driver", storageConfig.GraphDriverName}...)
   291  	}
   292  	for _, opt := range storageConfig.GraphDriverOptions {
   293  		command = append(command, []string{"--storage-opt", opt}...)
   294  	}
   295  	if config.Engine.EventsLogger != "" {
   296  		command = append(command, []string{"--events-backend", config.Engine.EventsLogger}...)
   297  	}
   298  
   299  	if syslog {
   300  		command = append(command, "--syslog")
   301  	}
   302  	command = append(command, []string{"container", "cleanup"}...)
   303  
   304  	if rm {
   305  		command = append(command, "--rm")
   306  	}
   307  
   308  	// This has to be absolutely last, to ensure that the exec session ID
   309  	// will be added after it by Libpod.
   310  	if exec {
   311  		command = append(command, "--exec")
   312  	}
   313  
   314  	return command, nil
   315  }