loadDaemonCliConfig: explicitly set default host
| ... | ... |
@@ -10,7 +10,7 @@ import ( |
| 10 | 10 |
"os" |
| 11 | 11 |
"path/filepath" |
| 12 | 12 |
"runtime" |
| 13 |
- "sort" |
|
| 13 |
+ "slices" |
|
| 14 | 14 |
"strings" |
| 15 | 15 |
"sync" |
| 16 | 16 |
"time" |
| ... | ... |
@@ -639,6 +639,30 @@ func loadDaemonCliConfig(opts *daemonOptions) (*config.Config, error) {
|
| 639 | 639 |
} |
| 640 | 640 |
} |
| 641 | 641 |
|
| 642 |
+ // TODO(thaJeztah): consider making empty strings an error. Existing behavior allowed for empty strings to be used as default, even if explicitly set (`dockerd -H ""`). |
|
| 643 |
+ conf.Hosts = slices.DeleteFunc(conf.Hosts, func(h string) bool {
|
|
| 644 |
+ return strings.TrimSpace(h) == "" |
|
| 645 |
+ }) |
|
| 646 |
+ if len(conf.Hosts) == 0 {
|
|
| 647 |
+ // Set the default host if no hosts are configured. |
|
| 648 |
+ // TODO(thaJeztah) can set defaults in config.New() instead? |
|
| 649 |
+ if conf.TLS != nil && *conf.TLS {
|
|
| 650 |
+ // If no host is configured, but the "--tls" flag is set, we |
|
| 651 |
+ // default to using a TCP connection instead of a unix-socket |
|
| 652 |
+ // or named pipe. |
|
| 653 |
+ // |
|
| 654 |
+ // See https://github.com/moby/moby/commit/0906195fbbd6f379c163b80f23e4c5a60bcfc5f0 |
|
| 655 |
+ conf.Hosts = append(conf.Hosts, dopts.DefaultTLSHost) |
|
| 656 |
+ } else {
|
|
| 657 |
+ // Otherwise use the default unix-socket (Linux) or named pipe (Windows). |
|
| 658 |
+ h, err := defaultAPISocketPath(honorXDG) |
|
| 659 |
+ if err != nil {
|
|
| 660 |
+ return nil, err |
|
| 661 |
+ } |
|
| 662 |
+ conf.Hosts = append(conf.Hosts, h) |
|
| 663 |
+ } |
|
| 664 |
+ } |
|
| 665 |
+ |
|
| 642 | 666 |
if err := normalizeHosts(conf); err != nil {
|
| 643 | 667 |
return nil, err |
| 644 | 668 |
} |
| ... | ... |
@@ -718,38 +742,44 @@ func loadDaemonCliConfig(opts *daemonOptions) (*config.Config, error) {
|
| 718 | 718 |
return conf, nil |
| 719 | 719 |
} |
| 720 | 720 |
|
| 721 |
-// normalizeHosts normalizes the configured config.Hosts and remove duplicates. |
|
| 721 |
+// defaultAPISocketPath returns the default path for the Unix socket (Linux) |
|
| 722 |
+// or named pipe (Windows). |
|
| 723 |
+// |
|
| 724 |
+// When running with rootlessKit, XDG dirs should be preferred, and the |
|
| 725 |
+// default is to listen on an unprivileged socket in [XDG_RUNTIME_DIR]. |
|
| 726 |
+// |
|
| 727 |
+// [XDG_RUNTIME_DIR]: https://specifications.freedesktop.org/basedir/0.8/#variables |
|
| 728 |
+func defaultAPISocketPath(honorXDG bool) (string, error) {
|
|
| 729 |
+ if honorXDG {
|
|
| 730 |
+ runtimeDir, err := homedir.GetRuntimeDir() |
|
| 731 |
+ if err != nil {
|
|
| 732 |
+ return "", err |
|
| 733 |
+ } |
|
| 734 |
+ return "unix://" + filepath.Join(runtimeDir, "docker.sock"), nil |
|
| 735 |
+ } |
|
| 736 |
+ |
|
| 737 |
+ // default unix-socket (Linux) or named pipe (Windows). |
|
| 738 |
+ return dopts.DefaultHost, nil |
|
| 739 |
+} |
|
| 740 |
+ |
|
| 741 |
+// normalizeHosts normalizes the configured config.Hosts and removes duplicates. |
|
| 722 | 742 |
// It returns an error if it fails to parse a host. |
| 723 | 743 |
func normalizeHosts(cfg *config.Config) error {
|
| 724 | 744 |
if len(cfg.Hosts) == 0 {
|
| 725 |
- // if no hosts are configured, create a single entry slice, so that the |
|
| 726 |
- // default is used. |
|
| 727 |
- // |
|
| 728 |
- // TODO(thaJeztah) implement a cleaner way for this; this depends on a |
|
| 729 |
- // side-effect of how we parse empty/partial hosts. |
|
| 730 |
- cfg.Hosts = make([]string, 1) |
|
| 731 |
- } |
|
| 732 |
- hosts := make([]string, 0, len(cfg.Hosts)) |
|
| 733 |
- seen := make(map[string]struct{}, len(cfg.Hosts))
|
|
| 734 |
- |
|
| 735 |
- useTLS := DefaultTLSValue |
|
| 736 |
- if cfg.TLS != nil {
|
|
| 737 |
- useTLS = *cfg.TLS |
|
| 745 |
+ return errors.New("no hosts specified")
|
|
| 738 | 746 |
} |
| 739 | 747 |
|
| 740 |
- for _, h := range cfg.Hosts {
|
|
| 741 |
- host, err := dopts.ParseHost(useTLS, honorXDG, h) |
|
| 748 |
+ hosts := slices.Clone(cfg.Hosts) |
|
| 749 |
+ for i, h := range hosts {
|
|
| 750 |
+ var err error |
|
| 751 |
+ hosts[i], err = dopts.ParseDaemonHost(h) |
|
| 742 | 752 |
if err != nil {
|
| 743 | 753 |
return err |
| 744 | 754 |
} |
| 745 |
- if _, ok := seen[host]; ok {
|
|
| 746 |
- continue |
|
| 747 |
- } |
|
| 748 |
- seen[host] = struct{}{}
|
|
| 749 |
- hosts = append(hosts, host) |
|
| 750 | 755 |
} |
| 751 |
- sort.Strings(hosts) |
|
| 752 |
- cfg.Hosts = hosts |
|
| 756 |
+ |
|
| 757 |
+ slices.Sort(hosts) |
|
| 758 |
+ cfg.Hosts = slices.Compact(hosts) |
|
| 753 | 759 |
return nil |
| 754 | 760 |
} |
| 755 | 761 |
|
| ... | ... |
@@ -3,11 +3,9 @@ package opts |
| 3 | 3 |
import ( |
| 4 | 4 |
"net" |
| 5 | 5 |
"net/url" |
| 6 |
- "path/filepath" |
|
| 7 | 6 |
"strconv" |
| 8 | 7 |
"strings" |
| 9 | 8 |
|
| 10 |
- "github.com/moby/moby/v2/pkg/homedir" |
|
| 11 | 9 |
"github.com/pkg/errors" |
| 12 | 10 |
) |
| 13 | 11 |
|
| ... | ... |
@@ -36,9 +34,9 @@ const ( |
| 36 | 36 |
// ValidateHost validates that the specified string is a valid host and returns it. |
| 37 | 37 |
func ValidateHost(val string) (string, error) {
|
| 38 | 38 |
host := strings.TrimSpace(val) |
| 39 |
- // The empty string means default and is not handled by parseDaemonHost |
|
| 39 |
+ // The empty string means default and is not handled by ParseDaemonHost |
|
| 40 | 40 |
if host != "" {
|
| 41 |
- _, err := parseDaemonHost(host) |
|
| 41 |
+ _, err := ParseDaemonHost(host) |
|
| 42 | 42 |
if err != nil {
|
| 43 | 43 |
return val, err |
| 44 | 44 |
} |
| ... | ... |
@@ -48,35 +46,9 @@ func ValidateHost(val string) (string, error) {
|
| 48 | 48 |
return val, nil |
| 49 | 49 |
} |
| 50 | 50 |
|
| 51 |
-// ParseHost and set defaults for a Daemon host string. |
|
| 52 |
-// defaultToTLS is preferred over defaultToUnixXDG. |
|
| 53 |
-func ParseHost(defaultToTLS, defaultToUnixXDG bool, val string) (string, error) {
|
|
| 54 |
- host := strings.TrimSpace(val) |
|
| 55 |
- if host == "" {
|
|
| 56 |
- if defaultToTLS {
|
|
| 57 |
- host = DefaultTLSHost |
|
| 58 |
- } else if defaultToUnixXDG {
|
|
| 59 |
- runtimeDir, err := homedir.GetRuntimeDir() |
|
| 60 |
- if err != nil {
|
|
| 61 |
- return "", err |
|
| 62 |
- } |
|
| 63 |
- host = "unix://" + filepath.Join(runtimeDir, "docker.sock") |
|
| 64 |
- } else {
|
|
| 65 |
- host = DefaultHost |
|
| 66 |
- } |
|
| 67 |
- } else {
|
|
| 68 |
- var err error |
|
| 69 |
- host, err = parseDaemonHost(host) |
|
| 70 |
- if err != nil {
|
|
| 71 |
- return val, err |
|
| 72 |
- } |
|
| 73 |
- } |
|
| 74 |
- return host, nil |
|
| 75 |
-} |
|
| 76 |
- |
|
| 77 |
-// parseDaemonHost parses the specified address and returns an address that will be used as the host. |
|
| 51 |
+// ParseDaemonHost parses the specified address and returns an address that will be used as the host. |
|
| 78 | 52 |
// Depending on the address specified, this may return one of the global Default* strings defined in hosts.go. |
| 79 |
-func parseDaemonHost(address string) (string, error) {
|
|
| 53 |
+func ParseDaemonHost(address string) (string, error) {
|
|
| 80 | 54 |
proto, addr, ok := strings.Cut(address, "://") |
| 81 | 55 |
if !ok && proto != "" {
|
| 82 | 56 |
addr = proto |
| ... | ... |
@@ -6,8 +6,8 @@ import ( |
| 6 | 6 |
"testing" |
| 7 | 7 |
) |
| 8 | 8 |
|
| 9 |
-func TestParseHost(t *testing.T) {
|
|
| 10 |
- invalid := map[string]string{
|
|
| 9 |
+func TestParseDockerDaemonHost(t *testing.T) {
|
|
| 10 |
+ invalids := map[string]string{
|
|
| 11 | 11 |
"something with spaces": `invalid bind address (something with spaces): parse "tcp://something with spaces": invalid character " " in host name`, |
| 12 | 12 |
"://": `invalid bind address (://): unsupported proto ''`, |
| 13 | 13 |
"unknown://": `invalid bind address (unknown://): unsupported proto 'unknown'`, |
| ... | ... |
@@ -20,57 +20,7 @@ func TestParseHost(t *testing.T) {
|
| 20 | 20 |
"tcp://[::1]:/": `invalid bind address (tcp://[::1]:/): should not contain a path element`, |
| 21 | 21 |
"tcp://[::1]:5555/": `invalid bind address (tcp://[::1]:5555/): should not contain a path element`, |
| 22 | 22 |
"tcp://[::1]:5555/p": `invalid bind address (tcp://[::1]:5555/p): should not contain a path element`, |
| 23 |
- " tcp://:5555/path ": `invalid bind address (tcp://:5555/path): should not contain a path element`, |
|
| 24 |
- } |
|
| 25 |
- |
|
| 26 |
- valid := map[string]string{
|
|
| 27 |
- "": DefaultHost, |
|
| 28 |
- " ": DefaultHost, |
|
| 29 |
- " ": DefaultHost, |
|
| 30 |
- "fd://": "fd://", |
|
| 31 |
- "fd://something": "fd://something", |
|
| 32 |
- "tcp://host:": fmt.Sprintf("tcp://host:%d", DefaultHTTPPort),
|
|
| 33 |
- "tcp://": DefaultTCPHost, |
|
| 34 |
- "tcp://:": DefaultTCPHost, |
|
| 35 |
- "tcp://:5555": fmt.Sprintf("tcp://%s:5555", DefaultHTTPHost), //nolint:nosprintfhostport // sprintf is more readable for this case.
|
|
| 36 |
- "tcp://[::1]": fmt.Sprintf(`tcp://[::1]:%d`, DefaultHTTPPort), |
|
| 37 |
- "tcp://[::1]:": fmt.Sprintf(`tcp://[::1]:%d`, DefaultHTTPPort), |
|
| 38 |
- "tcp://[::1]:5555": `tcp://[::1]:5555`, |
|
| 39 |
- "tcp://0.0.0.0:5555": "tcp://0.0.0.0:5555", |
|
| 40 |
- "tcp://192.168:5555": "tcp://192.168:5555", |
|
| 41 |
- "tcp://192.168.0.1:5555": "tcp://192.168.0.1:5555", |
|
| 42 |
- "tcp://0.0.0.0:1234567890": "tcp://0.0.0.0:1234567890", // yeah it's valid :P |
|
| 43 |
- "tcp://docker.com:5555": "tcp://docker.com:5555", |
|
| 44 |
- "unix://": "unix://" + DefaultUnixSocket, |
|
| 45 |
- "unix://path/to/socket": "unix://path/to/socket", |
|
| 46 |
- "npipe://": "npipe://" + DefaultNamedPipe, |
|
| 47 |
- "npipe:////./pipe/foo": "npipe:////./pipe/foo", |
|
| 48 |
- } |
|
| 49 |
- |
|
| 50 |
- for value, expectedError := range invalid {
|
|
| 51 |
- t.Run(value, func(t *testing.T) {
|
|
| 52 |
- _, err := ParseHost(false, false, value) |
|
| 53 |
- if err == nil || err.Error() != expectedError {
|
|
| 54 |
- t.Errorf(`expected error "%s", got "%v"`, expectedError, err) |
|
| 55 |
- } |
|
| 56 |
- }) |
|
| 57 |
- } |
|
| 58 | 23 |
|
| 59 |
- for value, expected := range valid {
|
|
| 60 |
- t.Run(value, func(t *testing.T) {
|
|
| 61 |
- actual, err := ParseHost(false, false, value) |
|
| 62 |
- if err != nil {
|
|
| 63 |
- t.Errorf(`unexpected error: "%v"`, err) |
|
| 64 |
- } |
|
| 65 |
- if actual != expected {
|
|
| 66 |
- t.Errorf(`expected "%s", got "%s""`, expected, actual) |
|
| 67 |
- } |
|
| 68 |
- }) |
|
| 69 |
- } |
|
| 70 |
-} |
|
| 71 |
- |
|
| 72 |
-func TestParseDockerDaemonHost(t *testing.T) {
|
|
| 73 |
- invalids := map[string]string{
|
|
| 74 | 24 |
"tcp:a.b.c.d": `invalid bind address (tcp:a.b.c.d): parse "tcp://tcp:a.b.c.d": invalid port ":a.b.c.d" after host`, |
| 75 | 25 |
"tcp:a.b.c.d/path": `invalid bind address (tcp:a.b.c.d/path): parse "tcp://tcp:a.b.c.d/path": invalid port ":a.b.c.d" after host`, |
| 76 | 26 |
"tcp://127.0.0.1/": "invalid bind address (tcp://127.0.0.1/): should not contain a path element", |
| ... | ... |
@@ -79,6 +29,7 @@ func TestParseDockerDaemonHost(t *testing.T) {
|
| 79 | 79 |
"tcp://unix:///run/docker.sock": "invalid bind address (tcp://unix:///run/docker.sock): should not contain a path element", |
| 80 | 80 |
" tcp://:5555/path ": "invalid bind address ( tcp://:5555/path ): unsupported proto ' tcp'", //nolint:gocritic // This is a valid test case. |
| 81 | 81 |
"": "invalid bind address (): unsupported proto ''", |
| 82 |
+ " ": `invalid bind address ( ): parse "tcp:// ": invalid character " " in host name`, |
|
| 82 | 83 |
":5555/path": "invalid bind address (:5555/path): should not contain a path element", |
| 83 | 84 |
"0.0.0.1:5555/path": "invalid bind address (0.0.0.1:5555/path): should not contain a path element", |
| 84 | 85 |
"[::1]:5555/path": "invalid bind address ([::1]:5555/path): should not contain a path element", |
| ... | ... |
@@ -89,34 +40,42 @@ func TestParseDockerDaemonHost(t *testing.T) {
|
| 89 | 89 |
"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", |
| 90 | 90 |
} |
| 91 | 91 |
valids := map[string]string{
|
| 92 |
- ":": DefaultTCPHost, |
|
| 93 |
- ":5555": fmt.Sprintf("tcp://%s:5555", DefaultHTTPHost), //nolint:nosprintfhostport // sprintf is more readable for this case.
|
|
| 94 |
- "0.0.0.1:": fmt.Sprintf("tcp://0.0.0.1:%d", DefaultHTTPPort),
|
|
| 95 |
- "0.0.0.1:5555": "tcp://0.0.0.1:5555", |
|
| 96 |
- "[::1]": fmt.Sprintf("tcp://[::1]:%d", DefaultHTTPPort),
|
|
| 97 |
- "[::1]:": fmt.Sprintf("tcp://[::1]:%d", DefaultHTTPPort),
|
|
| 98 |
- "[::1]:5555": "tcp://[::1]:5555", |
|
| 99 |
- "[0:0:0:0:0:0:0:1]": fmt.Sprintf("tcp://[0:0:0:0:0:0:0:1]:%d", DefaultHTTPPort),
|
|
| 100 |
- "[0:0:0:0:0:0:0:1]:": fmt.Sprintf("tcp://[0:0:0:0:0:0:0:1]:%d", DefaultHTTPPort),
|
|
| 101 |
- "[0:0:0:0:0:0:0:1]:5555": "tcp://[0:0:0:0:0:0:0:1]:5555", |
|
| 102 |
- "localhost": fmt.Sprintf("tcp://localhost:%d", DefaultHTTPPort),
|
|
| 103 |
- "localhost:": fmt.Sprintf("tcp://localhost:%d", DefaultHTTPPort),
|
|
| 104 |
- "localhost:5555": "tcp://localhost:5555", |
|
| 105 |
- "fd://": "fd://", |
|
| 106 |
- "fd://something": "fd://something", |
|
| 107 |
- "npipe://": "npipe://" + DefaultNamedPipe, |
|
| 108 |
- "npipe:////./pipe/foo": "npipe:////./pipe/foo", |
|
| 109 |
- "tcp://": DefaultTCPHost, |
|
| 110 |
- "tcp://:5555": fmt.Sprintf("tcp://%s:5555", DefaultHTTPHost), //nolint:nosprintfhostport // sprintf is more readable for this case.
|
|
| 111 |
- "tcp://[::1]": fmt.Sprintf("tcp://[::1]:%d", DefaultHTTPPort),
|
|
| 112 |
- "tcp://[::1]:": fmt.Sprintf("tcp://[::1]:%d", DefaultHTTPPort),
|
|
| 113 |
- "tcp://[::1]:5555": "tcp://[::1]:5555", |
|
| 114 |
- "unix://": "unix://" + DefaultUnixSocket, |
|
| 115 |
- "unix:///run/docker.sock": "unix:///run/docker.sock", |
|
| 92 |
+ ":": DefaultTCPHost, |
|
| 93 |
+ ":5555": fmt.Sprintf("tcp://%s:5555", DefaultHTTPHost), //nolint:nosprintfhostport // sprintf is more readable for this case.
|
|
| 94 |
+ "0.0.0.1:": fmt.Sprintf("tcp://0.0.0.1:%d", DefaultHTTPPort),
|
|
| 95 |
+ "0.0.0.1:5555": "tcp://0.0.0.1:5555", |
|
| 96 |
+ "[::1]": fmt.Sprintf("tcp://[::1]:%d", DefaultHTTPPort),
|
|
| 97 |
+ "[::1]:": fmt.Sprintf("tcp://[::1]:%d", DefaultHTTPPort),
|
|
| 98 |
+ "[::1]:5555": "tcp://[::1]:5555", |
|
| 99 |
+ "[0:0:0:0:0:0:0:1]": fmt.Sprintf("tcp://[0:0:0:0:0:0:0:1]:%d", DefaultHTTPPort),
|
|
| 100 |
+ "[0:0:0:0:0:0:0:1]:": fmt.Sprintf("tcp://[0:0:0:0:0:0:0:1]:%d", DefaultHTTPPort),
|
|
| 101 |
+ "[0:0:0:0:0:0:0:1]:5555": "tcp://[0:0:0:0:0:0:0:1]:5555", |
|
| 102 |
+ "localhost": fmt.Sprintf("tcp://localhost:%d", DefaultHTTPPort),
|
|
| 103 |
+ "localhost:": fmt.Sprintf("tcp://localhost:%d", DefaultHTTPPort),
|
|
| 104 |
+ "localhost:5555": "tcp://localhost:5555", |
|
| 105 |
+ "fd://": "fd://", |
|
| 106 |
+ "fd://something": "fd://something", |
|
| 107 |
+ "npipe://": "npipe://" + DefaultNamedPipe, |
|
| 108 |
+ "npipe:////./pipe/foo": "npipe:////./pipe/foo", |
|
| 109 |
+ "tcp://host:": fmt.Sprintf("tcp://host:%d", DefaultHTTPPort),
|
|
| 110 |
+ "tcp://": DefaultTCPHost, |
|
| 111 |
+ "tcp://:": DefaultTCPHost, |
|
| 112 |
+ "tcp://:5555": fmt.Sprintf("tcp://%s:5555", DefaultHTTPHost), //nolint:nosprintfhostport // sprintf is more readable for this case.
|
|
| 113 |
+ "tcp://[::1]": fmt.Sprintf(`tcp://[::1]:%d`, DefaultHTTPPort), |
|
| 114 |
+ "tcp://[::1]:": fmt.Sprintf(`tcp://[::1]:%d`, DefaultHTTPPort), |
|
| 115 |
+ "tcp://[::1]:5555": `tcp://[::1]:5555`, |
|
| 116 |
+ "tcp://0.0.0.0:5555": "tcp://0.0.0.0:5555", |
|
| 117 |
+ "tcp://192.168:5555": "tcp://192.168:5555", |
|
| 118 |
+ "tcp://192.168.0.1:5555": "tcp://192.168.0.1:5555", |
|
| 119 |
+ "tcp://0.0.0.0:1234567890": "tcp://0.0.0.0:1234567890", // yeah it's valid :P |
|
| 120 |
+ "tcp://docker.com:5555": "tcp://docker.com:5555", |
|
| 121 |
+ "unix://": "unix://" + DefaultUnixSocket, |
|
| 122 |
+ "unix://path/to/socket": "unix://path/to/socket", |
|
| 123 |
+ "unix:///run/docker.sock": "unix:///run/docker.sock", |
|
| 116 | 124 |
} |
| 117 | 125 |
for invalidAddr, expectedError := range invalids {
|
| 118 | 126 |
t.Run(invalidAddr, func(t *testing.T) {
|
| 119 |
- addr, err := parseDaemonHost(invalidAddr) |
|
| 127 |
+ addr, err := ParseDaemonHost(invalidAddr) |
|
| 120 | 128 |
if err == nil || err.Error() != expectedError {
|
| 121 | 129 |
t.Errorf(`expected error "%s", got "%v"`, expectedError, err) |
| 122 | 130 |
} |
| ... | ... |
@@ -127,7 +86,7 @@ func TestParseDockerDaemonHost(t *testing.T) {
|
| 127 | 127 |
} |
| 128 | 128 |
for validAddr, expectedAddr := range valids {
|
| 129 | 129 |
t.Run(validAddr, func(t *testing.T) {
|
| 130 |
- addr, err := parseDaemonHost(validAddr) |
|
| 130 |
+ addr, err := ParseDaemonHost(validAddr) |
|
| 131 | 131 |
if err != nil {
|
| 132 | 132 |
t.Errorf(`unexpected error: "%v"`, err) |
| 133 | 133 |
} |