diff --git a/cmd/podman/common/build.go b/cmd/podman/common/build.go index 5e60d7f0f3..95bad7320d 100644 --- a/cmd/podman/common/build.go +++ b/cmd/podman/common/build.go @@ -333,15 +333,15 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *Buil } if c.Flag("build-arg").Changed { for _, arg := range flags.BuildArg { - av := strings.SplitN(arg, "=", 2) - if len(av) > 1 { - args[av[0]] = av[1] + key, val, hasVal := strings.Cut(arg, "=") + if hasVal { + args[key] = val } else { // check if the env is set in the local environment and use that value if it is - if val, present := os.LookupEnv(av[0]); present { - args[av[0]] = val + if val, present := os.LookupEnv(key); present { + args[key] = val } else { - delete(args, av[0]) + delete(args, key) } } } @@ -450,15 +450,15 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *Buil additionalBuildContext := make(map[string]*buildahDefine.AdditionalBuildContext) if c.Flag("build-context").Changed { for _, contextString := range flags.BuildContext { - av := strings.SplitN(contextString, "=", 2) - if len(av) > 1 { - parseAdditionalBuildContext, err := parse.GetAdditionalBuildContext(av[1]) + key, val, hasVal := strings.Cut(contextString, "=") + if hasVal { + parseAdditionalBuildContext, err := parse.GetAdditionalBuildContext(val) if err != nil { return nil, fmt.Errorf("while parsing additional build context: %w", err) } - additionalBuildContext[av[0]] = &parseAdditionalBuildContext + additionalBuildContext[key] = &parseAdditionalBuildContext } else { - return nil, fmt.Errorf("while parsing additional build context: %q, accepts value in the form of key=value", av) + return nil, fmt.Errorf("while parsing additional build context: %s, accepts value in the form of key=value", contextString) } } } diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index 75825f1ea1..40804f5a9f 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -423,9 +423,9 @@ func prefixSlice(pre string, slice []string) []string { func suffixCompSlice(suf string, slice []string) []string { for i := range slice { - split := strings.SplitN(slice[i], "\t", 2) - if len(split) > 1 { - slice[i] = split[0] + suf + "\t" + split[1] + key, val, hasVal := strings.Cut(slice[i], "\t") + if hasVal { + slice[i] = key + suf + "\t" + val } else { slice[i] += suf } @@ -878,10 +878,10 @@ func AutocompleteScp(cmd *cobra.Command, args []string, toComplete string) ([]st } switch len(args) { case 0: - split := strings.SplitN(toComplete, "::", 2) - if len(split) > 1 { - imageSuggestions, _ := getImages(cmd, split[1]) - return prefixSlice(split[0]+"::", imageSuggestions), cobra.ShellCompDirectiveNoFileComp + prefix, imagesToComplete, isImages := strings.Cut(toComplete, "::") + if isImages { + imageSuggestions, _ := getImages(cmd, imagesToComplete) + return prefixSlice(prefix+"::", imageSuggestions), cobra.ShellCompDirectiveNoFileComp } connectionSuggestions, _ := AutocompleteSystemConnections(cmd, args, toComplete) imageSuggestions, _ := getImages(cmd, toComplete) @@ -893,9 +893,9 @@ func AutocompleteScp(cmd *cobra.Command, args []string, toComplete string) ([]st } return totalSuggestions, directive case 1: - split := strings.SplitN(args[0], "::", 2) - if len(split) > 1 { - if len(split[1]) > 0 { + _, imagesToComplete, isImages := strings.Cut(args[0], "::") + if isImages { + if len(imagesToComplete) > 0 { return nil, cobra.ShellCompDirectiveNoFileComp } imageSuggestions, _ := getImages(cmd, toComplete) @@ -1076,7 +1076,7 @@ func AutocompleteUserFlag(cmd *cobra.Command, args []string, toComplete string) var groups []string scanner := bufio.NewScanner(file) - user := strings.SplitN(toComplete, ":", 2)[0] + user, _, _ := strings.Cut(toComplete, ":") for scanner.Scan() { entries := strings.SplitN(scanner.Text(), ":", 4) groups = append(groups, user+":"+entries[0]) diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index b48b096878..8fe845d449 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -342,10 +342,10 @@ func PullImage(imageName string, cliVals *entities.ContainerCreateOptions) (stri if cliVals.Arch != "" || cliVals.OS != "" { return "", errors.New("--platform option can not be specified with --arch or --os") } - split := strings.SplitN(cliVals.Platform, "/", 2) - cliVals.OS = split[0] - if len(split) > 1 { - cliVals.Arch = split[1] + OS, Arch, hasArch := strings.Cut(cliVals.Platform, "/") + cliVals.OS = OS + if hasArch { + cliVals.Arch = Arch } } } diff --git a/cmd/podman/containers/pause.go b/cmd/podman/containers/pause.go index 4b0ae5fe36..402faaa26c 100644 --- a/cmd/podman/containers/pause.go +++ b/cmd/podman/containers/pause.go @@ -102,11 +102,11 @@ func pause(cmd *cobra.Command, args []string) error { } for _, f := range filters { - split := strings.SplitN(f, "=", 2) - if len(split) < 2 { + fname, filter, hasFilter := strings.Cut(f, "=") + if !hasFilter { return fmt.Errorf("invalid filter %q", f) } - pauseOpts.Filters[split[0]] = append(pauseOpts.Filters[split[0]], split[1]) + pauseOpts.Filters[fname] = append(pauseOpts.Filters[fname], filter) } responses, err := registry.ContainerEngine().ContainerPause(context.Background(), args, pauseOpts) diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go index 0c21a30ae8..4848cd6955 100644 --- a/cmd/podman/containers/ps.go +++ b/cmd/podman/containers/ps.go @@ -195,11 +195,11 @@ func ps(cmd *cobra.Command, _ []string) error { } for _, f := range filters { - split := strings.SplitN(f, "=", 2) - if len(split) == 1 { + fname, filter, hasFilter := strings.Cut(f, "=") + if !hasFilter { return fmt.Errorf("invalid filter %q", f) } - listOpts.Filters[split[0]] = append(listOpts.Filters[split[0]], split[1]) + listOpts.Filters[fname] = append(listOpts.Filters[fname], filter) } listContainers, err := getResponses() if err != nil { diff --git a/cmd/podman/containers/restart.go b/cmd/podman/containers/restart.go index e5e7b0add7..06da8a0a2a 100644 --- a/cmd/podman/containers/restart.go +++ b/cmd/podman/containers/restart.go @@ -114,11 +114,11 @@ func restart(cmd *cobra.Command, args []string) error { } for _, f := range filters { - split := strings.SplitN(f, "=", 2) - if len(split) < 2 { + fname, filter, hasFilter := strings.Cut(f, "=") + if !hasFilter { return fmt.Errorf("invalid filter %q", f) } - restartOpts.Filters[split[0]] = append(restartOpts.Filters[split[0]], split[1]) + restartOpts.Filters[fname] = append(restartOpts.Filters[fname], filter) } responses, err := registry.ContainerEngine().ContainerRestart(context.Background(), args, restartOpts) diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go index a29233cf3c..74d43da2ec 100644 --- a/cmd/podman/containers/rm.go +++ b/cmd/podman/containers/rm.go @@ -116,16 +116,16 @@ func rm(cmd *cobra.Command, args []string) error { } return fmt.Errorf("reading CIDFile: %w", err) } - id := strings.Split(string(content), "\n")[0] + id, _, _ := strings.Cut(string(content), "\n") args = append(args, id) } for _, f := range filters { - split := strings.SplitN(f, "=", 2) - if len(split) < 2 { + fname, filter, hasFilter := strings.Cut(f, "=") + if !hasFilter { return fmt.Errorf("invalid filter %q", f) } - rmOptions.Filters[split[0]] = append(rmOptions.Filters[split[0]], split[1]) + rmOptions.Filters[fname] = append(rmOptions.Filters[fname], filter) } if rmOptions.All { diff --git a/cmd/podman/containers/start.go b/cmd/podman/containers/start.go index eba46d707a..709e9d57b6 100644 --- a/cmd/podman/containers/start.go +++ b/cmd/podman/containers/start.go @@ -124,11 +124,11 @@ func start(cmd *cobra.Command, args []string) error { containers := utils.RemoveSlash(args) for _, f := range filters { - split := strings.SplitN(f, "=", 2) - if len(split) < 2 { + fname, filter, hasFilter := strings.Cut(f, "=") + if !hasFilter { return fmt.Errorf("invalid filter %q", f) } - startOptions.Filters[split[0]] = append(startOptions.Filters[split[0]], split[1]) + startOptions.Filters[fname] = append(startOptions.Filters[fname], filter) } responses, err := registry.ContainerEngine().ContainerStart(registry.GetContext(), containers, startOptions) diff --git a/cmd/podman/containers/stop.go b/cmd/podman/containers/stop.go index e62ec7b675..84822cf53e 100644 --- a/cmd/podman/containers/stop.go +++ b/cmd/podman/containers/stop.go @@ -119,11 +119,11 @@ func stop(cmd *cobra.Command, args []string) error { } for _, f := range filters { - split := strings.SplitN(f, "=", 2) - if len(split) < 2 { + fname, filter, hasFilter := strings.Cut(f, "=") + if !hasFilter { return fmt.Errorf("invalid filter %q", f) } - stopOptions.Filters[split[0]] = append(stopOptions.Filters[split[0]], split[1]) + stopOptions.Filters[fname] = append(stopOptions.Filters[fname], filter) } responses, err := registry.ContainerEngine().ContainerStop(context.Background(), args, stopOptions) diff --git a/cmd/podman/containers/unpause.go b/cmd/podman/containers/unpause.go index 68ef8e1f54..7d04daa578 100644 --- a/cmd/podman/containers/unpause.go +++ b/cmd/podman/containers/unpause.go @@ -110,11 +110,11 @@ func unpause(cmd *cobra.Command, args []string) error { } for _, f := range filters { - split := strings.SplitN(f, "=", 2) - if len(split) < 2 { + fname, filter, hasFilter := strings.Cut(f, "=") + if !hasFilter { return fmt.Errorf("invalid filter %q", f) } - unpauseOpts.Filters[split[0]] = append(unpauseOpts.Filters[split[0]], split[1]) + unpauseOpts.Filters[fname] = append(unpauseOpts.Filters[fname], filter) } responses, err := registry.ContainerEngine().ContainerUnpause(context.Background(), args, unpauseOpts) diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go index 2bcfd2055c..8399f6c9c2 100644 --- a/cmd/podman/images/pull.go +++ b/cmd/podman/images/pull.go @@ -149,10 +149,14 @@ func imagePull(cmd *cobra.Command, args []string) error { if pullOptions.Arch != "" || pullOptions.OS != "" { return errors.New("--platform option can not be specified with --arch or --os") } - split := strings.SplitN(platform, "/", 2) - pullOptions.OS = split[0] - if len(split) > 1 { - pullOptions.Arch = split[1] + + specs := strings.Split(platform, "/") + pullOptions.OS = specs[0] // may be empty + if len(specs) > 1 { + pullOptions.Arch = specs[1] + if len(specs) > 2 { + pullOptions.Variant = specs[2] + } } } diff --git a/cmd/podman/kube/play.go b/cmd/podman/kube/play.go index 8c6aa6a788..21bb45260b 100644 --- a/cmd/podman/kube/play.go +++ b/cmd/podman/kube/play.go @@ -246,18 +246,17 @@ func play(cmd *cobra.Command, args []string) error { } for _, annotation := range playOptions.annotations { - splitN := strings.SplitN(annotation, "=", 2) - if len(splitN) != 2 { + key, val, hasVal := strings.Cut(annotation, "=") + if !hasVal { return fmt.Errorf("annotation %q must include an '=' sign", annotation) } if playOptions.Annotations == nil { playOptions.Annotations = make(map[string]string) } - annotation := splitN[1] - if len(annotation) > define.MaxKubeAnnotation && !playOptions.UseLongAnnotations { - return fmt.Errorf("annotation exceeds maximum size, %d, of kubernetes annotation: %s", define.MaxKubeAnnotation, annotation) + if len(val) > define.MaxKubeAnnotation && !playOptions.UseLongAnnotations { + return fmt.Errorf("annotation exceeds maximum size, %d, of kubernetes annotation: %s", define.MaxKubeAnnotation, val) } - playOptions.Annotations[splitN[0]] = annotation + playOptions.Annotations[key] = val } for _, mac := range playOptions.macs { diff --git a/cmd/podman/networks/create.go b/cmd/podman/networks/create.go index 4998462602..2a8c641c7f 100644 --- a/cmd/podman/networks/create.go +++ b/cmd/podman/networks/create.go @@ -245,16 +245,16 @@ func parseRoute(routeStr string) (*types.Route, error) { } func parseRange(iprange string) (*types.LeaseRange, error) { - split := strings.SplitN(iprange, "-", 2) - if len(split) > 1 { + startIPString, endIPString, hasDash := strings.Cut(iprange, "-") + if hasDash { // range contains dash so assume form is start-end - start := net.ParseIP(split[0]) + start := net.ParseIP(startIPString) if start == nil { - return nil, fmt.Errorf("range start ip %q is not a ip address", split[0]) + return nil, fmt.Errorf("range start ip %q is not a ip address", startIPString) } - end := net.ParseIP(split[1]) + end := net.ParseIP(endIPString) if end == nil { - return nil, fmt.Errorf("range end ip %q is not a ip address", split[1]) + return nil, fmt.Errorf("range end ip %q is not a ip address", endIPString) } return &types.LeaseRange{ StartIP: start, diff --git a/cmd/podman/parse/filters.go b/cmd/podman/parse/filters.go index e4ab942afa..af9157cde7 100644 --- a/cmd/podman/parse/filters.go +++ b/cmd/podman/parse/filters.go @@ -9,11 +9,11 @@ import ( func FilterArgumentsIntoFilters(filters []string) (url.Values, error) { parsedFilters := make(url.Values) for _, f := range filters { - t := strings.SplitN(f, "=", 2) - if len(t) < 2 { + fname, filter, hasFilter := strings.Cut(f, "=") + if !hasFilter { return parsedFilters, fmt.Errorf("filter input must be in the form of filter=value: %s is invalid", f) } - parsedFilters.Add(t[0], t[1]) + parsedFilters.Add(fname, filter) } return parsedFilters, nil } diff --git a/cmd/podman/parse/net.go b/cmd/podman/parse/net.go index b1331464a7..9d6ae1947c 100644 --- a/cmd/podman/parse/net.go +++ b/cmd/podman/parse/net.go @@ -33,15 +33,15 @@ var ( // 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 { + name, ip, hasIP := strings.Cut(val, ":") + if !hasIP || len(name) == 0 { return "", fmt.Errorf("bad format for add-host: %q", val) } - if arr[1] == etchosts.HostGateway { + if ip == etchosts.HostGateway { return val, nil } - if _, err := validateIPAddress(arr[1]); err != nil { - return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1]) + if _, err := validateIPAddress(ip); err != nil { + return "", fmt.Errorf("invalid IP address in add-host: %q", ip) } return val, nil } @@ -82,45 +82,37 @@ func GetAllLabels(labelFile, inputLabels []string) (map[string]string, error) { } } for _, label := range inputLabels { - split := strings.SplitN(label, "=", 2) - if split[0] == "" { + key, value, _ := strings.Cut(label, "=") + if key == "" { return nil, fmt.Errorf("invalid label format: %q", label) } - value := "" - if len(split) > 1 { - value = split[1] - } - labels[split[0]] = value + labels[key] = value } return labels, nil } func parseEnvOrLabel(env map[string]string, line, configType string) error { - data := strings.SplitN(line, "=", 2) + key, val, hasVal := strings.Cut(line, "=") // catch invalid variables such as "=" or "=A" - if data[0] == "" { + if key == "" { return fmt.Errorf("invalid environment variable: %q", line) } // trim the front of a variable, but nothing else - name := strings.TrimLeft(data[0], whiteSpaces) + name := strings.TrimLeft(key, whiteSpaces) if strings.ContainsAny(name, whiteSpaces) { return fmt.Errorf("name %q has white spaces, poorly formatted name", name) } - if len(data) > 1 { - env[name] = data[1] + if hasVal { + env[name] = val } else { - if strings.HasSuffix(name, "*") { - name = strings.TrimSuffix(name, "*") + if name, hasStar := strings.CutSuffix(name, "*"); hasStar { for _, e := range os.Environ() { - part := strings.SplitN(e, "=", 2) - if len(part) < 2 { - continue - } - if strings.HasPrefix(part[0], name) { - env[part[0]] = part[1] + envKey, envVal, hasEq := strings.Cut(e, "=") + if hasEq && strings.HasPrefix(envKey, name) { + env[envKey] = envVal } } } else if configType == ENVType { diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go index 45fbd86165..4efc7e9f57 100644 --- a/cmd/podman/pods/ps.go +++ b/cmd/podman/pods/ps.go @@ -81,11 +81,11 @@ func pods(cmd *cobra.Command, _ []string) error { if cmd.Flag("filter").Changed { psInput.Filters = make(map[string][]string) for _, f := range inputFilters { - split := strings.SplitN(f, "=", 2) - if len(split) < 2 { + fname, filter, hasFilter := strings.Cut(f, "=") + if !hasFilter { return fmt.Errorf("filter input must be in the form of filter=value: %s is invalid", f) } - psInput.Filters[split[0]] = append(psInput.Filters[split[0]], split[1]) + psInput.Filters[fname] = append(psInput.Filters[fname], filter) } } responses, err := registry.ContainerEngine().PodPs(context.Background(), psInput) diff --git a/cmd/podman/system/connection/add.go b/cmd/podman/system/connection/add.go index 4c31a6d1b5..786bc0b955 100644 --- a/cmd/podman/system/connection/add.go +++ b/cmd/podman/system/connection/add.go @@ -261,15 +261,15 @@ func translateDest(path string) (string, error) { if path == "" { return "", nil } - split := strings.SplitN(path, "=", 2) - if len(split) == 1 { - return split[0], nil + key, val, hasVal := strings.Cut(path, "=") + if !hasVal { + return key, nil } - if split[0] != "host" { - return "", fmt.Errorf("\"host\" is requited for --docker option") + if key != "host" { + return "", fmt.Errorf("\"host\" is required for --docker option") } // "host=tcp://myserver:2376,ca=~/ca-file,cert=~/cert-file,key=~/key-file" - vals := strings.Split(split[1], ",") + vals := strings.Split(val, ",") if len(vals) > 1 { return "", fmt.Errorf("--docker additional options %q not supported", strings.Join(vals[1:], ",")) } diff --git a/libpod/container_commit.go b/libpod/container_commit.go index 9ef7970613..8dca951b9f 100644 --- a/libpod/container_commit.go +++ b/libpod/container_commit.go @@ -97,8 +97,8 @@ func (c *Container) Commit(ctx context.Context, destImage string, options Contai // Should we store the ENV we actually want in the spec separately? if c.config.Spec.Process != nil { for _, e := range c.config.Spec.Process.Env { - splitEnv := strings.SplitN(e, "=", 2) - importBuilder.SetEnv(splitEnv[0], splitEnv[1]) + key, val, _ := strings.Cut(e, "=") + importBuilder.SetEnv(key, val) } } // Expose ports diff --git a/libpod/container_internal_freebsd.go b/libpod/container_internal_freebsd.go index b594089200..042e165525 100644 --- a/libpod/container_internal_freebsd.go +++ b/libpod/container_internal_freebsd.go @@ -254,7 +254,7 @@ func (c *Container) addSharedNamespaces(g *generate.Generator) error { } needEnv := true for _, checkEnv := range g.Config.Process.Env { - if strings.SplitN(checkEnv, "=", 2)[0] == "HOSTNAME" { + if strings.HasPrefix(checkEnv, "HOSTNAME=") { needEnv = false break } diff --git a/libpod/kube.go b/libpod/kube.go index 12895e4c30..72514d03e1 100644 --- a/libpod/kube.go +++ b/libpod/kube.go @@ -73,13 +73,13 @@ func (p *Pod) GenerateForKube(ctx context.Context, getService, useLongAnnotation return nil, servicePorts, err } for _, host := range infraContainer.config.ContainerNetworkConfig.HostAdd { - hostSli := strings.SplitN(host, ":", 2) - if len(hostSli) != 2 { + hostname, ip, hasIP := strings.Cut(host, ":") + if !hasIP { return nil, servicePorts, errors.New("invalid hostAdd") } extraHost = append(extraHost, v1.HostAlias{ - IP: hostSli[1], - Hostnames: []string{hostSli[0]}, + IP: ip, + Hostnames: []string{hostname}, }) } ports, err = portMappingToContainerPort(infraContainer.config.PortMappings, getService) @@ -1001,10 +1001,10 @@ func containerToV1Container(ctx context.Context, c *Container, getService bool) dnsOptions := make([]v1.PodDNSConfigOption, 0) for _, option := range options { // the option can be "k:v" or just "k", no delimiter is required - opts := strings.SplitN(option, ":", 2) + name, value, _ := strings.Cut(option, ":") dnsOpt := v1.PodDNSConfigOption{ - Name: opts[0], - Value: &opts[1], + Name: name, + Value: &value, } dnsOptions = append(dnsOptions, dnsOpt) } @@ -1055,23 +1055,23 @@ func libpodEnvVarsToKubeEnvVars(envs []string, imageEnvs []string) ([]v1.EnvVar, envVars := make([]v1.EnvVar, 0, len(envs)) imageMap := make(map[string]string, len(imageEnvs)) for _, ie := range imageEnvs { - split := strings.SplitN(ie, "=", 2) - imageMap[split[0]] = split[1] + key, val, _ := strings.Cut(ie, "=") + imageMap[key] = val } for _, e := range envs { - split := strings.SplitN(e, "=", 2) - if len(split) != 2 { + envName, envValue, hasValue := strings.Cut(e, "=") + if !hasValue { return envVars, fmt.Errorf("environment variable %s is malformed; should be key=value", e) } - if defaultEnv[split[0]] == split[1] { + if defaultEnv[envName] == envValue { continue } - if imageMap[split[0]] == split[1] { + if imageMap[envName] == envValue { continue } ev := v1.EnvVar{ - Name: split[0], - Value: split[1], + Name: envName, + Value: envValue, } envVars = append(envVars, ev) } @@ -1286,25 +1286,22 @@ func generateKubeSecurityContext(c *Container) (*v1.SecurityContext, bool, error var selinuxOpts v1.SELinuxOptions selinuxHasData := false for _, label := range strings.Split(c.config.Spec.Annotations[define.InspectAnnotationLabel], ",label=") { - opts := strings.SplitN(label, ":", 2) - switch len(opts) { - case 2: - switch opts[0] { + opt, val, hasVal := strings.Cut(label, ":") + if hasVal { + switch opt { case "filetype": - selinuxOpts.FileType = opts[1] + selinuxOpts.FileType = val selinuxHasData = true case "type": - selinuxOpts.Type = opts[1] + selinuxOpts.Type = val selinuxHasData = true case "level": - selinuxOpts.Level = opts[1] - selinuxHasData = true - } - case 1: - if opts[0] == "disable" { - selinuxOpts.Type = "spc_t" + selinuxOpts.Level = val selinuxHasData = true } + } else if opt == "disable" { + selinuxOpts.Type = "spc_t" + selinuxHasData = true } } if selinuxHasData { diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index 32a8024ed2..a3b5846123 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -524,11 +524,11 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON, exposedPorts := make(nat.PortSet) for ep := range inspect.NetworkSettings.Ports { - splitp := strings.SplitN(ep, "/", 2) - if len(splitp) != 2 { + port, proto, ok := strings.Cut(ep, "/") + if !ok { return nil, fmt.Errorf("PORT/PROTOCOL Format required for %q", ep) } - exposedPort, err := nat.NewPort(splitp[1], splitp[0]) + exposedPort, err := nat.NewPort(proto, port) if err != nil { return nil, err } diff --git a/pkg/api/handlers/compat/exec.go b/pkg/api/handlers/compat/exec.go index 819a096ef1..68d3335b8a 100644 --- a/pkg/api/handlers/compat/exec.go +++ b/pkg/api/handlers/compat/exec.go @@ -49,12 +49,12 @@ func ExecCreateHandler(w http.ResponseWriter, r *http.Request) { } libpodConfig.Environment = make(map[string]string) for _, envStr := range input.Env { - split := strings.SplitN(envStr, "=", 2) - if len(split) != 2 { + key, val, hasVal := strings.Cut(envStr, "=") + if !hasVal { utils.Error(w, http.StatusBadRequest, fmt.Errorf("environment variable %q badly formed, must be key=value", envStr)) return } - libpodConfig.Environment[split[0]] = split[1] + libpodConfig.Environment[key] = val } libpodConfig.WorkDir = input.WorkingDir libpodConfig.Privileged = input.Privileged diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index 5619a95033..195a916227 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -330,15 +330,15 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { if len(secretOpt) > 0 { modifiedOpt := []string{} for _, token := range secretOpt { - arr := strings.SplitN(token, "=", 2) - if len(arr) > 1 { - if arr[0] == "src" { + key, val, hasVal := strings.Cut(token, "=") + if hasVal { + if key == "src" { /* move secret away from contextDir */ /* to make sure we dont accidentally commit temporary secrets to image*/ builderDirectory, _ := filepath.Split(contextDirectory) // following path is outside build context - newSecretPath := filepath.Join(builderDirectory, arr[1]) - oldSecretPath := filepath.Join(contextDirectory, arr[1]) + newSecretPath := filepath.Join(builderDirectory, val) + oldSecretPath := filepath.Join(contextDirectory, val) err := os.Rename(oldSecretPath, newSecretPath) if err != nil { utils.BadRequest(w, "secrets", query.Secrets, err) @@ -551,19 +551,19 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { utils.BadRequest(w, "securityopt", query.SecurityOpt, errors.New("no-new-privileges is not supported")) return } - con := strings.SplitN(opt, "=", 2) - if len(con) != 2 { + name, value, hasValue := strings.Cut(opt, "=") + if !hasValue { utils.BadRequest(w, "securityopt", query.SecurityOpt, fmt.Errorf("invalid --security-opt name=value pair: %q", opt)) return } - switch con[0] { + switch name { case "label": - labelOpts = append(labelOpts, con[1]) + labelOpts = append(labelOpts, value) case "apparmor": - apparmor = con[1] + apparmor = value case "seccomp": - seccomp = con[1] + seccomp = value default: utils.BadRequest(w, "securityopt", query.SecurityOpt, fmt.Errorf("invalid --security-opt 2: %q", opt)) return diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go index 2c53e2162c..6c7752b8f7 100644 --- a/pkg/api/handlers/libpod/manifests.go +++ b/pkg/api/handlers/libpod/manifests.go @@ -489,12 +489,12 @@ func ManifestModify(w http.ResponseWriter, r *http.Request) { } annotations := make(map[string]string) for _, annotationSpec := range body.ManifestAddOptions.Annotation { - spec := strings.SplitN(annotationSpec, "=", 2) - if len(spec) != 2 { - utils.Error(w, http.StatusBadRequest, fmt.Errorf("no value given for annotation %q", spec[0])) + key, val, hasVal := strings.Cut(annotationSpec, "=") + if !hasVal { + utils.Error(w, http.StatusBadRequest, fmt.Errorf("no value given for annotation %q", key)) return } - annotations[spec[0]] = spec[1] + annotations[key] = val } body.ManifestAddOptions.Annotations = envLib.Join(body.ManifestAddOptions.Annotations, annotations) body.ManifestAddOptions.Annotation = nil diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index 12ea2458bb..ecbf4d3aee 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -268,7 +268,7 @@ func normalizeAuthFileKey(authFileKey string) string { stripped = strings.TrimPrefix(stripped, "https://") if stripped != authFileKey { // URLs are interpreted to mean complete registries - stripped = strings.SplitN(stripped, "/", 2)[0] + stripped, _, _ = strings.Cut(stripped, "/") } // Only non-namespaced registry names (or URLs) need to be normalized; repo namespaces diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go index b286b1a496..0f0f2dee89 100644 --- a/pkg/bindings/images/build.go +++ b/pkg/bindings/images/build.go @@ -530,9 +530,9 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO if len(secretOpt) > 0 { modifiedOpt := []string{} for _, token := range secretOpt { - arr := strings.SplitN(token, "=", 2) - if len(arr) > 1 { - if arr[0] == "src" { + opt, val, hasVal := strings.Cut(token, "=") + if hasVal { + if opt == "src" { // read specified secret into a tmp file // move tmp file to tar and change secret source to relative tmp file tmpSecretFile, err := os.CreateTemp(options.ContextDirectory, "podman-build-secret") @@ -541,7 +541,7 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO } defer os.Remove(tmpSecretFile.Name()) // clean up defer tmpSecretFile.Close() - srcSecretFile, err := os.Open(arr[1]) + srcSecretFile, err := os.Open(val) if err != nil { return nil, err } diff --git a/pkg/copy/parse.go b/pkg/copy/parse.go index 50f1d211d8..d58942f571 100644 --- a/pkg/copy/parse.go +++ b/pkg/copy/parse.go @@ -40,9 +40,9 @@ func parseUserInput(input string) (container string, path string) { return } - if spl := strings.SplitN(path, ":", 2); len(spl) == 2 { - container = spl[0] - path = spl[1] + if parsedContainer, parsedPath, ok := strings.Cut(path, ":"); ok { + container = parsedContainer + path = parsedPath } return } diff --git a/pkg/domain/filters/containers.go b/pkg/domain/filters/containers.go index 52ddc746d4..d690a0bdc9 100644 --- a/pkg/domain/filters/containers.go +++ b/pkg/domain/filters/containers.go @@ -98,10 +98,10 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo var imageNameWithoutTag string // Compare with ImageID, ImageName // Will match ImageName if running image has tag latest for other tags exact complete filter must be given - imageNameSlice := strings.SplitN(rootfsImageName, ":", 2) - if len(imageNameSlice) == 2 { - imageNameWithoutTag = imageNameSlice[0] - imageTag = imageNameSlice[1] + name, tag, hasColon := strings.Cut(rootfsImageName, ":") + if hasColon { + imageNameWithoutTag = name + imageTag = tag } if (rootfsImageID == filterValue) || @@ -144,13 +144,8 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo //- volume=(|) return func(c *libpod.Container) bool { containerConfig := c.ConfigNoCopy() - var dest string for _, filterValue := range filterValues { - arr := strings.SplitN(filterValue, ":", 2) - source := arr[0] - if len(arr) == 2 { - dest = arr[1] - } + source, dest, _ := strings.Cut(filterValue, ":") for _, mount := range containerConfig.Spec.Mounts { if dest != "" && (mount.Source == source && mount.Destination == dest) { return true @@ -233,19 +228,10 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo // check if networkMode is configured as `container:` // perform a match against filter `container:` // networks is already going to be empty if `container:` is configured as Mode - if strings.HasPrefix(networkMode, "container:") { - networkModeContainerPart := strings.SplitN(networkMode, ":", 2) - if len(networkModeContainerPart) < 2 { - return false - } - networkModeContainerID := networkModeContainerPart[1] + if networkModeContainerID, ok := strings.CutPrefix(networkMode, "container:"); ok { for _, val := range filterValues { - if strings.HasPrefix(val, "container:") { - filterNetworkModePart := strings.SplitN(val, ":", 2) - if len(filterNetworkModePart) < 2 { - return false - } - filterNetworkModeIDorName := filterNetworkModePart[1] + if idOrName, ok := strings.CutPrefix(val, "container:"); ok { + filterNetworkModeIDorName := idOrName filterID, err := r.LookupContainerID(filterNetworkModeIDorName) if err != nil { return false diff --git a/pkg/domain/filters/volumes.go b/pkg/domain/filters/volumes.go index 3d629548d4..28e541b522 100644 --- a/pkg/domain/filters/volumes.go +++ b/pkg/domain/filters/volumes.go @@ -49,14 +49,7 @@ func GenerateVolumeFilters(filter string, filterValues []string, runtime *libpod case "opt": return func(v *libpod.Volume) bool { for _, val := range filterValues { - filterArray := strings.SplitN(val, "=", 2) - filterKey := filterArray[0] - var filterVal string - if len(filterArray) > 1 { - filterVal = filterArray[1] - } else { - filterVal = "" - } + filterKey, filterVal, _ := strings.Cut(val, "=") for labelKey, labelValue := range v.Options() { if labelKey == filterKey && (filterVal == "" || labelValue == filterVal) { diff --git a/pkg/domain/infra/abi/containers_runlabel.go b/pkg/domain/infra/abi/containers_runlabel.go index 463988c87e..26626fd501 100644 --- a/pkg/domain/infra/abi/containers_runlabel.go +++ b/pkg/domain/infra/abi/containers_runlabel.go @@ -135,7 +135,7 @@ func generateRunlabelCommand(runlabel string, img *libimage.Image, inputName str name = splitImageName[len(splitImageName)-1] // make sure to remove the tag from the image name, otherwise the name cannot // be used as container name because a colon is an illegal character - name = strings.SplitN(name, ":", 2)[0] + name, _, _ = strings.Cut(name, ":") } // Append the user-specified arguments to the runlabel (command). diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go index 24b89872ee..d5436a7129 100644 --- a/pkg/domain/infra/abi/manifest.go +++ b/pkg/domain/infra/abi/manifest.go @@ -230,11 +230,11 @@ func (ir *ImageEngine) ManifestAdd(ctx context.Context, name string, images []st if len(opts.Annotation) != 0 { annotations := make(map[string]string) for _, annotationSpec := range opts.Annotation { - spec := strings.SplitN(annotationSpec, "=", 2) - if len(spec) != 2 { - return "", fmt.Errorf("no value given for annotation %q", spec[0]) + key, val, hasVal := strings.Cut(annotationSpec, "=") + if !hasVal { + return "", fmt.Errorf("no value given for annotation %q", key) } - annotations[spec[0]] = spec[1] + annotations[key] = val } opts.Annotations = envLib.Join(opts.Annotations, annotations) } @@ -269,11 +269,11 @@ func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, name, image string, if len(opts.Annotation) != 0 { annotations := make(map[string]string) for _, annotationSpec := range opts.Annotation { - spec := strings.SplitN(annotationSpec, "=", 2) - if len(spec) != 2 { - return "", fmt.Errorf("no value given for annotation %q", spec[0]) + key, val, hasVal := strings.Cut(annotationSpec, "=") + if !hasVal { + return "", fmt.Errorf("no value given for annotation %q", key) } - annotations[spec[0]] = spec[1] + annotations[key] = val } opts.Annotations = envLib.Join(opts.Annotations, annotations) } diff --git a/pkg/domain/infra/abi/parse/parse.go b/pkg/domain/infra/abi/parse/parse.go index 0cb95aa042..19d26c39e2 100644 --- a/pkg/domain/infra/abi/parse/parse.go +++ b/pkg/domain/infra/abi/parse/parse.go @@ -27,64 +27,64 @@ func VolumeOptions(opts map[string]string) ([]libpod.VolumeCreateOption, error) for _, o := range splitVal { // Options will be formatted as either "opt" or // "opt=value" - splitO := strings.SplitN(o, "=", 2) - switch strings.ToLower(splitO[0]) { + opt, val, hasVal := strings.Cut(o, "=") + switch strings.ToLower(opt) { case "size": - size, err := units.FromHumanSize(splitO[1]) + size, err := units.FromHumanSize(val) if err != nil { - return nil, fmt.Errorf("cannot convert size %s to integer: %w", splitO[1], err) + return nil, fmt.Errorf("cannot convert size %s to integer: %w", val, err) } libpodOptions = append(libpodOptions, libpod.WithVolumeSize(uint64(size))) finalVal = append(finalVal, o) // set option "SIZE": "$size" - volumeOptions["SIZE"] = splitO[1] + volumeOptions["SIZE"] = val case "inodes": - inodes, err := strconv.ParseUint(splitO[1], 10, 64) + inodes, err := strconv.ParseUint(val, 10, 64) if err != nil { - return nil, fmt.Errorf("cannot convert inodes %s to integer: %w", splitO[1], err) + return nil, fmt.Errorf("cannot convert inodes %s to integer: %w", val, err) } libpodOptions = append(libpodOptions, libpod.WithVolumeInodes(inodes)) finalVal = append(finalVal, o) // set option "INODES": "$size" - volumeOptions["INODES"] = splitO[1] + volumeOptions["INODES"] = val case "uid": - if len(splitO) != 2 { + if !hasVal { return nil, fmt.Errorf("uid option must provide a UID: %w", define.ErrInvalidArg) } - intUID, err := strconv.Atoi(splitO[1]) + intUID, err := strconv.Atoi(val) if err != nil { - return nil, fmt.Errorf("cannot convert UID %s to integer: %w", splitO[1], err) + return nil, fmt.Errorf("cannot convert UID %s to integer: %w", val, err) } logrus.Debugf("Removing uid= from options and adding WithVolumeUID for UID %d", intUID) libpodOptions = append(libpodOptions, libpod.WithVolumeUID(intUID), libpod.WithVolumeNoChown()) finalVal = append(finalVal, o) // set option "UID": "$uid" - volumeOptions["UID"] = splitO[1] + volumeOptions["UID"] = val case "gid": - if len(splitO) != 2 { + if !hasVal { return nil, fmt.Errorf("gid option must provide a GID: %w", define.ErrInvalidArg) } - intGID, err := strconv.Atoi(splitO[1]) + intGID, err := strconv.Atoi(val) if err != nil { - return nil, fmt.Errorf("cannot convert GID %s to integer: %w", splitO[1], err) + return nil, fmt.Errorf("cannot convert GID %s to integer: %w", val, err) } logrus.Debugf("Removing gid= from options and adding WithVolumeGID for GID %d", intGID) libpodOptions = append(libpodOptions, libpod.WithVolumeGID(intGID), libpod.WithVolumeNoChown()) finalVal = append(finalVal, o) // set option "GID": "$gid" - volumeOptions["GID"] = splitO[1] + volumeOptions["GID"] = val case "noquota": logrus.Debugf("Removing noquota from options and adding WithVolumeDisableQuota") libpodOptions = append(libpodOptions, libpod.WithVolumeDisableQuota()) // set option "NOQUOTA": "true" volumeOptions["NOQUOTA"] = "true" case "timeout": - if len(splitO) != 2 { + if !hasVal { return nil, fmt.Errorf("timeout option must provide a valid timeout in seconds: %w", define.ErrInvalidArg) } - intTimeout, err := strconv.Atoi(splitO[1]) + intTimeout, err := strconv.Atoi(val) if err != nil { - return nil, fmt.Errorf("cannot convert Timeout %s to an integer: %w", splitO[1], err) + return nil, fmt.Errorf("cannot convert Timeout %s to an integer: %w", val, err) } if intTimeout < 0 { return nil, fmt.Errorf("volume timeout cannot be negative (got %d)", intTimeout) diff --git a/pkg/domain/infra/tunnel/manifest.go b/pkg/domain/infra/tunnel/manifest.go index 5b176e31ed..f4aec45b0b 100644 --- a/pkg/domain/infra/tunnel/manifest.go +++ b/pkg/domain/infra/tunnel/manifest.go @@ -64,11 +64,11 @@ func (ir *ImageEngine) ManifestAdd(_ context.Context, name string, imageNames [] if len(opts.Annotation) != 0 { annotations := make(map[string]string) for _, annotationSpec := range opts.Annotation { - spec := strings.SplitN(annotationSpec, "=", 2) - if len(spec) != 2 { - return "", fmt.Errorf("no value given for annotation %q", spec[0]) + key, val, hasVal := strings.Cut(annotationSpec, "=") + if !hasVal { + return "", fmt.Errorf("no value given for annotation %q", key) } - annotations[spec[0]] = spec[1] + annotations[key] = val } opts.Annotations = envLib.Join(opts.Annotations, annotations) } @@ -97,11 +97,11 @@ func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, name, images string if len(opts.Annotation) != 0 { annotations := make(map[string]string) for _, annotationSpec := range opts.Annotation { - spec := strings.SplitN(annotationSpec, "=", 2) - if len(spec) != 2 { - return "", fmt.Errorf("no value given for annotation %q", spec[0]) + key, val, hasVal := strings.Cut(annotationSpec, "=") + if !hasVal { + return "", fmt.Errorf("no value given for annotation %q", key) } - annotations[spec[0]] = spec[1] + annotations[key] = val } opts.Annotations = envLib.Join(opts.Annotations, annotations) } diff --git a/pkg/domain/utils/scp.go b/pkg/domain/utils/scp.go index 0bd1b0d3e6..cd466843d7 100644 --- a/pkg/domain/utils/scp.go +++ b/pkg/domain/utils/scp.go @@ -406,8 +406,8 @@ func GetServiceInformation(sshInfo *entities.ImageScpConnections, cliConnections var urlS string var iden string for i, val := range cliConnections { - splitEnv := strings.SplitN(val, "::", 2) - sshInfo.Connections = append(sshInfo.Connections, splitEnv[0]) + connection, _, _ := strings.Cut(val, "::") + sshInfo.Connections = append(sshInfo.Connections, connection) conn, found := cfg.Engine.ServiceDestinations[sshInfo.Connections[i]] if found { urlS = conn.URI diff --git a/pkg/domain/utils/utils.go b/pkg/domain/utils/utils.go index ee213e1b62..ca7a3210fb 100644 --- a/pkg/domain/utils/utils.go +++ b/pkg/domain/utils/utils.go @@ -34,8 +34,8 @@ func ToLibpodFilters(f url.Values) (filters []string) { func ToURLValues(f []string) (filters url.Values) { filters = make(url.Values) for _, v := range f { - t := strings.SplitN(v, "=", 2) - filters.Add(t[0], t[1]) + key, val, _ := strings.Cut(v, "=") + filters.Add(key, val) } return } diff --git a/pkg/env/env.go b/pkg/env/env.go index 8e87834f85..6dbfb02017 100644 --- a/pkg/env/env.go +++ b/pkg/env/env.go @@ -40,14 +40,9 @@ func Slice(m map[string]string) []string { // map. func Map(slice []string) map[string]string { envmap := make(map[string]string, len(slice)) - for _, val := range slice { - data := strings.SplitN(val, "=", 2) - - if len(data) > 1 { - envmap[data[0]] = data[1] - } else { - envmap[data[0]] = "" - } + for _, line := range slice { + key, val, _ := strings.Cut(line, "=") + envmap[key] = val } return envmap } @@ -94,26 +89,25 @@ func ParseFile(path string) (_ map[string]string, err error) { } func parseEnv(env map[string]string, line string) error { - data := strings.SplitN(line, "=", 2) + key, val, hasVal := strings.Cut(line, "=") // catch invalid variables such as "=" or "=A" - if data[0] == "" { + if key == "" { return fmt.Errorf("invalid variable: %q", line) } // trim the front of a variable, but nothing else - name := strings.TrimLeft(data[0], whiteSpaces) - if len(data) > 1 { - env[name] = data[1] + name := strings.TrimLeft(key, whiteSpaces) + if hasVal { + env[name] = val } else { - if strings.HasSuffix(name, "*") { - name = strings.TrimSuffix(name, "*") + if name, hasStar := strings.CutSuffix(name, "*"); hasStar { for _, e := range os.Environ() { - part := strings.SplitN(e, "=", 2) - if len(part) < 2 { + envKey, envVal, hasEq := strings.Cut(e, "=") + if !hasEq { continue } - if strings.HasPrefix(part[0], name) { - env[part[0]] = part[1] + if strings.HasPrefix(envKey, name) { + env[envKey] = envVal } } } else if val, ok := os.LookupEnv(name); ok { diff --git a/pkg/machine/qemu/command/command_test.go b/pkg/machine/qemu/command/command_test.go index e2307f3693..c56d9341f5 100644 --- a/pkg/machine/qemu/command/command_test.go +++ b/pkg/machine/qemu/command/command_test.go @@ -75,8 +75,8 @@ func TestPropagateHostEnv(t *testing.T) { // envs looks like: {"BAR": "bar", "FOO": "foo"} envs := make(map[string]string) for _, env := range envsRawArr { - item := strings.SplitN(env, "=", 2) - envs[item[0]] = strings.Trim(item[1], "\"") + key, value, _ := strings.Cut(env, "=") + envs[key] = strings.Trim(value, "\"") } for key, test := range tests { diff --git a/pkg/namespaces/namespaces.go b/pkg/namespaces/namespaces.go index 2731fe95ad..3c0622c7d5 100644 --- a/pkg/namespaces/namespaces.go +++ b/pkg/namespaces/namespaces.go @@ -48,24 +48,21 @@ func (n CgroupMode) IsNS() bool { // NS gets the path associated with a ns: cgroup ns func (n CgroupMode) NS() string { - parts := strings.SplitN(string(n), ":", 2) - if len(parts) > 1 { - return parts[1] - } - return "" + _, path, _ := strings.Cut(string(n), ":") + return path } // IsContainer indicates whether the container uses a new cgroup namespace. func (n CgroupMode) IsContainer() bool { - parts := strings.SplitN(string(n), ":", 2) - return len(parts) > 1 && parts[0] == containerType + typ, _, hasColon := strings.Cut(string(n), ":") + return hasColon && typ == containerType } // Container returns the name of the container whose cgroup namespace is going to be used. func (n CgroupMode) Container() string { - parts := strings.SplitN(string(n), ":", 2) - if len(parts) > 1 && parts[0] == containerType { - return parts[1] + typ, name, hasName := strings.Cut(string(n), ":") + if hasName && typ == containerType { + return name } return "" } @@ -123,36 +120,36 @@ func (n UsernsMode) IsDefaultValue() bool { // GetKeepIDOptions returns a KeepIDUserNsOptions with the settings to keepIDmatically set up // a user namespace. func (n UsernsMode) GetKeepIDOptions() (*KeepIDUserNsOptions, error) { - parts := strings.SplitN(string(n), ":", 2) - if parts[0] != "keep-id" { + nsmode, nsopts, hasOpts := strings.Cut(string(n), ":") + if nsmode != "keep-id" { return nil, fmt.Errorf("wrong user namespace mode") } options := KeepIDUserNsOptions{} - if len(parts) == 1 { + if !hasOpts { return &options, nil } - for _, o := range strings.Split(parts[1], ",") { - v := strings.SplitN(o, "=", 2) - if len(v) != 2 { + for _, o := range strings.Split(nsopts, ",") { + opt, val, hasVal := strings.Cut(o, "=") + if !hasVal { return nil, fmt.Errorf("invalid option specified: %q", o) } - switch v[0] { + switch opt { case "uid": - s, err := strconv.ParseUint(v[1], 10, 32) + s, err := strconv.ParseUint(val, 10, 32) if err != nil { return nil, err } v := uint32(s) options.UID = &v case "gid": - s, err := strconv.ParseUint(v[1], 10, 32) + s, err := strconv.ParseUint(val, 10, 32) if err != nil { return nil, err } v := uint32(s) options.GID = &v default: - return nil, fmt.Errorf("unknown option specified: %q", v[0]) + return nil, fmt.Errorf("unknown option specified: %q", opt) } } return &options, nil @@ -185,24 +182,21 @@ func (n UsernsMode) IsNS() bool { // NS gets the path associated with a ns: userns ns func (n UsernsMode) NS() string { - parts := strings.SplitN(string(n), ":", 2) - if len(parts) > 1 { - return parts[1] - } - return "" + _, path, _ := strings.Cut(string(n), ":") + return path } // IsContainer indicates whether container uses a container userns. func (n UsernsMode) IsContainer() bool { - parts := strings.SplitN(string(n), ":", 2) - return len(parts) > 1 && parts[0] == containerType + typ, _, hasName := strings.Cut(string(n), ":") + return hasName && typ == containerType } // Container is the id of the container which network this container is connected to. func (n UsernsMode) Container() string { - parts := strings.SplitN(string(n), ":", 2) - if len(parts) > 1 && parts[0] == containerType { - return parts[1] + typ, name, hasName := strings.Cut(string(n), ":") + if hasName && typ == containerType { + return name } return "" } @@ -222,15 +216,15 @@ func (n UTSMode) IsHost() bool { // IsContainer indicates whether the container uses a container's UTS namespace. func (n UTSMode) IsContainer() bool { - parts := strings.SplitN(string(n), ":", 2) - return len(parts) > 1 && parts[0] == containerType + typ, _, hasName := strings.Cut(string(n), ":") + return hasName && typ == containerType } // Container returns the name of the container whose uts namespace is going to be used. func (n UTSMode) Container() string { - parts := strings.SplitN(string(n), ":", 2) - if len(parts) > 1 && parts[0] == containerType { - return parts[1] + typ, name, hasName := strings.Cut(string(n), ":") + if hasName && typ == containerType { + return name } return "" } @@ -270,8 +264,8 @@ func (n IpcMode) IsShareable() bool { // IsContainer indicates whether the container uses another container's ipc namespace. func (n IpcMode) IsContainer() bool { - parts := strings.SplitN(string(n), ":", 2) - return len(parts) > 1 && parts[0] == containerType + typ, _, hasName := strings.Cut(string(n), ":") + return hasName && typ == containerType } // IsNone indicates whether container IpcMode is set to "none". @@ -291,9 +285,9 @@ func (n IpcMode) Valid() bool { // Container returns the name of the container ipc stack is going to be used. func (n IpcMode) Container() string { - parts := strings.SplitN(string(n), ":", 2) - if len(parts) > 1 && parts[0] == containerType { - return parts[1] + typ, name, hasName := strings.Cut(string(n), ":") + if hasName && typ == containerType { + return name } return "" } @@ -313,8 +307,8 @@ func (n PidMode) IsHost() bool { // IsContainer indicates whether the container uses a container's pid namespace. func (n PidMode) IsContainer() bool { - parts := strings.SplitN(string(n), ":", 2) - return len(parts) > 1 && parts[0] == containerType + typ, _, hasName := strings.Cut(string(n), ":") + return hasName && typ == containerType } // Valid indicates whether the pid namespace is valid. @@ -334,9 +328,9 @@ func (n PidMode) Valid() bool { // Container returns the name of the container whose pid namespace is going to be used. func (n PidMode) Container() string { - parts := strings.SplitN(string(n), ":", 2) - if len(parts) > 1 && parts[0] == containerType { - return parts[1] + typ, name, hasName := strings.Cut(string(n), ":") + if hasName && typ == containerType { + return name } return "" } @@ -366,15 +360,15 @@ func (n NetworkMode) IsPrivate() bool { // IsContainer indicates whether container uses a container network stack. func (n NetworkMode) IsContainer() bool { - parts := strings.SplitN(string(n), ":", 2) - return len(parts) > 1 && parts[0] == containerType + typ, _, hasName := strings.Cut(string(n), ":") + return hasName && typ == containerType } // Container is the id of the container which network this container is connected to. func (n NetworkMode) Container() string { - parts := strings.SplitN(string(n), ":", 2) - if len(parts) > 1 && parts[0] == containerType { - return parts[1] + typ, name, hasName := strings.Cut(string(n), ":") + if hasName && typ == containerType { + return name } return "" } @@ -409,11 +403,8 @@ func (n NetworkMode) IsNS() bool { // NS gets the path associated with a ns: network ns func (n NetworkMode) NS() string { - parts := strings.SplitN(string(n), ":", 2) - if len(parts) > 1 { - return parts[1] - } - return "" + _, path, _ := strings.Cut(string(n), ":") + return path } // IsPod returns whether the network refers to pod networking diff --git a/pkg/rctl/rctl.go b/pkg/rctl/rctl.go index 54ccf40b1b..75588c79c6 100644 --- a/pkg/rctl/rctl.go +++ b/pkg/rctl/rctl.go @@ -31,9 +31,8 @@ func GetRacct(filter string) (map[string]uint64, error) { entries := strings.Split(string(buf[:len]), ",") res := make(map[string]uint64) for _, entry := range entries { - kv := strings.SplitN(entry, "=", 2) - key := kv[0] - val, err := strconv.ParseUint(kv[1], 10, 0) + key, valstr, _ := strings.Cut(entry, "=") + val, err := strconv.ParseUint(valstr, 10, 0) if err != nil { logrus.Warnf("unexpected rctl entry, ignoring: %s", entry) } diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index 4471942f24..11541ab809 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -236,13 +236,8 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat } } - for _, v := range rtc.Containers.Annotations.Get() { - split := strings.SplitN(v, "=", 2) - k := split[0] - v := "" - if len(split) == 2 { - v = split[1] - } + for _, annotation := range rtc.Containers.Annotations.Get() { + k, v, _ := strings.Cut(annotation, "=") annotations[k] = v } // now pass in the values from client @@ -356,9 +351,9 @@ func ConfigToSpec(rt *libpod.Runtime, specg *specgen.SpecGenerator, containerID if conf.Spec.Process != nil && conf.Spec.Process.Env != nil { env := make(map[string]string) for _, entry := range conf.Spec.Process.Env { - split := strings.SplitN(entry, "=", 2) - if len(split) == 2 { - env[split[0]] = split[1] + key, val, hasVal := strings.Cut(entry, "=") + if hasVal { + env[key] = val } } specg.Env = env diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index c0878d5000..09483bbb9b 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -54,12 +54,12 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener s.ResourceLimits.Unified = make(map[string]string) } for _, cgroupConf := range rtc.Containers.CgroupConf.Get() { - cgr := strings.SplitN(cgroupConf, "=", 2) - if len(cgr) != 2 { - return nil, nil, nil, fmt.Errorf("CgroupConf %q from containers.conf invalid, must be name=value", cgr) + key, val, hasVal := strings.Cut(cgroupConf, "=") + if !hasVal { + return nil, nil, nil, fmt.Errorf("CgroupConf %s from containers.conf invalid, must be name=value", cgroupConf) } - if _, ok := s.ResourceLimits.Unified[cgr[0]]; !ok { - s.ResourceLimits.Unified[cgr[0]] = cgr[1] + if _, ok := s.ResourceLimits.Unified[key]; !ok { + s.ResourceLimits.Unified[key] = val } } } diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index 930f1497f0..f10fc21c68 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -221,29 +221,29 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener s.LogConfiguration.Options = make(map[string]string) for _, o := range opts.LogOptions { - split := strings.SplitN(o, "=", 2) - if len(split) < 2 { + opt, val, hasVal := strings.Cut(o, "=") + if !hasVal { return nil, fmt.Errorf("invalid log option %q", o) } - switch strings.ToLower(split[0]) { + switch strings.ToLower(opt) { case "driver": - s.LogConfiguration.Driver = split[1] + s.LogConfiguration.Driver = val case "path": - s.LogConfiguration.Path = split[1] + s.LogConfiguration.Path = val case "max-size": - logSize, err := units.FromHumanSize(split[1]) + logSize, err := units.FromHumanSize(val) if err != nil { return nil, err } s.LogConfiguration.Size = logSize default: - switch len(split[1]) { + switch len(val) { case 0: return nil, fmt.Errorf("invalid log option: %w", define.ErrInvalidArg) default: // tags for journald only if s.LogConfiguration.Driver == "" || s.LogConfiguration.Driver == define.JournaldLogging { - s.LogConfiguration.Options[split[0]] = split[1] + s.LogConfiguration.Options[opt] = val } else { logrus.Warnf("Can only set tags with journald log driver but driver is %q", s.LogConfiguration.Driver) } @@ -434,8 +434,8 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener // Environment Variables envs := map[string]string{} for _, env := range imageData.Config.Env { - keyval := strings.SplitN(env, "=", 2) - envs[keyval[0]] = keyval[1] + key, val, _ := strings.Cut(env, "=") + envs[key] = val } for _, env := range opts.Container.Env { diff --git a/pkg/specgen/generate/storage.go b/pkg/specgen/generate/storage.go index fa66359069..8c65783b43 100644 --- a/pkg/specgen/generate/storage.go +++ b/pkg/specgen/generate/storage.go @@ -262,9 +262,9 @@ func getVolumesFrom(volumesFrom []string, runtime *libpod.Runtime) (map[string]s for _, volume := range volumesFrom { var options []string - splitVol := strings.SplitN(volume, ":", 2) - if len(splitVol) == 2 { - splitOpts := strings.Split(splitVol[1], ",") + idOrName, volOpts, hasVolOpts := strings.Cut(volume, ":") + if hasVolOpts { + splitOpts := strings.Split(volOpts, ",") setRORW := false setZ := false for _, opt := range splitOpts { @@ -286,9 +286,9 @@ func getVolumesFrom(volumesFrom []string, runtime *libpod.Runtime) (map[string]s options = splitOpts } - ctr, err := runtime.LookupContainer(splitVol[0]) + ctr, err := runtime.LookupContainer(idOrName) if err != nil { - return nil, nil, fmt.Errorf("looking up container %q for volumes-from: %w", splitVol[0], err) + return nil, nil, fmt.Errorf("looking up container %q for volumes-from: %w", idOrName, err) } logrus.Debugf("Adding volumes from container %s", ctr.ID()) diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go index 04d4b08f43..4b8a6bee41 100644 --- a/pkg/specgen/namespaces.go +++ b/pkg/specgen/namespaces.go @@ -230,29 +230,23 @@ func (n *Namespace) validate() error { // function. func ParseNamespace(ns string) (Namespace, error) { toReturn := Namespace{} - switch { - case ns == "pod": + switch ns { + case "pod": toReturn.NSMode = FromPod - case ns == "host": + case "host": toReturn.NSMode = Host - case ns == "private", ns == "": + case "private", "": toReturn.NSMode = Private - case strings.HasPrefix(ns, "ns:"): - split := strings.SplitN(ns, ":", 2) - if len(split) != 2 { - return toReturn, fmt.Errorf("must provide a path to a namespace when specifying \"ns:\"") - } - toReturn.NSMode = Path - toReturn.Value = split[1] - case strings.HasPrefix(ns, "container:"): - split := strings.SplitN(ns, ":", 2) - if len(split) != 2 { - return toReturn, fmt.Errorf("must provide name or ID or a container when specifying \"container:\"") - } - toReturn.NSMode = FromContainer - toReturn.Value = split[1] default: - return toReturn, fmt.Errorf("unrecognized namespace mode %s passed", ns) + if value, ok := strings.CutPrefix(ns, "ns:"); ok { + toReturn.NSMode = Path + toReturn.Value = value + } else if value, ok := strings.CutPrefix(ns, "container:"); ok { + toReturn.NSMode = FromContainer + toReturn.Value = value + } else { + return toReturn, fmt.Errorf("unrecognized namespace mode %s passed", ns) + } } return toReturn, nil @@ -302,37 +296,32 @@ func ParseIPCNamespace(ns string) (Namespace, error) { // form. func ParseUserNamespace(ns string) (Namespace, error) { toReturn := Namespace{} - switch { - case ns == "auto": - toReturn.NSMode = Auto - return toReturn, nil - case strings.HasPrefix(ns, "auto:"): - split := strings.SplitN(ns, ":", 2) - if len(split) != 2 { - return toReturn, errors.New("invalid setting for auto: mode") - } + switch ns { + case "auto": toReturn.NSMode = Auto - toReturn.Value = split[1] return toReturn, nil - case ns == "keep-id": + case "keep-id": toReturn.NSMode = KeepID return toReturn, nil - case strings.HasPrefix(ns, "keep-id:"): - split := strings.SplitN(ns, ":", 2) - if len(split) != 2 { - return toReturn, errors.New("invalid setting for keep-id: mode") - } - toReturn.NSMode = KeepID - toReturn.Value = split[1] - return toReturn, nil - case ns == "nomap": + case "nomap": toReturn.NSMode = NoMap return toReturn, nil - case ns == "": + case "": toReturn.NSMode = Host return toReturn, nil + default: + if value, ok := strings.CutPrefix(ns, "auto:"); ok { + toReturn.NSMode = Auto + toReturn.Value = value + return toReturn, nil + } else if value, ok := strings.CutPrefix(ns, "keep-id:"); ok { + toReturn.NSMode = KeepID + toReturn.Value = value + return toReturn, nil + } else { + return ParseNamespace(ns) + } } - return ParseNamespace(ns) } // ParseNetworkFlag parses a network string slice into the network options @@ -352,10 +341,10 @@ func ParseNetworkFlag(networks []string, pastaNetworkNameExists bool) (Namespace switch { case ns == string(Slirp), strings.HasPrefix(ns, string(Slirp)+":"): - parts := strings.SplitN(ns, ":", 2) - if len(parts) > 1 { + key, options, hasOptions := strings.Cut(ns, ":") + if hasOptions { networkOptions = make(map[string][]string) - networkOptions[parts[0]] = strings.Split(parts[1], ",") + networkOptions[key] = strings.Split(options, ",") } toReturn.NSMode = Slirp case ns == string(FromPod): @@ -364,11 +353,11 @@ func ParseNetworkFlag(networks []string, pastaNetworkNameExists bool) (Namespace toReturn.NSMode = Private case ns == string(Bridge), strings.HasPrefix(ns, string(Bridge)+":"): toReturn.NSMode = Bridge - parts := strings.SplitN(ns, ":", 2) + _, options, hasOptions := strings.Cut(ns, ":") netOpts := types.PerNetworkOptions{} - if len(parts) > 1 { + if hasOptions { var err error - netOpts, err = parseBridgeNetworkOptions(parts[1]) + netOpts, err = parseBridgeNetworkOptions(options) if err != nil { return toReturn, nil, nil, err } @@ -381,30 +370,23 @@ func ParseNetworkFlag(networks []string, pastaNetworkNameExists bool) (Namespace case ns == string(Host): toReturn.NSMode = Host case strings.HasPrefix(ns, "ns:"): - split := strings.SplitN(ns, ":", 2) - if len(split) != 2 { - return toReturn, nil, nil, errors.New("must provide a path to a namespace when specifying \"ns:\"") - } + _, value, _ := strings.Cut(ns, ":") toReturn.NSMode = Path - toReturn.Value = split[1] + toReturn.Value = value case strings.HasPrefix(ns, string(FromContainer)+":"): - split := strings.SplitN(ns, ":", 2) - if len(split) != 2 { - return toReturn, nil, nil, errors.New("must provide name or ID or a container when specifying \"container:\"") - } + _, value, _ := strings.Cut(ns, ":") toReturn.NSMode = FromContainer - toReturn.Value = split[1] + toReturn.Value = value case ns == string(Pasta), strings.HasPrefix(ns, string(Pasta)+":"): - var parts []string + key, options, hasOptions := strings.Cut(ns, ":") if pastaNetworkNameExists { goto nextCase } - parts = strings.SplitN(ns, ":", 2) - if len(parts) > 1 { + if hasOptions { networkOptions = make(map[string][]string) - networkOptions[parts[0]] = strings.Split(parts[1], ",") + networkOptions[key] = strings.Split(options, ",") } toReturn.NSMode = Pasta break @@ -412,22 +394,22 @@ func ParseNetworkFlag(networks []string, pastaNetworkNameExists bool) (Namespace fallthrough default: // we should have a normal network - parts := strings.SplitN(ns, ":", 2) - if len(parts) == 1 { + name, options, hasOptions := strings.Cut(ns, ":") + if hasOptions { + if name == "" { + return toReturn, nil, nil, errors.New("network name cannot be empty") + } + netOpts, err := parseBridgeNetworkOptions(options) + if err != nil { + return toReturn, nil, nil, fmt.Errorf("invalid option for network %s: %w", name, err) + } + podmanNetworks[name] = netOpts + } else { // Assume we have been given a comma separated list of networks for backwards compat. networkList := strings.Split(ns, ",") for _, net := range networkList { podmanNetworks[net] = types.PerNetworkOptions{} } - } else { - if parts[0] == "" { - return toReturn, nil, nil, errors.New("network name cannot be empty") - } - netOpts, err := parseBridgeNetworkOptions(parts[1]) - if err != nil { - return toReturn, nil, nil, fmt.Errorf("invalid option for network %s: %w", parts[0], err) - } - podmanNetworks[parts[0]] = netOpts } // networks need bridge mode @@ -440,24 +422,24 @@ func ParseNetworkFlag(networks []string, pastaNetworkNameExists bool) (Namespace } for _, network := range networks[1:] { - parts := strings.SplitN(network, ":", 2) - if parts[0] == "" { + name, options, hasOptions := strings.Cut(network, ":") + if name == "" { return toReturn, nil, nil, fmt.Errorf("network name cannot be empty: %w", define.ErrInvalidArg) } // TODO (5.0): Don't accept string(Pasta) here once we drop pastaNetworkNameExists if slices.Contains([]string{string(Bridge), string(Slirp), string(FromPod), string(NoNetwork), - string(Default), string(Private), string(Path), string(FromContainer), string(Host)}, parts[0]) { - return toReturn, nil, nil, fmt.Errorf("can only set extra network names, selected mode %s conflicts with bridge: %w", parts[0], define.ErrInvalidArg) + string(Default), string(Private), string(Path), string(FromContainer), string(Host)}, name) { + return toReturn, nil, nil, fmt.Errorf("can only set extra network names, selected mode %s conflicts with bridge: %w", name, define.ErrInvalidArg) } netOpts := types.PerNetworkOptions{} - if len(parts) > 1 { + if hasOptions { var err error - netOpts, err = parseBridgeNetworkOptions(parts[1]) + netOpts, err = parseBridgeNetworkOptions(options) if err != nil { - return toReturn, nil, nil, fmt.Errorf("invalid option for network %s: %w", parts[0], err) + return toReturn, nil, nil, fmt.Errorf("invalid option for network %s: %w", name, err) } } - podmanNetworks[parts[0]] = netOpts + podmanNetworks[name] = netOpts } } @@ -471,36 +453,36 @@ func parseBridgeNetworkOptions(opts string) (types.PerNetworkOptions, error) { } allopts := strings.Split(opts, ",") for _, opt := range allopts { - split := strings.SplitN(opt, "=", 2) - switch split[0] { + name, value, _ := strings.Cut(opt, "=") + switch name { case "ip", "ip6": - ip := net.ParseIP(split[1]) + ip := net.ParseIP(value) if ip == nil { - return netOpts, fmt.Errorf("invalid ip address %q", split[1]) + return netOpts, fmt.Errorf("invalid ip address %q", value) } netOpts.StaticIPs = append(netOpts.StaticIPs, ip) case "mac": - mac, err := net.ParseMAC(split[1]) + mac, err := net.ParseMAC(value) if err != nil { return netOpts, err } netOpts.StaticMAC = types.HardwareAddr(mac) case "alias": - if split[1] == "" { + if value == "" { return netOpts, errors.New("alias cannot be empty") } - netOpts.Aliases = append(netOpts.Aliases, split[1]) + netOpts.Aliases = append(netOpts.Aliases, value) case "interface_name": - if split[1] == "" { + if value == "" { return netOpts, errors.New("interface_name cannot be empty") } - netOpts.InterfaceName = split[1] + netOpts.InterfaceName = value default: - return netOpts, fmt.Errorf("unknown bridge network option: %s", split[0]) + return netOpts, fmt.Errorf("unknown bridge network option: %s", name) } } return netOpts, nil diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go index 306a19b394..e9e90cae43 100644 --- a/pkg/specgenutil/specgen.go +++ b/pkg/specgenutil/specgen.go @@ -502,11 +502,11 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions // Last, add user annotations for _, annotation := range c.Annotation { - splitAnnotation := strings.SplitN(annotation, "=", 2) - if len(splitAnnotation) < 2 { + key, val, hasVal := strings.Cut(annotation, "=") + if !hasVal { return errors.New("annotations must be formatted KEY=VALUE") } - annotations[splitAnnotation[0]] = splitAnnotation[1] + annotations[key] = val } if len(s.Annotations) == 0 { s.Annotations = annotations @@ -515,11 +515,11 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions if len(c.StorageOpts) > 0 { opts := make(map[string]string, len(c.StorageOpts)) for _, opt := range c.StorageOpts { - split := strings.SplitN(opt, "=", 2) - if len(split) != 2 { + key, val, hasVal := strings.Cut(opt, "=") + if !hasVal { return errors.New("storage-opt must be formatted KEY=VALUE") } - opts[split[0]] = split[1] + opts[key] = val } s.StorageOpts = opts } @@ -673,11 +673,11 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions // check if key=value and convert sysmap := make(map[string]string) for _, ctl := range c.Sysctl { - splitCtl := strings.SplitN(ctl, "=", 2) - if len(splitCtl) < 2 { + key, val, hasVal := strings.Cut(ctl, "=") + if !hasVal { return fmt.Errorf("invalid sysctl value %q", ctl) } - sysmap[splitCtl[0]] = splitCtl[1] + sysmap[key] = val } if len(s.Sysctl) == 0 || len(c.Sysctl) != 0 { s.Sysctl = sysmap @@ -690,48 +690,51 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions for _, opt := range c.SecurityOpt { // Docker deprecated the ":" syntax but still supports it, // so we need to as well - var con []string + var key, val string + var hasVal bool if strings.Contains(opt, "=") { - con = strings.SplitN(opt, "=", 2) + key, val, hasVal = strings.Cut(opt, "=") } else { - con = strings.SplitN(opt, ":", 2) + key, val, hasVal = strings.Cut(opt, ":") } - if len(con) != 2 && - con[0] != "no-new-privileges" { + if !hasVal && + key != "no-new-privileges" { return fmt.Errorf("invalid --security-opt 1: %q", opt) } - switch con[0] { + switch key { case "apparmor": - s.ContainerSecurityConfig.ApparmorProfile = con[1] - s.Annotations[define.InspectAnnotationApparmor] = con[1] + s.ContainerSecurityConfig.ApparmorProfile = val + s.Annotations[define.InspectAnnotationApparmor] = val case "label": - if con[1] == "nested" { + if val == "nested" { s.ContainerSecurityConfig.LabelNested = true continue } // TODO selinux opts and label opts are the same thing - s.ContainerSecurityConfig.SelinuxOpts = append(s.ContainerSecurityConfig.SelinuxOpts, con[1]) + s.ContainerSecurityConfig.SelinuxOpts = append(s.ContainerSecurityConfig.SelinuxOpts, val) s.Annotations[define.InspectAnnotationLabel] = strings.Join(s.ContainerSecurityConfig.SelinuxOpts, ",label=") case "mask": - s.ContainerSecurityConfig.Mask = append(s.ContainerSecurityConfig.Mask, strings.Split(con[1], ":")...) + s.ContainerSecurityConfig.Mask = append(s.ContainerSecurityConfig.Mask, strings.Split(val, ":")...) case "proc-opts": - s.ProcOpts = strings.Split(con[1], ",") + s.ProcOpts = strings.Split(val, ",") case "seccomp": - s.SeccompProfilePath = con[1] - s.Annotations[define.InspectAnnotationSeccomp] = con[1] + s.SeccompProfilePath = val + s.Annotations[define.InspectAnnotationSeccomp] = val // this option is for docker compatibility, it is the same as unmask=ALL case "systempaths": - if con[1] == "unconfined" { + if val == "unconfined" { s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, []string{"ALL"}...) } else { - return fmt.Errorf("invalid systempaths option %q, only `unconfined` is supported", con[1]) + return fmt.Errorf("invalid systempaths option %q, only `unconfined` is supported", val) } case "unmask": - s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, con[1:]...) + if hasVal { + s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, val) + } case "no-new-privileges": noNewPrivileges := true - if len(con) == 2 { - noNewPrivileges, err = strconv.ParseBool(con[1]) + if hasVal { + noNewPrivileges, err = strconv.ParseBool(val) if err != nil { return fmt.Errorf("invalid --security-opt 2: %q", opt) } @@ -813,23 +816,23 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions logOpts := make(map[string]string) for _, o := range c.LogOptions { - split := strings.SplitN(o, "=", 2) - if len(split) < 2 { + key, val, hasVal := strings.Cut(o, "=") + if !hasVal { return fmt.Errorf("invalid log option %q", o) } - switch strings.ToLower(split[0]) { + switch strings.ToLower(key) { case "driver": - s.LogConfiguration.Driver = split[1] + s.LogConfiguration.Driver = val case "path": - s.LogConfiguration.Path = split[1] + s.LogConfiguration.Path = val case "max-size": - logSize, err := units.FromHumanSize(split[1]) + logSize, err := units.FromHumanSize(val) if err != nil { return err } s.LogConfiguration.Size = logSize default: - logOpts[split[0]] = split[1] + logOpts[key] = val } } if len(s.LogConfiguration.Options) == 0 || len(c.LogOptions) != 0 { @@ -1004,23 +1007,23 @@ func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, start func parseWeightDevices(weightDevs []string) (map[string]specs.LinuxWeightDevice, error) { wd := make(map[string]specs.LinuxWeightDevice) - for _, val := range weightDevs { - split := strings.SplitN(val, ":", 2) - if len(split) != 2 { - return nil, fmt.Errorf("bad format: %s", val) + for _, dev := range weightDevs { + key, val, hasVal := strings.Cut(dev, ":") + if !hasVal { + return nil, fmt.Errorf("bad format: %s", dev) } - if !strings.HasPrefix(split[0], "/dev/") { - return nil, fmt.Errorf("bad format for device path: %s", val) + if !strings.HasPrefix(key, "/dev/") { + return nil, fmt.Errorf("bad format for device path: %s", dev) } - weight, err := strconv.ParseUint(split[1], 10, 0) + weight, err := strconv.ParseUint(val, 10, 0) if err != nil { - return nil, fmt.Errorf("invalid weight for device: %s", val) + return nil, fmt.Errorf("invalid weight for device: %s", dev) } if weight > 0 && (weight < 10 || weight > 1000) { - return nil, fmt.Errorf("invalid weight for device: %s", val) + return nil, fmt.Errorf("invalid weight for device: %s", dev) } w := uint16(weight) - wd[split[0]] = specs.LinuxWeightDevice{ + wd[key] = specs.LinuxWeightDevice{ Weight: &w, LeafWeight: nil, } @@ -1030,41 +1033,41 @@ func parseWeightDevices(weightDevs []string) (map[string]specs.LinuxWeightDevice func parseThrottleBPSDevices(bpsDevices []string) (map[string]specs.LinuxThrottleDevice, error) { td := make(map[string]specs.LinuxThrottleDevice) - for _, val := range bpsDevices { - split := strings.SplitN(val, ":", 2) - if len(split) != 2 { - return nil, fmt.Errorf("bad format: %s", val) + for _, dev := range bpsDevices { + key, val, hasVal := strings.Cut(dev, ":") + if !hasVal { + return nil, fmt.Errorf("bad format: %s", dev) } - if !strings.HasPrefix(split[0], "/dev/") { - return nil, fmt.Errorf("bad format for device path: %s", val) + if !strings.HasPrefix(key, "/dev/") { + return nil, fmt.Errorf("bad format for device path: %s", dev) } - rate, err := units.RAMInBytes(split[1]) + rate, err := units.RAMInBytes(val) 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) + 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", dev) } 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 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", dev) } - td[split[0]] = specs.LinuxThrottleDevice{Rate: uint64(rate)} + td[key] = specs.LinuxThrottleDevice{Rate: uint64(rate)} } return td, nil } func parseThrottleIOPsDevices(iopsDevices []string) (map[string]specs.LinuxThrottleDevice, error) { td := make(map[string]specs.LinuxThrottleDevice) - for _, val := range iopsDevices { - split := strings.SplitN(val, ":", 2) - if len(split) != 2 { - return nil, fmt.Errorf("bad format: %s", val) + for _, dev := range iopsDevices { + key, val, hasVal := strings.Cut(dev, ":") + if !hasVal { + return nil, fmt.Errorf("bad format: %s", dev) } - if !strings.HasPrefix(split[0], "/dev/") { - return nil, fmt.Errorf("bad format for device path: %s", val) + if !strings.HasPrefix(key, "/dev/") { + return nil, fmt.Errorf("bad format for device path: %s", dev) } - rate, err := strconv.ParseUint(split[1], 10, 64) + rate, err := strconv.ParseUint(val, 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) + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :. Number must be a positive integer", dev) } - td[split[0]] = specs.LinuxThrottleDevice{Rate: rate} + td[key] = specs.LinuxThrottleDevice{Rate: rate} } return td, nil } @@ -1103,42 +1106,42 @@ func parseSecrets(secrets []string) ([]specgen.Secret, map[string]string, error) } for _, val := range split { - kv := strings.SplitN(val, "=", 2) - if len(kv) < 2 { + name, value, hasValue := strings.Cut(val, "=") + if !hasValue { return nil, nil, fmt.Errorf("option %s must be in form option=value: %w", val, secretParseError) } - switch kv[0] { + switch name { case "source": - source = kv[1] + source = value case "type": if secretType != "" { return nil, nil, fmt.Errorf("cannot set more than one secret type: %w", secretParseError) } - if kv[1] != "mount" && kv[1] != "env" { - return nil, nil, fmt.Errorf("type %s is invalid: %w", kv[1], secretParseError) + if value != "mount" && value != "env" { + return nil, nil, fmt.Errorf("type %s is invalid: %w", value, secretParseError) } - secretType = kv[1] + secretType = value case "target": - target = kv[1] + target = value case "mode": mountOnly = true - mode64, err := strconv.ParseUint(kv[1], 8, 32) + mode64, err := strconv.ParseUint(value, 8, 32) if err != nil { - return nil, nil, fmt.Errorf("mode %s invalid: %w", kv[1], secretParseError) + return nil, nil, fmt.Errorf("mode %s invalid: %w", value, secretParseError) } mode = uint32(mode64) case "uid", "UID": mountOnly = true - uid64, err := strconv.ParseUint(kv[1], 10, 32) + uid64, err := strconv.ParseUint(value, 10, 32) if err != nil { - return nil, nil, fmt.Errorf("UID %s invalid: %w", kv[1], secretParseError) + return nil, nil, fmt.Errorf("UID %s invalid: %w", value, secretParseError) } uid = uint32(uid64) case "gid", "GID": mountOnly = true - gid64, err := strconv.ParseUint(kv[1], 10, 32) + gid64, err := strconv.ParseUint(value, 10, 32) if err != nil { - return nil, nil, fmt.Errorf("GID %s invalid: %w", kv[1], secretParseError) + return nil, nil, fmt.Errorf("GID %s invalid: %w", value, secretParseError) } gid = uint32(gid64) @@ -1203,17 +1206,17 @@ func parseLinuxResourcesDeviceAccess(device string) (specs.LinuxDeviceCgroup, er return specs.LinuxDeviceCgroup{}, fmt.Errorf("invalid device type in device-access-add: %s", devType) } - number := strings.SplitN(value[1], ":", 2) - if number[0] != "*" { - i, err := strconv.ParseUint(number[0], 10, 64) + majorNumber, minorNumber, hasMinor := strings.Cut(value[1], ":") + if majorNumber != "*" { + i, err := strconv.ParseUint(majorNumber, 10, 64) if err != nil { return specs.LinuxDeviceCgroup{}, err } m := int64(i) major = &m } - if len(number) == 2 && number[1] != "*" { - i, err := strconv.ParseUint(number[1], 10, 64) + if hasMinor && minorNumber != "*" { + i, err := strconv.ParseUint(minorNumber, 10, 64) if err != nil { return specs.LinuxDeviceCgroup{}, err } @@ -1263,11 +1266,11 @@ func GetResources(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) unifieds := make(map[string]string) for _, unified := range c.CgroupConf { - splitUnified := strings.SplitN(unified, "=", 2) - if len(splitUnified) < 2 { + key, val, hasVal := strings.Cut(unified, "=") + if !hasVal { return nil, errors.New("--cgroup-conf must be formatted KEY=VALUE") } - unifieds[splitUnified[0]] = splitUnified[1] + unifieds[key] = val } if len(unifieds) > 0 { s.ResourceLimits.Unified = unifieds diff --git a/pkg/specgenutil/volumes.go b/pkg/specgenutil/volumes.go index 4e663f6e73..5155e779fd 100644 --- a/pkg/specgenutil/volumes.go +++ b/pkg/specgenutil/volumes.go @@ -256,29 +256,29 @@ func parseMountOptions(mountType string, args []string) (*spec.Mount, error) { var setTmpcopyup, setRORW, setSuid, setDev, setExec, setRelabel, setOwnership, setSwap bool mnt := spec.Mount{} - for _, val := range args { - kv := strings.SplitN(val, "=", 2) - switch kv[0] { + for _, arg := range args { + name, value, hasValue := strings.Cut(arg, "=") + switch name { case "bind-nonrecursive": if mountType != define.TypeBind { - return nil, fmt.Errorf("%q option not supported for %q mount types", kv[0], mountType) + return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType) } mnt.Options = append(mnt.Options, define.TypeBind) case "bind-propagation": if mountType != define.TypeBind { - return nil, fmt.Errorf("%q option not supported for %q mount types", kv[0], mountType) + return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType) } - if len(kv) == 1 { - return nil, fmt.Errorf("%v: %w", kv[0], errOptionArg) + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) } - mnt.Options = append(mnt.Options, kv[1]) + mnt.Options = append(mnt.Options, value) case "consistency": // Often used on MACs and mistakenly on Linux platforms. // Since Docker ignores this option so shall we. continue case "idmap": - if len(kv) > 1 { - mnt.Options = append(mnt.Options, fmt.Sprintf("idmap=%s", kv[1])) + if hasValue { + mnt.Options = append(mnt.Options, fmt.Sprintf("idmap=%s", value)) } else { mnt.Options = append(mnt.Options, "idmap") } @@ -294,42 +294,41 @@ func parseMountOptions(mountType string, args []string) (*spec.Mount, error) { // ro=[true|false] // rw // rw=[true|false] - if kv[0] == "readonly" { - kv[0] = "ro" - } - switch len(kv) { - case 1: - mnt.Options = append(mnt.Options, kv[0]) - case 2: - switch strings.ToLower(kv[1]) { + if name == "readonly" { + name = "ro" + } + if hasValue { + switch strings.ToLower(value) { case "true": - mnt.Options = append(mnt.Options, kv[0]) + mnt.Options = append(mnt.Options, name) case "false": // Set the opposite only for rw // ro's opposite is the default - if kv[0] == "rw" { + if name == "rw" { mnt.Options = append(mnt.Options, "ro") } } + } else { + mnt.Options = append(mnt.Options, name) } case "nodev", "dev": if setDev { return nil, fmt.Errorf("cannot pass 'nodev' and 'dev' mnt.Options more than once: %w", errOptionArg) } setDev = true - mnt.Options = append(mnt.Options, kv[0]) + mnt.Options = append(mnt.Options, name) case "noexec", "exec": if setExec { return nil, fmt.Errorf("cannot pass 'noexec' and 'exec' mnt.Options more than once: %w", errOptionArg) } setExec = true - mnt.Options = append(mnt.Options, kv[0]) + mnt.Options = append(mnt.Options, name) case "nosuid", "suid": if setSuid { return nil, fmt.Errorf("cannot pass 'nosuid' and 'suid' mnt.Options more than once: %w", errOptionArg) } setSuid = true - mnt.Options = append(mnt.Options, kv[0]) + mnt.Options = append(mnt.Options, name) case "noswap": if setSwap { return nil, fmt.Errorf("cannot pass 'noswap' mnt.Options more than once: %w", errOptionArg) @@ -338,80 +337,80 @@ func parseMountOptions(mountType string, args []string) (*spec.Mount, error) { return nil, fmt.Errorf("the 'noswap' option is only allowed with rootful tmpfs mounts: %w", errOptionArg) } setSwap = true - mnt.Options = append(mnt.Options, kv[0]) + mnt.Options = append(mnt.Options, name) case "relabel": if setRelabel { return nil, fmt.Errorf("cannot pass 'relabel' option more than once: %w", errOptionArg) } setRelabel = true - if len(kv) != 2 { - return nil, fmt.Errorf("%s mount option must be 'private' or 'shared': %w", kv[0], util.ErrBadMntOption) + if !hasValue { + return nil, fmt.Errorf("%s mount option must be 'private' or 'shared': %w", name, util.ErrBadMntOption) } - switch kv[1] { + switch value { case "private": mnt.Options = append(mnt.Options, "Z") case "shared": mnt.Options = append(mnt.Options, "z") default: - return nil, fmt.Errorf("%s mount option must be 'private' or 'shared': %w", kv[0], util.ErrBadMntOption) + return nil, fmt.Errorf("%s mount option must be 'private' or 'shared': %w", name, util.ErrBadMntOption) } case "shared", "rshared", "private", "rprivate", "slave", "rslave", "unbindable", "runbindable", "Z", "z", "no-dereference": - mnt.Options = append(mnt.Options, kv[0]) + mnt.Options = append(mnt.Options, name) case "src", "source": if mountType == define.TypeTmpfs { - return nil, fmt.Errorf("%q option not supported for %q mount types", kv[0], mountType) + return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType) } if mnt.Source != "" { - return nil, fmt.Errorf("cannot pass %q option more than once: %w", kv[0], errOptionArg) + return nil, fmt.Errorf("cannot pass %q option more than once: %w", name, errOptionArg) } - if len(kv) == 1 { - return nil, fmt.Errorf("%v: %w", kv[0], errOptionArg) + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) } - if len(kv[1]) == 0 { + if len(value) == 0 { return nil, fmt.Errorf("host directory cannot be empty: %w", errOptionArg) } - mnt.Source = kv[1] + mnt.Source = value case "target", "dst", "destination": if mnt.Destination != "" { - return nil, fmt.Errorf("cannot pass %q option more than once: %w", kv[0], errOptionArg) + return nil, fmt.Errorf("cannot pass %q option more than once: %w", name, errOptionArg) } - if len(kv) == 1 { - return nil, fmt.Errorf("%v: %w", kv[0], errOptionArg) + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) } - if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { + if err := parse.ValidateVolumeCtrDir(value); err != nil { return nil, err } - mnt.Destination = unixPathClean(kv[1]) + mnt.Destination = unixPathClean(value) case "tmpcopyup", "notmpcopyup": if mountType != define.TypeTmpfs { - return nil, fmt.Errorf("%q option not supported for %q mount types", kv[0], mountType) + return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType) } if setTmpcopyup { return nil, fmt.Errorf("cannot pass 'tmpcopyup' and 'notmpcopyup' mnt.Options more than once: %w", errOptionArg) } setTmpcopyup = true - mnt.Options = append(mnt.Options, kv[0]) + mnt.Options = append(mnt.Options, name) case "tmpfs-mode": if mountType != define.TypeTmpfs { - return nil, fmt.Errorf("%q option not supported for %q mount types", kv[0], mountType) + return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType) } - if len(kv) == 1 { - return nil, fmt.Errorf("%v: %w", kv[0], errOptionArg) + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) } - mnt.Options = append(mnt.Options, fmt.Sprintf("mode=%s", kv[1])) + mnt.Options = append(mnt.Options, fmt.Sprintf("mode=%s", value)) case "tmpfs-size": if mountType != define.TypeTmpfs { - return nil, fmt.Errorf("%q option not supported for %q mount types", kv[0], mountType) + return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType) } - if len(kv) == 1 { - return nil, fmt.Errorf("%v: %w", kv[0], errOptionArg) + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) } - mnt.Options = append(mnt.Options, fmt.Sprintf("size=%s", kv[1])) + mnt.Options = append(mnt.Options, fmt.Sprintf("size=%s", value)) case "U", "chown": if setOwnership { return nil, fmt.Errorf("cannot pass 'U' or 'chown' option more than once: %w", errOptionArg) } - ok, err := validChownFlag(val) + ok, err := validChownFlag(value) if err != nil { return nil, err } @@ -421,16 +420,16 @@ func parseMountOptions(mountType string, args []string) (*spec.Mount, error) { setOwnership = true case "volume-label": if mountType != define.TypeVolume { - return nil, fmt.Errorf("%q option not supported for %q mount types", kv[0], mountType) + return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType) } return nil, fmt.Errorf("the --volume-label option is not presently implemented") case "volume-opt": if mountType != define.TypeVolume { - return nil, fmt.Errorf("%q option not supported for %q mount types", kv[0], mountType) + return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType) } - mnt.Options = append(mnt.Options, val) + mnt.Options = append(mnt.Options, arg) default: - return nil, fmt.Errorf("%s: %w", kv[0], util.ErrBadMntOption) + return nil, fmt.Errorf("%s: %w", name, util.ErrBadMntOption) } } if mountType != "glob" && len(mnt.Destination) == 0 { @@ -534,22 +533,22 @@ func getDevptsMount(args []string) (spec.Mount, error) { var setDest bool - for _, val := range args { - kv := strings.SplitN(val, "=", 2) - switch kv[0] { + for _, arg := range args { + name, value, hasValue := strings.Cut(arg, "=") + switch name { case "uid", "gid", "mode", "ptxmode", "newinstance", "max": - newMount.Options = append(newMount.Options, val) + newMount.Options = append(newMount.Options, arg) case "target", "dst", "destination": - if len(kv) == 1 { - return newMount, fmt.Errorf("%v: %w", kv[0], errOptionArg) + if !hasValue { + return newMount, fmt.Errorf("%v: %w", name, errOptionArg) } - if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { + if err := parse.ValidateVolumeCtrDir(value); err != nil { return newMount, err } - newMount.Destination = unixPathClean(kv[1]) + newMount.Destination = unixPathClean(value) setDest = true default: - return newMount, fmt.Errorf("%s: %w", kv[0], util.ErrBadMntOption) + return newMount, fmt.Errorf("%s: %w", name, util.ErrBadMntOption) } } @@ -586,37 +585,37 @@ func getNamedVolume(args []string) (*specgen.NamedVolume, error) { func getImageVolume(args []string) (*specgen.ImageVolume, error) { newVolume := new(specgen.ImageVolume) - for _, val := range args { - kv := strings.SplitN(val, "=", 2) - switch kv[0] { + for _, arg := range args { + name, value, hasValue := strings.Cut(arg, "=") + switch name { case "src", "source": - if len(kv) == 1 { - return nil, fmt.Errorf("%v: %w", kv[0], errOptionArg) + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) } - newVolume.Source = kv[1] + newVolume.Source = value case "target", "dst", "destination": - if len(kv) == 1 { - return nil, fmt.Errorf("%v: %w", kv[0], errOptionArg) + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) } - if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { + if err := parse.ValidateVolumeCtrDir(value); err != nil { return nil, err } - newVolume.Destination = unixPathClean(kv[1]) + newVolume.Destination = unixPathClean(value) case "rw", "readwrite": - switch kv[1] { + switch value { case "true": newVolume.ReadWrite = true case "false": // Nothing to do. RO is default. default: - return nil, fmt.Errorf("invalid rw value %q: %w", kv[1], util.ErrBadMntOption) + return nil, fmt.Errorf("invalid rw value %q: %w", value, util.ErrBadMntOption) } case "consistency": // Often used on MACs and mistakenly on Linux platforms. // Since Docker ignores this option so shall we. continue default: - return nil, fmt.Errorf("%s: %w", kv[0], util.ErrBadMntOption) + return nil, fmt.Errorf("%s: %w", name, util.ErrBadMntOption) } } @@ -660,24 +659,16 @@ func getTmpfsMounts(tmpfsFlag []string) (map[string]spec.Mount, error) { } // validChownFlag ensures that the U or chown flag is correctly used -func validChownFlag(flag string) (bool, error) { - kv := strings.SplitN(flag, "=", 2) - switch len(kv) { - case 1: - case 2: - // U=[true|false] - switch strings.ToLower(kv[1]) { - case "true": - case "false": - return false, nil - default: - return false, fmt.Errorf("'U' or 'chown' must be set to true or false, instead received %q: %w", kv[1], errOptionArg) - } +func validChownFlag(value string) (bool, error) { + // U=[true|false] + switch { + case strings.EqualFold(value, "true"), value == "": + return true, nil + case strings.EqualFold(value, "false"): + return false, nil default: - return false, fmt.Errorf("badly formatted option %q: %w", flag, errOptionArg) + return false, fmt.Errorf("'U' or 'chown' must be set to true or false, instead received %q: %w", value, errOptionArg) } - - return true, nil } // Use path instead of filepath to preserve Unix style paths on Windows diff --git a/pkg/specgenutil/volumes_test.go b/pkg/specgenutil/volumes_test.go index 712d9fd455..18009a8e99 100644 --- a/pkg/specgenutil/volumes_test.go +++ b/pkg/specgenutil/volumes_test.go @@ -13,41 +13,41 @@ func Test_validChownFlag(t *testing.T) { wantErr bool }{ { - name: "U true", + name: "lower-case true", args: args{ - flag: "U=true", + flag: "true", }, want: true, wantErr: false, }, { - name: "U true case does not matter", + name: "case-insensitive true", args: args{ - flag: "u=True", + flag: "True", }, want: true, wantErr: false, }, { - name: "U is false", + name: "lower-case false", args: args{ - flag: "U=false", + flag: "false", }, want: false, wantErr: false, }, { - name: "chown should also work", + name: "case-insensitive false", args: args{ - flag: "chown=true", + flag: "falsE", }, - want: true, + want: false, wantErr: false, }, { name: "garbage value should fail", args: args{ - flag: "U=foobar", + flag: "foobar", }, want: false, wantErr: true, diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go index 821c993df4..e7bb77d6db 100644 --- a/pkg/systemd/generate/containers.go +++ b/pkg/systemd/generate/containers.go @@ -471,8 +471,8 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst // because it does try to red the value from the environment if !strings.Contains(env, "=") { for _, containerEnv := range info.containerEnv { - split := strings.SplitN(containerEnv, "=", 2) - if split[0] == env { + key, _, _ := strings.Cut(containerEnv, "=") + if key == env { info.ExtraEnvs = append(info.ExtraEnvs, escapeSystemdArg(containerEnv)) } } diff --git a/pkg/systemd/quadlet/quadlet.go b/pkg/systemd/quadlet/quadlet.go index a7845cade9..e18857aa7d 100644 --- a/pkg/systemd/quadlet/quadlet.go +++ b/pkg/systemd/quadlet/quadlet.go @@ -1118,10 +1118,10 @@ func ConvertKube(kube *parser.UnitFile, names map[string]string, isUser bool) (* for _, update := range updateMaps { annotation := fmt.Sprintf("--annotation=%s", autoUpdateLabel) updateType := update - val := strings.SplitN(update, "/", 2) - if len(val) == 2 { - annotation = annotation + "/" + val[0] - updateType = val[1] + annoValue, typ, hasSlash := strings.Cut(update, "/") + if hasSlash { + annotation = annotation + "/" + annoValue + updateType = typ } execStart.addf("%s=%s", annotation, updateType) } @@ -1741,13 +1741,13 @@ func resolveContainerMountParams(containerUnitFile, serviceUnitFile *parser.Unit sourceIndex := -1 originalSource := "" for i, token := range tokens { - kv := strings.SplitN(token, "=", 2) - if kv[0] == "source" || kv[0] == "src" { - if len(kv) < 2 { + key, val, hasVal := strings.Cut(token, "=") + if key == "source" || key == "src" { + if !hasVal { return "", fmt.Errorf("source parameter does not include a value") } sourceIndex = i - originalSource = kv[1] + originalSource = val } } diff --git a/pkg/timetype/timestamp.go b/pkg/timetype/timestamp.go index 4defa32ea6..3c70ac971f 100644 --- a/pkg/timetype/timestamp.go +++ b/pkg/timetype/timestamp.go @@ -116,19 +116,19 @@ func ParseTimestamps(value string, def int64) (int64, int64, error) { } func parseTimestamp(value string) (int64, int64, error) { - sa := strings.SplitN(value, ".", 2) - s, err := strconv.ParseInt(sa[0], 10, 64) + spart, npart, hasParts := strings.Cut(value, ".") + s, err := strconv.ParseInt(spart, 10, 64) if err != nil { return s, 0, err } - if len(sa) != 2 { + if !hasParts { return s, 0, nil } - n, err := strconv.ParseInt(sa[1], 10, 64) + n, err := strconv.ParseInt(npart, 10, 64) if err != nil { return s, n, err } // should already be in nanoseconds but just in case convert n to nanoseconds - n = int64(float64(n) * math.Pow(float64(10), float64(9-len(sa[1])))) + n = int64(float64(n) * math.Pow(float64(10), float64(9-len(npart)))) return s, n, nil } diff --git a/pkg/trust/policy.go b/pkg/trust/policy.go index b5d8e7a41c..1424ffe91e 100644 --- a/pkg/trust/policy.go +++ b/pkg/trust/policy.go @@ -131,8 +131,11 @@ func parseUids(colonDelimitKeys []byte) []string { continue } parseduid := uid - if strings.Contains(uid, "<") && strings.Contains(uid, ">") { - parseduid = strings.SplitN(strings.SplitAfterN(uid, "<", 2)[1], ">", 2)[0] + if ltidx := strings.Index(uid, "<"); ltidx != -1 { + subuid := parseduid[ltidx+1:] + if gtidx := strings.Index(subuid, ">"); gtidx != -1 { + parseduid = subuid[:gtidx] + } } parseduids = append(parseduids, parseduid) } diff --git a/pkg/util/filters.go b/pkg/util/filters.go index 765b5c4db0..d1e33b1081 100644 --- a/pkg/util/filters.go +++ b/pkg/util/filters.go @@ -65,9 +65,9 @@ func PrepareFilters(r *http.Request) (*map[string][]string, error) { } filterMap := map[string][]string{} for _, filter := range filtersList { - split := strings.SplitN(filter, "=", 2) - if len(split) > 1 { - filterMap[split[0]] = append(filterMap[split[0]], split[1]) + fname, filter, hasFilter := strings.Cut(filter, "=") + if hasFilter { + filterMap[fname] = append(filterMap[fname], filter) } } return &filterMap, nil diff --git a/pkg/util/mountOpts.go b/pkg/util/mountOpts.go index a35ac77f77..2b515aa5df 100644 --- a/pkg/util/mountOpts.go +++ b/pkg/util/mountOpts.go @@ -34,7 +34,7 @@ func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string newOptions := make([]string, 0, len(options)) for _, opt := range options { // Some options have parameters - size, mode - splitOpt := strings.SplitN(opt, "=", 2) + key, _, _ := strings.Cut(opt, "=") // add advanced options such as upperdir=/path and workdir=/path, when overlay is specified if foundOverlay { @@ -47,11 +47,11 @@ func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string continue } } - if strings.HasPrefix(splitOpt[0], "subpath") { + if strings.HasPrefix(key, "subpath") { newOptions = append(newOptions, opt) continue } - if strings.HasPrefix(splitOpt[0], "idmap") { + if strings.HasPrefix(key, "idmap") { if foundIdmap { return nil, fmt.Errorf("the 'idmap' option can only be set once: %w", ErrDupeMntOption) } @@ -60,7 +60,7 @@ func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string continue } - switch splitOpt[0] { + switch key { case "copy", "nocopy": if foundCopy { return nil, fmt.Errorf("only one of 'nocopy' and 'copy' can be used: %w", ErrDupeMntOption) @@ -210,13 +210,13 @@ func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string } func ParseDriverOpts(option string) (string, string, error) { - token := strings.SplitN(option, "=", 2) - if len(token) != 2 { + _, val, hasVal := strings.Cut(option, "=") + if !hasVal { return "", "", fmt.Errorf("cannot parse driver opts: %w", ErrBadMntOption) } - opt := strings.SplitN(token[1], "=", 2) - if len(opt) != 2 { + optKey, optVal, hasOptVal := strings.Cut(val, "=") + if !hasOptVal { return "", "", fmt.Errorf("cannot parse driver opts: %w", ErrBadMntOption) } - return opt[0], opt[1], nil + return optKey, optVal, nil } diff --git a/pkg/util/utils.go b/pkg/util/utils.go index f9faf07fd1..0eefd60559 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -55,14 +55,8 @@ func init() { // Helper function to determine the username/password passed // in the creds string. It could be either or both. func parseCreds(creds string) (string, string) { - if creds == "" { - return "", "" - } - up := strings.SplitN(creds, ":", 2) - if len(up) == 1 { - return up[0], "" - } - return up[0], up[1] + username, password, _ := strings.Cut(creds, ":") + return username, password } // Takes build context and validates `.containerignore` or `.dockerignore` @@ -918,12 +912,12 @@ func parseAutoIDMap(mapSpec string, mapSetting string, parentMapping []ruser.IDM // GetAutoOptions returns an AutoUserNsOptions with the settings to automatically set up // a user namespace. func GetAutoOptions(n namespaces.UsernsMode) (*stypes.AutoUserNsOptions, error) { - parts := strings.SplitN(string(n), ":", 2) - if parts[0] != "auto" { + mode, opts, hasOpts := strings.Cut(string(n), ":") + if mode != "auto" { return nil, fmt.Errorf("wrong user namespace mode") } options := stypes.AutoUserNsOptions{} - if len(parts) == 1 { + if !hasOpts { return &options, nil } @@ -937,32 +931,32 @@ func GetAutoOptions(n namespaces.UsernsMode) (*stypes.AutoUserNsOptions, error) } } - for _, o := range strings.Split(parts[1], ",") { - v := strings.SplitN(o, "=", 2) - if len(v) != 2 { + for _, o := range strings.Split(opts, ",") { + key, val, hasVal := strings.Cut(o, "=") + if !hasVal { return nil, fmt.Errorf("invalid option specified: %q", o) } - switch v[0] { + switch key { case "size": - s, err := strconv.ParseUint(v[1], 10, 32) + s, err := strconv.ParseUint(val, 10, 32) if err != nil { return nil, err } options.Size = uint32(s) case "uidmapping": - mapping, err := parseAutoIDMap(v[1], "UID", parentUIDMap) + mapping, err := parseAutoIDMap(val, "UID", parentUIDMap) if err != nil { return nil, err } options.AdditionalUIDMappings = append(options.AdditionalUIDMappings, mapping...) case "gidmapping": - mapping, err := parseAutoIDMap(v[1], "GID", parentGIDMap) + mapping, err := parseAutoIDMap(val, "GID", parentGIDMap) if err != nil { return nil, err } options.AdditionalGIDMappings = append(options.AdditionalGIDMappings, mapping...) default: - return nil, fmt.Errorf("unknown option specified: %q", v[0]) + return nil, fmt.Errorf("unknown option specified: %q", key) } } return &options, nil @@ -1077,9 +1071,9 @@ func getTomlStorage(storeOptions *stypes.StoreOptions) *tomlConfig { config.Storage.RunRoot = storeOptions.RunRoot config.Storage.GraphRoot = storeOptions.GraphRoot for _, i := range storeOptions.GraphDriverOptions { - s := strings.SplitN(i, "=", 2) - if s[0] == "overlay.mount_program" && len(s) == 2 { - config.Storage.Options.MountProgram = s[1] + program, hasPrefix := strings.CutPrefix(i, "overlay.mount_program=") + if hasPrefix { + config.Storage.Options.MountProgram = program } } diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index f3f02da3d8..d04e7e38c8 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -209,12 +209,11 @@ var _ = SynchronizedAfterSuite(func() { scanner := bufio.NewScanner(f) for scanner.Scan() { text := scanner.Text() - timing := strings.SplitN(text, "\t\t", 2) - if len(timing) != 2 { + name, durationString, ok := strings.Cut(text, "\t\t") + if !ok { Fail(fmt.Sprintf("incorrect timing line: %q", text)) } - name := timing[0] - duration, err := strconv.ParseFloat(timing[1], 64) + duration, err := strconv.ParseFloat(durationString, 64) Expect(err).ToNot(HaveOccurred(), "failed to parse float from timings file") testTimings = append(testTimings, testResult{name: name, length: duration}) } diff --git a/test/e2e/quadlet_test.go b/test/e2e/quadlet_test.go index 8fac352f2b..ef0057da25 100644 --- a/test/e2e/quadlet_test.go +++ b/test/e2e/quadlet_test.go @@ -202,12 +202,8 @@ func keyValueStringToMap(keyValueString, separator string) (map[string]string, e return nil, err } for _, param := range keyVarList[0] { - val := "" - kv := strings.SplitN(param, "=", 2) - if len(kv) == 2 { - val = kv[1] - } - keyValMap[kv[0]] = val + key, val, _ := strings.Cut(param, "=") + keyValMap[key] = val } return keyValMap, nil