From 484a26d540e42baf3517fc55bed57988470f7584 Mon Sep 17 00:00:00 2001 From: baude Date: Thu, 26 Oct 2017 21:03:21 -0500 Subject: [PATCH] cmd/kpod/parse.go: Provided by Urvashi Mohnani Signed-off-by: baude --- cmd/kpod/parse.go | 872 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 872 insertions(+) create mode 100644 cmd/kpod/parse.go diff --git a/cmd/kpod/parse.go b/cmd/kpod/parse.go new file mode 100644 index 00000000..6437a74a --- /dev/null +++ b/cmd/kpod/parse.go @@ -0,0 +1,872 @@ +// most of these validate and parse functions have been taken from projectatomic/docker +// and modified for cri-o +package main + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net" + "os" + "os/user" + "path" + "regexp" + "strconv" + "strings" + + units "github.com/docker/go-units" + specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" +) + +// Note: for flags that are in the form , use the RAMInBytes function +// from the units package in docker/go-units/size.go + +var ( + whiteSpaces = " \t" + alphaRegexp = regexp.MustCompile(`[a-zA-Z]`) + domainRegexp = regexp.MustCompile(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`) +) + +// validateExtraHost validates that the specified string is a valid extrahost and returns it. +// ExtraHost is in the form of name:ip where the ip has to be a valid ip (ipv4 or ipv6). +// for add-host flag +func validateExtraHost(val string) (string, error) { + // allow for IPv6 addresses in extra hosts by only splitting on first ":" + arr := strings.SplitN(val, ":", 2) + if len(arr) != 2 || len(arr[0]) == 0 { + return "", fmt.Errorf("bad format for add-host: %q", val) + } + if _, err := validateIPAddress(arr[1]); err != nil { + return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1]) + } + return val, nil +} + +// validateIPAddress validates an Ip address. +// for dns, ip, and ip6 flags also +func validateIPAddress(val string) (string, error) { + var ip = net.ParseIP(strings.TrimSpace(val)) + if ip != nil { + return ip.String(), nil + } + return "", fmt.Errorf("%s is not an ip address", val) +} + +// validateAttach validates that the specified string is a valid attach option. +// for attach flag +func validateAttach(val string) (string, error) { + s := strings.ToLower(val) + for _, str := range []string{"stdin", "stdout", "stderr"} { + if s == str { + return s, nil + } + } + return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR") +} + +// validate the blkioWeight falls in the range of 10 to 1000 +// for blkio-weight flag +func validateBlkioWeight(val int64) (int64, error) { + if val >= 10 && val <= 1000 { + return val, nil + } + return -1, errors.Errorf("invalid blkio weight %q, should be between 10 and 1000", val) +} + +// weightDevice is a structure that holds device:weight pair +type weightDevice struct { + path string + weight uint16 +} + +func (w *weightDevice) String() string { + return fmt.Sprintf("%s:%d", w.path, w.weight) +} + +// validateweightDevice validates that the specified string has a valid device-weight format +// for blkio-weight-device flag +func validateweightDevice(val string) (*weightDevice, error) { + split := strings.SplitN(val, ":", 2) + if len(split) != 2 { + return nil, fmt.Errorf("bad format: %s", val) + } + if !strings.HasPrefix(split[0], "/dev/") { + return nil, fmt.Errorf("bad format for device path: %s", val) + } + weight, err := strconv.ParseUint(split[1], 10, 0) + if err != nil { + return nil, fmt.Errorf("invalid weight for device: %s", val) + } + if weight > 0 && (weight < 10 || weight > 1000) { + return nil, fmt.Errorf("invalid weight for device: %s", val) + } + + return &weightDevice{ + path: split[0], + weight: uint16(weight), + }, nil +} + +// parseDevice parses a device mapping string to a container.DeviceMapping struct +// for device flag +func parseDevice(device string) (*pb.Device, error) { + _, err := validateDevice(device) + if err != nil { + return nil, errors.Wrapf(err, "device string not valid %q", device) + } + + src := "" + dst := "" + permissions := "rwm" + arr := strings.Split(device, ":") + switch len(arr) { + case 3: + permissions = arr[2] + fallthrough + case 2: + if validDeviceMode(arr[1]) { + permissions = arr[1] + } else { + dst = arr[1] + } + fallthrough + case 1: + src = arr[0] + default: + return nil, fmt.Errorf("invalid device specification: %s", device) + } + + if dst == "" { + dst = src + } + + deviceMapping := &pb.Device{ + ContainerPath: dst, + HostPath: src, + Permissions: permissions, + } + return deviceMapping, nil +} + +// validDeviceMode checks if the mode for device is valid or not. +// Valid mode is a composition of r (read), w (write), and m (mknod). +func validDeviceMode(mode string) bool { + var legalDeviceMode = map[rune]bool{ + 'r': true, + 'w': true, + 'm': true, + } + if mode == "" { + return false + } + for _, c := range mode { + if !legalDeviceMode[c] { + return false + } + legalDeviceMode[c] = false + } + return true +} + +// validateDevice validates a path for devices +// It will make sure 'val' is in the form: +// [host-dir:]container-path[:mode] +// It also validates the device mode. +func validateDevice(val string) (string, error) { + return validatePath(val, validDeviceMode) +} + +func validatePath(val string, validator func(string) bool) (string, error) { + var containerPath string + var mode string + + if strings.Count(val, ":") > 2 { + return val, fmt.Errorf("bad format for path: %s", val) + } + + split := strings.SplitN(val, ":", 3) + if split[0] == "" { + return val, fmt.Errorf("bad format for path: %s", val) + } + switch len(split) { + case 1: + containerPath = split[0] + val = path.Clean(containerPath) + case 2: + if isValid := validator(split[1]); isValid { + containerPath = split[0] + mode = split[1] + val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode) + } else { + containerPath = split[1] + val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath)) + } + case 3: + containerPath = split[1] + mode = split[2] + if isValid := validator(split[2]); !isValid { + return val, fmt.Errorf("bad mode specified: %s", mode) + } + val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode) + } + + if !path.IsAbs(containerPath) { + return val, fmt.Errorf("%s is not an absolute path", containerPath) + } + return val, nil +} + +// throttleDevice is a structure that holds device:rate_per_second pair +type throttleDevice struct { + path string + rate uint64 +} + +func (t *throttleDevice) String() string { + return fmt.Sprintf("%s:%d", t.path, t.rate) +} + +// validateBpsDevice validates that the specified string has a valid device-rate format +// for device-read-bps and device-write-bps flags +func validateBpsDevice(val string) (*throttleDevice, error) { + split := strings.SplitN(val, ":", 2) + if len(split) != 2 { + return nil, fmt.Errorf("bad format: %s", val) + } + if !strings.HasPrefix(split[0], "/dev/") { + return nil, fmt.Errorf("bad format for device path: %s", val) + } + rate, err := units.RAMInBytes(split[1]) + if err != nil { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :[]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) + } + if rate < 0 { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :[]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) + } + + return &throttleDevice{ + path: split[0], + rate: uint64(rate), + }, nil +} + +// validateIOpsDevice validates that the specified string has a valid device-rate format +// for device-write-iops and device-read-iops flags +func validateIOpsDevice(val string) (*throttleDevice, error) { + split := strings.SplitN(val, ":", 2) + if len(split) != 2 { + return nil, fmt.Errorf("bad format: %s", val) + } + if !strings.HasPrefix(split[0], "/dev/") { + return nil, fmt.Errorf("bad format for device path: %s", val) + } + rate, err := strconv.ParseUint(split[1], 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :. Number must be a positive integer", val) + } + if rate < 0 { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :. Number must be a positive integer", val) + } + + return &throttleDevice{ + path: split[0], + rate: uint64(rate), + }, nil +} + +// validateDNSSearch validates domain for resolvconf search configuration. +// A zero length domain is represented by a dot (.). +// for dns-search flag +func validateDNSSearch(val string) (string, error) { + if val = strings.Trim(val, " "); val == "." { + return val, nil + } + return validateDomain(val) +} + +func validateDomain(val string) (string, error) { + if alphaRegexp.FindString(val) == "" { + return "", fmt.Errorf("%s is not a valid domain", val) + } + ns := domainRegexp.FindSubmatch([]byte(val)) + if len(ns) > 0 && len(ns[1]) < 255 { + return string(ns[1]), nil + } + return "", fmt.Errorf("%s is not a valid domain", val) +} + +// validateEnv validates an environment variable and returns it. +// If no value is specified, it returns the current value using os.Getenv. +// for env flag +func validateEnv(val string) (string, error) { + arr := strings.Split(val, "=") + if len(arr) > 1 { + return val, nil + } + if !doesEnvExist(val) { + return val, nil + } + return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil +} + +func doesEnvExist(name string) bool { + for _, entry := range os.Environ() { + parts := strings.SplitN(entry, "=", 2) + if parts[0] == name { + return true + } + } + return false +} + +// reads a file of line terminated key=value pairs, and overrides any keys +// present in the file with additional pairs specified in the override parameter +// for env-file and labels-file flags +func readKVStrings(files []string, override []string) ([]string, error) { + envVariables := []string{} + for _, ef := range files { + parsedVars, err := parseEnvFile(ef) + if err != nil { + return nil, err + } + envVariables = append(envVariables, parsedVars...) + } + // parse the '-e' and '--env' after, to allow override + envVariables = append(envVariables, override...) + + return envVariables, nil +} + +// parseEnvFile reads a file with environment variables enumerated by lines +func parseEnvFile(filename string) ([]string, error) { + fh, err := os.Open(filename) + if err != nil { + return []string{}, err + } + defer fh.Close() + + lines := []string{} + scanner := bufio.NewScanner(fh) + for scanner.Scan() { + // trim the line from all leading whitespace first + line := strings.TrimLeft(scanner.Text(), whiteSpaces) + // line is not empty, and not starting with '#' + if len(line) > 0 && !strings.HasPrefix(line, "#") { + data := strings.SplitN(line, "=", 2) + + // trim the front of a variable, but nothing else + variable := strings.TrimLeft(data[0], whiteSpaces) + if strings.ContainsAny(variable, whiteSpaces) { + return []string{}, errors.Errorf("variable %q has white spaces, poorly formatted environment", variable) + } + + if len(data) > 1 { + + // pass the value through, no trimming + lines = append(lines, fmt.Sprintf("%s=%s", variable, data[1])) + } else { + // if only a pass-through variable is given, clean it up. + lines = append(lines, fmt.Sprintf("%s=%s", strings.TrimSpace(line), os.Getenv(line))) + } + } + } + return lines, scanner.Err() +} + +// NsIpc represents the container ipc stack. +// for ipc flag +type NsIpc string + +// IsPrivate indicates whether the container uses its private ipc stack. +func (n NsIpc) IsPrivate() bool { + return !(n.IsHost() || n.IsContainer()) +} + +// IsHost indicates whether the container uses the host's ipc stack. +func (n NsIpc) IsHost() bool { + return n == "host" +} + +// IsContainer indicates whether the container uses a container's ipc stack. +func (n NsIpc) IsContainer() bool { + parts := strings.SplitN(string(n), ":", 2) + return len(parts) > 1 && parts[0] == "container" +} + +// Valid indicates whether the ipc stack is valid. +func (n NsIpc) Valid() bool { + parts := strings.Split(string(n), ":") + switch mode := parts[0]; mode { + case "", "host": + case "container": + if len(parts) != 2 || parts[1] == "" { + return false + } + default: + return false + } + return true +} + +// Container returns the name of the container ipc stack is going to be used. +func (n NsIpc) Container() string { + parts := strings.SplitN(string(n), ":", 2) + if len(parts) > 1 { + return parts[1] + } + return "" +} + +// validateLabel validates that the specified string is a valid label, and returns it. +// Labels are in the form on key=value. +// for label flag +func validateLabel(val string) (string, error) { + if strings.Count(val, "=") < 1 { + return "", fmt.Errorf("bad attribute format: %s", val) + } + return val, nil +} + +// validateMACAddress validates a MAC address. +// for mac-address flag +func validateMACAddress(val string) (string, error) { + _, err := net.ParseMAC(strings.TrimSpace(val)) + if err != nil { + return "", err + } + return val, nil +} + +// validateLink validates that the specified string has a valid link format (containerName:alias). +func validateLink(val string) (string, error) { + if _, _, err := parseLink(val); err != nil { + return val, err + } + return val, nil +} + +// parseLink parses and validates the specified string as a link format (name:alias) +func parseLink(val string) (string, string, error) { + if val == "" { + return "", "", fmt.Errorf("empty string specified for links") + } + arr := strings.Split(val, ":") + if len(arr) > 2 { + return "", "", fmt.Errorf("bad format for links: %s", val) + } + if len(arr) == 1 { + return val, val, nil + } + // This is kept because we can actually get a HostConfig with links + // from an already created container and the format is not `foo:bar` + // but `/foo:/c1/bar` + if strings.HasPrefix(arr[0], "/") { + _, alias := path.Split(arr[1]) + return arr[0][1:], alias, nil + } + return arr[0], arr[1], nil +} + +// parseLoggingOpts validates the logDriver and logDriverOpts +// for log-opt and log-driver flags +func parseLoggingOpts(logDriver string, logDriverOpt []string) (map[string]string, error) { + logOptsMap := convertKVStringsToMap(logDriverOpt) + if logDriver == "none" && len(logDriverOpt) > 0 { + return map[string]string{}, errors.Errorf("invalid logging opts for driver %s", logDriver) + } + return logOptsMap, nil +} + +// NsPid represents the pid namespace of the container. +//for pid flag +type NsPid string + +// IsPrivate indicates whether the container uses its own new pid namespace. +func (n NsPid) IsPrivate() bool { + return !(n.IsHost() || n.IsContainer()) +} + +// IsHost indicates whether the container uses the host's pid namespace. +func (n NsPid) IsHost() bool { + return n == "host" +} + +// IsContainer indicates whether the container uses a container's pid namespace. +func (n NsPid) IsContainer() bool { + parts := strings.SplitN(string(n), ":", 2) + return len(parts) > 1 && parts[0] == "container" +} + +// Valid indicates whether the pid namespace is valid. +func (n NsPid) Valid() bool { + parts := strings.Split(string(n), ":") + switch mode := parts[0]; mode { + case "", "host": + case "container": + if len(parts) != 2 || parts[1] == "" { + return false + } + default: + return false + } + return true +} + +// Container returns the name of the container whose pid namespace is going to be used. +func (n NsPid) Container() string { + parts := strings.SplitN(string(n), ":", 2) + if len(parts) > 1 { + return parts[1] + } + return "" +} + +// parsePortSpecs receives port specs in the format of ip:public:private/proto and parses +// these in to the internal types +// for publish, publish-all, and expose flags +func parsePortSpecs(ports []string) ([]*pb.PortMapping, error) { + var portMappings []*pb.PortMapping + for _, rawPort := range ports { + portMapping, err := parsePortSpec(rawPort) + if err != nil { + return nil, err + } + + portMappings = append(portMappings, portMapping...) + } + return portMappings, nil +} + +func validateProto(proto string) bool { + for _, availableProto := range []string{"tcp", "udp"} { + if availableProto == proto { + return true + } + } + return false +} + +// parsePortSpec parses a port specification string into a slice of PortMappings +func parsePortSpec(rawPort string) ([]*pb.PortMapping, error) { + var proto string + rawIP, hostPort, containerPort := splitParts(rawPort) + proto, containerPort = splitProtoPort(containerPort) + + // Strip [] from IPV6 addresses + ip, _, err := net.SplitHostPort(rawIP + ":") + if err != nil { + return nil, fmt.Errorf("Invalid ip address %v: %s", rawIP, err) + } + if ip != "" && net.ParseIP(ip) == nil { + return nil, fmt.Errorf("Invalid ip address: %s", ip) + } + if containerPort == "" { + return nil, fmt.Errorf("No port specified: %s", rawPort) + } + + startPort, endPort, err := parsePortRange(containerPort) + if err != nil { + return nil, fmt.Errorf("Invalid containerPort: %s", containerPort) + } + + var startHostPort, endHostPort uint64 = 0, 0 + if len(hostPort) > 0 { + startHostPort, endHostPort, err = parsePortRange(hostPort) + if err != nil { + return nil, fmt.Errorf("Invalid hostPort: %s", hostPort) + } + } + + if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) { + // Allow host port range iff containerPort is not a range. + // In this case, use the host port range as the dynamic + // host port range to allocate into. + if endPort != startPort { + return nil, fmt.Errorf("Invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort) + } + } + + if !validateProto(strings.ToLower(proto)) { + return nil, fmt.Errorf("invalid proto: %s", proto) + } + + protocol := pb.Protocol_TCP + if strings.ToLower(proto) == "udp" { + protocol = pb.Protocol_UDP + } + + var ports []*pb.PortMapping + for i := uint64(0); i <= (endPort - startPort); i++ { + containerPort = strconv.FormatUint(startPort+i, 10) + if len(hostPort) > 0 { + hostPort = strconv.FormatUint(startHostPort+i, 10) + } + // Set hostPort to a range only if there is a single container port + // and a dynamic host port. + if startPort == endPort && startHostPort != endHostPort { + hostPort = fmt.Sprintf("%s-%s", hostPort, strconv.FormatUint(endHostPort, 10)) + } + + ctrPort, err := strconv.ParseInt(containerPort, 10, 32) + if err != nil { + return nil, err + } + hPort, err := strconv.ParseInt(hostPort, 10, 32) + if err != nil { + return nil, err + } + + port := &pb.PortMapping{ + Protocol: protocol, + ContainerPort: int32(ctrPort), + HostPort: int32(hPort), + HostIp: ip, + } + + ports = append(ports, port) + } + return ports, nil +} + +// parsePortRange parses and validates the specified string as a port-range (8000-9000) +func parsePortRange(ports string) (uint64, uint64, error) { + if ports == "" { + return 0, 0, fmt.Errorf("empty string specified for ports") + } + if !strings.Contains(ports, "-") { + start, err := strconv.ParseUint(ports, 10, 16) + end := start + return start, end, err + } + + parts := strings.Split(ports, "-") + start, err := strconv.ParseUint(parts[0], 10, 16) + if err != nil { + return 0, 0, err + } + end, err := strconv.ParseUint(parts[1], 10, 16) + if err != nil { + return 0, 0, err + } + if end < start { + return 0, 0, fmt.Errorf("Invalid range specified for the Port: %s", ports) + } + return start, end, nil +} + +// splitParts separates the different parts of rawPort +func splitParts(rawport string) (string, string, string) { + parts := strings.Split(rawport, ":") + n := len(parts) + containerport := parts[n-1] + + switch n { + case 1: + return "", "", containerport + case 2: + return "", parts[0], containerport + case 3: + return parts[0], parts[1], containerport + default: + return strings.Join(parts[:n-2], ":"), parts[n-2], containerport + } +} + +// splitProtoPort splits a port in the format of port/proto +func splitProtoPort(rawPort string) (string, string) { + parts := strings.Split(rawPort, "/") + l := len(parts) + if len(rawPort) == 0 || l == 0 || len(parts[0]) == 0 { + return "", "" + } + if l == 1 { + return "tcp", rawPort + } + if len(parts[1]) == 0 { + return "tcp", parts[0] + } + return parts[1], parts[0] +} + +// takes a local seccomp file and reads its file contents +// for security-opt flag +func parseSecurityOpts(securityOpts []string) ([]string, error) { + for key, opt := range securityOpts { + con := strings.SplitN(opt, "=", 2) + if len(con) == 1 && con[0] != "no-new-privileges" { + if strings.Index(opt, ":") != -1 { + con = strings.SplitN(opt, ":", 2) + } else { + return securityOpts, fmt.Errorf("Invalid --security-opt: %q", opt) + } + } + if con[0] == "seccomp" && con[1] != "unconfined" { + f, err := ioutil.ReadFile(con[1]) + if err != nil { + return securityOpts, fmt.Errorf("opening seccomp profile (%s) failed: %v", con[1], err) + } + b := bytes.NewBuffer(nil) + if err := json.Compact(b, f); err != nil { + return securityOpts, fmt.Errorf("compacting json for seccomp profile (%s) failed: %v", con[1], err) + } + securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes()) + } + } + + return securityOpts, nil +} + +// parses storage options per container into a map +// for storage-opt flag +func parseStorageOpts(storageOpts []string) (map[string]string, error) { + m := make(map[string]string) + for _, option := range storageOpts { + if strings.Contains(option, "=") { + opt := strings.SplitN(option, "=", 2) + m[opt[0]] = opt[1] + } else { + return nil, errors.Errorf("invalid storage option %q", option) + } + } + return m, nil +} + +// parseUser parses the the uid and gid in the format [:] +// for user flag +// FIXME: Issue from https://github.com/projectatomic/buildah/issues/66 +func parseUser(rootdir, userspec string) (specs.User, error) { + var gid64 uint64 + var gerr error = user.UnknownGroupError("error looking up group") + + spec := strings.SplitN(userspec, ":", 2) + userspec = spec[0] + groupspec := "" + if userspec == "" { + return specs.User{}, nil + } + if len(spec) > 1 { + groupspec = spec[1] + } + + uid64, uerr := strconv.ParseUint(userspec, 10, 32) + if uerr == nil && groupspec == "" { + // We parsed the user name as a number, and there's no group + // component, so we need to look up the user's primary GID. + var name string + name, gid64, gerr = lookupGroupForUIDInContainer(rootdir, uid64) + if gerr == nil { + userspec = name + } else { + if userrec, err := user.LookupId(userspec); err == nil { + gid64, gerr = strconv.ParseUint(userrec.Gid, 10, 32) + userspec = userrec.Name + } + } + } + if uerr != nil { + uid64, gid64, uerr = lookupUserInContainer(rootdir, userspec) + gerr = uerr + } + if uerr != nil { + if userrec, err := user.Lookup(userspec); err == nil { + uid64, uerr = strconv.ParseUint(userrec.Uid, 10, 32) + gid64, gerr = strconv.ParseUint(userrec.Gid, 10, 32) + } + } + + if groupspec != "" { + gid64, gerr = strconv.ParseUint(groupspec, 10, 32) + if gerr != nil { + gid64, gerr = lookupGroupInContainer(rootdir, groupspec) + } + if gerr != nil { + if group, err := user.LookupGroup(groupspec); err == nil { + gid64, gerr = strconv.ParseUint(group.Gid, 10, 32) + } + } + } + + if uerr == nil && gerr == nil { + u := specs.User{ + UID: uint32(uid64), + GID: uint32(gid64), + Username: userspec, + } + return u, nil + } + + err := errors.Wrapf(uerr, "error determining run uid") + if uerr == nil { + err = errors.Wrapf(gerr, "error determining run gid") + } + return specs.User{}, err +} + +// convertKVStringsToMap converts ["key=value"] to {"key":"value"} +func convertKVStringsToMap(values []string) map[string]string { + result := make(map[string]string, len(values)) + for _, value := range values { + kv := strings.SplitN(value, "=", 2) + if len(kv) == 1 { + result[kv[0]] = "" + } else { + result[kv[0]] = kv[1] + } + } + + return result +} + +// NsUser represents userns mode in the container. +// for userns flag +type NsUser string + +// IsHost indicates whether the container uses the host's userns. +func (n NsUser) IsHost() bool { + return n == "host" +} + +// IsPrivate indicates whether the container uses the a private userns. +func (n NsUser) IsPrivate() bool { + return !(n.IsHost()) +} + +// Valid indicates whether the userns is valid. +func (n NsUser) Valid() bool { + parts := strings.Split(string(n), ":") + switch mode := parts[0]; mode { + case "", "host": + default: + return false + } + return true +} + +// NsUts represents the UTS namespace of the container. +// for uts flag +type NsUts string + +// IsPrivate indicates whether the container uses its private UTS namespace. +func (n NsUts) IsPrivate() bool { + return !(n.IsHost()) +} + +// IsHost indicates whether the container uses the host's UTS namespace. +func (n NsUts) IsHost() bool { + return n == "host" +} + +// Valid indicates whether the UTS namespace is valid. +func (n NsUts) Valid() bool { + parts := strings.Split(string(n), ":") + switch mode := parts[0]; mode { + case "", "host": + default: + return false + } + return true +}