diff options
-rw-r--r-- | cmd/podman/shared/parse/parse.go | 353 | ||||
-rw-r--r-- | cmd/podman/shared/parse/parse_test.go | 99 |
2 files changed, 99 insertions, 353 deletions
diff --git a/cmd/podman/shared/parse/parse.go b/cmd/podman/shared/parse/parse.go index a3751835b..7bc2652cb 100644 --- a/cmd/podman/shared/parse/parse.go +++ b/cmd/podman/shared/parse/parse.go @@ -5,15 +5,10 @@ package parse import ( "bufio" - "bytes" - "encoding/json" "fmt" - "io/ioutil" "net" "os" - "path" "regexp" - "strconv" "strings" "github.com/pkg/errors" @@ -72,77 +67,6 @@ func validateIPAddress(val string) (string, error) { 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) { //nolint - 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) { //nolint - if val >= 10 && val <= 1000 { - return val, nil - } - return -1, errors.Errorf("invalid blkio weight %q, should be between 10 and 1000", val) -} - -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 -} - -// 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) { //nolint - 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) @@ -154,30 +78,6 @@ func ValidateDomain(val string) (string, error) { 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) { //nolint - 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 @@ -241,259 +141,6 @@ func parseEnvFile(env map[string]string, filename string) error { return scanner.Err() } -// 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) { //nolint - 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) { //nolint - _, err := net.ParseMAC(strings.TrimSpace(val)) - if err != nil { - return "", err - } - return val, nil -} - -// parseLoggingOpts validates the logDriver and logDriverOpts -// for log-opt and log-driver flags -func parseLoggingOpts(logDriver string, logDriverOpt []string) (map[string]string, error) { //nolint - 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 -} - -// 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) ([]*PortMapping, error) { //nolint - var portMappings []*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) ([]*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<empty>", 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 := Protocol_TCP - if strings.ToLower(proto) == "udp" { - protocol = Protocol_UDP - } - - var ports []*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 := &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) { //nolint - 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 -} - -// 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 -} - -// Takes a stringslice and converts to a uint32slice -func stringSlicetoUint32Slice(inputSlice []string) ([]uint32, error) { - var outputSlice []uint32 - for _, v := range inputSlice { - u, err := strconv.ParseUint(v, 10, 32) - if err != nil { - return outputSlice, err - } - outputSlice = append(outputSlice, uint32(u)) - } - return outputSlice, nil -} - // ValidateFileName returns an error if filename contains ":" // as it is currently not supported func ValidateFileName(filename string) error { diff --git a/cmd/podman/shared/parse/parse_test.go b/cmd/podman/shared/parse/parse_test.go new file mode 100644 index 000000000..0a221c244 --- /dev/null +++ b/cmd/podman/shared/parse/parse_test.go @@ -0,0 +1,99 @@ +//nolint +// most of these validate and parse functions have been taken from projectatomic/docker +// and modified for cri-o +package parse + +import ( + "testing" +) + +func TestValidateExtraHost(t *testing.T) { + type args struct { + val string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + //2001:0db8:85a3:0000:0000:8a2e:0370:7334 + {name: "good-ipv4", args: args{val: "foobar:192.168.1.1"}, want: "foobar:192.168.1.1", wantErr: false}, + {name: "bad-ipv4", args: args{val: "foobar:999.999.999.99"}, want: "", wantErr: true}, + {name: "bad-ipv4", args: args{val: "foobar:999.999.999"}, want: "", wantErr: true}, + {name: "noname-ipv4", args: args{val: "192.168.1.1"}, want: "", wantErr: true}, + {name: "noname-ipv4", args: args{val: ":192.168.1.1"}, want: "", wantErr: true}, + {name: "noip", args: args{val: "foobar:"}, want: "", wantErr: true}, + {name: "noip", args: args{val: "foobar"}, want: "", wantErr: true}, + {name: "good-ipv6", args: args{val: "foobar:2001:0db8:85a3:0000:0000:8a2e:0370:7334"}, want: "foobar:2001:0db8:85a3:0000:0000:8a2e:0370:7334", wantErr: false}, + {name: "bad-ipv6", args: args{val: "foobar:0db8:85a3:0000:0000:8a2e:0370:7334"}, want: "", wantErr: true}, + {name: "bad-ipv6", args: args{val: "foobar:0db8:85a3:0000:0000:8a2e:0370:7334.0000.0000.000"}, want: "", wantErr: true}, + {name: "noname-ipv6", args: args{val: "2001:0db8:85a3:0000:0000:8a2e:0370:7334"}, want: "", wantErr: true}, + {name: "noname-ipv6", args: args{val: ":2001:0db8:85a3:0000:0000:8a2e:0370:7334"}, want: "", wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ValidateExtraHost(tt.args.val) + if (err != nil) != tt.wantErr { + t.Errorf("ValidateExtraHost() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ValidateExtraHost() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_validateIPAddress(t *testing.T) { + type args struct { + val string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + {name: "ipv4-good", args: args{val: "192.168.1.1"}, want: "192.168.1.1", wantErr: false}, + {name: "ipv4-bad", args: args{val: "192.168.1.1.1"}, want: "", wantErr: true}, + {name: "ipv4-bad", args: args{val: "192."}, want: "", wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := validateIPAddress(tt.args.val) + if (err != nil) != tt.wantErr { + t.Errorf("validateIPAddress() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("validateIPAddress() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestValidateFileName(t *testing.T) { + type args struct { + filename string + } + tests := []struct { + name string + args args + wantErr bool + }{ + {name: "good", args: args{filename: "/som/rand/path"}, wantErr: false}, + {name: "good", args: args{filename: "som/rand/path"}, wantErr: false}, + {name: "good", args: args{filename: "/"}, wantErr: false}, + {name: "bad", args: args{filename: "/:"}, wantErr: true}, + {name: "bad", args: args{filename: ":/"}, wantErr: true}, + {name: "bad", args: args{filename: "/some/rand:/path"}, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := ValidateFileName(tt.args.filename); (err != nil) != tt.wantErr { + t.Errorf("ValidateFileName() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} |