Replace strings.SplitN with strings.Cut

Cut is a cleaner & more performant api relative to SplitN(_, _, 2) added in go 1.18

Previously applied this refactoring to buildah:
https://github.com/containers/buildah/pull/5239

Signed-off-by: Philip Dubé <philip@peerdb.io>
This commit is contained in:
Philip Dubé 2024-01-02 18:31:25 +00:00
parent f1ea4fbb3d
commit 522934d5cf
56 changed files with 596 additions and 678 deletions

View File

@ -333,15 +333,15 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *Buil
} }
if c.Flag("build-arg").Changed { if c.Flag("build-arg").Changed {
for _, arg := range flags.BuildArg { for _, arg := range flags.BuildArg {
av := strings.SplitN(arg, "=", 2) key, val, hasVal := strings.Cut(arg, "=")
if len(av) > 1 { if hasVal {
args[av[0]] = av[1] args[key] = val
} else { } else {
// check if the env is set in the local environment and use that value if it is // 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 { if val, present := os.LookupEnv(key); present {
args[av[0]] = val args[key] = val
} else { } 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) additionalBuildContext := make(map[string]*buildahDefine.AdditionalBuildContext)
if c.Flag("build-context").Changed { if c.Flag("build-context").Changed {
for _, contextString := range flags.BuildContext { for _, contextString := range flags.BuildContext {
av := strings.SplitN(contextString, "=", 2) key, val, hasVal := strings.Cut(contextString, "=")
if len(av) > 1 { if hasVal {
parseAdditionalBuildContext, err := parse.GetAdditionalBuildContext(av[1]) parseAdditionalBuildContext, err := parse.GetAdditionalBuildContext(val)
if err != nil { if err != nil {
return nil, fmt.Errorf("while parsing additional build context: %w", err) return nil, fmt.Errorf("while parsing additional build context: %w", err)
} }
additionalBuildContext[av[0]] = &parseAdditionalBuildContext additionalBuildContext[key] = &parseAdditionalBuildContext
} else { } 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)
} }
} }
} }

View File

@ -423,9 +423,9 @@ func prefixSlice(pre string, slice []string) []string {
func suffixCompSlice(suf string, slice []string) []string { func suffixCompSlice(suf string, slice []string) []string {
for i := range slice { for i := range slice {
split := strings.SplitN(slice[i], "\t", 2) key, val, hasVal := strings.Cut(slice[i], "\t")
if len(split) > 1 { if hasVal {
slice[i] = split[0] + suf + "\t" + split[1] slice[i] = key + suf + "\t" + val
} else { } else {
slice[i] += suf slice[i] += suf
} }
@ -878,10 +878,10 @@ func AutocompleteScp(cmd *cobra.Command, args []string, toComplete string) ([]st
} }
switch len(args) { switch len(args) {
case 0: case 0:
split := strings.SplitN(toComplete, "::", 2) prefix, imagesToComplete, isImages := strings.Cut(toComplete, "::")
if len(split) > 1 { if isImages {
imageSuggestions, _ := getImages(cmd, split[1]) imageSuggestions, _ := getImages(cmd, imagesToComplete)
return prefixSlice(split[0]+"::", imageSuggestions), cobra.ShellCompDirectiveNoFileComp return prefixSlice(prefix+"::", imageSuggestions), cobra.ShellCompDirectiveNoFileComp
} }
connectionSuggestions, _ := AutocompleteSystemConnections(cmd, args, toComplete) connectionSuggestions, _ := AutocompleteSystemConnections(cmd, args, toComplete)
imageSuggestions, _ := getImages(cmd, toComplete) imageSuggestions, _ := getImages(cmd, toComplete)
@ -893,9 +893,9 @@ func AutocompleteScp(cmd *cobra.Command, args []string, toComplete string) ([]st
} }
return totalSuggestions, directive return totalSuggestions, directive
case 1: case 1:
split := strings.SplitN(args[0], "::", 2) _, imagesToComplete, isImages := strings.Cut(args[0], "::")
if len(split) > 1 { if isImages {
if len(split[1]) > 0 { if len(imagesToComplete) > 0 {
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
} }
imageSuggestions, _ := getImages(cmd, toComplete) imageSuggestions, _ := getImages(cmd, toComplete)
@ -1076,7 +1076,7 @@ func AutocompleteUserFlag(cmd *cobra.Command, args []string, toComplete string)
var groups []string var groups []string
scanner := bufio.NewScanner(file) scanner := bufio.NewScanner(file)
user := strings.SplitN(toComplete, ":", 2)[0] user, _, _ := strings.Cut(toComplete, ":")
for scanner.Scan() { for scanner.Scan() {
entries := strings.SplitN(scanner.Text(), ":", 4) entries := strings.SplitN(scanner.Text(), ":", 4)
groups = append(groups, user+":"+entries[0]) groups = append(groups, user+":"+entries[0])

View File

@ -342,10 +342,10 @@ func PullImage(imageName string, cliVals *entities.ContainerCreateOptions) (stri
if cliVals.Arch != "" || cliVals.OS != "" { if cliVals.Arch != "" || cliVals.OS != "" {
return "", errors.New("--platform option can not be specified with --arch or --os") return "", errors.New("--platform option can not be specified with --arch or --os")
} }
split := strings.SplitN(cliVals.Platform, "/", 2) OS, Arch, hasArch := strings.Cut(cliVals.Platform, "/")
cliVals.OS = split[0] cliVals.OS = OS
if len(split) > 1 { if hasArch {
cliVals.Arch = split[1] cliVals.Arch = Arch
} }
} }
} }

View File

@ -102,11 +102,11 @@ func pause(cmd *cobra.Command, args []string) error {
} }
for _, f := range filters { for _, f := range filters {
split := strings.SplitN(f, "=", 2) fname, filter, hasFilter := strings.Cut(f, "=")
if len(split) < 2 { if !hasFilter {
return fmt.Errorf("invalid filter %q", f) 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) responses, err := registry.ContainerEngine().ContainerPause(context.Background(), args, pauseOpts)

View File

@ -195,11 +195,11 @@ func ps(cmd *cobra.Command, _ []string) error {
} }
for _, f := range filters { for _, f := range filters {
split := strings.SplitN(f, "=", 2) fname, filter, hasFilter := strings.Cut(f, "=")
if len(split) == 1 { if !hasFilter {
return fmt.Errorf("invalid filter %q", f) 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() listContainers, err := getResponses()
if err != nil { if err != nil {

View File

@ -114,11 +114,11 @@ func restart(cmd *cobra.Command, args []string) error {
} }
for _, f := range filters { for _, f := range filters {
split := strings.SplitN(f, "=", 2) fname, filter, hasFilter := strings.Cut(f, "=")
if len(split) < 2 { if !hasFilter {
return fmt.Errorf("invalid filter %q", f) 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) responses, err := registry.ContainerEngine().ContainerRestart(context.Background(), args, restartOpts)

View File

@ -116,16 +116,16 @@ func rm(cmd *cobra.Command, args []string) error {
} }
return fmt.Errorf("reading CIDFile: %w", err) return fmt.Errorf("reading CIDFile: %w", err)
} }
id := strings.Split(string(content), "\n")[0] id, _, _ := strings.Cut(string(content), "\n")
args = append(args, id) args = append(args, id)
} }
for _, f := range filters { for _, f := range filters {
split := strings.SplitN(f, "=", 2) fname, filter, hasFilter := strings.Cut(f, "=")
if len(split) < 2 { if !hasFilter {
return fmt.Errorf("invalid filter %q", f) 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 { if rmOptions.All {

View File

@ -124,11 +124,11 @@ func start(cmd *cobra.Command, args []string) error {
containers := utils.RemoveSlash(args) containers := utils.RemoveSlash(args)
for _, f := range filters { for _, f := range filters {
split := strings.SplitN(f, "=", 2) fname, filter, hasFilter := strings.Cut(f, "=")
if len(split) < 2 { if !hasFilter {
return fmt.Errorf("invalid filter %q", f) 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) responses, err := registry.ContainerEngine().ContainerStart(registry.GetContext(), containers, startOptions)

View File

@ -119,11 +119,11 @@ func stop(cmd *cobra.Command, args []string) error {
} }
for _, f := range filters { for _, f := range filters {
split := strings.SplitN(f, "=", 2) fname, filter, hasFilter := strings.Cut(f, "=")
if len(split) < 2 { if !hasFilter {
return fmt.Errorf("invalid filter %q", f) 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) responses, err := registry.ContainerEngine().ContainerStop(context.Background(), args, stopOptions)

View File

@ -110,11 +110,11 @@ func unpause(cmd *cobra.Command, args []string) error {
} }
for _, f := range filters { for _, f := range filters {
split := strings.SplitN(f, "=", 2) fname, filter, hasFilter := strings.Cut(f, "=")
if len(split) < 2 { if !hasFilter {
return fmt.Errorf("invalid filter %q", f) 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) responses, err := registry.ContainerEngine().ContainerUnpause(context.Background(), args, unpauseOpts)

View File

@ -149,10 +149,14 @@ func imagePull(cmd *cobra.Command, args []string) error {
if pullOptions.Arch != "" || pullOptions.OS != "" { if pullOptions.Arch != "" || pullOptions.OS != "" {
return errors.New("--platform option can not be specified with --arch or --os") return errors.New("--platform option can not be specified with --arch or --os")
} }
split := strings.SplitN(platform, "/", 2)
pullOptions.OS = split[0] specs := strings.Split(platform, "/")
if len(split) > 1 { pullOptions.OS = specs[0] // may be empty
pullOptions.Arch = split[1] if len(specs) > 1 {
pullOptions.Arch = specs[1]
if len(specs) > 2 {
pullOptions.Variant = specs[2]
}
} }
} }

View File

@ -246,18 +246,17 @@ func play(cmd *cobra.Command, args []string) error {
} }
for _, annotation := range playOptions.annotations { for _, annotation := range playOptions.annotations {
splitN := strings.SplitN(annotation, "=", 2) key, val, hasVal := strings.Cut(annotation, "=")
if len(splitN) != 2 { if !hasVal {
return fmt.Errorf("annotation %q must include an '=' sign", annotation) return fmt.Errorf("annotation %q must include an '=' sign", annotation)
} }
if playOptions.Annotations == nil { if playOptions.Annotations == nil {
playOptions.Annotations = make(map[string]string) playOptions.Annotations = make(map[string]string)
} }
annotation := splitN[1] if len(val) > define.MaxKubeAnnotation && !playOptions.UseLongAnnotations {
if len(annotation) > define.MaxKubeAnnotation && !playOptions.UseLongAnnotations { return fmt.Errorf("annotation exceeds maximum size, %d, of kubernetes annotation: %s", define.MaxKubeAnnotation, val)
return fmt.Errorf("annotation exceeds maximum size, %d, of kubernetes annotation: %s", define.MaxKubeAnnotation, annotation)
} }
playOptions.Annotations[splitN[0]] = annotation playOptions.Annotations[key] = val
} }
for _, mac := range playOptions.macs { for _, mac := range playOptions.macs {

View File

@ -245,16 +245,16 @@ func parseRoute(routeStr string) (*types.Route, error) {
} }
func parseRange(iprange string) (*types.LeaseRange, error) { func parseRange(iprange string) (*types.LeaseRange, error) {
split := strings.SplitN(iprange, "-", 2) startIPString, endIPString, hasDash := strings.Cut(iprange, "-")
if len(split) > 1 { if hasDash {
// range contains dash so assume form is start-end // range contains dash so assume form is start-end
start := net.ParseIP(split[0]) start := net.ParseIP(startIPString)
if start == nil { 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 { 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{ return &types.LeaseRange{
StartIP: start, StartIP: start,

View File

@ -9,11 +9,11 @@ import (
func FilterArgumentsIntoFilters(filters []string) (url.Values, error) { func FilterArgumentsIntoFilters(filters []string) (url.Values, error) {
parsedFilters := make(url.Values) parsedFilters := make(url.Values)
for _, f := range filters { for _, f := range filters {
t := strings.SplitN(f, "=", 2) fname, filter, hasFilter := strings.Cut(f, "=")
if len(t) < 2 { if !hasFilter {
return parsedFilters, fmt.Errorf("filter input must be in the form of filter=value: %s is invalid", f) 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 return parsedFilters, nil
} }

View File

@ -33,15 +33,15 @@ var (
// for add-host flag // for add-host flag
func ValidateExtraHost(val string) (string, error) { func ValidateExtraHost(val string) (string, error) {
// allow for IPv6 addresses in extra hosts by only splitting on first ":" // allow for IPv6 addresses in extra hosts by only splitting on first ":"
arr := strings.SplitN(val, ":", 2) name, ip, hasIP := strings.Cut(val, ":")
if len(arr) != 2 || len(arr[0]) == 0 { if !hasIP || len(name) == 0 {
return "", fmt.Errorf("bad format for add-host: %q", val) return "", fmt.Errorf("bad format for add-host: %q", val)
} }
if arr[1] == etchosts.HostGateway { if ip == etchosts.HostGateway {
return val, nil return val, nil
} }
if _, err := validateIPAddress(arr[1]); err != nil { if _, err := validateIPAddress(ip); err != nil {
return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1]) return "", fmt.Errorf("invalid IP address in add-host: %q", ip)
} }
return val, nil return val, nil
} }
@ -82,45 +82,37 @@ func GetAllLabels(labelFile, inputLabels []string) (map[string]string, error) {
} }
} }
for _, label := range inputLabels { for _, label := range inputLabels {
split := strings.SplitN(label, "=", 2) key, value, _ := strings.Cut(label, "=")
if split[0] == "" { if key == "" {
return nil, fmt.Errorf("invalid label format: %q", label) return nil, fmt.Errorf("invalid label format: %q", label)
} }
value := "" labels[key] = value
if len(split) > 1 {
value = split[1]
}
labels[split[0]] = value
} }
return labels, nil return labels, nil
} }
func parseEnvOrLabel(env map[string]string, line, configType string) error { 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" // catch invalid variables such as "=" or "=A"
if data[0] == "" { if key == "" {
return fmt.Errorf("invalid environment variable: %q", line) return fmt.Errorf("invalid environment variable: %q", line)
} }
// trim the front of a variable, but nothing else // 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) { if strings.ContainsAny(name, whiteSpaces) {
return fmt.Errorf("name %q has white spaces, poorly formatted name", name) return fmt.Errorf("name %q has white spaces, poorly formatted name", name)
} }
if len(data) > 1 { if hasVal {
env[name] = data[1] env[name] = val
} else { } else {
if strings.HasSuffix(name, "*") { if name, hasStar := strings.CutSuffix(name, "*"); hasStar {
name = strings.TrimSuffix(name, "*")
for _, e := range os.Environ() { for _, e := range os.Environ() {
part := strings.SplitN(e, "=", 2) envKey, envVal, hasEq := strings.Cut(e, "=")
if len(part) < 2 { if hasEq && strings.HasPrefix(envKey, name) {
continue env[envKey] = envVal
}
if strings.HasPrefix(part[0], name) {
env[part[0]] = part[1]
} }
} }
} else if configType == ENVType { } else if configType == ENVType {

View File

@ -81,11 +81,11 @@ func pods(cmd *cobra.Command, _ []string) error {
if cmd.Flag("filter").Changed { if cmd.Flag("filter").Changed {
psInput.Filters = make(map[string][]string) psInput.Filters = make(map[string][]string)
for _, f := range inputFilters { for _, f := range inputFilters {
split := strings.SplitN(f, "=", 2) fname, filter, hasFilter := strings.Cut(f, "=")
if len(split) < 2 { if !hasFilter {
return fmt.Errorf("filter input must be in the form of filter=value: %s is invalid", f) 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) responses, err := registry.ContainerEngine().PodPs(context.Background(), psInput)

View File

@ -261,15 +261,15 @@ func translateDest(path string) (string, error) {
if path == "" { if path == "" {
return "", nil return "", nil
} }
split := strings.SplitN(path, "=", 2) key, val, hasVal := strings.Cut(path, "=")
if len(split) == 1 { if !hasVal {
return split[0], nil return key, nil
} }
if split[0] != "host" { if key != "host" {
return "", fmt.Errorf("\"host\" is requited for --docker option") return "", fmt.Errorf("\"host\" is required for --docker option")
} }
// "host=tcp://myserver:2376,ca=~/ca-file,cert=~/cert-file,key=~/key-file" // "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 { if len(vals) > 1 {
return "", fmt.Errorf("--docker additional options %q not supported", strings.Join(vals[1:], ",")) return "", fmt.Errorf("--docker additional options %q not supported", strings.Join(vals[1:], ","))
} }

View File

@ -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? // Should we store the ENV we actually want in the spec separately?
if c.config.Spec.Process != nil { if c.config.Spec.Process != nil {
for _, e := range c.config.Spec.Process.Env { for _, e := range c.config.Spec.Process.Env {
splitEnv := strings.SplitN(e, "=", 2) key, val, _ := strings.Cut(e, "=")
importBuilder.SetEnv(splitEnv[0], splitEnv[1]) importBuilder.SetEnv(key, val)
} }
} }
// Expose ports // Expose ports

View File

@ -254,7 +254,7 @@ func (c *Container) addSharedNamespaces(g *generate.Generator) error {
} }
needEnv := true needEnv := true
for _, checkEnv := range g.Config.Process.Env { for _, checkEnv := range g.Config.Process.Env {
if strings.SplitN(checkEnv, "=", 2)[0] == "HOSTNAME" { if strings.HasPrefix(checkEnv, "HOSTNAME=") {
needEnv = false needEnv = false
break break
} }

View File

@ -73,13 +73,13 @@ func (p *Pod) GenerateForKube(ctx context.Context, getService, useLongAnnotation
return nil, servicePorts, err return nil, servicePorts, err
} }
for _, host := range infraContainer.config.ContainerNetworkConfig.HostAdd { for _, host := range infraContainer.config.ContainerNetworkConfig.HostAdd {
hostSli := strings.SplitN(host, ":", 2) hostname, ip, hasIP := strings.Cut(host, ":")
if len(hostSli) != 2 { if !hasIP {
return nil, servicePorts, errors.New("invalid hostAdd") return nil, servicePorts, errors.New("invalid hostAdd")
} }
extraHost = append(extraHost, v1.HostAlias{ extraHost = append(extraHost, v1.HostAlias{
IP: hostSli[1], IP: ip,
Hostnames: []string{hostSli[0]}, Hostnames: []string{hostname},
}) })
} }
ports, err = portMappingToContainerPort(infraContainer.config.PortMappings, getService) 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) dnsOptions := make([]v1.PodDNSConfigOption, 0)
for _, option := range options { for _, option := range options {
// the option can be "k:v" or just "k", no delimiter is required // 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{ dnsOpt := v1.PodDNSConfigOption{
Name: opts[0], Name: name,
Value: &opts[1], Value: &value,
} }
dnsOptions = append(dnsOptions, dnsOpt) dnsOptions = append(dnsOptions, dnsOpt)
} }
@ -1055,23 +1055,23 @@ func libpodEnvVarsToKubeEnvVars(envs []string, imageEnvs []string) ([]v1.EnvVar,
envVars := make([]v1.EnvVar, 0, len(envs)) envVars := make([]v1.EnvVar, 0, len(envs))
imageMap := make(map[string]string, len(imageEnvs)) imageMap := make(map[string]string, len(imageEnvs))
for _, ie := range imageEnvs { for _, ie := range imageEnvs {
split := strings.SplitN(ie, "=", 2) key, val, _ := strings.Cut(ie, "=")
imageMap[split[0]] = split[1] imageMap[key] = val
} }
for _, e := range envs { for _, e := range envs {
split := strings.SplitN(e, "=", 2) envName, envValue, hasValue := strings.Cut(e, "=")
if len(split) != 2 { if !hasValue {
return envVars, fmt.Errorf("environment variable %s is malformed; should be key=value", e) 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 continue
} }
if imageMap[split[0]] == split[1] { if imageMap[envName] == envValue {
continue continue
} }
ev := v1.EnvVar{ ev := v1.EnvVar{
Name: split[0], Name: envName,
Value: split[1], Value: envValue,
} }
envVars = append(envVars, ev) envVars = append(envVars, ev)
} }
@ -1286,25 +1286,22 @@ func generateKubeSecurityContext(c *Container) (*v1.SecurityContext, bool, error
var selinuxOpts v1.SELinuxOptions var selinuxOpts v1.SELinuxOptions
selinuxHasData := false selinuxHasData := false
for _, label := range strings.Split(c.config.Spec.Annotations[define.InspectAnnotationLabel], ",label=") { for _, label := range strings.Split(c.config.Spec.Annotations[define.InspectAnnotationLabel], ",label=") {
opts := strings.SplitN(label, ":", 2) opt, val, hasVal := strings.Cut(label, ":")
switch len(opts) { if hasVal {
case 2: switch opt {
switch opts[0] {
case "filetype": case "filetype":
selinuxOpts.FileType = opts[1] selinuxOpts.FileType = val
selinuxHasData = true selinuxHasData = true
case "type": case "type":
selinuxOpts.Type = opts[1] selinuxOpts.Type = val
selinuxHasData = true selinuxHasData = true
case "level": case "level":
selinuxOpts.Level = opts[1] selinuxOpts.Level = val
selinuxHasData = true
}
case 1:
if opts[0] == "disable" {
selinuxOpts.Type = "spc_t"
selinuxHasData = true selinuxHasData = true
} }
} else if opt == "disable" {
selinuxOpts.Type = "spc_t"
selinuxHasData = true
} }
} }
if selinuxHasData { if selinuxHasData {

View File

@ -524,11 +524,11 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON,
exposedPorts := make(nat.PortSet) exposedPorts := make(nat.PortSet)
for ep := range inspect.NetworkSettings.Ports { for ep := range inspect.NetworkSettings.Ports {
splitp := strings.SplitN(ep, "/", 2) port, proto, ok := strings.Cut(ep, "/")
if len(splitp) != 2 { if !ok {
return nil, fmt.Errorf("PORT/PROTOCOL Format required for %q", ep) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -49,12 +49,12 @@ func ExecCreateHandler(w http.ResponseWriter, r *http.Request) {
} }
libpodConfig.Environment = make(map[string]string) libpodConfig.Environment = make(map[string]string)
for _, envStr := range input.Env { for _, envStr := range input.Env {
split := strings.SplitN(envStr, "=", 2) key, val, hasVal := strings.Cut(envStr, "=")
if len(split) != 2 { if !hasVal {
utils.Error(w, http.StatusBadRequest, fmt.Errorf("environment variable %q badly formed, must be key=value", envStr)) utils.Error(w, http.StatusBadRequest, fmt.Errorf("environment variable %q badly formed, must be key=value", envStr))
return return
} }
libpodConfig.Environment[split[0]] = split[1] libpodConfig.Environment[key] = val
} }
libpodConfig.WorkDir = input.WorkingDir libpodConfig.WorkDir = input.WorkingDir
libpodConfig.Privileged = input.Privileged libpodConfig.Privileged = input.Privileged

View File

@ -330,15 +330,15 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
if len(secretOpt) > 0 { if len(secretOpt) > 0 {
modifiedOpt := []string{} modifiedOpt := []string{}
for _, token := range secretOpt { for _, token := range secretOpt {
arr := strings.SplitN(token, "=", 2) key, val, hasVal := strings.Cut(token, "=")
if len(arr) > 1 { if hasVal {
if arr[0] == "src" { if key == "src" {
/* move secret away from contextDir */ /* move secret away from contextDir */
/* to make sure we dont accidentally commit temporary secrets to image*/ /* to make sure we dont accidentally commit temporary secrets to image*/
builderDirectory, _ := filepath.Split(contextDirectory) builderDirectory, _ := filepath.Split(contextDirectory)
// following path is outside build context // following path is outside build context
newSecretPath := filepath.Join(builderDirectory, arr[1]) newSecretPath := filepath.Join(builderDirectory, val)
oldSecretPath := filepath.Join(contextDirectory, arr[1]) oldSecretPath := filepath.Join(contextDirectory, val)
err := os.Rename(oldSecretPath, newSecretPath) err := os.Rename(oldSecretPath, newSecretPath)
if err != nil { if err != nil {
utils.BadRequest(w, "secrets", query.Secrets, err) 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")) utils.BadRequest(w, "securityopt", query.SecurityOpt, errors.New("no-new-privileges is not supported"))
return return
} }
con := strings.SplitN(opt, "=", 2) name, value, hasValue := strings.Cut(opt, "=")
if len(con) != 2 { if !hasValue {
utils.BadRequest(w, "securityopt", query.SecurityOpt, fmt.Errorf("invalid --security-opt name=value pair: %q", opt)) utils.BadRequest(w, "securityopt", query.SecurityOpt, fmt.Errorf("invalid --security-opt name=value pair: %q", opt))
return return
} }
switch con[0] { switch name {
case "label": case "label":
labelOpts = append(labelOpts, con[1]) labelOpts = append(labelOpts, value)
case "apparmor": case "apparmor":
apparmor = con[1] apparmor = value
case "seccomp": case "seccomp":
seccomp = con[1] seccomp = value
default: default:
utils.BadRequest(w, "securityopt", query.SecurityOpt, fmt.Errorf("invalid --security-opt 2: %q", opt)) utils.BadRequest(w, "securityopt", query.SecurityOpt, fmt.Errorf("invalid --security-opt 2: %q", opt))
return return

View File

@ -489,12 +489,12 @@ func ManifestModify(w http.ResponseWriter, r *http.Request) {
} }
annotations := make(map[string]string) annotations := make(map[string]string)
for _, annotationSpec := range body.ManifestAddOptions.Annotation { for _, annotationSpec := range body.ManifestAddOptions.Annotation {
spec := strings.SplitN(annotationSpec, "=", 2) key, val, hasVal := strings.Cut(annotationSpec, "=")
if len(spec) != 2 { if !hasVal {
utils.Error(w, http.StatusBadRequest, fmt.Errorf("no value given for annotation %q", spec[0])) utils.Error(w, http.StatusBadRequest, fmt.Errorf("no value given for annotation %q", key))
return return
} }
annotations[spec[0]] = spec[1] annotations[key] = val
} }
body.ManifestAddOptions.Annotations = envLib.Join(body.ManifestAddOptions.Annotations, annotations) body.ManifestAddOptions.Annotations = envLib.Join(body.ManifestAddOptions.Annotations, annotations)
body.ManifestAddOptions.Annotation = nil body.ManifestAddOptions.Annotation = nil

View File

@ -268,7 +268,7 @@ func normalizeAuthFileKey(authFileKey string) string {
stripped = strings.TrimPrefix(stripped, "https://") stripped = strings.TrimPrefix(stripped, "https://")
if stripped != authFileKey { // URLs are interpreted to mean complete registries 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 // Only non-namespaced registry names (or URLs) need to be normalized; repo namespaces

View File

@ -530,9 +530,9 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO
if len(secretOpt) > 0 { if len(secretOpt) > 0 {
modifiedOpt := []string{} modifiedOpt := []string{}
for _, token := range secretOpt { for _, token := range secretOpt {
arr := strings.SplitN(token, "=", 2) opt, val, hasVal := strings.Cut(token, "=")
if len(arr) > 1 { if hasVal {
if arr[0] == "src" { if opt == "src" {
// read specified secret into a tmp file // read specified secret into a tmp file
// move tmp file to tar and change secret source to relative tmp file // move tmp file to tar and change secret source to relative tmp file
tmpSecretFile, err := os.CreateTemp(options.ContextDirectory, "podman-build-secret") 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 os.Remove(tmpSecretFile.Name()) // clean up
defer tmpSecretFile.Close() defer tmpSecretFile.Close()
srcSecretFile, err := os.Open(arr[1]) srcSecretFile, err := os.Open(val)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -40,9 +40,9 @@ func parseUserInput(input string) (container string, path string) {
return return
} }
if spl := strings.SplitN(path, ":", 2); len(spl) == 2 { if parsedContainer, parsedPath, ok := strings.Cut(path, ":"); ok {
container = spl[0] container = parsedContainer
path = spl[1] path = parsedPath
} }
return return
} }

View File

@ -98,10 +98,10 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo
var imageNameWithoutTag string var imageNameWithoutTag string
// Compare with ImageID, ImageName // Compare with ImageID, ImageName
// Will match ImageName if running image has tag latest for other tags exact complete filter must be given // Will match ImageName if running image has tag latest for other tags exact complete filter must be given
imageNameSlice := strings.SplitN(rootfsImageName, ":", 2) name, tag, hasColon := strings.Cut(rootfsImageName, ":")
if len(imageNameSlice) == 2 { if hasColon {
imageNameWithoutTag = imageNameSlice[0] imageNameWithoutTag = name
imageTag = imageNameSlice[1] imageTag = tag
} }
if (rootfsImageID == filterValue) || if (rootfsImageID == filterValue) ||
@ -144,13 +144,8 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo
//- volume=(<volume-name>|<mount-point-destination>) //- volume=(<volume-name>|<mount-point-destination>)
return func(c *libpod.Container) bool { return func(c *libpod.Container) bool {
containerConfig := c.ConfigNoCopy() containerConfig := c.ConfigNoCopy()
var dest string
for _, filterValue := range filterValues { for _, filterValue := range filterValues {
arr := strings.SplitN(filterValue, ":", 2) source, dest, _ := strings.Cut(filterValue, ":")
source := arr[0]
if len(arr) == 2 {
dest = arr[1]
}
for _, mount := range containerConfig.Spec.Mounts { for _, mount := range containerConfig.Spec.Mounts {
if dest != "" && (mount.Source == source && mount.Destination == dest) { if dest != "" && (mount.Source == source && mount.Destination == dest) {
return true return true
@ -233,19 +228,10 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo
// check if networkMode is configured as `container:<ctr>` // check if networkMode is configured as `container:<ctr>`
// perform a match against filter `container:<IDorName>` // perform a match against filter `container:<IDorName>`
// networks is already going to be empty if `container:<ctr>` is configured as Mode // networks is already going to be empty if `container:<ctr>` is configured as Mode
if strings.HasPrefix(networkMode, "container:") { if networkModeContainerID, ok := strings.CutPrefix(networkMode, "container:"); ok {
networkModeContainerPart := strings.SplitN(networkMode, ":", 2)
if len(networkModeContainerPart) < 2 {
return false
}
networkModeContainerID := networkModeContainerPart[1]
for _, val := range filterValues { for _, val := range filterValues {
if strings.HasPrefix(val, "container:") { if idOrName, ok := strings.CutPrefix(val, "container:"); ok {
filterNetworkModePart := strings.SplitN(val, ":", 2) filterNetworkModeIDorName := idOrName
if len(filterNetworkModePart) < 2 {
return false
}
filterNetworkModeIDorName := filterNetworkModePart[1]
filterID, err := r.LookupContainerID(filterNetworkModeIDorName) filterID, err := r.LookupContainerID(filterNetworkModeIDorName)
if err != nil { if err != nil {
return false return false

View File

@ -49,14 +49,7 @@ func GenerateVolumeFilters(filter string, filterValues []string, runtime *libpod
case "opt": case "opt":
return func(v *libpod.Volume) bool { return func(v *libpod.Volume) bool {
for _, val := range filterValues { for _, val := range filterValues {
filterArray := strings.SplitN(val, "=", 2) filterKey, filterVal, _ := strings.Cut(val, "=")
filterKey := filterArray[0]
var filterVal string
if len(filterArray) > 1 {
filterVal = filterArray[1]
} else {
filterVal = ""
}
for labelKey, labelValue := range v.Options() { for labelKey, labelValue := range v.Options() {
if labelKey == filterKey && (filterVal == "" || labelValue == filterVal) { if labelKey == filterKey && (filterVal == "" || labelValue == filterVal) {

View File

@ -135,7 +135,7 @@ func generateRunlabelCommand(runlabel string, img *libimage.Image, inputName str
name = splitImageName[len(splitImageName)-1] name = splitImageName[len(splitImageName)-1]
// make sure to remove the tag from the image name, otherwise the name cannot // 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 // 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). // Append the user-specified arguments to the runlabel (command).

View File

@ -230,11 +230,11 @@ func (ir *ImageEngine) ManifestAdd(ctx context.Context, name string, images []st
if len(opts.Annotation) != 0 { if len(opts.Annotation) != 0 {
annotations := make(map[string]string) annotations := make(map[string]string)
for _, annotationSpec := range opts.Annotation { for _, annotationSpec := range opts.Annotation {
spec := strings.SplitN(annotationSpec, "=", 2) key, val, hasVal := strings.Cut(annotationSpec, "=")
if len(spec) != 2 { if !hasVal {
return "", fmt.Errorf("no value given for annotation %q", spec[0]) 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) 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 { if len(opts.Annotation) != 0 {
annotations := make(map[string]string) annotations := make(map[string]string)
for _, annotationSpec := range opts.Annotation { for _, annotationSpec := range opts.Annotation {
spec := strings.SplitN(annotationSpec, "=", 2) key, val, hasVal := strings.Cut(annotationSpec, "=")
if len(spec) != 2 { if !hasVal {
return "", fmt.Errorf("no value given for annotation %q", spec[0]) 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) opts.Annotations = envLib.Join(opts.Annotations, annotations)
} }

View File

@ -27,64 +27,64 @@ func VolumeOptions(opts map[string]string) ([]libpod.VolumeCreateOption, error)
for _, o := range splitVal { for _, o := range splitVal {
// Options will be formatted as either "opt" or // Options will be formatted as either "opt" or
// "opt=value" // "opt=value"
splitO := strings.SplitN(o, "=", 2) opt, val, hasVal := strings.Cut(o, "=")
switch strings.ToLower(splitO[0]) { switch strings.ToLower(opt) {
case "size": case "size":
size, err := units.FromHumanSize(splitO[1]) size, err := units.FromHumanSize(val)
if err != nil { 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))) libpodOptions = append(libpodOptions, libpod.WithVolumeSize(uint64(size)))
finalVal = append(finalVal, o) finalVal = append(finalVal, o)
// set option "SIZE": "$size" // set option "SIZE": "$size"
volumeOptions["SIZE"] = splitO[1] volumeOptions["SIZE"] = val
case "inodes": case "inodes":
inodes, err := strconv.ParseUint(splitO[1], 10, 64) inodes, err := strconv.ParseUint(val, 10, 64)
if err != nil { 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)) libpodOptions = append(libpodOptions, libpod.WithVolumeInodes(inodes))
finalVal = append(finalVal, o) finalVal = append(finalVal, o)
// set option "INODES": "$size" // set option "INODES": "$size"
volumeOptions["INODES"] = splitO[1] volumeOptions["INODES"] = val
case "uid": case "uid":
if len(splitO) != 2 { if !hasVal {
return nil, fmt.Errorf("uid option must provide a UID: %w", define.ErrInvalidArg) 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 { 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) logrus.Debugf("Removing uid= from options and adding WithVolumeUID for UID %d", intUID)
libpodOptions = append(libpodOptions, libpod.WithVolumeUID(intUID), libpod.WithVolumeNoChown()) libpodOptions = append(libpodOptions, libpod.WithVolumeUID(intUID), libpod.WithVolumeNoChown())
finalVal = append(finalVal, o) finalVal = append(finalVal, o)
// set option "UID": "$uid" // set option "UID": "$uid"
volumeOptions["UID"] = splitO[1] volumeOptions["UID"] = val
case "gid": case "gid":
if len(splitO) != 2 { if !hasVal {
return nil, fmt.Errorf("gid option must provide a GID: %w", define.ErrInvalidArg) 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 { 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) logrus.Debugf("Removing gid= from options and adding WithVolumeGID for GID %d", intGID)
libpodOptions = append(libpodOptions, libpod.WithVolumeGID(intGID), libpod.WithVolumeNoChown()) libpodOptions = append(libpodOptions, libpod.WithVolumeGID(intGID), libpod.WithVolumeNoChown())
finalVal = append(finalVal, o) finalVal = append(finalVal, o)
// set option "GID": "$gid" // set option "GID": "$gid"
volumeOptions["GID"] = splitO[1] volumeOptions["GID"] = val
case "noquota": case "noquota":
logrus.Debugf("Removing noquota from options and adding WithVolumeDisableQuota") logrus.Debugf("Removing noquota from options and adding WithVolumeDisableQuota")
libpodOptions = append(libpodOptions, libpod.WithVolumeDisableQuota()) libpodOptions = append(libpodOptions, libpod.WithVolumeDisableQuota())
// set option "NOQUOTA": "true" // set option "NOQUOTA": "true"
volumeOptions["NOQUOTA"] = "true" volumeOptions["NOQUOTA"] = "true"
case "timeout": case "timeout":
if len(splitO) != 2 { if !hasVal {
return nil, fmt.Errorf("timeout option must provide a valid timeout in seconds: %w", define.ErrInvalidArg) 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 { 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 { if intTimeout < 0 {
return nil, fmt.Errorf("volume timeout cannot be negative (got %d)", intTimeout) return nil, fmt.Errorf("volume timeout cannot be negative (got %d)", intTimeout)

View File

@ -64,11 +64,11 @@ func (ir *ImageEngine) ManifestAdd(_ context.Context, name string, imageNames []
if len(opts.Annotation) != 0 { if len(opts.Annotation) != 0 {
annotations := make(map[string]string) annotations := make(map[string]string)
for _, annotationSpec := range opts.Annotation { for _, annotationSpec := range opts.Annotation {
spec := strings.SplitN(annotationSpec, "=", 2) key, val, hasVal := strings.Cut(annotationSpec, "=")
if len(spec) != 2 { if !hasVal {
return "", fmt.Errorf("no value given for annotation %q", spec[0]) 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) 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 { if len(opts.Annotation) != 0 {
annotations := make(map[string]string) annotations := make(map[string]string)
for _, annotationSpec := range opts.Annotation { for _, annotationSpec := range opts.Annotation {
spec := strings.SplitN(annotationSpec, "=", 2) key, val, hasVal := strings.Cut(annotationSpec, "=")
if len(spec) != 2 { if !hasVal {
return "", fmt.Errorf("no value given for annotation %q", spec[0]) 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) opts.Annotations = envLib.Join(opts.Annotations, annotations)
} }

View File

@ -406,8 +406,8 @@ func GetServiceInformation(sshInfo *entities.ImageScpConnections, cliConnections
var urlS string var urlS string
var iden string var iden string
for i, val := range cliConnections { for i, val := range cliConnections {
splitEnv := strings.SplitN(val, "::", 2) connection, _, _ := strings.Cut(val, "::")
sshInfo.Connections = append(sshInfo.Connections, splitEnv[0]) sshInfo.Connections = append(sshInfo.Connections, connection)
conn, found := cfg.Engine.ServiceDestinations[sshInfo.Connections[i]] conn, found := cfg.Engine.ServiceDestinations[sshInfo.Connections[i]]
if found { if found {
urlS = conn.URI urlS = conn.URI

View File

@ -34,8 +34,8 @@ func ToLibpodFilters(f url.Values) (filters []string) {
func ToURLValues(f []string) (filters url.Values) { func ToURLValues(f []string) (filters url.Values) {
filters = make(url.Values) filters = make(url.Values)
for _, v := range f { for _, v := range f {
t := strings.SplitN(v, "=", 2) key, val, _ := strings.Cut(v, "=")
filters.Add(t[0], t[1]) filters.Add(key, val)
} }
return return
} }

32
pkg/env/env.go vendored
View File

@ -40,14 +40,9 @@ func Slice(m map[string]string) []string {
// map. // map.
func Map(slice []string) map[string]string { func Map(slice []string) map[string]string {
envmap := make(map[string]string, len(slice)) envmap := make(map[string]string, len(slice))
for _, val := range slice { for _, line := range slice {
data := strings.SplitN(val, "=", 2) key, val, _ := strings.Cut(line, "=")
envmap[key] = val
if len(data) > 1 {
envmap[data[0]] = data[1]
} else {
envmap[data[0]] = ""
}
} }
return envmap return envmap
} }
@ -94,26 +89,25 @@ func ParseFile(path string) (_ map[string]string, err error) {
} }
func parseEnv(env map[string]string, line string) 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" // catch invalid variables such as "=" or "=A"
if data[0] == "" { if key == "" {
return fmt.Errorf("invalid variable: %q", line) return fmt.Errorf("invalid variable: %q", line)
} }
// trim the front of a variable, but nothing else // trim the front of a variable, but nothing else
name := strings.TrimLeft(data[0], whiteSpaces) name := strings.TrimLeft(key, whiteSpaces)
if len(data) > 1 { if hasVal {
env[name] = data[1] env[name] = val
} else { } else {
if strings.HasSuffix(name, "*") { if name, hasStar := strings.CutSuffix(name, "*"); hasStar {
name = strings.TrimSuffix(name, "*")
for _, e := range os.Environ() { for _, e := range os.Environ() {
part := strings.SplitN(e, "=", 2) envKey, envVal, hasEq := strings.Cut(e, "=")
if len(part) < 2 { if !hasEq {
continue continue
} }
if strings.HasPrefix(part[0], name) { if strings.HasPrefix(envKey, name) {
env[part[0]] = part[1] env[envKey] = envVal
} }
} }
} else if val, ok := os.LookupEnv(name); ok { } else if val, ok := os.LookupEnv(name); ok {

View File

@ -75,8 +75,8 @@ func TestPropagateHostEnv(t *testing.T) {
// envs looks like: {"BAR": "bar", "FOO": "foo"} // envs looks like: {"BAR": "bar", "FOO": "foo"}
envs := make(map[string]string) envs := make(map[string]string)
for _, env := range envsRawArr { for _, env := range envsRawArr {
item := strings.SplitN(env, "=", 2) key, value, _ := strings.Cut(env, "=")
envs[item[0]] = strings.Trim(item[1], "\"") envs[key] = strings.Trim(value, "\"")
} }
for key, test := range tests { for key, test := range tests {

View File

@ -48,24 +48,21 @@ func (n CgroupMode) IsNS() bool {
// NS gets the path associated with a ns:<path> cgroup ns // NS gets the path associated with a ns:<path> cgroup ns
func (n CgroupMode) NS() string { func (n CgroupMode) NS() string {
parts := strings.SplitN(string(n), ":", 2) _, path, _ := strings.Cut(string(n), ":")
if len(parts) > 1 { return path
return parts[1]
}
return ""
} }
// IsContainer indicates whether the container uses a new cgroup namespace. // IsContainer indicates whether the container uses a new cgroup namespace.
func (n CgroupMode) IsContainer() bool { func (n CgroupMode) IsContainer() bool {
parts := strings.SplitN(string(n), ":", 2) typ, _, hasColon := strings.Cut(string(n), ":")
return len(parts) > 1 && parts[0] == containerType return hasColon && typ == containerType
} }
// Container returns the name of the container whose cgroup namespace is going to be used. // Container returns the name of the container whose cgroup namespace is going to be used.
func (n CgroupMode) Container() string { func (n CgroupMode) Container() string {
parts := strings.SplitN(string(n), ":", 2) typ, name, hasName := strings.Cut(string(n), ":")
if len(parts) > 1 && parts[0] == containerType { if hasName && typ == containerType {
return parts[1] return name
} }
return "" return ""
} }
@ -123,36 +120,36 @@ func (n UsernsMode) IsDefaultValue() bool {
// GetKeepIDOptions returns a KeepIDUserNsOptions with the settings to keepIDmatically set up // GetKeepIDOptions returns a KeepIDUserNsOptions with the settings to keepIDmatically set up
// a user namespace. // a user namespace.
func (n UsernsMode) GetKeepIDOptions() (*KeepIDUserNsOptions, error) { func (n UsernsMode) GetKeepIDOptions() (*KeepIDUserNsOptions, error) {
parts := strings.SplitN(string(n), ":", 2) nsmode, nsopts, hasOpts := strings.Cut(string(n), ":")
if parts[0] != "keep-id" { if nsmode != "keep-id" {
return nil, fmt.Errorf("wrong user namespace mode") return nil, fmt.Errorf("wrong user namespace mode")
} }
options := KeepIDUserNsOptions{} options := KeepIDUserNsOptions{}
if len(parts) == 1 { if !hasOpts {
return &options, nil return &options, nil
} }
for _, o := range strings.Split(parts[1], ",") { for _, o := range strings.Split(nsopts, ",") {
v := strings.SplitN(o, "=", 2) opt, val, hasVal := strings.Cut(o, "=")
if len(v) != 2 { if !hasVal {
return nil, fmt.Errorf("invalid option specified: %q", o) return nil, fmt.Errorf("invalid option specified: %q", o)
} }
switch v[0] { switch opt {
case "uid": case "uid":
s, err := strconv.ParseUint(v[1], 10, 32) s, err := strconv.ParseUint(val, 10, 32)
if err != nil { if err != nil {
return nil, err return nil, err
} }
v := uint32(s) v := uint32(s)
options.UID = &v options.UID = &v
case "gid": case "gid":
s, err := strconv.ParseUint(v[1], 10, 32) s, err := strconv.ParseUint(val, 10, 32)
if err != nil { if err != nil {
return nil, err return nil, err
} }
v := uint32(s) v := uint32(s)
options.GID = &v options.GID = &v
default: default:
return nil, fmt.Errorf("unknown option specified: %q", v[0]) return nil, fmt.Errorf("unknown option specified: %q", opt)
} }
} }
return &options, nil return &options, nil
@ -185,24 +182,21 @@ func (n UsernsMode) IsNS() bool {
// NS gets the path associated with a ns:<path> userns ns // NS gets the path associated with a ns:<path> userns ns
func (n UsernsMode) NS() string { func (n UsernsMode) NS() string {
parts := strings.SplitN(string(n), ":", 2) _, path, _ := strings.Cut(string(n), ":")
if len(parts) > 1 { return path
return parts[1]
}
return ""
} }
// IsContainer indicates whether container uses a container userns. // IsContainer indicates whether container uses a container userns.
func (n UsernsMode) IsContainer() bool { func (n UsernsMode) IsContainer() bool {
parts := strings.SplitN(string(n), ":", 2) typ, _, hasName := strings.Cut(string(n), ":")
return len(parts) > 1 && parts[0] == containerType return hasName && typ == containerType
} }
// Container is the id of the container which network this container is connected to. // Container is the id of the container which network this container is connected to.
func (n UsernsMode) Container() string { func (n UsernsMode) Container() string {
parts := strings.SplitN(string(n), ":", 2) typ, name, hasName := strings.Cut(string(n), ":")
if len(parts) > 1 && parts[0] == containerType { if hasName && typ == containerType {
return parts[1] return name
} }
return "" return ""
} }
@ -222,15 +216,15 @@ func (n UTSMode) IsHost() bool {
// IsContainer indicates whether the container uses a container's UTS namespace. // IsContainer indicates whether the container uses a container's UTS namespace.
func (n UTSMode) IsContainer() bool { func (n UTSMode) IsContainer() bool {
parts := strings.SplitN(string(n), ":", 2) typ, _, hasName := strings.Cut(string(n), ":")
return len(parts) > 1 && parts[0] == containerType return hasName && typ == containerType
} }
// Container returns the name of the container whose uts namespace is going to be used. // Container returns the name of the container whose uts namespace is going to be used.
func (n UTSMode) Container() string { func (n UTSMode) Container() string {
parts := strings.SplitN(string(n), ":", 2) typ, name, hasName := strings.Cut(string(n), ":")
if len(parts) > 1 && parts[0] == containerType { if hasName && typ == containerType {
return parts[1] return name
} }
return "" return ""
} }
@ -270,8 +264,8 @@ func (n IpcMode) IsShareable() bool {
// IsContainer indicates whether the container uses another container's ipc namespace. // IsContainer indicates whether the container uses another container's ipc namespace.
func (n IpcMode) IsContainer() bool { func (n IpcMode) IsContainer() bool {
parts := strings.SplitN(string(n), ":", 2) typ, _, hasName := strings.Cut(string(n), ":")
return len(parts) > 1 && parts[0] == containerType return hasName && typ == containerType
} }
// IsNone indicates whether container IpcMode is set to "none". // 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. // Container returns the name of the container ipc stack is going to be used.
func (n IpcMode) Container() string { func (n IpcMode) Container() string {
parts := strings.SplitN(string(n), ":", 2) typ, name, hasName := strings.Cut(string(n), ":")
if len(parts) > 1 && parts[0] == containerType { if hasName && typ == containerType {
return parts[1] return name
} }
return "" return ""
} }
@ -313,8 +307,8 @@ func (n PidMode) IsHost() bool {
// IsContainer indicates whether the container uses a container's pid namespace. // IsContainer indicates whether the container uses a container's pid namespace.
func (n PidMode) IsContainer() bool { func (n PidMode) IsContainer() bool {
parts := strings.SplitN(string(n), ":", 2) typ, _, hasName := strings.Cut(string(n), ":")
return len(parts) > 1 && parts[0] == containerType return hasName && typ == containerType
} }
// Valid indicates whether the pid namespace is valid. // 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. // Container returns the name of the container whose pid namespace is going to be used.
func (n PidMode) Container() string { func (n PidMode) Container() string {
parts := strings.SplitN(string(n), ":", 2) typ, name, hasName := strings.Cut(string(n), ":")
if len(parts) > 1 && parts[0] == containerType { if hasName && typ == containerType {
return parts[1] return name
} }
return "" return ""
} }
@ -366,15 +360,15 @@ func (n NetworkMode) IsPrivate() bool {
// IsContainer indicates whether container uses a container network stack. // IsContainer indicates whether container uses a container network stack.
func (n NetworkMode) IsContainer() bool { func (n NetworkMode) IsContainer() bool {
parts := strings.SplitN(string(n), ":", 2) typ, _, hasName := strings.Cut(string(n), ":")
return len(parts) > 1 && parts[0] == containerType return hasName && typ == containerType
} }
// Container is the id of the container which network this container is connected to. // Container is the id of the container which network this container is connected to.
func (n NetworkMode) Container() string { func (n NetworkMode) Container() string {
parts := strings.SplitN(string(n), ":", 2) typ, name, hasName := strings.Cut(string(n), ":")
if len(parts) > 1 && parts[0] == containerType { if hasName && typ == containerType {
return parts[1] return name
} }
return "" return ""
} }
@ -409,11 +403,8 @@ func (n NetworkMode) IsNS() bool {
// NS gets the path associated with a ns:<path> network ns // NS gets the path associated with a ns:<path> network ns
func (n NetworkMode) NS() string { func (n NetworkMode) NS() string {
parts := strings.SplitN(string(n), ":", 2) _, path, _ := strings.Cut(string(n), ":")
if len(parts) > 1 { return path
return parts[1]
}
return ""
} }
// IsPod returns whether the network refers to pod networking // IsPod returns whether the network refers to pod networking

View File

@ -31,9 +31,8 @@ func GetRacct(filter string) (map[string]uint64, error) {
entries := strings.Split(string(buf[:len]), ",") entries := strings.Split(string(buf[:len]), ",")
res := make(map[string]uint64) res := make(map[string]uint64)
for _, entry := range entries { for _, entry := range entries {
kv := strings.SplitN(entry, "=", 2) key, valstr, _ := strings.Cut(entry, "=")
key := kv[0] val, err := strconv.ParseUint(valstr, 10, 0)
val, err := strconv.ParseUint(kv[1], 10, 0)
if err != nil { if err != nil {
logrus.Warnf("unexpected rctl entry, ignoring: %s", entry) logrus.Warnf("unexpected rctl entry, ignoring: %s", entry)
} }

View File

@ -236,13 +236,8 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat
} }
} }
for _, v := range rtc.Containers.Annotations.Get() { for _, annotation := range rtc.Containers.Annotations.Get() {
split := strings.SplitN(v, "=", 2) k, v, _ := strings.Cut(annotation, "=")
k := split[0]
v := ""
if len(split) == 2 {
v = split[1]
}
annotations[k] = v annotations[k] = v
} }
// now pass in the values from client // 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 { if conf.Spec.Process != nil && conf.Spec.Process.Env != nil {
env := make(map[string]string) env := make(map[string]string)
for _, entry := range conf.Spec.Process.Env { for _, entry := range conf.Spec.Process.Env {
split := strings.SplitN(entry, "=", 2) key, val, hasVal := strings.Cut(entry, "=")
if len(split) == 2 { if hasVal {
env[split[0]] = split[1] env[key] = val
} }
} }
specg.Env = env specg.Env = env

View File

@ -54,12 +54,12 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
s.ResourceLimits.Unified = make(map[string]string) s.ResourceLimits.Unified = make(map[string]string)
} }
for _, cgroupConf := range rtc.Containers.CgroupConf.Get() { for _, cgroupConf := range rtc.Containers.CgroupConf.Get() {
cgr := strings.SplitN(cgroupConf, "=", 2) key, val, hasVal := strings.Cut(cgroupConf, "=")
if len(cgr) != 2 { if !hasVal {
return nil, nil, nil, fmt.Errorf("CgroupConf %q from containers.conf invalid, must be name=value", cgr) 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 { if _, ok := s.ResourceLimits.Unified[key]; !ok {
s.ResourceLimits.Unified[cgr[0]] = cgr[1] s.ResourceLimits.Unified[key] = val
} }
} }
} }

View File

@ -221,29 +221,29 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
s.LogConfiguration.Options = make(map[string]string) s.LogConfiguration.Options = make(map[string]string)
for _, o := range opts.LogOptions { for _, o := range opts.LogOptions {
split := strings.SplitN(o, "=", 2) opt, val, hasVal := strings.Cut(o, "=")
if len(split) < 2 { if !hasVal {
return nil, fmt.Errorf("invalid log option %q", o) return nil, fmt.Errorf("invalid log option %q", o)
} }
switch strings.ToLower(split[0]) { switch strings.ToLower(opt) {
case "driver": case "driver":
s.LogConfiguration.Driver = split[1] s.LogConfiguration.Driver = val
case "path": case "path":
s.LogConfiguration.Path = split[1] s.LogConfiguration.Path = val
case "max-size": case "max-size":
logSize, err := units.FromHumanSize(split[1]) logSize, err := units.FromHumanSize(val)
if err != nil { if err != nil {
return nil, err return nil, err
} }
s.LogConfiguration.Size = logSize s.LogConfiguration.Size = logSize
default: default:
switch len(split[1]) { switch len(val) {
case 0: case 0:
return nil, fmt.Errorf("invalid log option: %w", define.ErrInvalidArg) return nil, fmt.Errorf("invalid log option: %w", define.ErrInvalidArg)
default: default:
// tags for journald only // tags for journald only
if s.LogConfiguration.Driver == "" || s.LogConfiguration.Driver == define.JournaldLogging { if s.LogConfiguration.Driver == "" || s.LogConfiguration.Driver == define.JournaldLogging {
s.LogConfiguration.Options[split[0]] = split[1] s.LogConfiguration.Options[opt] = val
} else { } else {
logrus.Warnf("Can only set tags with journald log driver but driver is %q", s.LogConfiguration.Driver) 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 // Environment Variables
envs := map[string]string{} envs := map[string]string{}
for _, env := range imageData.Config.Env { for _, env := range imageData.Config.Env {
keyval := strings.SplitN(env, "=", 2) key, val, _ := strings.Cut(env, "=")
envs[keyval[0]] = keyval[1] envs[key] = val
} }
for _, env := range opts.Container.Env { for _, env := range opts.Container.Env {

View File

@ -262,9 +262,9 @@ func getVolumesFrom(volumesFrom []string, runtime *libpod.Runtime) (map[string]s
for _, volume := range volumesFrom { for _, volume := range volumesFrom {
var options []string var options []string
splitVol := strings.SplitN(volume, ":", 2) idOrName, volOpts, hasVolOpts := strings.Cut(volume, ":")
if len(splitVol) == 2 { if hasVolOpts {
splitOpts := strings.Split(splitVol[1], ",") splitOpts := strings.Split(volOpts, ",")
setRORW := false setRORW := false
setZ := false setZ := false
for _, opt := range splitOpts { for _, opt := range splitOpts {
@ -286,9 +286,9 @@ func getVolumesFrom(volumesFrom []string, runtime *libpod.Runtime) (map[string]s
options = splitOpts options = splitOpts
} }
ctr, err := runtime.LookupContainer(splitVol[0]) ctr, err := runtime.LookupContainer(idOrName)
if err != nil { 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()) logrus.Debugf("Adding volumes from container %s", ctr.ID())

View File

@ -230,29 +230,23 @@ func (n *Namespace) validate() error {
// function. // function.
func ParseNamespace(ns string) (Namespace, error) { func ParseNamespace(ns string) (Namespace, error) {
toReturn := Namespace{} toReturn := Namespace{}
switch { switch ns {
case ns == "pod": case "pod":
toReturn.NSMode = FromPod toReturn.NSMode = FromPod
case ns == "host": case "host":
toReturn.NSMode = Host toReturn.NSMode = Host
case ns == "private", ns == "": case "private", "":
toReturn.NSMode = 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: 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 return toReturn, nil
@ -302,37 +296,32 @@ func ParseIPCNamespace(ns string) (Namespace, error) {
// form. // form.
func ParseUserNamespace(ns string) (Namespace, error) { func ParseUserNamespace(ns string) (Namespace, error) {
toReturn := Namespace{} toReturn := Namespace{}
switch { switch ns {
case ns == "auto": case "auto":
toReturn.NSMode = Auto toReturn.NSMode = Auto
return toReturn, nil return toReturn, nil
case strings.HasPrefix(ns, "auto:"): case "keep-id":
split := strings.SplitN(ns, ":", 2)
if len(split) != 2 {
return toReturn, errors.New("invalid setting for auto: mode")
}
toReturn.NSMode = Auto
toReturn.Value = split[1]
return toReturn, nil
case ns == "keep-id":
toReturn.NSMode = KeepID toReturn.NSMode = KeepID
return toReturn, nil return toReturn, nil
case strings.HasPrefix(ns, "keep-id:"): case "nomap":
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":
toReturn.NSMode = NoMap toReturn.NSMode = NoMap
return toReturn, nil return toReturn, nil
case ns == "": case "":
toReturn.NSMode = Host toReturn.NSMode = Host
return toReturn, nil 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 // ParseNetworkFlag parses a network string slice into the network options
@ -352,10 +341,10 @@ func ParseNetworkFlag(networks []string, pastaNetworkNameExists bool) (Namespace
switch { switch {
case ns == string(Slirp), strings.HasPrefix(ns, string(Slirp)+":"): case ns == string(Slirp), strings.HasPrefix(ns, string(Slirp)+":"):
parts := strings.SplitN(ns, ":", 2) key, options, hasOptions := strings.Cut(ns, ":")
if len(parts) > 1 { if hasOptions {
networkOptions = make(map[string][]string) networkOptions = make(map[string][]string)
networkOptions[parts[0]] = strings.Split(parts[1], ",") networkOptions[key] = strings.Split(options, ",")
} }
toReturn.NSMode = Slirp toReturn.NSMode = Slirp
case ns == string(FromPod): case ns == string(FromPod):
@ -364,11 +353,11 @@ func ParseNetworkFlag(networks []string, pastaNetworkNameExists bool) (Namespace
toReturn.NSMode = Private toReturn.NSMode = Private
case ns == string(Bridge), strings.HasPrefix(ns, string(Bridge)+":"): case ns == string(Bridge), strings.HasPrefix(ns, string(Bridge)+":"):
toReturn.NSMode = Bridge toReturn.NSMode = Bridge
parts := strings.SplitN(ns, ":", 2) _, options, hasOptions := strings.Cut(ns, ":")
netOpts := types.PerNetworkOptions{} netOpts := types.PerNetworkOptions{}
if len(parts) > 1 { if hasOptions {
var err error var err error
netOpts, err = parseBridgeNetworkOptions(parts[1]) netOpts, err = parseBridgeNetworkOptions(options)
if err != nil { if err != nil {
return toReturn, nil, nil, err return toReturn, nil, nil, err
} }
@ -381,30 +370,23 @@ func ParseNetworkFlag(networks []string, pastaNetworkNameExists bool) (Namespace
case ns == string(Host): case ns == string(Host):
toReturn.NSMode = Host toReturn.NSMode = Host
case strings.HasPrefix(ns, "ns:"): case strings.HasPrefix(ns, "ns:"):
split := strings.SplitN(ns, ":", 2) _, value, _ := strings.Cut(ns, ":")
if len(split) != 2 {
return toReturn, nil, nil, errors.New("must provide a path to a namespace when specifying \"ns:\"")
}
toReturn.NSMode = Path toReturn.NSMode = Path
toReturn.Value = split[1] toReturn.Value = value
case strings.HasPrefix(ns, string(FromContainer)+":"): case strings.HasPrefix(ns, string(FromContainer)+":"):
split := strings.SplitN(ns, ":", 2) _, value, _ := strings.Cut(ns, ":")
if len(split) != 2 {
return toReturn, nil, nil, errors.New("must provide name or ID or a container when specifying \"container:\"")
}
toReturn.NSMode = FromContainer toReturn.NSMode = FromContainer
toReturn.Value = split[1] toReturn.Value = value
case ns == string(Pasta), strings.HasPrefix(ns, string(Pasta)+":"): case ns == string(Pasta), strings.HasPrefix(ns, string(Pasta)+":"):
var parts []string key, options, hasOptions := strings.Cut(ns, ":")
if pastaNetworkNameExists { if pastaNetworkNameExists {
goto nextCase goto nextCase
} }
parts = strings.SplitN(ns, ":", 2) if hasOptions {
if len(parts) > 1 {
networkOptions = make(map[string][]string) networkOptions = make(map[string][]string)
networkOptions[parts[0]] = strings.Split(parts[1], ",") networkOptions[key] = strings.Split(options, ",")
} }
toReturn.NSMode = Pasta toReturn.NSMode = Pasta
break break
@ -412,22 +394,22 @@ func ParseNetworkFlag(networks []string, pastaNetworkNameExists bool) (Namespace
fallthrough fallthrough
default: default:
// we should have a normal network // we should have a normal network
parts := strings.SplitN(ns, ":", 2) name, options, hasOptions := strings.Cut(ns, ":")
if len(parts) == 1 { 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. // Assume we have been given a comma separated list of networks for backwards compat.
networkList := strings.Split(ns, ",") networkList := strings.Split(ns, ",")
for _, net := range networkList { for _, net := range networkList {
podmanNetworks[net] = types.PerNetworkOptions{} 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 // networks need bridge mode
@ -440,24 +422,24 @@ func ParseNetworkFlag(networks []string, pastaNetworkNameExists bool) (Namespace
} }
for _, network := range networks[1:] { for _, network := range networks[1:] {
parts := strings.SplitN(network, ":", 2) name, options, hasOptions := strings.Cut(network, ":")
if parts[0] == "" { if name == "" {
return toReturn, nil, nil, fmt.Errorf("network name cannot be empty: %w", define.ErrInvalidArg) 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 // 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), if slices.Contains([]string{string(Bridge), string(Slirp), string(FromPod), string(NoNetwork),
string(Default), string(Private), string(Path), string(FromContainer), string(Host)}, parts[0]) { 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", parts[0], define.ErrInvalidArg) 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{} netOpts := types.PerNetworkOptions{}
if len(parts) > 1 { if hasOptions {
var err error var err error
netOpts, err = parseBridgeNetworkOptions(parts[1]) netOpts, err = parseBridgeNetworkOptions(options)
if err != nil { 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, ",") allopts := strings.Split(opts, ",")
for _, opt := range allopts { for _, opt := range allopts {
split := strings.SplitN(opt, "=", 2) name, value, _ := strings.Cut(opt, "=")
switch split[0] { switch name {
case "ip", "ip6": case "ip", "ip6":
ip := net.ParseIP(split[1]) ip := net.ParseIP(value)
if ip == nil { 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) netOpts.StaticIPs = append(netOpts.StaticIPs, ip)
case "mac": case "mac":
mac, err := net.ParseMAC(split[1]) mac, err := net.ParseMAC(value)
if err != nil { if err != nil {
return netOpts, err return netOpts, err
} }
netOpts.StaticMAC = types.HardwareAddr(mac) netOpts.StaticMAC = types.HardwareAddr(mac)
case "alias": case "alias":
if split[1] == "" { if value == "" {
return netOpts, errors.New("alias cannot be empty") return netOpts, errors.New("alias cannot be empty")
} }
netOpts.Aliases = append(netOpts.Aliases, split[1]) netOpts.Aliases = append(netOpts.Aliases, value)
case "interface_name": case "interface_name":
if split[1] == "" { if value == "" {
return netOpts, errors.New("interface_name cannot be empty") return netOpts, errors.New("interface_name cannot be empty")
} }
netOpts.InterfaceName = split[1] netOpts.InterfaceName = value
default: 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 return netOpts, nil

View File

@ -502,11 +502,11 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
// Last, add user annotations // Last, add user annotations
for _, annotation := range c.Annotation { for _, annotation := range c.Annotation {
splitAnnotation := strings.SplitN(annotation, "=", 2) key, val, hasVal := strings.Cut(annotation, "=")
if len(splitAnnotation) < 2 { if !hasVal {
return errors.New("annotations must be formatted KEY=VALUE") return errors.New("annotations must be formatted KEY=VALUE")
} }
annotations[splitAnnotation[0]] = splitAnnotation[1] annotations[key] = val
} }
if len(s.Annotations) == 0 { if len(s.Annotations) == 0 {
s.Annotations = annotations s.Annotations = annotations
@ -515,11 +515,11 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
if len(c.StorageOpts) > 0 { if len(c.StorageOpts) > 0 {
opts := make(map[string]string, len(c.StorageOpts)) opts := make(map[string]string, len(c.StorageOpts))
for _, opt := range c.StorageOpts { for _, opt := range c.StorageOpts {
split := strings.SplitN(opt, "=", 2) key, val, hasVal := strings.Cut(opt, "=")
if len(split) != 2 { if !hasVal {
return errors.New("storage-opt must be formatted KEY=VALUE") return errors.New("storage-opt must be formatted KEY=VALUE")
} }
opts[split[0]] = split[1] opts[key] = val
} }
s.StorageOpts = opts s.StorageOpts = opts
} }
@ -673,11 +673,11 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
// check if key=value and convert // check if key=value and convert
sysmap := make(map[string]string) sysmap := make(map[string]string)
for _, ctl := range c.Sysctl { for _, ctl := range c.Sysctl {
splitCtl := strings.SplitN(ctl, "=", 2) key, val, hasVal := strings.Cut(ctl, "=")
if len(splitCtl) < 2 { if !hasVal {
return fmt.Errorf("invalid sysctl value %q", ctl) 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 { if len(s.Sysctl) == 0 || len(c.Sysctl) != 0 {
s.Sysctl = sysmap s.Sysctl = sysmap
@ -690,48 +690,51 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
for _, opt := range c.SecurityOpt { for _, opt := range c.SecurityOpt {
// Docker deprecated the ":" syntax but still supports it, // Docker deprecated the ":" syntax but still supports it,
// so we need to as well // so we need to as well
var con []string var key, val string
var hasVal bool
if strings.Contains(opt, "=") { if strings.Contains(opt, "=") {
con = strings.SplitN(opt, "=", 2) key, val, hasVal = strings.Cut(opt, "=")
} else { } else {
con = strings.SplitN(opt, ":", 2) key, val, hasVal = strings.Cut(opt, ":")
} }
if len(con) != 2 && if !hasVal &&
con[0] != "no-new-privileges" { key != "no-new-privileges" {
return fmt.Errorf("invalid --security-opt 1: %q", opt) return fmt.Errorf("invalid --security-opt 1: %q", opt)
} }
switch con[0] { switch key {
case "apparmor": case "apparmor":
s.ContainerSecurityConfig.ApparmorProfile = con[1] s.ContainerSecurityConfig.ApparmorProfile = val
s.Annotations[define.InspectAnnotationApparmor] = con[1] s.Annotations[define.InspectAnnotationApparmor] = val
case "label": case "label":
if con[1] == "nested" { if val == "nested" {
s.ContainerSecurityConfig.LabelNested = true s.ContainerSecurityConfig.LabelNested = true
continue continue
} }
// TODO selinux opts and label opts are the same thing // 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=") s.Annotations[define.InspectAnnotationLabel] = strings.Join(s.ContainerSecurityConfig.SelinuxOpts, ",label=")
case "mask": 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": case "proc-opts":
s.ProcOpts = strings.Split(con[1], ",") s.ProcOpts = strings.Split(val, ",")
case "seccomp": case "seccomp":
s.SeccompProfilePath = con[1] s.SeccompProfilePath = val
s.Annotations[define.InspectAnnotationSeccomp] = con[1] s.Annotations[define.InspectAnnotationSeccomp] = val
// this option is for docker compatibility, it is the same as unmask=ALL // this option is for docker compatibility, it is the same as unmask=ALL
case "systempaths": case "systempaths":
if con[1] == "unconfined" { if val == "unconfined" {
s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, []string{"ALL"}...) s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, []string{"ALL"}...)
} else { } 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": 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": case "no-new-privileges":
noNewPrivileges := true noNewPrivileges := true
if len(con) == 2 { if hasVal {
noNewPrivileges, err = strconv.ParseBool(con[1]) noNewPrivileges, err = strconv.ParseBool(val)
if err != nil { if err != nil {
return fmt.Errorf("invalid --security-opt 2: %q", opt) 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) logOpts := make(map[string]string)
for _, o := range c.LogOptions { for _, o := range c.LogOptions {
split := strings.SplitN(o, "=", 2) key, val, hasVal := strings.Cut(o, "=")
if len(split) < 2 { if !hasVal {
return fmt.Errorf("invalid log option %q", o) return fmt.Errorf("invalid log option %q", o)
} }
switch strings.ToLower(split[0]) { switch strings.ToLower(key) {
case "driver": case "driver":
s.LogConfiguration.Driver = split[1] s.LogConfiguration.Driver = val
case "path": case "path":
s.LogConfiguration.Path = split[1] s.LogConfiguration.Path = val
case "max-size": case "max-size":
logSize, err := units.FromHumanSize(split[1]) logSize, err := units.FromHumanSize(val)
if err != nil { if err != nil {
return err return err
} }
s.LogConfiguration.Size = logSize s.LogConfiguration.Size = logSize
default: default:
logOpts[split[0]] = split[1] logOpts[key] = val
} }
} }
if len(s.LogConfiguration.Options) == 0 || len(c.LogOptions) != 0 { 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) { func parseWeightDevices(weightDevs []string) (map[string]specs.LinuxWeightDevice, error) {
wd := make(map[string]specs.LinuxWeightDevice) wd := make(map[string]specs.LinuxWeightDevice)
for _, val := range weightDevs { for _, dev := range weightDevs {
split := strings.SplitN(val, ":", 2) key, val, hasVal := strings.Cut(dev, ":")
if len(split) != 2 { if !hasVal {
return nil, fmt.Errorf("bad format: %s", val) return nil, fmt.Errorf("bad format: %s", dev)
} }
if !strings.HasPrefix(split[0], "/dev/") { if !strings.HasPrefix(key, "/dev/") {
return nil, fmt.Errorf("bad format for device path: %s", val) 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 { 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) { 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) w := uint16(weight)
wd[split[0]] = specs.LinuxWeightDevice{ wd[key] = specs.LinuxWeightDevice{
Weight: &w, Weight: &w,
LeafWeight: nil, LeafWeight: nil,
} }
@ -1030,41 +1033,41 @@ func parseWeightDevices(weightDevs []string) (map[string]specs.LinuxWeightDevice
func parseThrottleBPSDevices(bpsDevices []string) (map[string]specs.LinuxThrottleDevice, error) { func parseThrottleBPSDevices(bpsDevices []string) (map[string]specs.LinuxThrottleDevice, error) {
td := make(map[string]specs.LinuxThrottleDevice) td := make(map[string]specs.LinuxThrottleDevice)
for _, val := range bpsDevices { for _, dev := range bpsDevices {
split := strings.SplitN(val, ":", 2) key, val, hasVal := strings.Cut(dev, ":")
if len(split) != 2 { if !hasVal {
return nil, fmt.Errorf("bad format: %s", val) return nil, fmt.Errorf("bad format: %s", dev)
} }
if !strings.HasPrefix(split[0], "/dev/") { if !strings.HasPrefix(key, "/dev/") {
return nil, fmt.Errorf("bad format for device path: %s", val) 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 { if err != nil {
return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. 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 <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", dev)
} }
if rate < 0 { if rate < 0 {
return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. 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 <device-path>:<number>[<unit>]. 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 return td, nil
} }
func parseThrottleIOPsDevices(iopsDevices []string) (map[string]specs.LinuxThrottleDevice, error) { func parseThrottleIOPsDevices(iopsDevices []string) (map[string]specs.LinuxThrottleDevice, error) {
td := make(map[string]specs.LinuxThrottleDevice) td := make(map[string]specs.LinuxThrottleDevice)
for _, val := range iopsDevices { for _, dev := range iopsDevices {
split := strings.SplitN(val, ":", 2) key, val, hasVal := strings.Cut(dev, ":")
if len(split) != 2 { if !hasVal {
return nil, fmt.Errorf("bad format: %s", val) return nil, fmt.Errorf("bad format: %s", dev)
} }
if !strings.HasPrefix(split[0], "/dev/") { if !strings.HasPrefix(key, "/dev/") {
return nil, fmt.Errorf("bad format for device path: %s", val) 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 { if err != nil {
return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val) return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", dev)
} }
td[split[0]] = specs.LinuxThrottleDevice{Rate: rate} td[key] = specs.LinuxThrottleDevice{Rate: rate}
} }
return td, nil return td, nil
} }
@ -1103,42 +1106,42 @@ func parseSecrets(secrets []string) ([]specgen.Secret, map[string]string, error)
} }
for _, val := range split { for _, val := range split {
kv := strings.SplitN(val, "=", 2) name, value, hasValue := strings.Cut(val, "=")
if len(kv) < 2 { if !hasValue {
return nil, nil, fmt.Errorf("option %s must be in form option=value: %w", val, secretParseError) return nil, nil, fmt.Errorf("option %s must be in form option=value: %w", val, secretParseError)
} }
switch kv[0] { switch name {
case "source": case "source":
source = kv[1] source = value
case "type": case "type":
if secretType != "" { if secretType != "" {
return nil, nil, fmt.Errorf("cannot set more than one secret type: %w", secretParseError) return nil, nil, fmt.Errorf("cannot set more than one secret type: %w", secretParseError)
} }
if kv[1] != "mount" && kv[1] != "env" { if value != "mount" && value != "env" {
return nil, nil, fmt.Errorf("type %s is invalid: %w", kv[1], secretParseError) return nil, nil, fmt.Errorf("type %s is invalid: %w", value, secretParseError)
} }
secretType = kv[1] secretType = value
case "target": case "target":
target = kv[1] target = value
case "mode": case "mode":
mountOnly = true mountOnly = true
mode64, err := strconv.ParseUint(kv[1], 8, 32) mode64, err := strconv.ParseUint(value, 8, 32)
if err != nil { 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) mode = uint32(mode64)
case "uid", "UID": case "uid", "UID":
mountOnly = true mountOnly = true
uid64, err := strconv.ParseUint(kv[1], 10, 32) uid64, err := strconv.ParseUint(value, 10, 32)
if err != nil { 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) uid = uint32(uid64)
case "gid", "GID": case "gid", "GID":
mountOnly = true mountOnly = true
gid64, err := strconv.ParseUint(kv[1], 10, 32) gid64, err := strconv.ParseUint(value, 10, 32)
if err != nil { 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) 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) return specs.LinuxDeviceCgroup{}, fmt.Errorf("invalid device type in device-access-add: %s", devType)
} }
number := strings.SplitN(value[1], ":", 2) majorNumber, minorNumber, hasMinor := strings.Cut(value[1], ":")
if number[0] != "*" { if majorNumber != "*" {
i, err := strconv.ParseUint(number[0], 10, 64) i, err := strconv.ParseUint(majorNumber, 10, 64)
if err != nil { if err != nil {
return specs.LinuxDeviceCgroup{}, err return specs.LinuxDeviceCgroup{}, err
} }
m := int64(i) m := int64(i)
major = &m major = &m
} }
if len(number) == 2 && number[1] != "*" { if hasMinor && minorNumber != "*" {
i, err := strconv.ParseUint(number[1], 10, 64) i, err := strconv.ParseUint(minorNumber, 10, 64)
if err != nil { if err != nil {
return specs.LinuxDeviceCgroup{}, err return specs.LinuxDeviceCgroup{}, err
} }
@ -1263,11 +1266,11 @@ func GetResources(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions)
unifieds := make(map[string]string) unifieds := make(map[string]string)
for _, unified := range c.CgroupConf { for _, unified := range c.CgroupConf {
splitUnified := strings.SplitN(unified, "=", 2) key, val, hasVal := strings.Cut(unified, "=")
if len(splitUnified) < 2 { if !hasVal {
return nil, errors.New("--cgroup-conf must be formatted KEY=VALUE") return nil, errors.New("--cgroup-conf must be formatted KEY=VALUE")
} }
unifieds[splitUnified[0]] = splitUnified[1] unifieds[key] = val
} }
if len(unifieds) > 0 { if len(unifieds) > 0 {
s.ResourceLimits.Unified = unifieds s.ResourceLimits.Unified = unifieds

View File

@ -256,29 +256,29 @@ func parseMountOptions(mountType string, args []string) (*spec.Mount, error) {
var setTmpcopyup, setRORW, setSuid, setDev, setExec, setRelabel, setOwnership, setSwap bool var setTmpcopyup, setRORW, setSuid, setDev, setExec, setRelabel, setOwnership, setSwap bool
mnt := spec.Mount{} mnt := spec.Mount{}
for _, val := range args { for _, arg := range args {
kv := strings.SplitN(val, "=", 2) name, value, hasValue := strings.Cut(arg, "=")
switch kv[0] { switch name {
case "bind-nonrecursive": case "bind-nonrecursive":
if mountType != define.TypeBind { 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) mnt.Options = append(mnt.Options, define.TypeBind)
case "bind-propagation": case "bind-propagation":
if mountType != define.TypeBind { 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 { if !hasValue {
return nil, fmt.Errorf("%v: %w", kv[0], errOptionArg) return nil, fmt.Errorf("%v: %w", name, errOptionArg)
} }
mnt.Options = append(mnt.Options, kv[1]) mnt.Options = append(mnt.Options, value)
case "consistency": case "consistency":
// Often used on MACs and mistakenly on Linux platforms. // Often used on MACs and mistakenly on Linux platforms.
// Since Docker ignores this option so shall we. // Since Docker ignores this option so shall we.
continue continue
case "idmap": case "idmap":
if len(kv) > 1 { if hasValue {
mnt.Options = append(mnt.Options, fmt.Sprintf("idmap=%s", kv[1])) mnt.Options = append(mnt.Options, fmt.Sprintf("idmap=%s", value))
} else { } else {
mnt.Options = append(mnt.Options, "idmap") mnt.Options = append(mnt.Options, "idmap")
} }
@ -294,42 +294,41 @@ func parseMountOptions(mountType string, args []string) (*spec.Mount, error) {
// ro=[true|false] // ro=[true|false]
// rw // rw
// rw=[true|false] // rw=[true|false]
if kv[0] == "readonly" { if name == "readonly" {
kv[0] = "ro" name = "ro"
} }
switch len(kv) { if hasValue {
case 1: switch strings.ToLower(value) {
mnt.Options = append(mnt.Options, kv[0])
case 2:
switch strings.ToLower(kv[1]) {
case "true": case "true":
mnt.Options = append(mnt.Options, kv[0]) mnt.Options = append(mnt.Options, name)
case "false": case "false":
// Set the opposite only for rw // Set the opposite only for rw
// ro's opposite is the default // ro's opposite is the default
if kv[0] == "rw" { if name == "rw" {
mnt.Options = append(mnt.Options, "ro") mnt.Options = append(mnt.Options, "ro")
} }
} }
} else {
mnt.Options = append(mnt.Options, name)
} }
case "nodev", "dev": case "nodev", "dev":
if setDev { if setDev {
return nil, fmt.Errorf("cannot pass 'nodev' and 'dev' mnt.Options more than once: %w", errOptionArg) return nil, fmt.Errorf("cannot pass 'nodev' and 'dev' mnt.Options more than once: %w", errOptionArg)
} }
setDev = true setDev = true
mnt.Options = append(mnt.Options, kv[0]) mnt.Options = append(mnt.Options, name)
case "noexec", "exec": case "noexec", "exec":
if setExec { if setExec {
return nil, fmt.Errorf("cannot pass 'noexec' and 'exec' mnt.Options more than once: %w", errOptionArg) return nil, fmt.Errorf("cannot pass 'noexec' and 'exec' mnt.Options more than once: %w", errOptionArg)
} }
setExec = true setExec = true
mnt.Options = append(mnt.Options, kv[0]) mnt.Options = append(mnt.Options, name)
case "nosuid", "suid": case "nosuid", "suid":
if setSuid { if setSuid {
return nil, fmt.Errorf("cannot pass 'nosuid' and 'suid' mnt.Options more than once: %w", errOptionArg) return nil, fmt.Errorf("cannot pass 'nosuid' and 'suid' mnt.Options more than once: %w", errOptionArg)
} }
setSuid = true setSuid = true
mnt.Options = append(mnt.Options, kv[0]) mnt.Options = append(mnt.Options, name)
case "noswap": case "noswap":
if setSwap { if setSwap {
return nil, fmt.Errorf("cannot pass 'noswap' mnt.Options more than once: %w", errOptionArg) 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) return nil, fmt.Errorf("the 'noswap' option is only allowed with rootful tmpfs mounts: %w", errOptionArg)
} }
setSwap = true setSwap = true
mnt.Options = append(mnt.Options, kv[0]) mnt.Options = append(mnt.Options, name)
case "relabel": case "relabel":
if setRelabel { if setRelabel {
return nil, fmt.Errorf("cannot pass 'relabel' option more than once: %w", errOptionArg) return nil, fmt.Errorf("cannot pass 'relabel' option more than once: %w", errOptionArg)
} }
setRelabel = true setRelabel = true
if len(kv) != 2 { if !hasValue {
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)
} }
switch kv[1] { switch value {
case "private": case "private":
mnt.Options = append(mnt.Options, "Z") mnt.Options = append(mnt.Options, "Z")
case "shared": case "shared":
mnt.Options = append(mnt.Options, "z") mnt.Options = append(mnt.Options, "z")
default: 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": 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": case "src", "source":
if mountType == define.TypeTmpfs { 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 != "" { 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 { if !hasValue {
return nil, fmt.Errorf("%v: %w", kv[0], errOptionArg) 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) return nil, fmt.Errorf("host directory cannot be empty: %w", errOptionArg)
} }
mnt.Source = kv[1] mnt.Source = value
case "target", "dst", "destination": case "target", "dst", "destination":
if mnt.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 { if !hasValue {
return nil, fmt.Errorf("%v: %w", kv[0], errOptionArg) 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 return nil, err
} }
mnt.Destination = unixPathClean(kv[1]) mnt.Destination = unixPathClean(value)
case "tmpcopyup", "notmpcopyup": case "tmpcopyup", "notmpcopyup":
if mountType != define.TypeTmpfs { 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 { if setTmpcopyup {
return nil, fmt.Errorf("cannot pass 'tmpcopyup' and 'notmpcopyup' mnt.Options more than once: %w", errOptionArg) return nil, fmt.Errorf("cannot pass 'tmpcopyup' and 'notmpcopyup' mnt.Options more than once: %w", errOptionArg)
} }
setTmpcopyup = true setTmpcopyup = true
mnt.Options = append(mnt.Options, kv[0]) mnt.Options = append(mnt.Options, name)
case "tmpfs-mode": case "tmpfs-mode":
if mountType != define.TypeTmpfs { 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 { if !hasValue {
return nil, fmt.Errorf("%v: %w", kv[0], errOptionArg) 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": case "tmpfs-size":
if mountType != define.TypeTmpfs { 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 { if !hasValue {
return nil, fmt.Errorf("%v: %w", kv[0], errOptionArg) 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": case "U", "chown":
if setOwnership { if setOwnership {
return nil, fmt.Errorf("cannot pass 'U' or 'chown' option more than once: %w", errOptionArg) 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 { if err != nil {
return nil, err return nil, err
} }
@ -421,16 +420,16 @@ func parseMountOptions(mountType string, args []string) (*spec.Mount, error) {
setOwnership = true setOwnership = true
case "volume-label": case "volume-label":
if mountType != define.TypeVolume { 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") return nil, fmt.Errorf("the --volume-label option is not presently implemented")
case "volume-opt": case "volume-opt":
if mountType != define.TypeVolume { 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: 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 { if mountType != "glob" && len(mnt.Destination) == 0 {
@ -534,22 +533,22 @@ func getDevptsMount(args []string) (spec.Mount, error) {
var setDest bool var setDest bool
for _, val := range args { for _, arg := range args {
kv := strings.SplitN(val, "=", 2) name, value, hasValue := strings.Cut(arg, "=")
switch kv[0] { switch name {
case "uid", "gid", "mode", "ptxmode", "newinstance", "max": case "uid", "gid", "mode", "ptxmode", "newinstance", "max":
newMount.Options = append(newMount.Options, val) newMount.Options = append(newMount.Options, arg)
case "target", "dst", "destination": case "target", "dst", "destination":
if len(kv) == 1 { if !hasValue {
return newMount, fmt.Errorf("%v: %w", kv[0], errOptionArg) 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 return newMount, err
} }
newMount.Destination = unixPathClean(kv[1]) newMount.Destination = unixPathClean(value)
setDest = true setDest = true
default: 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) { func getImageVolume(args []string) (*specgen.ImageVolume, error) {
newVolume := new(specgen.ImageVolume) newVolume := new(specgen.ImageVolume)
for _, val := range args { for _, arg := range args {
kv := strings.SplitN(val, "=", 2) name, value, hasValue := strings.Cut(arg, "=")
switch kv[0] { switch name {
case "src", "source": case "src", "source":
if len(kv) == 1 { if !hasValue {
return nil, fmt.Errorf("%v: %w", kv[0], errOptionArg) return nil, fmt.Errorf("%v: %w", name, errOptionArg)
} }
newVolume.Source = kv[1] newVolume.Source = value
case "target", "dst", "destination": case "target", "dst", "destination":
if len(kv) == 1 { if !hasValue {
return nil, fmt.Errorf("%v: %w", kv[0], errOptionArg) 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 return nil, err
} }
newVolume.Destination = unixPathClean(kv[1]) newVolume.Destination = unixPathClean(value)
case "rw", "readwrite": case "rw", "readwrite":
switch kv[1] { switch value {
case "true": case "true":
newVolume.ReadWrite = true newVolume.ReadWrite = true
case "false": case "false":
// Nothing to do. RO is default. // Nothing to do. RO is default.
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": case "consistency":
// Often used on MACs and mistakenly on Linux platforms. // Often used on MACs and mistakenly on Linux platforms.
// Since Docker ignores this option so shall we. // Since Docker ignores this option so shall we.
continue continue
default: 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 // validChownFlag ensures that the U or chown flag is correctly used
func validChownFlag(flag string) (bool, error) { func validChownFlag(value string) (bool, error) {
kv := strings.SplitN(flag, "=", 2) // U=[true|false]
switch len(kv) { switch {
case 1: case strings.EqualFold(value, "true"), value == "":
case 2: return true, nil
// U=[true|false] case strings.EqualFold(value, "false"):
switch strings.ToLower(kv[1]) { return false, nil
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)
}
default: 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 // Use path instead of filepath to preserve Unix style paths on Windows

View File

@ -13,41 +13,41 @@ func Test_validChownFlag(t *testing.T) {
wantErr bool wantErr bool
}{ }{
{ {
name: "U true", name: "lower-case true",
args: args{ args: args{
flag: "U=true", flag: "true",
}, },
want: true, want: true,
wantErr: false, wantErr: false,
}, },
{ {
name: "U true case does not matter", name: "case-insensitive true",
args: args{ args: args{
flag: "u=True", flag: "True",
}, },
want: true, want: true,
wantErr: false, wantErr: false,
}, },
{ {
name: "U is false", name: "lower-case false",
args: args{ args: args{
flag: "U=false", flag: "false",
}, },
want: false, want: false,
wantErr: false, wantErr: false,
}, },
{ {
name: "chown should also work", name: "case-insensitive false",
args: args{ args: args{
flag: "chown=true", flag: "falsE",
}, },
want: true, want: false,
wantErr: false, wantErr: false,
}, },
{ {
name: "garbage value should fail", name: "garbage value should fail",
args: args{ args: args{
flag: "U=foobar", flag: "foobar",
}, },
want: false, want: false,
wantErr: true, wantErr: true,

View File

@ -471,8 +471,8 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst
// because it does try to red the value from the environment // because it does try to red the value from the environment
if !strings.Contains(env, "=") { if !strings.Contains(env, "=") {
for _, containerEnv := range info.containerEnv { for _, containerEnv := range info.containerEnv {
split := strings.SplitN(containerEnv, "=", 2) key, _, _ := strings.Cut(containerEnv, "=")
if split[0] == env { if key == env {
info.ExtraEnvs = append(info.ExtraEnvs, escapeSystemdArg(containerEnv)) info.ExtraEnvs = append(info.ExtraEnvs, escapeSystemdArg(containerEnv))
} }
} }

View File

@ -1118,10 +1118,10 @@ func ConvertKube(kube *parser.UnitFile, names map[string]string, isUser bool) (*
for _, update := range updateMaps { for _, update := range updateMaps {
annotation := fmt.Sprintf("--annotation=%s", autoUpdateLabel) annotation := fmt.Sprintf("--annotation=%s", autoUpdateLabel)
updateType := update updateType := update
val := strings.SplitN(update, "/", 2) annoValue, typ, hasSlash := strings.Cut(update, "/")
if len(val) == 2 { if hasSlash {
annotation = annotation + "/" + val[0] annotation = annotation + "/" + annoValue
updateType = val[1] updateType = typ
} }
execStart.addf("%s=%s", annotation, updateType) execStart.addf("%s=%s", annotation, updateType)
} }
@ -1741,13 +1741,13 @@ func resolveContainerMountParams(containerUnitFile, serviceUnitFile *parser.Unit
sourceIndex := -1 sourceIndex := -1
originalSource := "" originalSource := ""
for i, token := range tokens { for i, token := range tokens {
kv := strings.SplitN(token, "=", 2) key, val, hasVal := strings.Cut(token, "=")
if kv[0] == "source" || kv[0] == "src" { if key == "source" || key == "src" {
if len(kv) < 2 { if !hasVal {
return "", fmt.Errorf("source parameter does not include a value") return "", fmt.Errorf("source parameter does not include a value")
} }
sourceIndex = i sourceIndex = i
originalSource = kv[1] originalSource = val
} }
} }

View File

@ -116,19 +116,19 @@ func ParseTimestamps(value string, def int64) (int64, int64, error) {
} }
func parseTimestamp(value string) (int64, int64, error) { func parseTimestamp(value string) (int64, int64, error) {
sa := strings.SplitN(value, ".", 2) spart, npart, hasParts := strings.Cut(value, ".")
s, err := strconv.ParseInt(sa[0], 10, 64) s, err := strconv.ParseInt(spart, 10, 64)
if err != nil { if err != nil {
return s, 0, err return s, 0, err
} }
if len(sa) != 2 { if !hasParts {
return s, 0, nil return s, 0, nil
} }
n, err := strconv.ParseInt(sa[1], 10, 64) n, err := strconv.ParseInt(npart, 10, 64)
if err != nil { if err != nil {
return s, n, err return s, n, err
} }
// should already be in nanoseconds but just in case convert n to nanoseconds // 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 return s, n, nil
} }

View File

@ -131,8 +131,11 @@ func parseUids(colonDelimitKeys []byte) []string {
continue continue
} }
parseduid := uid parseduid := uid
if strings.Contains(uid, "<") && strings.Contains(uid, ">") { if ltidx := strings.Index(uid, "<"); ltidx != -1 {
parseduid = strings.SplitN(strings.SplitAfterN(uid, "<", 2)[1], ">", 2)[0] subuid := parseduid[ltidx+1:]
if gtidx := strings.Index(subuid, ">"); gtidx != -1 {
parseduid = subuid[:gtidx]
}
} }
parseduids = append(parseduids, parseduid) parseduids = append(parseduids, parseduid)
} }

View File

@ -65,9 +65,9 @@ func PrepareFilters(r *http.Request) (*map[string][]string, error) {
} }
filterMap := map[string][]string{} filterMap := map[string][]string{}
for _, filter := range filtersList { for _, filter := range filtersList {
split := strings.SplitN(filter, "=", 2) fname, filter, hasFilter := strings.Cut(filter, "=")
if len(split) > 1 { if hasFilter {
filterMap[split[0]] = append(filterMap[split[0]], split[1]) filterMap[fname] = append(filterMap[fname], filter)
} }
} }
return &filterMap, nil return &filterMap, nil

View File

@ -34,7 +34,7 @@ func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string
newOptions := make([]string, 0, len(options)) newOptions := make([]string, 0, len(options))
for _, opt := range options { for _, opt := range options {
// Some options have parameters - size, mode // 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 // add advanced options such as upperdir=/path and workdir=/path, when overlay is specified
if foundOverlay { if foundOverlay {
@ -47,11 +47,11 @@ func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string
continue continue
} }
} }
if strings.HasPrefix(splitOpt[0], "subpath") { if strings.HasPrefix(key, "subpath") {
newOptions = append(newOptions, opt) newOptions = append(newOptions, opt)
continue continue
} }
if strings.HasPrefix(splitOpt[0], "idmap") { if strings.HasPrefix(key, "idmap") {
if foundIdmap { if foundIdmap {
return nil, fmt.Errorf("the 'idmap' option can only be set once: %w", ErrDupeMntOption) 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 continue
} }
switch splitOpt[0] { switch key {
case "copy", "nocopy": case "copy", "nocopy":
if foundCopy { if foundCopy {
return nil, fmt.Errorf("only one of 'nocopy' and 'copy' can be used: %w", ErrDupeMntOption) 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) { func ParseDriverOpts(option string) (string, string, error) {
token := strings.SplitN(option, "=", 2) _, val, hasVal := strings.Cut(option, "=")
if len(token) != 2 { if !hasVal {
return "", "", fmt.Errorf("cannot parse driver opts: %w", ErrBadMntOption) return "", "", fmt.Errorf("cannot parse driver opts: %w", ErrBadMntOption)
} }
opt := strings.SplitN(token[1], "=", 2) optKey, optVal, hasOptVal := strings.Cut(val, "=")
if len(opt) != 2 { if !hasOptVal {
return "", "", fmt.Errorf("cannot parse driver opts: %w", ErrBadMntOption) return "", "", fmt.Errorf("cannot parse driver opts: %w", ErrBadMntOption)
} }
return opt[0], opt[1], nil return optKey, optVal, nil
} }

View File

@ -55,14 +55,8 @@ func init() {
// Helper function to determine the username/password passed // Helper function to determine the username/password passed
// in the creds string. It could be either or both. // in the creds string. It could be either or both.
func parseCreds(creds string) (string, string) { func parseCreds(creds string) (string, string) {
if creds == "" { username, password, _ := strings.Cut(creds, ":")
return "", "" return username, password
}
up := strings.SplitN(creds, ":", 2)
if len(up) == 1 {
return up[0], ""
}
return up[0], up[1]
} }
// Takes build context and validates `.containerignore` or `.dockerignore` // 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 // GetAutoOptions returns an AutoUserNsOptions with the settings to automatically set up
// a user namespace. // a user namespace.
func GetAutoOptions(n namespaces.UsernsMode) (*stypes.AutoUserNsOptions, error) { func GetAutoOptions(n namespaces.UsernsMode) (*stypes.AutoUserNsOptions, error) {
parts := strings.SplitN(string(n), ":", 2) mode, opts, hasOpts := strings.Cut(string(n), ":")
if parts[0] != "auto" { if mode != "auto" {
return nil, fmt.Errorf("wrong user namespace mode") return nil, fmt.Errorf("wrong user namespace mode")
} }
options := stypes.AutoUserNsOptions{} options := stypes.AutoUserNsOptions{}
if len(parts) == 1 { if !hasOpts {
return &options, nil return &options, nil
} }
@ -937,32 +931,32 @@ func GetAutoOptions(n namespaces.UsernsMode) (*stypes.AutoUserNsOptions, error)
} }
} }
for _, o := range strings.Split(parts[1], ",") { for _, o := range strings.Split(opts, ",") {
v := strings.SplitN(o, "=", 2) key, val, hasVal := strings.Cut(o, "=")
if len(v) != 2 { if !hasVal {
return nil, fmt.Errorf("invalid option specified: %q", o) return nil, fmt.Errorf("invalid option specified: %q", o)
} }
switch v[0] { switch key {
case "size": case "size":
s, err := strconv.ParseUint(v[1], 10, 32) s, err := strconv.ParseUint(val, 10, 32)
if err != nil { if err != nil {
return nil, err return nil, err
} }
options.Size = uint32(s) options.Size = uint32(s)
case "uidmapping": case "uidmapping":
mapping, err := parseAutoIDMap(v[1], "UID", parentUIDMap) mapping, err := parseAutoIDMap(val, "UID", parentUIDMap)
if err != nil { if err != nil {
return nil, err return nil, err
} }
options.AdditionalUIDMappings = append(options.AdditionalUIDMappings, mapping...) options.AdditionalUIDMappings = append(options.AdditionalUIDMappings, mapping...)
case "gidmapping": case "gidmapping":
mapping, err := parseAutoIDMap(v[1], "GID", parentGIDMap) mapping, err := parseAutoIDMap(val, "GID", parentGIDMap)
if err != nil { if err != nil {
return nil, err return nil, err
} }
options.AdditionalGIDMappings = append(options.AdditionalGIDMappings, mapping...) options.AdditionalGIDMappings = append(options.AdditionalGIDMappings, mapping...)
default: default:
return nil, fmt.Errorf("unknown option specified: %q", v[0]) return nil, fmt.Errorf("unknown option specified: %q", key)
} }
} }
return &options, nil return &options, nil
@ -1077,9 +1071,9 @@ func getTomlStorage(storeOptions *stypes.StoreOptions) *tomlConfig {
config.Storage.RunRoot = storeOptions.RunRoot config.Storage.RunRoot = storeOptions.RunRoot
config.Storage.GraphRoot = storeOptions.GraphRoot config.Storage.GraphRoot = storeOptions.GraphRoot
for _, i := range storeOptions.GraphDriverOptions { for _, i := range storeOptions.GraphDriverOptions {
s := strings.SplitN(i, "=", 2) program, hasPrefix := strings.CutPrefix(i, "overlay.mount_program=")
if s[0] == "overlay.mount_program" && len(s) == 2 { if hasPrefix {
config.Storage.Options.MountProgram = s[1] config.Storage.Options.MountProgram = program
} }
} }

View File

@ -209,12 +209,11 @@ var _ = SynchronizedAfterSuite(func() {
scanner := bufio.NewScanner(f) scanner := bufio.NewScanner(f)
for scanner.Scan() { for scanner.Scan() {
text := scanner.Text() text := scanner.Text()
timing := strings.SplitN(text, "\t\t", 2) name, durationString, ok := strings.Cut(text, "\t\t")
if len(timing) != 2 { if !ok {
Fail(fmt.Sprintf("incorrect timing line: %q", text)) Fail(fmt.Sprintf("incorrect timing line: %q", text))
} }
name := timing[0] duration, err := strconv.ParseFloat(durationString, 64)
duration, err := strconv.ParseFloat(timing[1], 64)
Expect(err).ToNot(HaveOccurred(), "failed to parse float from timings file") Expect(err).ToNot(HaveOccurred(), "failed to parse float from timings file")
testTimings = append(testTimings, testResult{name: name, length: duration}) testTimings = append(testTimings, testResult{name: name, length: duration})
} }

View File

@ -202,12 +202,8 @@ func keyValueStringToMap(keyValueString, separator string) (map[string]string, e
return nil, err return nil, err
} }
for _, param := range keyVarList[0] { for _, param := range keyVarList[0] {
val := "" key, val, _ := strings.Cut(param, "=")
kv := strings.SplitN(param, "=", 2) keyValMap[key] = val
if len(kv) == 2 {
val = kv[1]
}
keyValMap[kv[0]] = val
} }
return keyValMap, nil return keyValMap, nil