Relax dns search to accept empty domain

In that case /etc/resolv.conf will be generated with no search
option. Usage: --dns-search=.

Docker-DCO-1.1-Signed-off-by: Fabio Falci <fabiofalci@gmail.com> (github: fabiofalci)
This commit is contained in:
Fabio Falci 2014-06-26 12:03:23 +01:00
parent 44d737707c
commit 804b00cd7d
7 changed files with 158 additions and 11 deletions

View File

@ -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")

View File

@ -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")
}

View File

@ -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)

View File

@ -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)
}
}
}

View File

@ -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
}
}
}

View File

@ -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)
}
}

View File

@ -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