mirror of https://github.com/containers/podman.git
				
				
				
			
		
			
				
	
	
		
			316 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			316 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			Go
		
	
	
	
| package specgenutil
 | |
| 
 | |
| import (
 | |
| 	"io/ioutil"
 | |
| 	"net"
 | |
| 	"os"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/containers/common/libnetwork/types"
 | |
| 	"github.com/containers/common/pkg/config"
 | |
| 	storageTypes "github.com/containers/storage/types"
 | |
| 	"github.com/pkg/errors"
 | |
| 	"github.com/sirupsen/logrus"
 | |
| )
 | |
| 
 | |
| // ReadPodIDFile reads the specified file and returns its content (i.e., first
 | |
| // line).
 | |
| func ReadPodIDFile(path string) (string, error) {
 | |
| 	content, err := ioutil.ReadFile(path)
 | |
| 	if err != nil {
 | |
| 		return "", errors.Wrap(err, "error reading pod ID file")
 | |
| 	}
 | |
| 	return strings.Split(string(content), "\n")[0], nil
 | |
| }
 | |
| 
 | |
| // ReadPodIDFiles reads the specified files and returns their content (i.e.,
 | |
| // first line).
 | |
| func ReadPodIDFiles(files []string) ([]string, error) {
 | |
| 	ids := []string{}
 | |
| 	for _, file := range files {
 | |
| 		id, err := ReadPodIDFile(file)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		ids = append(ids, id)
 | |
| 	}
 | |
| 	return ids, nil
 | |
| }
 | |
| 
 | |
| // CreateExpose parses user-provided exposed port definitions and converts them
 | |
| // into SpecGen format.
 | |
| // TODO: The SpecGen format should really handle ranges more sanely - we could
 | |
| // be massively inflating what is sent over the wire with a large range.
 | |
| func CreateExpose(expose []string) (map[uint16]string, error) {
 | |
| 	toReturn := make(map[uint16]string)
 | |
| 
 | |
| 	for _, e := range expose {
 | |
| 		// Check for protocol
 | |
| 		proto := "tcp"
 | |
| 		splitProto := strings.Split(e, "/")
 | |
| 		if len(splitProto) > 2 {
 | |
| 			return nil, errors.Errorf("invalid expose format - protocol can only be specified once")
 | |
| 		} else if len(splitProto) == 2 {
 | |
| 			proto = splitProto[1]
 | |
| 		}
 | |
| 
 | |
| 		// Check for a range
 | |
| 		start, len, err := parseAndValidateRange(splitProto[0])
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		var index uint16
 | |
| 		for index = 0; index < len; index++ {
 | |
| 			portNum := start + index
 | |
| 			protocols, ok := toReturn[portNum]
 | |
| 			if !ok {
 | |
| 				toReturn[portNum] = proto
 | |
| 			} else {
 | |
| 				newProto := strings.Join(append(strings.Split(protocols, ","), strings.Split(proto, ",")...), ",")
 | |
| 				toReturn[portNum] = newProto
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return toReturn, nil
 | |
| }
 | |
| 
 | |
| // CreatePortBindings iterates ports mappings into SpecGen format.
 | |
| func CreatePortBindings(ports []string) ([]types.PortMapping, error) {
 | |
| 	// --publish is formatted as follows:
 | |
| 	// [[hostip:]hostport[-endPort]:]containerport[-endPort][/protocol]
 | |
| 	toReturn := make([]types.PortMapping, 0, len(ports))
 | |
| 
 | |
| 	for _, p := range ports {
 | |
| 		var (
 | |
| 			ctrPort                 string
 | |
| 			proto, hostIP, hostPort *string
 | |
| 		)
 | |
| 
 | |
| 		splitProto := strings.Split(p, "/")
 | |
| 		switch len(splitProto) {
 | |
| 		case 1:
 | |
| 			// No protocol was provided
 | |
| 		case 2:
 | |
| 			proto = &(splitProto[1])
 | |
| 		default:
 | |
| 			return nil, errors.Errorf("invalid port format - protocol can only be specified once")
 | |
| 		}
 | |
| 
 | |
| 		remainder := splitProto[0]
 | |
| 		haveV6 := false
 | |
| 
 | |
| 		// Check for an IPv6 address in brackets
 | |
| 		splitV6 := strings.Split(remainder, "]")
 | |
| 		switch len(splitV6) {
 | |
| 		case 1:
 | |
| 			// Do nothing, proceed as before
 | |
| 		case 2:
 | |
| 			// We potentially have an IPv6 address
 | |
| 			haveV6 = true
 | |
| 			if !strings.HasPrefix(splitV6[0], "[") {
 | |
| 				return nil, errors.Errorf("invalid port format - IPv6 addresses must be enclosed by []")
 | |
| 			}
 | |
| 			if !strings.HasPrefix(splitV6[1], ":") {
 | |
| 				return nil, errors.Errorf("invalid port format - IPv6 address must be followed by a colon (':')")
 | |
| 			}
 | |
| 			ipNoPrefix := strings.TrimPrefix(splitV6[0], "[")
 | |
| 			hostIP = &ipNoPrefix
 | |
| 			remainder = strings.TrimPrefix(splitV6[1], ":")
 | |
| 		default:
 | |
| 			return nil, errors.Errorf("invalid port format - at most one IPv6 address can be specified in a --publish")
 | |
| 		}
 | |
| 
 | |
| 		splitPort := strings.Split(remainder, ":")
 | |
| 		switch len(splitPort) {
 | |
| 		case 1:
 | |
| 			if haveV6 {
 | |
| 				return nil, errors.Errorf("invalid port format - must provide host and destination port if specifying an IP")
 | |
| 			}
 | |
| 			ctrPort = splitPort[0]
 | |
| 		case 2:
 | |
| 			hostPort = &(splitPort[0])
 | |
| 			ctrPort = splitPort[1]
 | |
| 		case 3:
 | |
| 			if haveV6 {
 | |
| 				return nil, errors.Errorf("invalid port format - when v6 address specified, must be [ipv6]:hostPort:ctrPort")
 | |
| 			}
 | |
| 			hostIP = &(splitPort[0])
 | |
| 			hostPort = &(splitPort[1])
 | |
| 			ctrPort = splitPort[2]
 | |
| 		default:
 | |
| 			return nil, errors.Errorf("invalid port format - format is [[hostIP:]hostPort:]containerPort")
 | |
| 		}
 | |
| 
 | |
| 		newPort, err := parseSplitPort(hostIP, hostPort, ctrPort, proto)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		toReturn = append(toReturn, newPort)
 | |
| 	}
 | |
| 
 | |
| 	return toReturn, nil
 | |
| }
 | |
| 
 | |
| // parseSplitPort parses individual components of the --publish flag to produce
 | |
| // a single port mapping in SpecGen format.
 | |
| func parseSplitPort(hostIP, hostPort *string, ctrPort string, protocol *string) (types.PortMapping, error) {
 | |
| 	newPort := types.PortMapping{}
 | |
| 	if ctrPort == "" {
 | |
| 		return newPort, errors.Errorf("must provide a non-empty container port to publish")
 | |
| 	}
 | |
| 	ctrStart, ctrLen, err := parseAndValidateRange(ctrPort)
 | |
| 	if err != nil {
 | |
| 		return newPort, errors.Wrapf(err, "error parsing container port")
 | |
| 	}
 | |
| 	newPort.ContainerPort = ctrStart
 | |
| 	newPort.Range = ctrLen
 | |
| 
 | |
| 	if protocol != nil {
 | |
| 		if *protocol == "" {
 | |
| 			return newPort, errors.Errorf("must provide a non-empty protocol to publish")
 | |
| 		}
 | |
| 		newPort.Protocol = *protocol
 | |
| 	}
 | |
| 	if hostIP != nil {
 | |
| 		if *hostIP == "" {
 | |
| 			return newPort, errors.Errorf("must provide a non-empty container host IP to publish")
 | |
| 		} else if *hostIP != "0.0.0.0" {
 | |
| 			// If hostIP is 0.0.0.0, leave it unset - CNI treats
 | |
| 			// 0.0.0.0 and empty differently, Docker does not.
 | |
| 			testIP := net.ParseIP(*hostIP)
 | |
| 			if testIP == nil {
 | |
| 				return newPort, errors.Errorf("cannot parse %q as an IP address", *hostIP)
 | |
| 			}
 | |
| 			newPort.HostIP = testIP.String()
 | |
| 		}
 | |
| 	}
 | |
| 	if hostPort != nil {
 | |
| 		if *hostPort == "" {
 | |
| 			// Set 0 as a placeholder. The server side of Specgen
 | |
| 			// will find a random, open, unused port to use.
 | |
| 			newPort.HostPort = 0
 | |
| 		} else {
 | |
| 			hostStart, hostLen, err := parseAndValidateRange(*hostPort)
 | |
| 			if err != nil {
 | |
| 				return newPort, errors.Wrapf(err, "error parsing host port")
 | |
| 			}
 | |
| 			if hostLen != ctrLen {
 | |
| 				return newPort, errors.Errorf("host and container port ranges have different lengths: %d vs %d", hostLen, ctrLen)
 | |
| 			}
 | |
| 			newPort.HostPort = hostStart
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	hport := newPort.HostPort
 | |
| 	logrus.Debugf("Adding port mapping from %d to %d length %d protocol %q", hport, newPort.ContainerPort, newPort.Range, newPort.Protocol)
 | |
| 
 | |
| 	return newPort, nil
 | |
| }
 | |
| 
 | |
| // Parse and validate a port range.
 | |
| // Returns start port, length of range, error.
 | |
| func parseAndValidateRange(portRange string) (uint16, uint16, error) {
 | |
| 	splitRange := strings.Split(portRange, "-")
 | |
| 	if len(splitRange) > 2 {
 | |
| 		return 0, 0, errors.Errorf("invalid port format - port ranges are formatted as startPort-stopPort")
 | |
| 	}
 | |
| 
 | |
| 	if splitRange[0] == "" {
 | |
| 		return 0, 0, errors.Errorf("port numbers cannot be negative")
 | |
| 	}
 | |
| 
 | |
| 	startPort, err := parseAndValidatePort(splitRange[0])
 | |
| 	if err != nil {
 | |
| 		return 0, 0, err
 | |
| 	}
 | |
| 
 | |
| 	var rangeLen uint16 = 1
 | |
| 	if len(splitRange) == 2 {
 | |
| 		if splitRange[1] == "" {
 | |
| 			return 0, 0, errors.Errorf("must provide ending number for port range")
 | |
| 		}
 | |
| 		endPort, err := parseAndValidatePort(splitRange[1])
 | |
| 		if err != nil {
 | |
| 			return 0, 0, err
 | |
| 		}
 | |
| 		if endPort <= startPort {
 | |
| 			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)
 | |
| 		}
 | |
| 		// Our range is the total number of ports
 | |
| 		// involved, so we need to add 1 (8080:8081 is
 | |
| 		// 2 ports, for example, not 1)
 | |
| 		rangeLen = endPort - startPort + 1
 | |
| 	}
 | |
| 
 | |
| 	return startPort, rangeLen, nil
 | |
| }
 | |
| 
 | |
| // Turn a single string into a valid U16 port.
 | |
| func parseAndValidatePort(port string) (uint16, error) {
 | |
| 	num, err := strconv.Atoi(port)
 | |
| 	if err != nil {
 | |
| 		return 0, errors.Wrapf(err, "invalid port number")
 | |
| 	}
 | |
| 	if num < 1 || num > 65535 {
 | |
| 		return 0, errors.Errorf("port numbers must be between 1 and 65535 (inclusive), got %d", num)
 | |
| 	}
 | |
| 	return uint16(num), nil
 | |
| }
 | |
| 
 | |
| func CreateExitCommandArgs(storageConfig storageTypes.StoreOptions, config *config.Config, syslog, rm, exec bool) ([]string, error) {
 | |
| 	// We need a cleanup process for containers in the current model.
 | |
| 	// But we can't assume that the caller is Podman - it could be another
 | |
| 	// user of the API.
 | |
| 	// As such, provide a way to specify a path to Podman, so we can
 | |
| 	// still invoke a cleanup process.
 | |
| 
 | |
| 	podmanPath, err := os.Executable()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	command := []string{podmanPath,
 | |
| 		"--root", storageConfig.GraphRoot,
 | |
| 		"--runroot", storageConfig.RunRoot,
 | |
| 		"--log-level", logrus.GetLevel().String(),
 | |
| 		"--cgroup-manager", config.Engine.CgroupManager,
 | |
| 		"--tmpdir", config.Engine.TmpDir,
 | |
| 		"--network-config-dir", config.Network.NetworkConfigDir,
 | |
| 		"--network-backend", config.Network.NetworkBackend,
 | |
| 		"--volumepath", config.Engine.VolumePath,
 | |
| 	}
 | |
| 	if config.Engine.OCIRuntime != "" {
 | |
| 		command = append(command, []string{"--runtime", config.Engine.OCIRuntime}...)
 | |
| 	}
 | |
| 	if storageConfig.GraphDriverName != "" {
 | |
| 		command = append(command, []string{"--storage-driver", storageConfig.GraphDriverName}...)
 | |
| 	}
 | |
| 	for _, opt := range storageConfig.GraphDriverOptions {
 | |
| 		command = append(command, []string{"--storage-opt", opt}...)
 | |
| 	}
 | |
| 	if config.Engine.EventsLogger != "" {
 | |
| 		command = append(command, []string{"--events-backend", config.Engine.EventsLogger}...)
 | |
| 	}
 | |
| 
 | |
| 	if syslog {
 | |
| 		command = append(command, "--syslog")
 | |
| 	}
 | |
| 	command = append(command, []string{"container", "cleanup"}...)
 | |
| 
 | |
| 	if rm {
 | |
| 		command = append(command, "--rm")
 | |
| 	}
 | |
| 
 | |
| 	// This has to be absolutely last, to ensure that the exec session ID
 | |
| 	// will be added after it by Libpod.
 | |
| 	if exec {
 | |
| 		command = append(command, "--exec")
 | |
| 	}
 | |
| 
 | |
| 	return command, nil
 | |
| }
 |