diff --git a/api/types/filters/parse.go b/api/types/filters/parse.go index 52c190ec79..e1b84df817 100644 --- a/api/types/filters/parse.go +++ b/api/types/filters/parse.go @@ -166,13 +166,13 @@ func (args Args) MatchKVList(key string, sources map[string]string) bool { } for value := range fieldValues { - testKV := strings.SplitN(value, "=", 2) + testK, testV, hasValue := strings.Cut(value, "=") - v, ok := sources[testKV[0]] + v, ok := sources[testK] if !ok { return false } - if len(testKV) == 2 && testKV[1] != v { + if hasValue && testV != v { return false } } diff --git a/api/types/time/timestamp.go b/api/types/time/timestamp.go index 5fddd54163..cab5c32e3f 100644 --- a/api/types/time/timestamp.go +++ b/api/types/time/timestamp.go @@ -105,27 +105,27 @@ func GetTimestamp(value string, reference time.Time) (string, error) { // since := time.Unix(seconds, nanoseconds) // // returns seconds as defaultSeconds if value == "" -func ParseTimestamps(value string, defaultSeconds int64) (int64, int64, error) { +func ParseTimestamps(value string, defaultSeconds int64) (seconds int64, nanoseconds int64, err error) { if value == "" { return defaultSeconds, 0, nil } return parseTimestamp(value) } -func parseTimestamp(value string) (int64, int64, error) { - sa := strings.SplitN(value, ".", 2) - s, err := strconv.ParseInt(sa[0], 10, 64) +func parseTimestamp(value string) (sec int64, nsec int64, err error) { + s, n, ok := strings.Cut(value, ".") + sec, err = strconv.ParseInt(s, 10, 64) if err != nil { - return s, 0, err + return sec, 0, err } - if len(sa) != 2 { - return s, 0, nil + if !ok { + return sec, 0, nil } - n, err := strconv.ParseInt(sa[1], 10, 64) + nsec, err = strconv.ParseInt(n, 10, 64) if err != nil { - return s, n, err + return sec, nsec, err } // should already be in nanoseconds but just in case convert n to nanoseconds - n = int64(float64(n) * math.Pow(float64(10), float64(9-len(sa[1])))) - return s, n, nil + nsec = int64(float64(nsec) * math.Pow(float64(10), float64(9-len(n)))) + return sec, nsec, nil } diff --git a/api/types/types.go b/api/types/types.go index ab4b7fb829..cd5916ae15 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -348,20 +348,19 @@ func DecodeSecurityOptions(opts []string) ([]SecurityOpt, error) { continue } secopt := SecurityOpt{} - split := strings.Split(opt, ",") - for _, s := range split { - kv := strings.SplitN(s, "=", 2) - if len(kv) != 2 { + for _, s := range strings.Split(opt, ",") { + k, v, ok := strings.Cut(s, "=") + if !ok { return nil, fmt.Errorf("invalid security option %q", s) } - if kv[0] == "" || kv[1] == "" { + if k == "" || v == "" { return nil, errors.New("invalid empty security option") } - if kv[0] == "name" { - secopt.Name = kv[1] + if k == "name" { + secopt.Name = v continue } - secopt.Options = append(secopt.Options, KeyValue{Key: kv[0], Value: kv[1]}) + secopt.Options = append(secopt.Options, KeyValue{Key: k, Value: v}) } so = append(so, secopt) } diff --git a/builder/dockerfile/dispatchers.go b/builder/dockerfile/dispatchers.go index 43b0fbf3cf..acd115eade 100644 --- a/builder/dockerfile/dispatchers.go +++ b/builder/dockerfile/dispatchers.go @@ -46,8 +46,7 @@ func dispatchEnv(ctx context.Context, d dispatchRequest, c *instructions.EnvComm commitMessage.WriteString(" " + newVar) gotOne := false for i, envVar := range runConfig.Env { - envParts := strings.SplitN(envVar, "=", 2) - compareFrom := envParts[0] + compareFrom, _, _ := strings.Cut(envVar, "=") if shell.EqualEnvKeys(compareFrom, name) { runConfig.Env[i] = newVar gotOne = true @@ -408,9 +407,9 @@ func dispatchRun(ctx context.Context, d dispatchRequest, c *instructions.RunComm // These args are transparent so resulting image should be the same regardless // of the value. func prependEnvOnCmd(buildArgs *BuildArgs, buildArgVars []string, cmd strslice.StrSlice) strslice.StrSlice { - var tmpBuildEnv []string + tmpBuildEnv := make([]string, 0, len(buildArgVars)) for _, env := range buildArgVars { - key := strings.SplitN(env, "=", 2)[0] + key, _, _ := strings.Cut(env, "=") if buildArgs.IsReferencedOrNotBuiltin(key) { tmpBuildEnv = append(tmpBuildEnv, env) } @@ -418,7 +417,7 @@ func prependEnvOnCmd(buildArgs *BuildArgs, buildArgVars []string, cmd strslice.S sort.Strings(tmpBuildEnv) tmpEnv := append([]string{fmt.Sprintf("|%d", len(tmpBuildEnv))}, tmpBuildEnv...) - return strslice.StrSlice(append(tmpEnv, cmd...)) + return append(tmpEnv, cmd...) } // CMD foo diff --git a/builder/remotecontext/git/gitutils.go b/builder/remotecontext/git/gitutils.go index 8ca6918aee..f9b2b4b9c4 100644 --- a/builder/remotecontext/git/gitutils.go +++ b/builder/remotecontext/git/gitutils.go @@ -97,15 +97,10 @@ func parseRemoteURL(remoteURL string) (gitRepo, error) { remoteURL = "https://" + remoteURL } - var fragment string if strings.HasPrefix(remoteURL, "git@") { // git@.. is not an URL, so cannot be parsed as URL - parts := strings.SplitN(remoteURL, "#", 2) - - repo.remote = parts[0] - if len(parts) == 2 { - fragment = parts[1] - } + var fragment string + repo.remote, fragment, _ = strings.Cut(remoteURL, "#") repo.ref, repo.subdir = getRefAndSubdir(fragment) } else { u, err := url.Parse(remoteURL) @@ -126,15 +121,11 @@ func parseRemoteURL(remoteURL string) (gitRepo, error) { } func getRefAndSubdir(fragment string) (ref string, subdir string) { - refAndDir := strings.SplitN(fragment, ":", 2) - ref = "master" - if len(refAndDir[0]) != 0 { - ref = refAndDir[0] + ref, subdir, _ = strings.Cut(fragment, ":") + if ref == "" { + ref = "master" } - if len(refAndDir) > 1 && len(refAndDir[1]) != 0 { - subdir = refAndDir[1] - } - return + return ref, subdir } func fetchArgs(remoteURL string, ref string) []string { diff --git a/client/client.go b/client/client.go index 26a0fa2756..224ed6f454 100644 --- a/client/client.go +++ b/client/client.go @@ -282,13 +282,12 @@ func (cli *Client) HTTPClient() *http.Client { // ParseHostURL parses a url string, validates the string is a host url, and // returns the parsed URL func ParseHostURL(host string) (*url.URL, error) { - protoAddrParts := strings.SplitN(host, "://", 2) - if len(protoAddrParts) == 1 { + proto, addr, ok := strings.Cut(host, "://") + if !ok || addr == "" { return nil, errors.Errorf("unable to parse docker host `%s`", host) } var basePath string - proto, addr := protoAddrParts[0], protoAddrParts[1] if proto == "tcp" { parsed, err := url.Parse("tcp://" + addr) if err != nil { diff --git a/client/ping.go b/client/ping.go index 27e8695cb5..347ae71e02 100644 --- a/client/ping.go +++ b/client/ping.go @@ -64,10 +64,10 @@ func parsePingResponse(cli *Client, resp serverResponse) (types.Ping, error) { ping.BuilderVersion = types.BuilderVersion(bv) } if si := resp.header.Get("Swarm"); si != "" { - parts := strings.SplitN(si, "/", 2) + state, role, _ := strings.Cut(si, "/") ping.SwarmStatus = &swarm.Status{ - NodeState: swarm.LocalNodeState(parts[0]), - ControlAvailable: len(parts) == 2 && parts[1] == "manager", + NodeState: swarm.LocalNodeState(state), + ControlAvailable: role == "manager", } } err := cli.checkResponseErr(resp) diff --git a/cmd/dockerd/daemon.go b/cmd/dockerd/daemon.go index 95f89f5b2c..8cab93c81f 100644 --- a/cmd/dockerd/daemon.go +++ b/cmd/dockerd/daemon.go @@ -662,13 +662,11 @@ func loadListeners(cli *DaemonCli, serverConfig *apiserver.Config) ([]string, er for i := 0; i < len(serverConfig.Hosts); i++ { protoAddr := serverConfig.Hosts[i] - protoAddrParts := strings.SplitN(serverConfig.Hosts[i], "://", 2) - if len(protoAddrParts) != 2 { + proto, addr, ok := strings.Cut(protoAddr, "://") + if !ok || addr == "" { return nil, fmt.Errorf("bad format %s, expected PROTO://ADDR", protoAddr) } - proto, addr := protoAddrParts[0], protoAddrParts[1] - // It's a bad idea to bind to TCP without tlsverify. authEnabled := serverConfig.TLSConfig != nil && serverConfig.TLSConfig.ClientAuth == tls.RequireAndVerifyClientCert if proto == "tcp" && !authEnabled { @@ -719,7 +717,7 @@ func loadListeners(cli *DaemonCli, serverConfig *apiserver.Config) ([]string, er return nil, err } logrus.Debugf("Listener created for HTTP on %s (%s)", proto, addr) - hosts = append(hosts, protoAddrParts[1]) + hosts = append(hosts, addr) cli.api.Accept(addr, ls...) } diff --git a/daemon/cluster/executor/container/controller.go b/daemon/cluster/executor/container/controller.go index 486342775f..c5f56baf1a 100644 --- a/daemon/cluster/executor/container/controller.go +++ b/daemon/cluster/executor/container/controller.go @@ -637,18 +637,18 @@ func parsePortMap(portMap nat.PortMap) ([]*api.PortConfig, error) { exposedPorts := make([]*api.PortConfig, 0, len(portMap)) for portProtocol, mapping := range portMap { - parts := strings.SplitN(string(portProtocol), "/", 2) - if len(parts) != 2 { + p, proto, ok := strings.Cut(string(portProtocol), "/") + if !ok { return nil, fmt.Errorf("invalid port mapping: %s", portProtocol) } - port, err := strconv.ParseUint(parts[0], 10, 16) + port, err := strconv.ParseUint(p, 10, 16) if err != nil { return nil, err } var protocol api.PortConfig_Protocol - switch strings.ToLower(parts[1]) { + switch strings.ToLower(proto) { case "tcp": protocol = api.ProtocolTCP case "udp": @@ -656,7 +656,7 @@ func parsePortMap(portMap nat.PortMap) ([]*api.PortConfig, error) { case "sctp": protocol = api.ProtocolSCTP default: - return nil, fmt.Errorf("invalid protocol: %s", parts[1]) + return nil, fmt.Errorf("invalid protocol: %s", proto) } for _, binding := range mapping { diff --git a/daemon/cluster/executor/container/executor.go b/daemon/cluster/executor/container/executor.go index fd5b31a686..8af800aa3c 100644 --- a/daemon/cluster/executor/container/executor.go +++ b/daemon/cluster/executor/container/executor.go @@ -114,11 +114,11 @@ func (e *executor) Describe(ctx context.Context) (*api.NodeDescription, error) { // parse []string labels into a map[string]string labels := map[string]string{} for _, l := range info.Labels { - stringSlice := strings.SplitN(l, "=", 2) + k, v, ok := strings.Cut(l, "=") // this will take the last value in the list for a given key // ideally, one shouldn't assign multiple values to the same key - if len(stringSlice) > 1 { - labels[stringSlice[0]] = stringSlice[1] + if ok { + labels[k] = v } } diff --git a/daemon/commit.go b/daemon/commit.go index 7b7d4e4446..f7b69d13d0 100644 --- a/daemon/commit.go +++ b/daemon/commit.go @@ -38,16 +38,16 @@ func merge(userConf, imageConf *containertypes.Config) error { } else { for _, imageEnv := range imageConf.Env { found := false - imageEnvKey := strings.Split(imageEnv, "=")[0] + imageEnvKey, _, _ := strings.Cut(imageEnv, "=") for _, userEnv := range userConf.Env { - userEnvKey := strings.Split(userEnv, "=")[0] + userEnvKey, _, _ := strings.Cut(userEnv, "=") if isWindows { // Case insensitive environment variables on Windows - imageEnvKey = strings.ToUpper(imageEnvKey) - userEnvKey = strings.ToUpper(userEnvKey) + found = strings.EqualFold(imageEnvKey, userEnvKey) + } else { + found = imageEnvKey == userEnvKey } - if imageEnvKey == userEnvKey { - found = true + if found { break } } diff --git a/daemon/container_operations.go b/daemon/container_operations.go index 85ad568cbc..b3ad66739a 100644 --- a/daemon/container_operations.go +++ b/daemon/container_operations.go @@ -107,18 +107,18 @@ func (daemon *Daemon) buildSandboxOptions(container *container.Container) ([]lib if _, err := opts.ValidateExtraHost(extraHost); err != nil { return nil, err } - parts := strings.SplitN(extraHost, ":", 2) + host, ip, _ := strings.Cut(extraHost, ":") // If the IP Address is a string called "host-gateway", replace this // value with the IP address stored in the daemon level HostGatewayIP // config variable - if parts[1] == opts.HostGatewayName { + if ip == opts.HostGatewayName { gateway := daemon.configStore.HostGatewayIP.String() if gateway == "" { return nil, fmt.Errorf("unable to derive the IP value for host-gateway") } - parts[1] = gateway + ip = gateway } - sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(parts[0], parts[1])) + sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(host, ip)) } if container.HostConfig.PortBindings != nil { diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go index 33e4eeecbf..5e7abf3239 100644 --- a/daemon/daemon_unix.go +++ b/daemon/daemon_unix.go @@ -215,26 +215,27 @@ func parseSecurityOpt(container *container.Container, config *containertypes.Hos continue } - var con []string + var k, v string + var ok bool if strings.Contains(opt, "=") { - con = strings.SplitN(opt, "=", 2) + k, v, ok = strings.Cut(opt, "=") } else if strings.Contains(opt, ":") { - con = strings.SplitN(opt, ":", 2) + k, v, ok = strings.Cut(opt, ":") logrus.Warn("Security options with `:` as a separator are deprecated and will be completely unsupported in 17.04, use `=` instead.") } - if len(con) != 2 { + if !ok { return fmt.Errorf("invalid --security-opt 1: %q", opt) } - switch con[0] { + switch k { case "label": - labelOpts = append(labelOpts, con[1]) + labelOpts = append(labelOpts, v) case "apparmor": - container.AppArmorProfile = con[1] + container.AppArmorProfile = v case "seccomp": - container.SeccompProfile = con[1] + container.SeccompProfile = v case "no-new-privileges": - noNewPrivileges, err := strconv.ParseBool(con[1]) + noNewPrivileges, err := strconv.ParseBool(v) if err != nil { return fmt.Errorf("invalid --security-opt 2: %q", opt) } @@ -1360,8 +1361,8 @@ func (daemon *Daemon) registerLinks(container *container.Container, hostConfig * return errors.Wrapf(err, "could not get container for %s", name) } for child.HostConfig.NetworkMode.IsContainer() { - parts := strings.SplitN(string(child.HostConfig.NetworkMode), ":", 2) - child, err = daemon.GetContainer(parts[1]) + cid := child.HostConfig.NetworkMode.ConnectedContainer() + child, err = daemon.GetContainer(cid) if err != nil { if errdefs.IsNotFound(err) { // Trying to link to a non-existing container is not valid, and @@ -1370,7 +1371,7 @@ func (daemon *Daemon) registerLinks(container *container.Container, hostConfig * // image could not be found (see moby/moby#39823) err = errdefs.InvalidParameter(err) } - return errors.Wrapf(err, "Could not get container for %s", parts[1]) + return errors.Wrapf(err, "could not get container for %s", cid) } } if child.HostConfig.NetworkMode.IsHost() { diff --git a/daemon/events/testutils/testutils.go b/daemon/events/testutils/testutils.go index c9ef45da2a..cb72f9e7de 100644 --- a/daemon/events/testutils/testutils.go +++ b/daemon/events/testutils/testutils.go @@ -57,9 +57,9 @@ func Scan(text string) (*events.Message, error) { } attrs := make(map[string]string) - for _, a := range strings.SplitN(md["attributes"], ", ", -1) { - kv := strings.SplitN(a, "=", 2) - attrs[kv[0]] = kv[1] + for _, a := range strings.Split(md["attributes"], ", ") { + k, v, _ := strings.Cut(a, "=") + attrs[k] = v } return &events.Message{ diff --git a/daemon/graphdriver/windows/windows.go b/daemon/graphdriver/windows/windows.go index 32be3e059c..4328dee93e 100644 --- a/daemon/graphdriver/windows/windows.go +++ b/daemon/graphdriver/windows/windows.go @@ -110,15 +110,16 @@ func InitFilter(home string, options []string, _ idtools.IdentityMapping) (graph return nil, fmt.Errorf("windowsfilter failed to create '%s': %v", home, err) } - storageOpt := make(map[string]string) - storageOpt["size"] = defaultSandboxSize - - for _, v := range options { - opt := strings.SplitN(v, "=", 2) - storageOpt[strings.ToLower(opt[0])] = opt[1] + storageOpt := map[string]string{ + "size": defaultSandboxSize, } - storageOptions, err := parseStorageOpt(storageOpt) + for _, o := range options { + k, v, _ := strings.Cut(o, "=") + storageOpt[strings.ToLower(k)] = v + } + + opts, err := parseStorageOpt(storageOpt) if err != nil { return nil, fmt.Errorf("windowsfilter failed to parse default storage options - %s", err) } @@ -130,7 +131,7 @@ func InitFilter(home string, options []string, _ idtools.IdentityMapping) (graph }, cache: make(map[string]string), ctr: graphdriver.NewRefCounter(&checker{}), - defaultStorageOpts: storageOptions, + defaultStorageOpts: opts, } return d, nil } diff --git a/daemon/graphdriver/zfs/zfs_freebsd.go b/daemon/graphdriver/zfs/zfs_freebsd.go index f15aae0596..5d86dc3a97 100644 --- a/daemon/graphdriver/zfs/zfs_freebsd.go +++ b/daemon/graphdriver/zfs/zfs_freebsd.go @@ -1,7 +1,6 @@ package zfs // import "github.com/docker/docker/daemon/graphdriver/zfs" import ( - "fmt" "strings" "github.com/docker/docker/daemon/graphdriver" @@ -12,7 +11,7 @@ import ( func checkRootdirFs(rootdir string) error { var buf unix.Statfs_t if err := unix.Statfs(rootdir, &buf); err != nil { - return fmt.Errorf("Failed to access '%s': %s", rootdir, err) + return err } // on FreeBSD buf.Fstypename contains ['z', 'f', 's', 0 ... ] @@ -24,15 +23,14 @@ func checkRootdirFs(rootdir string) error { return nil } +const maxlen = 12 + func getMountpoint(id string) string { - maxlen := 12 - - // we need to preserve filesystem suffix - suffix := strings.SplitN(id, "-", 2) - - if len(suffix) > 1 { - return id[:maxlen] + "-" + suffix[1] + id, suffix, _ := strings.Cut(id, "-") + id = id[:maxlen] + if suffix != "" { + // preserve filesystem suffix. + id += "-" + suffix } - - return id[:maxlen] + return id } diff --git a/daemon/info.go b/daemon/info.go index 4162870042..44af037b5e 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -206,9 +206,7 @@ func (daemon *Daemon) fillAPIInfo(v *types.Info) { cfg := daemon.configStore for _, host := range cfg.Hosts { // cnf.Hosts is normalized during startup, so should always have a scheme/proto - h := strings.SplitN(host, "://", 2) - proto := h[0] - addr := h[1] + proto, addr, _ := strings.Cut(host, "://") if proto != "tcp" { continue } diff --git a/daemon/links/links.go b/daemon/links/links.go index a51c97d1f0..ba4381a565 100644 --- a/daemon/links/links.go +++ b/daemon/links/links.go @@ -94,15 +94,15 @@ func (l *Link) ToEnv() []string { if l.ChildEnvironment != nil { for _, v := range l.ChildEnvironment { - parts := strings.SplitN(v, "=", 2) - if len(parts) < 2 { + name, val, ok := strings.Cut(v, "=") + if !ok { continue } // Ignore a few variables that are added during docker build (and not really relevant to linked containers) - if parts[0] == "HOME" || parts[0] == "PATH" { + if name == "HOME" || name == "PATH" { continue } - env = append(env, fmt.Sprintf("%s_ENV_%s=%s", alias, parts[0], parts[1])) + env = append(env, fmt.Sprintf("%s_ENV_%s=%s", alias, name, val)) } } return env diff --git a/daemon/logger/journald/internal/sdjournal/sdjournal.go b/daemon/logger/journald/internal/sdjournal/sdjournal.go index c1f3c8ee3c..5d78f17fdb 100644 --- a/daemon/logger/journald/internal/sdjournal/sdjournal.go +++ b/daemon/logger/journald/internal/sdjournal/sdjournal.go @@ -234,8 +234,8 @@ func (j *Journal) Data() (map[string]string, error) { return m, fmt.Errorf("journald: error enumerating entry data: %w", syscall.Errno(-rc)) } - kv := strings.SplitN(C.GoStringN((*C.char)(data), C.int(len)), "=", 2) - m[kv[0]] = kv[1] + k, v, _ := strings.Cut(C.GoStringN((*C.char)(data), C.int(len)), "=") + m[k] = v } } diff --git a/daemon/logger/loginfo.go b/daemon/logger/loginfo.go index 12034421fc..7c8e4b2dfd 100644 --- a/daemon/logger/loginfo.go +++ b/daemon/logger/loginfo.go @@ -59,8 +59,8 @@ func (info *Info) ExtraAttributes(keyMod func(string) string) (map[string]string envMapping := make(map[string]string) for _, e := range info.ContainerEnv { - if kv := strings.SplitN(e, "=", 2); len(kv) == 2 { - envMapping[kv[0]] = kv[1] + if k, v, ok := strings.Cut(e, "="); ok { + envMapping[k] = v } } diff --git a/daemon/oci_linux.go b/daemon/oci_linux.go index 82b035f903..2806abfaae 100644 --- a/daemon/oci_linux.go +++ b/daemon/oci_linux.go @@ -241,8 +241,7 @@ func WithNamespaces(daemon *Daemon, c *container.Container) coci.SpecOpts { // network if !c.Config.NetworkDisabled { ns := specs.LinuxNamespace{Type: "network"} - parts := strings.SplitN(string(c.HostConfig.NetworkMode), ":", 2) - if parts[0] == "container" { + if c.HostConfig.NetworkMode.IsContainer() { nc, err := daemon.getNetworkedContainer(c.ID, c.HostConfig.NetworkMode.ConnectedContainer()) if err != nil { return err diff --git a/daemon/oci_windows.go b/daemon/oci_windows.go index 38764c524a..9c55925ed2 100644 --- a/daemon/oci_windows.go +++ b/daemon/oci_windows.go @@ -279,29 +279,31 @@ func (daemon *Daemon) setWindowsCredentialSpec(c *container.Container, s *specs. // this doesn't seem like a great idea? credentialSpec := "" + // TODO(thaJeztah): extract validating and parsing SecurityOpt to a reusable function. for _, secOpt := range c.HostConfig.SecurityOpt { - optSplits := strings.SplitN(secOpt, "=", 2) - if len(optSplits) != 2 { + k, v, ok := strings.Cut(secOpt, "=") + if !ok { return errdefs.InvalidParameter(fmt.Errorf("invalid security option: no equals sign in supplied value %s", secOpt)) } - if !strings.EqualFold(optSplits[0], "credentialspec") { - return errdefs.InvalidParameter(fmt.Errorf("security option not supported: %s", optSplits[0])) + // FIXME(thaJeztah): options should not be case-insensitive + if !strings.EqualFold(k, "credentialspec") { + return errdefs.InvalidParameter(fmt.Errorf("security option not supported: %s", k)) } - credSpecSplits := strings.SplitN(optSplits[1], "://", 2) - if len(credSpecSplits) != 2 || credSpecSplits[1] == "" { + scheme, value, ok := strings.Cut(v, "://") + if !ok || value == "" { return errInvalidCredentialSpecSecOpt } - value := credSpecSplits[1] - var err error - switch strings.ToLower(credSpecSplits[0]) { + switch strings.ToLower(scheme) { case "file": - if credentialSpec, err = readCredentialSpecFile(c.ID, daemon.root, filepath.Clean(value)); err != nil { + credentialSpec, err = readCredentialSpecFile(c.ID, daemon.root, filepath.Clean(value)) + if err != nil { return errdefs.InvalidParameter(err) } case "registry": - if credentialSpec, err = readCredentialSpecRegistry(c.ID, value); err != nil { + credentialSpec, err = readCredentialSpecRegistry(c.ID, value) + if err != nil { return errdefs.InvalidParameter(err) } case "config": @@ -439,44 +441,41 @@ func readCredentialSpecRegistry(id, name string) (string, error) { // This allows for staging on machines which do not have the necessary components. func readCredentialSpecFile(id, root, location string) (string, error) { if filepath.IsAbs(location) { - return "", fmt.Errorf("invalid credential spec - file:// path cannot be absolute") + return "", fmt.Errorf("invalid credential spec: file:// path cannot be absolute") } base := filepath.Join(root, credentialSpecFileLocation) full := filepath.Join(base, location) if !strings.HasPrefix(full, base) { - return "", fmt.Errorf("invalid credential spec - file:// path must be under %s", base) + return "", fmt.Errorf("invalid credential spec: file:// path must be under %s", base) } bcontents, err := os.ReadFile(full) if err != nil { - return "", errors.Wrapf(err, "credential spec for container %s could not be read from file %q", id, full) + return "", errors.Wrapf(err, "failed to load credential spec for container %s", id) } return string(bcontents[:]), nil } func setupWindowsDevices(devices []containertypes.DeviceMapping) (specDevices []specs.WindowsDevice, err error) { - if len(devices) == 0 { - return - } - for _, deviceMapping := range devices { - devicePath := deviceMapping.PathOnHost - if strings.HasPrefix(devicePath, "class/") { - devicePath = strings.Replace(devicePath, "class/", "class://", 1) + if strings.HasPrefix(deviceMapping.PathOnHost, "class/") { + specDevices = append(specDevices, specs.WindowsDevice{ + ID: strings.TrimPrefix(deviceMapping.PathOnHost, "class/"), + IDType: "class", + }) + } else { + idType, id, ok := strings.Cut(deviceMapping.PathOnHost, "://") + if !ok { + return nil, errors.Errorf("invalid device assignment path: '%s', must be 'class/ID' or 'IDType://ID'", deviceMapping.PathOnHost) + } + if idType == "" { + return nil, errors.Errorf("invalid device assignment path: '%s', IDType cannot be empty", deviceMapping.PathOnHost) + } + specDevices = append(specDevices, specs.WindowsDevice{ + ID: id, + IDType: idType, + }) } - - srcParts := strings.SplitN(devicePath, "://", 2) - if len(srcParts) != 2 { - return nil, errors.Errorf("invalid device assignment path: '%s', must be 'class/ID' or 'IDType://ID'", deviceMapping.PathOnHost) - } - if srcParts[0] == "" { - return nil, errors.Errorf("invalid device assignment path: '%s', IDType cannot be empty", deviceMapping.PathOnHost) - } - wd := specs.WindowsDevice{ - ID: srcParts[1], - IDType: srcParts[0], - } - specDevices = append(specDevices, wd) } - return + return specDevices, nil } diff --git a/daemon/oci_windows_test.go b/daemon/oci_windows_test.go index ea0330b368..9237ddc12e 100644 --- a/daemon/oci_windows_test.go +++ b/daemon/oci_windows_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/fs" containertypes "github.com/docker/docker/api/types/container" @@ -87,7 +88,7 @@ func TestSetWindowsCredentialSpecInSpec(t *testing.T) { spec := &specs.Spec{} err := daemon.setWindowsCredentialSpec(containerFactory(`file://C:\path\to\my\credspec.json`), spec) - assert.ErrorContains(t, err, "invalid credential spec - file:// path cannot be absolute") + assert.ErrorContains(t, err, "invalid credential spec: file:// path cannot be absolute") assert.Check(t, spec.Windows == nil) }) @@ -96,7 +97,7 @@ func TestSetWindowsCredentialSpecInSpec(t *testing.T) { spec := &specs.Spec{} err := daemon.setWindowsCredentialSpec(containerFactory(`file://..\credspec.json`), spec) - assert.ErrorContains(t, err, fmt.Sprintf("invalid credential spec - file:// path must be under %s", credSpecsDir)) + assert.ErrorContains(t, err, fmt.Sprintf("invalid credential spec: file:// path must be under %s", credSpecsDir)) assert.Check(t, spec.Windows == nil) }) @@ -105,9 +106,8 @@ func TestSetWindowsCredentialSpecInSpec(t *testing.T) { spec := &specs.Spec{} err := daemon.setWindowsCredentialSpec(containerFactory("file://i-dont-exist.json"), spec) - assert.ErrorContains(t, err, fmt.Sprintf("credential spec for container %s could not be read from file", dummyContainerID)) - assert.ErrorContains(t, err, "The system cannot find") - + assert.Check(t, is.ErrorContains(err, fmt.Sprintf("failed to load credential spec for container %s", dummyContainerID))) + assert.Check(t, is.ErrorIs(err, os.ErrNotExist)) assert.Check(t, spec.Windows == nil) }) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 34969a5a7b..7ce337c52e 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -211,21 +211,21 @@ func (s *DockerCLIBuildSuite) TestBuildEnvironmentReplacementEnv(c *testing.T) { envCount := 0 for _, env := range envResult { - parts := strings.SplitN(env, "=", 2) - if parts[0] == "bar" { + k, v, _ := strings.Cut(env, "=") + if k == "bar" { found = true - if parts[1] != "zzz" { - c.Fatalf("Could not find replaced var for env `bar`: got %q instead of `zzz`", parts[1]) + if v != "zzz" { + c.Fatalf("Could not find replaced var for env `bar`: got %q instead of `zzz`", v) } - } else if strings.HasPrefix(parts[0], "env") { + } else if strings.HasPrefix(k, "env") { envCount++ - if parts[1] != "zzz" { - c.Fatalf("%s should be 'zzz' but instead its %q", parts[0], parts[1]) + if v != "zzz" { + c.Fatalf("%s should be 'zzz' but instead its %q", k, v) } - } else if strings.HasPrefix(parts[0], "env") { + } else if strings.HasPrefix(k, "env") { envCount++ - if parts[1] != "foo" { - c.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1]) + if v != "foo" { + c.Fatalf("%s should be 'foo' but instead its %q", k, v) } } } diff --git a/integration-cli/docker_cli_links_test.go b/integration-cli/docker_cli_links_test.go index 902cb104fc..bdcd4c59a5 100644 --- a/integration-cli/docker_cli_links_test.go +++ b/integration-cli/docker_cli_links_test.go @@ -10,7 +10,7 @@ import ( "github.com/docker/docker/runconfig" "gotest.tools/v3/assert" - "gotest.tools/v3/assert/cmp" + is "gotest.tools/v3/assert/cmp" ) type DockerCLILinksSuite struct { @@ -39,11 +39,8 @@ func (s *DockerCLILinksSuite) TestLinksInvalidContainerTarget(c *testing.T) { out, _, err := dockerCmdWithError("run", "--link", "bogus:alias", "busybox", "true") // an invalid container target should produce an error - assert.Assert(c, err != nil, "out: %s", out) - // an invalid container target should produce an error - // note: convert the output to lowercase first as the error string - // capitalization was changed after API version 1.32 - assert.Assert(c, strings.Contains(strings.ToLower(out), "could not get container")) + assert.Check(c, is.ErrorContains(err, "could not get container for bogus")) + assert.Check(c, is.Contains(out, "could not get container")) } func (s *DockerCLILinksSuite) TestLinksPingLinkedContainers(c *testing.T) { @@ -163,7 +160,7 @@ func (s *DockerCLILinksSuite) TestLinksHostsFilesInject(c *testing.T) { readContainerFileWithExec(c, idOne, "/etc/hosts") contentTwo := readContainerFileWithExec(c, idTwo, "/etc/hosts") // Host is not present in updated hosts file - assert.Assert(c, strings.Contains(string(contentTwo), "onetwo")) + assert.Assert(c, is.Contains(string(contentTwo), "onetwo")) } func (s *DockerCLILinksSuite) TestLinksUpdateOnRestart(c *testing.T) { @@ -183,29 +180,29 @@ func (s *DockerCLILinksSuite) TestLinksUpdateOnRestart(c *testing.T) { return string(matches[1]) } ip := getIP(content, "one") - assert.Equal(c, ip, realIP) + assert.Check(c, is.Equal(ip, realIP)) ip = getIP(content, "onetwo") - assert.Equal(c, ip, realIP) + assert.Check(c, is.Equal(ip, realIP)) dockerCmd(c, "restart", "one") realIP = inspectField(c, "one", "NetworkSettings.Networks.bridge.IPAddress") content = readContainerFileWithExec(c, id, "/etc/hosts") ip = getIP(content, "one") - assert.Equal(c, ip, realIP) + assert.Check(c, is.Equal(ip, realIP)) ip = getIP(content, "onetwo") - assert.Equal(c, ip, realIP) + assert.Check(c, is.Equal(ip, realIP)) } func (s *DockerCLILinksSuite) TestLinksEnvs(c *testing.T) { testRequires(c, DaemonIsLinux) dockerCmd(c, "run", "-d", "-e", "e1=", "-e", "e2=v2", "-e", "e3=v3=v3", "--name=first", "busybox", "top") out, _ := dockerCmd(c, "run", "--name=second", "--link=first:first", "busybox", "env") - assert.Assert(c, strings.Contains(out, "FIRST_ENV_e1=\n")) - assert.Assert(c, strings.Contains(out, "FIRST_ENV_e2=v2")) - assert.Assert(c, strings.Contains(out, "FIRST_ENV_e3=v3=v3")) + assert.Assert(c, is.Contains(out, "FIRST_ENV_e1=\n")) + assert.Assert(c, is.Contains(out, "FIRST_ENV_e2=v2")) + assert.Assert(c, is.Contains(out, "FIRST_ENV_e3=v3=v3")) } func (s *DockerCLILinksSuite) TestLinkShortDefinition(c *testing.T) { @@ -230,16 +227,16 @@ func (s *DockerCLILinksSuite) TestLinksNetworkHostContainer(c *testing.T) { out, _, err := dockerCmdWithError("run", "--name", "should_fail", "--link", "host_container:tester", "busybox", "true") // Running container linking to a container with --net host should have failed - assert.Assert(c, err != nil, "out: %s", out) + assert.Check(c, err != nil, "out: %s", out) // Running container linking to a container with --net host should have failed - assert.Assert(c, strings.Contains(out, runconfig.ErrConflictHostNetworkAndLinks.Error())) + assert.Check(c, is.Contains(out, runconfig.ErrConflictHostNetworkAndLinks.Error())) } func (s *DockerCLILinksSuite) TestLinksEtcHostsRegularFile(c *testing.T) { testRequires(c, DaemonIsLinux, NotUserNamespace) out, _ := dockerCmd(c, "run", "--net=host", "busybox", "ls", "-la", "/etc/hosts") // /etc/hosts should be a regular file - assert.Assert(c, cmp.Regexp("^-.+\n$", out)) + assert.Assert(c, is.Regexp("^-.+\n$", out)) } func (s *DockerCLILinksSuite) TestLinksMultipleWithSameName(c *testing.T) { diff --git a/integration/internal/container/ops.go b/integration/internal/container/ops.go index f3101a816c..18c93ea0ab 100644 --- a/integration/internal/container/ops.go +++ b/integration/internal/container/ops.go @@ -1,7 +1,6 @@ package container import ( - "fmt" "strings" containertypes "github.com/docker/docker/api/types/container" @@ -91,23 +90,20 @@ func WithVolume(target string) func(*TestContainerConfig) { // WithBind sets the bind mount of the container func WithBind(src, target string) func(*TestContainerConfig) { return func(c *TestContainerConfig) { - c.HostConfig.Binds = append(c.HostConfig.Binds, fmt.Sprintf("%s:%s", src, target)) + c.HostConfig.Binds = append(c.HostConfig.Binds, src+":"+target) } } -// WithTmpfs sets a target path in the container to a tmpfs -func WithTmpfs(target string) func(config *TestContainerConfig) { +// WithTmpfs sets a target path in the container to a tmpfs, with optional options +// (separated with a colon). +func WithTmpfs(targetAndOpts string) func(config *TestContainerConfig) { return func(c *TestContainerConfig) { if c.HostConfig.Tmpfs == nil { c.HostConfig.Tmpfs = make(map[string]string) } - spec := strings.SplitN(target, ":", 2) - var opts string - if len(spec) > 1 { - opts = spec[1] - } - c.HostConfig.Tmpfs[spec[0]] = opts + target, opts, _ := strings.Cut(targetAndOpts, ":") + c.HostConfig.Tmpfs[target] = opts } } diff --git a/libcontainerd/local/utils_windows.go b/libcontainerd/local/utils_windows.go index ccb52ba4f2..b395edba18 100644 --- a/libcontainerd/local/utils_windows.go +++ b/libcontainerd/local/utils_windows.go @@ -7,9 +7,8 @@ import "strings" func setupEnvironmentVariables(a []string) map[string]string { r := make(map[string]string) for _, s := range a { - arr := strings.SplitN(s, "=", 2) - if len(arr) == 2 { - r[arr[0]] = arr[1] + if k, v, ok := strings.Cut(s, "="); ok { + r[k] = v } } return r diff --git a/libnetwork/datastore/datastore.go b/libnetwork/datastore/datastore.go index f413f93600..f85a9a57d9 100644 --- a/libnetwork/datastore/datastore.go +++ b/libnetwork/datastore/datastore.go @@ -222,12 +222,12 @@ func newClient(scope string, kv string, addr string, config *store.Config, cache } } - store, err := libkv.NewStore(store.Backend(kv), addrs, config) + s, err := libkv.NewStore(store.Backend(kv), addrs, config) if err != nil { return nil, err } - ds := &datastore{scope: scope, store: store, active: true, watchCh: make(chan struct{}), sequential: sequential} + ds := &datastore{scope: scope, store: s, active: true, watchCh: make(chan struct{}), sequential: sequential} if cached { ds.cache = newCache(ds) } diff --git a/libnetwork/etchosts/etchosts.go b/libnetwork/etchosts/etchosts.go index 52063eeefd..92ea9cad4c 100644 --- a/libnetwork/etchosts/etchosts.go +++ b/libnetwork/etchosts/etchosts.go @@ -75,20 +75,19 @@ func Build(path, IP, hostname, domainname string, extraContent []Record) error { content := bytes.NewBuffer(nil) if IP != "" { - //set main record + // set main record var mainRec Record mainRec.IP = IP // User might have provided a FQDN in hostname or split it across hostname // and domainname. We want the FQDN and the bare hostname. fqdn := hostname if domainname != "" { - fqdn = fmt.Sprintf("%s.%s", fqdn, domainname) + fqdn += "." + domainname } - parts := strings.SplitN(fqdn, ".", 2) - if len(parts) == 2 { - mainRec.Hosts = fmt.Sprintf("%s %s", fqdn, parts[0]) - } else { - mainRec.Hosts = fqdn + mainRec.Hosts = fqdn + + if hostName, _, ok := strings.Cut(fqdn, "."); ok { + mainRec.Hosts += " " + hostName } if _, err := mainRec.WriteTo(content); err != nil { return err diff --git a/libnetwork/netlabel/labels.go b/libnetwork/netlabel/labels.go index f5075a6c34..6a188d4b62 100644 --- a/libnetwork/netlabel/labels.go +++ b/libnetwork/netlabel/labels.go @@ -30,7 +30,7 @@ const ( // DNSServers A list of DNS servers associated with the endpoint DNSServers = Prefix + ".endpoint.dnsservers" - //EnableIPv6 constant represents enabling IPV6 at network level + // EnableIPv6 constant represents enabling IPV6 at network level EnableIPv6 = Prefix + ".enable_ipv6" // DriverMTU constant represents the MTU size for the network driver @@ -106,27 +106,18 @@ func MakeKVClient(scope string) string { // Key extracts the key portion of the label func Key(label string) (key string) { - if kv := strings.SplitN(label, "=", 2); len(kv) > 0 { - key = kv[0] - } - return + key, _, _ = strings.Cut(label, "=") + return key } // Value extracts the value portion of the label func Value(label string) (value string) { - if kv := strings.SplitN(label, "=", 2); len(kv) > 1 { - value = kv[1] - } - return + _, value, _ = strings.Cut(label, "=") + return value } // KeyValue decomposes the label in the (key,value) pair func KeyValue(label string) (key string, value string) { - if kv := strings.SplitN(label, "=", 2); len(kv) > 0 { - key = kv[0] - if len(kv) > 1 { - value = kv[1] - } - } - return + key, value, _ = strings.Cut(label, "=") + return key, value } diff --git a/libnetwork/sandbox_dns_unix.go b/libnetwork/sandbox_dns_unix.go index 8206df6742..384d7e1ac1 100644 --- a/libnetwork/sandbox_dns_unix.go +++ b/libnetwork/sandbox_dns_unix.go @@ -110,19 +110,18 @@ func (sb *sandbox) updateHostsFile(ifaceIPs []string) error { // User might have provided a FQDN in hostname or split it across hostname // and domainname. We want the FQDN and the bare hostname. fqdn := sb.config.hostName - mhost := sb.config.hostName if sb.config.domainName != "" { - fqdn = fmt.Sprintf("%s.%s", fqdn, sb.config.domainName) + fqdn += "." + sb.config.domainName } + hosts := fqdn - parts := strings.SplitN(fqdn, ".", 2) - if len(parts) == 2 { - mhost = fmt.Sprintf("%s %s", fqdn, parts[0]) + if hostName, _, ok := strings.Cut(fqdn, "."); ok { + hosts += " " + hostName } var extraContent []etchosts.Record for _, ip := range ifaceIPs { - extraContent = append(extraContent, etchosts.Record{Hosts: mhost, IP: ip}) + extraContent = append(extraContent, etchosts.Record{Hosts: hosts, IP: ip}) } sb.addHostsEntries(extraContent) diff --git a/opts/address_pools.go b/opts/address_pools.go index 6274b35a87..a34f98b0c0 100644 --- a/opts/address_pools.go +++ b/opts/address_pools.go @@ -31,19 +31,17 @@ func (p *PoolsOpt) Set(value string) error { poolsDef := types.NetworkToSplit{} for _, field := range fields { - parts := strings.SplitN(field, "=", 2) - if len(parts) != 2 { + // TODO(thaJeztah): this should not be case-insensitive. + key, val, ok := strings.Cut(strings.ToLower(field), "=") + if !ok { return fmt.Errorf("invalid field '%s' must be a key=value pair", field) } - key := strings.ToLower(parts[0]) - value := strings.ToLower(parts[1]) - switch key { case "base": - poolsDef.Base = value + poolsDef.Base = val case "size": - size, err := strconv.Atoi(value) + size, err := strconv.Atoi(val) if err != nil { return fmt.Errorf("invalid size value: %q (must be integer): %v", value, err) } diff --git a/opts/env.go b/opts/env.go index 97e1a8c8a2..74c62ad07f 100644 --- a/opts/env.go +++ b/opts/env.go @@ -16,15 +16,15 @@ import ( // // The only validation here is to check if name is empty, per #25099 func ValidateEnv(val string) (string, error) { - arr := strings.SplitN(val, "=", 2) - if arr[0] == "" { + k, _, ok := strings.Cut(val, "=") + if k == "" { return "", errors.New("invalid environment variable: " + val) } - if len(arr) > 1 { + if ok { return val, nil } - if envVal, ok := os.LookupEnv(arr[0]); ok { - return arr[0] + "=" + envVal, nil + if envVal, ok := os.LookupEnv(k); ok { + return k + "=" + envVal, nil } return val, nil } diff --git a/opts/hosts.go b/opts/hosts.go index 8c3d151852..412c431fd4 100644 --- a/opts/hosts.go +++ b/opts/hosts.go @@ -60,8 +60,7 @@ func ParseHost(defaultToTLS, defaultToUnixXDG bool, val string) (string, error) if err != nil { return "", err } - socket := filepath.Join(runtimeDir, "docker.sock") - host = "unix://" + socket + host = "unix://" + filepath.Join(runtimeDir, "docker.sock") } else { host = DefaultHost } @@ -77,23 +76,32 @@ func ParseHost(defaultToTLS, defaultToUnixXDG bool, val string) (string, error) // parseDaemonHost parses the specified address and returns an address that will be used as the host. // Depending on the address specified, this may return one of the global Default* strings defined in hosts.go. -func parseDaemonHost(addr string) (string, error) { - addrParts := strings.SplitN(addr, "://", 2) - if len(addrParts) == 1 && addrParts[0] != "" { - addrParts = []string{"tcp", addrParts[0]} +func parseDaemonHost(address string) (string, error) { + proto, addr, ok := strings.Cut(address, "://") + if !ok && proto != "" { + addr = proto + proto = "tcp" } - switch addrParts[0] { + switch proto { case "tcp": - return ParseTCPAddr(addr, DefaultTCPHost) + return ParseTCPAddr(address, DefaultTCPHost) case "unix": - return parseSimpleProtoAddr("unix", addrParts[1], DefaultUnixSocket) + a, err := parseSimpleProtoAddr(proto, addr, DefaultUnixSocket) + if err != nil { + return "", errors.Wrapf(err, "invalid bind address (%s)", address) + } + return a, nil case "npipe": - return parseSimpleProtoAddr("npipe", addrParts[1], DefaultNamedPipe) + a, err := parseSimpleProtoAddr(proto, addr, DefaultNamedPipe) + if err != nil { + return "", errors.Wrapf(err, "invalid bind address (%s)", address) + } + return a, nil case "fd": - return addr, nil + return address, nil default: - return "", errors.Errorf("invalid bind address (%s): unsupported proto '%s'", addr, addrParts[0]) + return "", errors.Errorf("invalid bind address (%s): unsupported proto '%s'", address, proto) } } @@ -102,9 +110,8 @@ func parseDaemonHost(addr string) (string, error) { // socket address, either using the address parsed from addr, or the contents of // defaultAddr if addr is a blank string. func parseSimpleProtoAddr(proto, addr, defaultAddr string) (string, error) { - addr = strings.TrimPrefix(addr, proto+"://") if strings.Contains(addr, "://") { - return "", errors.Errorf("invalid proto, expected %s: %s", proto, addr) + return "", errors.Errorf("invalid %s address: %s", proto, addr) } if addr == "" { addr = defaultAddr @@ -172,14 +179,14 @@ func parseTCPAddr(address string, strict bool) (*url.URL, error) { // ExtraHost is in the form of name:ip where the ip has to be a valid ip (IPv4 or IPv6). func ValidateExtraHost(val string) (string, error) { // allow for IPv6 addresses in extra hosts by only splitting on first ":" - arr := strings.SplitN(val, ":", 2) - if len(arr) != 2 || len(arr[0]) == 0 { + name, ip, ok := strings.Cut(val, ":") + if !ok || name == "" { return "", errors.Errorf("bad format for add-host: %q", val) } // Skip IPaddr validation for special "host-gateway" string - if arr[1] != HostGatewayName { - if _, err := ValidateIPAddress(arr[1]); err != nil { - return "", errors.Errorf("invalid IP address in add-host: %q", arr[1]) + if ip != HostGatewayName { + if _, err := ValidateIPAddress(ip); err != nil { + return "", errors.Errorf("invalid IP address in add-host: %q", ip) } } return val, nil diff --git a/opts/hosts_test.go b/opts/hosts_test.go index cb5a0842db..1f03b88d2e 100644 --- a/opts/hosts_test.go +++ b/opts/hosts_test.go @@ -85,6 +85,8 @@ func TestParseDockerDaemonHost(t *testing.T) { "[0:0:0:0:0:0:0:1]:5555/path": "invalid bind address ([0:0:0:0:0:0:0:1]:5555/path): should not contain a path element", "tcp://:5555/path": "invalid bind address (tcp://:5555/path): should not contain a path element", "localhost:5555/path": "invalid bind address (localhost:5555/path): should not contain a path element", + "unix://tcp://127.0.0.1": "invalid bind address (unix://tcp://127.0.0.1): invalid unix address: tcp://127.0.0.1", + "unix://unix://tcp://127.0.0.1": "invalid bind address (unix://unix://tcp://127.0.0.1): invalid unix address: unix://tcp://127.0.0.1", } valids := map[string]string{ ":": DefaultTCPHost, @@ -130,7 +132,7 @@ func TestParseDockerDaemonHost(t *testing.T) { t.Errorf(`unexpected error: "%v"`, err) } if addr != expectedAddr { - t.Errorf(`expected "%s", got "%s""`, expectedAddr, addr) + t.Errorf(`expected "%s", got "%s"`, expectedAddr, addr) } }) } @@ -210,18 +212,6 @@ func TestParseTCP(t *testing.T) { } } -func TestParseInvalidUnixAddrInvalid(t *testing.T) { - if _, err := parseSimpleProtoAddr("unix", "tcp://127.0.0.1", "unix:///var/run/docker.sock"); err == nil || err.Error() != "invalid proto, expected unix: tcp://127.0.0.1" { - t.Fatalf("Expected an error, got %v", err) - } - if _, err := parseSimpleProtoAddr("unix", "unix://tcp://127.0.0.1", "/var/run/docker.sock"); err == nil || err.Error() != "invalid proto, expected unix: tcp://127.0.0.1" { - t.Fatalf("Expected an error, got %v", err) - } - if v, err := parseSimpleProtoAddr("unix", "", "/var/run/docker.sock"); err != nil || v != "unix:///var/run/docker.sock" { - t.Fatalf("Expected an %v, got %v", v, "unix:///var/run/docker.sock") - } -} - func TestValidateExtraHosts(t *testing.T) { valid := []string{ `myhost:192.168.0.1`, diff --git a/opts/opts.go b/opts/opts.go index 60a093f28c..aacd30af08 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -162,12 +162,8 @@ func (opts *MapOpts) Set(value string) error { } value = v } - vals := strings.SplitN(value, "=", 2) - if len(vals) == 1 { - (opts.values)[vals[0]] = "" - } else { - (opts.values)[vals[0]] = vals[1] - } + k, v, _ := strings.Cut(value, "=") + (opts.values)[k] = v return nil } diff --git a/opts/runtime.go b/opts/runtime.go index 4b9babf0a5..72516308af 100644 --- a/opts/runtime.go +++ b/opts/runtime.go @@ -29,27 +29,29 @@ func (o *RuntimeOpt) Name() string { // Set validates and updates the list of Runtimes func (o *RuntimeOpt) Set(val string) error { - parts := strings.SplitN(val, "=", 2) - if len(parts) != 2 { + k, v, ok := strings.Cut(val, "=") + if !ok { return fmt.Errorf("invalid runtime argument: %s", val) } - parts[0] = strings.TrimSpace(parts[0]) - parts[1] = strings.TrimSpace(parts[1]) - if parts[0] == "" || parts[1] == "" { + // TODO(thaJeztah): this should not accept spaces. + k = strings.TrimSpace(k) + v = strings.TrimSpace(v) + if k == "" || v == "" { return fmt.Errorf("invalid runtime argument: %s", val) } - parts[0] = strings.ToLower(parts[0]) - if parts[0] == o.stockRuntimeName { + // TODO(thaJeztah): this should not be case-insensitive. + k = strings.ToLower(k) + if k == o.stockRuntimeName { return fmt.Errorf("runtime name '%s' is reserved", o.stockRuntimeName) } - if _, ok := (*o.values)[parts[0]]; ok { - return fmt.Errorf("runtime '%s' was already defined", parts[0]) + if _, ok := (*o.values)[k]; ok { + return fmt.Errorf("runtime '%s' was already defined", k) } - (*o.values)[parts[0]] = types.Runtime{Path: parts[1]} + (*o.values)[k] = types.Runtime{Path: v} return nil } diff --git a/pkg/parsers/kernel/kernel_darwin.go b/pkg/parsers/kernel/kernel_darwin.go index afb5b2e98e..73f233e782 100644 --- a/pkg/parsers/kernel/kernel_darwin.go +++ b/pkg/parsers/kernel/kernel_darwin.go @@ -26,27 +26,24 @@ func GetKernelVersion() (*VersionInfo, error) { // getRelease uses `system_profiler SPSoftwareDataType` to get OSX kernel version func getRelease(osName string) (string, error) { - var release string - data := strings.Split(osName, "\n") - for _, line := range data { + for _, line := range strings.Split(osName, "\n") { if !strings.Contains(line, "Kernel Version") { continue } // It has the format like ' Kernel Version: Darwin 14.5.0' - content := strings.SplitN(line, ":", 2) - if len(content) != 2 { - return "", fmt.Errorf("Kernel Version is invalid") + _, ver, ok := strings.Cut(line, ":") + if !ok { + return "", fmt.Errorf("kernel Version is invalid") } - prettyNames := strings.SplitN(strings.TrimSpace(content[1]), " ", 2) - - if len(prettyNames) != 2 { - return "", fmt.Errorf("Kernel Version needs to be 'Darwin x.x.x' ") + _, release, ok := strings.Cut(strings.TrimSpace(ver), " ") + if !ok { + return "", fmt.Errorf("kernel version needs to be 'Darwin x.x.x'") } - release = prettyNames[1] + return release, nil } - return release, nil + return "", nil } func getSPSoftwareDataType() (string, error) { diff --git a/pkg/parsers/operatingsystem/operatingsystem_linux.go b/pkg/parsers/operatingsystem/operatingsystem_linux.go index 1abddad9f4..167314db63 100644 --- a/pkg/parsers/operatingsystem/operatingsystem_linux.go +++ b/pkg/parsers/operatingsystem/operatingsystem_linux.go @@ -5,7 +5,6 @@ package operatingsystem // import "github.com/docker/docker/pkg/parsers/operatin import ( "bufio" "bytes" - "fmt" "os" "strings" ) @@ -44,23 +43,22 @@ func getValueFromOsRelease(key string) (string, error) { osReleaseFile, err := os.Open(etcOsRelease) if err != nil { if !os.IsNotExist(err) { - return "", fmt.Errorf("Error opening %s: %v", etcOsRelease, err) + return "", err } osReleaseFile, err = os.Open(altOsRelease) if err != nil { - return "", fmt.Errorf("Error opening %s: %v", altOsRelease, err) + return "", err } } defer osReleaseFile.Close() var value string - keyWithTrailingEqual := key + "=" scanner := bufio.NewScanner(osReleaseFile) for scanner.Scan() { line := scanner.Text() - if strings.HasPrefix(line, keyWithTrailingEqual) { - data := strings.SplitN(line, "=", 2) - value = strings.Trim(data[1], `"' `) // remove leading/trailing quotes and whitespace + if strings.HasPrefix(line, key+"=") { + value = strings.TrimPrefix(line, key+"=") + value = strings.Trim(value, `"' `) // remove leading/trailing quotes and whitespace } } diff --git a/pkg/parsers/parsers.go b/pkg/parsers/parsers.go index e6d7b33ec0..e438b5a40a 100644 --- a/pkg/parsers/parsers.go +++ b/pkg/parsers/parsers.go @@ -9,13 +9,14 @@ import ( "strings" ) -// ParseKeyValueOpt parses and validates the specified string as a key/value pair (key=value) -func ParseKeyValueOpt(opt string) (string, string, error) { - parts := strings.SplitN(opt, "=", 2) - if len(parts) != 2 { - return "", "", fmt.Errorf("Unable to parse key/value option: %s", opt) +// ParseKeyValueOpt parses and validates the specified string as a key/value +// pair (key=value). +func ParseKeyValueOpt(opt string) (key string, value string, err error) { + k, v, ok := strings.Cut(opt, "=") + if !ok { + return "", "", fmt.Errorf("unable to parse key/value option: %s", opt) } - return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil + return strings.TrimSpace(k), strings.TrimSpace(v), nil } // ParseUintListMaximum parses and validates the specified string as the value @@ -75,12 +76,12 @@ func parseUintList(val string, maximum int) (map[int]bool, error) { } availableInts[v] = true } else { - split := strings.SplitN(r, "-", 2) - min, err := strconv.Atoi(split[0]) + minS, maxS, _ := strings.Cut(r, "-") + min, err := strconv.Atoi(minS) if err != nil { return nil, errInvalidFormat } - max, err := strconv.Atoi(split[1]) + max, err := strconv.Atoi(maxS) if err != nil { return nil, errInvalidFormat } diff --git a/pkg/parsers/parsers_test.go b/pkg/parsers/parsers_test.go index 12e5969091..dca3a07b91 100644 --- a/pkg/parsers/parsers_test.go +++ b/pkg/parsers/parsers_test.go @@ -7,8 +7,8 @@ import ( func TestParseKeyValueOpt(t *testing.T) { invalids := map[string]string{ - "": "Unable to parse key/value option: ", - "key": "Unable to parse key/value option: key", + "": "unable to parse key/value option: ", + "key": "unable to parse key/value option: key", } for invalid, expectedError := range invalids { if _, _, err := ParseKeyValueOpt(invalid); err == nil || err.Error() != expectedError { diff --git a/pkg/tarsum/tarsum.go b/pkg/tarsum/tarsum.go index 5ea65f1ecd..87a8450aea 100644 --- a/pkg/tarsum/tarsum.go +++ b/pkg/tarsum/tarsum.go @@ -62,13 +62,11 @@ func NewTarSumHash(r io.Reader, dc bool, v Version, tHash THash) (TarSum, error) // NewTarSumForLabel creates a new TarSum using the provided TarSum version+hash label. func NewTarSumForLabel(r io.Reader, disableCompression bool, label string) (TarSum, error) { - parts := strings.SplitN(label, "+", 2) - if len(parts) != 2 { + versionName, hashName, ok := strings.Cut(label, "+") + if !ok { return nil, errors.New("tarsum label string should be of the form: {tarsum_version}+{hash_name}") } - versionName, hashName := parts[0], parts[1] - version, ok := tarSumVersionsByName[versionName] if !ok { return nil, fmt.Errorf("unknown TarSum version name: %q", versionName) diff --git a/pkg/tarsum/versioning.go b/pkg/tarsum/versioning.go index aa1f171862..edc3ec8cfd 100644 --- a/pkg/tarsum/versioning.go +++ b/pkg/tarsum/versioning.go @@ -69,16 +69,12 @@ func (tsv Version) String() string { // GetVersionFromTarsum returns the Version from the provided string. func GetVersionFromTarsum(tarsum string) (Version, error) { - tsv := tarsum - if strings.Contains(tarsum, "+") { - tsv = strings.SplitN(tarsum, "+", 2)[0] + versionName, _, _ := strings.Cut(tarsum, "+") + version, ok := tarSumVersionsByName[versionName] + if !ok { + return -1, ErrNotVersion } - for v, s := range tarSumVersions { - if s == tsv { - return v, nil - } - } - return -1, ErrNotVersion + return version, nil } // Errors that may be returned by functions in this package diff --git a/plugin/defs.go b/plugin/defs.go index 9a3577a72b..90268ef767 100644 --- a/plugin/defs.go +++ b/plugin/defs.go @@ -1,7 +1,6 @@ package plugin // import "github.com/docker/docker/plugin" import ( - "fmt" "strings" "sync" @@ -56,15 +55,13 @@ func WithEnv(env []string) CreateOpt { } } for _, line := range env { - if pair := strings.SplitN(line, "=", 2); len(pair) > 1 { - effectiveEnv[pair[0]] = pair[1] + if k, v, ok := strings.Cut(line, "="); ok { + effectiveEnv[k] = v } } - p.PluginObj.Settings.Env = make([]string, len(effectiveEnv)) - i := 0 + p.PluginObj.Settings.Env = make([]string, 0, len(effectiveEnv)) for key, value := range effectiveEnv { - p.PluginObj.Settings.Env[i] = fmt.Sprintf("%s=%s", key, value) - i++ + p.PluginObj.Settings.Env = append(p.PluginObj.Settings.Env, key+"="+value) } } } diff --git a/plugin/v2/settable.go b/plugin/v2/settable.go index efda564705..c63d449eb7 100644 --- a/plugin/v2/settable.go +++ b/plugin/v2/settable.go @@ -92,11 +92,11 @@ func (set *settable) isSettable(allowedSettableFields []string, settable []strin func updateSettingsEnv(env *[]string, set *settable) { for i, e := range *env { - if parts := strings.SplitN(e, "=", 2); parts[0] == set.name { - (*env)[i] = fmt.Sprintf("%s=%s", set.name, set.value) + if name, _, _ := strings.Cut(e, "="); name == set.name { + (*env)[i] = set.name + "=" + set.value return } } - *env = append(*env, fmt.Sprintf("%s=%s", set.name, set.value)) + *env = append(*env, set.name+"="+set.value) } diff --git a/runconfig/opts/parse.go b/runconfig/opts/parse.go index 8f7baeb637..834f32d73d 100644 --- a/runconfig/opts/parse.go +++ b/runconfig/opts/parse.go @@ -8,12 +8,8 @@ import ( func ConvertKVStringsToMap(values []string) map[string]string { result := make(map[string]string, len(values)) for _, value := range values { - kv := strings.SplitN(value, "=", 2) - if len(kv) == 1 { - result[kv[0]] = "" - } else { - result[kv[0]] = kv[1] - } + k, v, _ := strings.Cut(value, "=") + result[k] = v } return result diff --git a/volume/local/local.go b/volume/local/local.go index aca3871b2e..512e666eb8 100644 --- a/volume/local/local.go +++ b/volume/local/local.go @@ -371,11 +371,9 @@ func (v *localVolume) saveOpts() error { // getAddress finds out address/hostname from options func getAddress(opts string) string { - optsList := strings.Split(opts, ",") - for i := 0; i < len(optsList); i++ { - if strings.HasPrefix(optsList[i], "addr=") { - addr := strings.SplitN(optsList[i], "=", 2)[1] - return addr + for _, opt := range strings.Split(opts, ",") { + if strings.HasPrefix(opt, "addr=") { + return strings.TrimPrefix(opt, "addr=") } } return "" @@ -383,11 +381,9 @@ func getAddress(opts string) string { // getPassword finds out a password from options func getPassword(opts string) string { - optsList := strings.Split(opts, ",") - for i := 0; i < len(optsList); i++ { - if strings.HasPrefix(optsList[i], "password=") { - passwd := strings.SplitN(optsList[i], "=", 2)[1] - return passwd + for _, opt := range strings.Split(opts, ",") { + if strings.HasPrefix(opt, "password=") { + return strings.TrimPrefix(opt, "password=") } } return "" diff --git a/volume/mounts/linux_parser.go b/volume/mounts/linux_parser.go index bcabe45720..17bc3c9878 100644 --- a/volume/mounts/linux_parser.go +++ b/volume/mounts/linux_parser.go @@ -23,18 +23,6 @@ type linuxParser struct { fi fileInfoProvider } -func linuxSplitRawSpec(raw string) ([]string, error) { - if strings.Count(raw, ":") > 2 { - return nil, errInvalidSpec(raw) - } - - arr := strings.SplitN(raw, ":", 3) - if arr[0] == "" { - return nil, errInvalidSpec(raw) - } - return arr, nil -} - func linuxValidateNotRoot(p string) error { p = path.Clean(strings.ReplaceAll(p, `\`, `/`)) if p == "/" { @@ -214,9 +202,9 @@ func (p *linuxParser) ReadWrite(mode string) bool { } func (p *linuxParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) { - arr, err := linuxSplitRawSpec(raw) - if err != nil { - return nil, err + arr := strings.SplitN(raw, ":", 4) + if arr[0] == "" { + return nil, errInvalidSpec(raw) } var spec mount.Mount @@ -334,26 +322,23 @@ func (p *linuxParser) ParseVolumesFrom(spec string) (string, string, error) { return "", "", fmt.Errorf("volumes-from specification cannot be an empty string") } - specParts := strings.SplitN(spec, ":", 2) - id := specParts[0] - mode := "rw" - - if len(specParts) == 2 { - mode = specParts[1] - if !linuxValidMountMode(mode) { - return "", "", errInvalidMode(mode) - } - // For now don't allow propagation properties while importing - // volumes from data container. These volumes will inherit - // the same propagation property as of the original volume - // in data container. This probably can be relaxed in future. - if linuxHasPropagation(mode) { - return "", "", errInvalidMode(mode) - } - // Do not allow copy modes on volumes-from - if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet { - return "", "", errInvalidMode(mode) - } + id, mode, _ := strings.Cut(spec, ":") + if mode == "" { + return id, "rw", nil + } + if !linuxValidMountMode(mode) { + return "", "", errInvalidMode(mode) + } + // For now don't allow propagation properties while importing + // volumes from data container. These volumes will inherit + // the same propagation property as of the original volume + // in data container. This probably can be relaxed in future. + if linuxHasPropagation(mode) { + return "", "", errInvalidMode(mode) + } + // Do not allow copy modes on volumes-from + if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet { + return "", "", errInvalidMode(mode) } return id, mode, nil } diff --git a/volume/mounts/windows_parser.go b/volume/mounts/windows_parser.go index 94f24cfc54..7800aa8c08 100644 --- a/volume/mounts/windows_parser.go +++ b/volume/mounts/windows_parser.go @@ -415,20 +415,18 @@ func (p *windowsParser) ParseVolumesFrom(spec string) (string, string, error) { return "", "", fmt.Errorf("volumes-from specification cannot be an empty string") } - specParts := strings.SplitN(spec, ":", 2) - id := specParts[0] - mode := "rw" + id, mode, _ := strings.Cut(spec, ":") + if mode == "" { + return id, "rw", nil + } - if len(specParts) == 2 { - mode = specParts[1] - if !windowsValidMountMode(mode) { - return "", "", errInvalidMode(mode) - } + if !windowsValidMountMode(mode) { + return "", "", errInvalidMode(mode) + } - // Do not allow copy modes on volumes-from - if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet { - return "", "", errInvalidMode(mode) - } + // Do not allow copy modes on volumes-from + if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet { + return "", "", errInvalidMode(mode) } return id, mode, nil }