diff --git a/docker/docker.go b/docker/docker.go index 30d43bc6a8..147c91d21d 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -52,7 +52,7 @@ func main() { flSocketGroup = flag.String([]string{"G", "-group"}, "docker", "Group to assign the unix socket specified by -H when running in daemon mode\nuse '' (the empty string) to disable setting of a group") flEnableCors = flag.Bool([]string{"#api-enable-cors", "-api-enable-cors"}, false, "Enable CORS headers in the remote API") flDns = opts.NewListOpts(opts.ValidateIp4Address) - flDnsSearch = opts.NewListOpts(opts.ValidateDomain) + flDnsSearch = opts.NewListOpts(opts.ValidateDnsSearch) flEnableIptables = flag.Bool([]string{"#iptables", "-iptables"}, true, "Enable Docker's addition of iptables rules") flEnableIpForward = flag.Bool([]string{"#ip-forward", "-ip-forward"}, true, "Enable net.ipv4.ip_forward") flDefaultIp = flag.String([]string{"#ip", "-ip"}, "0.0.0.0", "Default IP address to use when binding container ports") diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index eda1b3f0a5..9f52d58d12 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "io/ioutil" "os" "os/exec" "reflect" @@ -10,6 +11,8 @@ import ( "strings" "sync" "testing" + + "github.com/dotcloud/docker/pkg/networkfs/resolvconf" ) // "test123" should be printed by docker run @@ -983,3 +986,109 @@ func TestDisallowBindMountingRootToRoot(t *testing.T) { logDone("run - bind mount /:/ as volume should fail") } + +func TestDnsDefaultOptions(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "cat", "/etc/resolv.conf") + + actual, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, actual) + } + + resolvConf, err := ioutil.ReadFile("/etc/resolv.conf") + if os.IsNotExist(err) { + t.Fatalf("/etc/resolv.conf does not exist") + } + + if actual != string(resolvConf) { + t.Fatalf("expected resolv.conf is not the same of actual") + } + + deleteAllContainers() + + logDone("run - dns default options") +} + +func TestDnsOptions(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--dns=127.0.0.1", "--dns-search=mydomain", "busybox", "cat", "/etc/resolv.conf") + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + + actual := strings.Replace(strings.Trim(out, "\r\n"), "\n", " ", -1) + if actual != "nameserver 127.0.0.1 search mydomain" { + t.Fatalf("expected 'nameserver 127.0.0.1 search mydomain', but says: '%s'", actual) + } + + cmd = exec.Command(dockerBinary, "run", "--dns=127.0.0.1", "--dns-search=.", "busybox", "cat", "/etc/resolv.conf") + + out, _, err = runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + + actual = strings.Replace(strings.Trim(strings.Trim(out, "\r\n"), " "), "\n", " ", -1) + if actual != "nameserver 127.0.0.1" { + t.Fatalf("expected 'nameserver 127.0.0.1', but says: '%s'", actual) + } + + logDone("run - dns options") +} + +func TestDnsOptionsBasedOnHostResolvConf(t *testing.T) { + resolvConf, err := ioutil.ReadFile("/etc/resolv.conf") + if os.IsNotExist(err) { + t.Fatalf("/etc/resolv.conf does not exist") + } + + hostNamservers := resolvconf.GetNameservers(resolvConf) + hostSearch := resolvconf.GetSearchDomains(resolvConf) + + cmd := exec.Command(dockerBinary, "run", "--dns=127.0.0.1", "busybox", "cat", "/etc/resolv.conf") + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + + if actualNameservers := resolvconf.GetNameservers([]byte(out)); string(actualNameservers[0]) != "127.0.0.1" { + t.Fatalf("expected '127.0.0.1', but says: '%s'", string(actualNameservers[0])) + } + + actualSearch := resolvconf.GetSearchDomains([]byte(out)) + if len(actualSearch) != len(hostSearch) { + t.Fatalf("expected '%s' search domain(s), but it has: '%s'", len(hostSearch), len(actualSearch)) + } + for i := range actualSearch { + if actualSearch[i] != hostSearch[i] { + t.Fatalf("expected '%s' domain, but says: '%s'", actualSearch[i], hostSearch[i]) + } + } + + cmd = exec.Command(dockerBinary, "run", "--dns-search=mydomain", "busybox", "cat", "/etc/resolv.conf") + + out, _, err = runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + + actualNameservers := resolvconf.GetNameservers([]byte(out)) + if len(actualNameservers) != len(hostNamservers) { + t.Fatalf("expected '%s' nameserver(s), but it has: '%s'", len(hostNamservers), len(actualNameservers)) + } + for i := range actualNameservers { + if actualNameservers[i] != hostNamservers[i] { + t.Fatalf("expected '%s' nameserver, but says: '%s'", actualNameservers[i], hostNamservers[i]) + } + } + + if actualSearch = resolvconf.GetSearchDomains([]byte(out)); string(actualSearch[0]) != "mydomain" { + t.Fatalf("expected 'mydomain', but says: '%s'", string(actualSearch[0])) + } + + deleteAllContainers() + + logDone("run - dns options based on host resolv.conf") +} diff --git a/opts/opts.go b/opts/opts.go index 67f1c8fd48..d17b57e07c 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -137,7 +137,16 @@ func ValidateIp4Address(val string) (string, error) { return "", fmt.Errorf("%s is not an ip4 address", val) } -func ValidateDomain(val string) (string, error) { +// Validates domain for resolvconf search configuration. +// A zero length domain is represented by . +func ValidateDnsSearch(val string) (string, error) { + if val = strings.Trim(val, " "); val == "." { + return val, nil + } + return validateDomain(val) +} + +func validateDomain(val string) (string, error) { alpha := regexp.MustCompile(`[a-zA-Z]`) if alpha.FindString(val) == "" { return "", fmt.Errorf("%s is not a valid domain", val) diff --git a/opts/opts_test.go b/opts/opts_test.go index 299cbfe503..b18088b934 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -23,8 +23,9 @@ func TestValidateIP4(t *testing.T) { } -func TestValidateDomain(t *testing.T) { +func TestValidateDnsSearch(t *testing.T) { valid := []string{ + `.`, `a`, `a.`, `1.foo`, @@ -49,7 +50,8 @@ func TestValidateDomain(t *testing.T) { invalid := []string{ ``, - `.`, + ` `, + ` `, `17`, `17.`, `.17`, @@ -65,14 +67,14 @@ func TestValidateDomain(t *testing.T) { } for _, domain := range valid { - if ret, err := ValidateDomain(domain); err != nil || ret == "" { - t.Fatalf("ValidateDomain(`"+domain+"`) got %s %s", ret, err) + if ret, err := ValidateDnsSearch(domain); err != nil || ret == "" { + t.Fatalf("ValidateDnsSearch(`"+domain+"`) got %s %s", ret, err) } } for _, domain := range invalid { - if ret, err := ValidateDomain(domain); err == nil || ret != "" { - t.Fatalf("ValidateDomain(`"+domain+"`) got %s %s", ret, err) + if ret, err := ValidateDnsSearch(domain); err == nil || ret != "" { + t.Fatalf("ValidateDnsSearch(`"+domain+"`) got %s %s", ret, err) } } } diff --git a/pkg/networkfs/resolvconf/resolvconf.go b/pkg/networkfs/resolvconf/resolvconf.go index d6854fb3b1..38ae56432d 100644 --- a/pkg/networkfs/resolvconf/resolvconf.go +++ b/pkg/networkfs/resolvconf/resolvconf.go @@ -78,8 +78,10 @@ func Build(path string, dns, dnsSearch []string) error { } } if len(dnsSearch) > 0 { - if _, err := content.WriteString("search " + strings.Join(dnsSearch, " ") + "\n"); err != nil { - return err + if searchString := strings.Join(dnsSearch, " "); strings.Trim(searchString, " ") != "." { + if _, err := content.WriteString("search " + searchString + "\n"); err != nil { + return err + } } } diff --git a/pkg/networkfs/resolvconf/resolvconf_test.go b/pkg/networkfs/resolvconf/resolvconf_test.go index fd20712376..6187acbae7 100644 --- a/pkg/networkfs/resolvconf/resolvconf_test.go +++ b/pkg/networkfs/resolvconf/resolvconf_test.go @@ -131,3 +131,28 @@ func TestBuild(t *testing.T) { t.Fatalf("Expected to find '%s' got '%s'", expected, content) } } + +func TestBuildWithZeroLengthDomainSearch(t *testing.T) { + file, err := ioutil.TempFile("", "") + if err != nil { + t.Fatal(err) + } + defer os.Remove(file.Name()) + + err = Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"."}) + if err != nil { + t.Fatal(err) + } + + content, err := ioutil.ReadFile(file.Name()) + if err != nil { + t.Fatal(err) + } + + if expected := "nameserver ns1\nnameserver ns2\nnameserver ns3\n"; !bytes.Contains(content, []byte(expected)) { + t.Fatalf("Expected to find '%s' got '%s'", expected, content) + } + if notExpected := "search ."; bytes.Contains(content, []byte(notExpected)) { + t.Fatalf("Expected to not find '%s' got '%s'", notExpected, content) + } +} diff --git a/runconfig/parse.go b/runconfig/parse.go index dfd9f4ddd3..ae79186716 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -45,7 +45,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf flPublish opts.ListOpts flExpose opts.ListOpts flDns opts.ListOpts - flDnsSearch = opts.NewListOpts(opts.ValidateDomain) + flDnsSearch = opts.NewListOpts(opts.ValidateDnsSearch) flVolumesFrom opts.ListOpts flLxcOpts opts.ListOpts flEnvFile opts.ListOpts