Add custom DNS settings to service update

This fix adds `--dns-add`, `--dns-rm`, `--dns-opt-add`, `--dns-opt-rm`,
`--dns-search-add` and `--dns-search-rm` to `service update`.

An integration test and a unit test have been added to cover the changes in this fix.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
This commit is contained in:
Yong Tang 2016-10-26 20:05:39 -07:00
parent 9e8adbecf5
commit a39c0cf007
8 changed files with 184 additions and 5 deletions

View file

@ -475,8 +475,14 @@ const (
flagContainerLabelRemove = "container-label-rm"
flagContainerLabelAdd = "container-label-add"
flagDNS = "dns"
flagDNSOptions = "dns-opt"
flagDNSRemove = "dns-rm"
flagDNSAdd = "dns-add"
flagDNSOptions = "dns-options"
flagDNSOptionsRemove = "dns-options-rm"
flagDNSOptionsAdd = "dns-options-add"
flagDNSSearch = "dns-search"
flagDNSSearchRemove = "dns-search-rm"
flagDNSSearchAdd = "dns-search-add"
flagEndpointMode = "endpoint-mode"
flagHostname = "hostname"
flagEnv = "env"

View file

@ -48,6 +48,9 @@ func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
flags.Var(newListOptsVar(), flagMountRemove, "Remove a mount by its target path")
flags.Var(newListOptsVar(), flagPublishRemove, "Remove a published port by its target port")
flags.Var(newListOptsVar(), flagConstraintRemove, "Remove a constraint")
flags.Var(newListOptsVar(), flagDNSRemove, "Remove custom DNS servers")
flags.Var(newListOptsVar(), flagDNSOptionsRemove, "Remove DNS options")
flags.Var(newListOptsVar(), flagDNSSearchRemove, "Remove DNS search domains")
flags.Var(&opts.labels, flagLabelAdd, "Add or update a service label")
flags.Var(&opts.containerLabels, flagContainerLabelAdd, "Add or update a container label")
flags.Var(&opts.env, flagEnvAdd, "Add or update an environment variable")
@ -55,6 +58,10 @@ func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
flags.StringSliceVar(&opts.constraints, flagConstraintAdd, []string{}, "Add or update a placement constraint")
flags.Var(&opts.endpoint.ports, flagPublishAdd, "Add or update a published port")
flags.StringSliceVar(&opts.groups, flagGroupAdd, []string{}, "Add an additional supplementary user group to the container")
flags.Var(&opts.dns, flagDNSAdd, "Add or update custom DNS servers")
flags.Var(&opts.dnsOptions, flagDNSOptionsAdd, "Add or update DNS options")
flags.Var(&opts.dnsSearch, flagDNSSearchAdd, "Add or update custom DNS search domains")
return cmd
}
@ -257,6 +264,15 @@ func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
}
}
if anyChanged(flags, flagDNSAdd, flagDNSRemove, flagDNSOptionsAdd, flagDNSOptionsRemove, flagDNSSearchAdd, flagDNSSearchRemove) {
if cspec.DNSConfig == nil {
cspec.DNSConfig = &swarm.DNSConfig{}
}
if err := updateDNSConfig(flags, &cspec.DNSConfig); err != nil {
return err
}
}
if err := updateLogDriver(flags, &spec.TaskTemplate); err != nil {
return err
}
@ -484,6 +500,71 @@ func updateGroups(flags *pflag.FlagSet, groups *[]string) error {
return nil
}
func removeDuplicates(entries []string) []string {
hit := map[string]bool{}
newEntries := []string{}
for _, v := range entries {
if !hit[v] {
newEntries = append(newEntries, v)
hit[v] = true
}
}
return newEntries
}
func updateDNSConfig(flags *pflag.FlagSet, config **swarm.DNSConfig) error {
newConfig := &swarm.DNSConfig{}
nameservers := (*config).Nameservers
if flags.Changed(flagDNSAdd) {
values := flags.Lookup(flagDNSAdd).Value.(*opts.ListOpts).GetAll()
nameservers = append(nameservers, values...)
}
nameservers = removeDuplicates(nameservers)
toRemove := buildToRemoveSet(flags, flagDNSRemove)
for _, nameserver := range nameservers {
if _, exists := toRemove[nameserver]; !exists {
newConfig.Nameservers = append(newConfig.Nameservers, nameserver)
}
}
// Sort so that result is predictable.
sort.Strings(newConfig.Nameservers)
search := (*config).Search
if flags.Changed(flagDNSSearchAdd) {
values := flags.Lookup(flagDNSSearchAdd).Value.(*opts.ListOpts).GetAll()
search = append(search, values...)
}
search = removeDuplicates(search)
toRemove = buildToRemoveSet(flags, flagDNSSearchRemove)
for _, entry := range search {
if _, exists := toRemove[entry]; !exists {
newConfig.Search = append(newConfig.Search, entry)
}
}
// Sort so that result is predictable.
sort.Strings(newConfig.Search)
options := (*config).Options
if flags.Changed(flagDNSOptionsAdd) {
values := flags.Lookup(flagDNSOptionsAdd).Value.(*opts.ListOpts).GetAll()
options = append(options, values...)
}
options = removeDuplicates(options)
toRemove = buildToRemoveSet(flags, flagDNSOptionsRemove)
for _, option := range options {
if _, exists := toRemove[option]; !exists {
newConfig.Options = append(newConfig.Options, option)
}
}
// Sort so that result is predictable.
sort.Strings(newConfig.Options)
*config = newConfig
return nil
}
type byPortConfig []swarm.PortConfig
func (r byPortConfig) Len() int { return len(r) }

View file

@ -120,6 +120,52 @@ func TestUpdateGroups(t *testing.T) {
assert.Equal(t, groups[2], "wheel")
}
func TestUpdateDNSConfig(t *testing.T) {
flags := newUpdateCommand(nil).Flags()
// IPv4, with duplicates
flags.Set("dns-add", "1.1.1.1")
flags.Set("dns-add", "1.1.1.1")
flags.Set("dns-add", "2.2.2.2")
flags.Set("dns-rm", "3.3.3.3")
flags.Set("dns-rm", "2.2.2.2")
// IPv6
flags.Set("dns-add", "2001:db8:abc8::1")
// Invalid dns record
assert.Error(t, flags.Set("dns-add", "x.y.z.w"), "x.y.z.w is not an ip address")
// domains with duplicates
flags.Set("dns-search-add", "example.com")
flags.Set("dns-search-add", "example.com")
flags.Set("dns-search-add", "example.org")
flags.Set("dns-search-rm", "example.org")
// Invalid dns search domain
assert.Error(t, flags.Set("dns-search-add", "example$com"), "example$com is not a valid domain")
flags.Set("dns-options-add", "ndots:9")
flags.Set("dns-options-rm", "timeout:3")
config := &swarm.DNSConfig{
Nameservers: []string{"3.3.3.3", "5.5.5.5"},
Search: []string{"localdomain"},
Options: []string{"timeout:3"},
}
updateDNSConfig(flags, &config)
assert.Equal(t, len(config.Nameservers), 3)
assert.Equal(t, config.Nameservers[0], "1.1.1.1")
assert.Equal(t, config.Nameservers[1], "2001:db8:abc8::1")
assert.Equal(t, config.Nameservers[2], "5.5.5.5")
assert.Equal(t, len(config.Search), 2)
assert.Equal(t, config.Search[0], "example.com")
assert.Equal(t, config.Search[1], "localdomain")
assert.Equal(t, len(config.Options), 1)
assert.Equal(t, config.Options[0], "ndots:9")
}
func TestUpdateMounts(t *testing.T) {
flags := newUpdateCommand(nil).Flags()
flags.Set("mount-add", "type=volume,source=vol2,target=/toadd")

View file

@ -168,6 +168,7 @@ This section lists each version from latest to oldest. Each listing includes a
* `GET /info` now returns more structured information about security options.
* The `HostConfig` field now includes `CpuCount` that represents the number of CPUs available for execution by the container. Windows daemon only.
* `POST /services/create` and `POST /services/(id or name)/update` now accept the `TTY` parameter, which allocate a pseudo-TTY in container.
* `POST /services/create` and `POST /services/(id or name)/update` now accept the `DNSConfig` parameter, which specifies DNS related configurations in resolver configuration file (resolv.conf) through `Nameservers`, `Search`, and `Options`.
### v1.24 API changes

View file

@ -5113,7 +5113,12 @@ image](#create-an-image) section for more details.
}
],
"User": "33",
"TTY": false
"TTY": false,
"DNSConfig": {
"Nameservers": ["8.8.8.8"],
"Search": ["example.org"],
"Options": ["timeout:3"]
}
},
"LogDriver": {
"Name": "json-file",
@ -5208,6 +5213,11 @@ image](#create-an-image) section for more details.
- **Options** - key/value map of driver specific options.
- **StopGracePeriod** Amount of time to wait for the container to terminate before
forcefully killing it.
- **DNSConfig** Specification for DNS related configurations in
resolver configuration file (resolv.conf).
- **Nameservers** A list of the IP addresses of the name servers.
- **Search** A search list for host-name lookup.
- **Options** A list of internal resolver variables to be modified (e.g., `debug`, `ndots:3`, etc.).
- **LogDriver** - Log configuration for containers created as part of the
service.
- **Name** - Name of the logging driver to use (`json-file`, `syslog`,
@ -5393,7 +5403,12 @@ image](#create-an-image) section for more details.
"Args": [
"top"
],
"TTY": true
"TTY": true,
"DNSConfig": {
"Nameservers": ["8.8.8.8"],
"Search": ["example.org"],
"Options": ["timeout:3"]
}
},
"Resources": {
"Limits": {},
@ -5459,6 +5474,11 @@ image](#create-an-image) section for more details.
- **Options** - key/value map of driver specific options
- **StopGracePeriod** Amount of time to wait for the container to terminate before
forcefully killing it.
- **DNSConfig** Specification for DNS related configurations in
resolver configuration file (resolv.conf).
- **Nameservers** A list of the IP addresses of the name servers.
- **Search** A search list for host-name lookup.
- **Options** A list of internal resolver variables to be modified (e.g., `debug`, `ndots:3`, etc.).
- **Resources** Resource requirements which apply to each individual container created as part
of the service.
- **Limits** Define resources limits.

View file

@ -24,7 +24,7 @@ Options:
--constraint value Placement constraints (default [])
--container-label value Service container labels (default [])
--dns list Set custom DNS servers (default [])
--dns-opt list Set DNS options (default [])
--dns-options list Set DNS options (default [])
--dns-search list Set custom DNS search domains (default [])
--endpoint-mode string Endpoint mode (vip or dnsrr)
-e, --env value Set environment variables (default [])

View file

@ -26,6 +26,12 @@ Options:
--constraint-rm list Remove a constraint (default [])
--container-label-add list Add or update a container label (default [])
--container-label-rm list Remove a container label by its key (default [])
--dns-add list Add or update custom DNS servers (default [])
--dns-options-add list Add or update DNS options (default [])
--dns-options-rm list Remove DNS options (default [])
--dns-rm list Remove custom DNS servers (default [])
--dns-search-add list Add or update custom DNS search domains (default [])
--dns-search-rm list Remove DNS search domains (default [])
--endpoint-mode string Endpoint mode (vip or dnsrr)
--env-add list Add or update an environment variable (default [])
--env-rm list Remove an environment variable (default [])

View file

@ -795,7 +795,7 @@ func (s *DockerSwarmSuite) TestDNSConfig(c *check.C) {
// Create a service
name := "top"
_, err := d.Cmd("service", "create", "--name", name, "--dns=1.2.3.4", "--dns-search=example.com", "--dns-opt=timeout:3", "busybox", "top")
_, err := d.Cmd("service", "create", "--name", name, "--dns=1.2.3.4", "--dns-search=example.com", "--dns-options=timeout:3", "busybox", "top")
c.Assert(err, checker.IsNil)
// Make sure task has been deployed.
@ -816,3 +816,22 @@ func (s *DockerSwarmSuite) TestDNSConfig(c *check.C) {
c.Assert(out, checker.Contains, expectedOutput2, check.Commentf("Expected '%s', but got %q", expectedOutput2, out))
c.Assert(out, checker.Contains, expectedOutput3, check.Commentf("Expected '%s', but got %q", expectedOutput3, out))
}
func (s *DockerSwarmSuite) TestDNSConfigUpdate(c *check.C) {
d := s.AddDaemon(c, true, true)
// Create a service
name := "top"
_, err := d.Cmd("service", "create", "--name", name, "busybox", "top")
c.Assert(err, checker.IsNil)
// Make sure task has been deployed.
waitAndAssert(c, defaultReconciliationTimeout, d.checkActiveContainerCount, checker.Equals, 1)
_, err = d.Cmd("service", "update", "--dns-add=1.2.3.4", "--dns-search-add=example.com", "--dns-options-add=timeout:3", name)
c.Assert(err, checker.IsNil)
out, err := d.Cmd("service", "inspect", "--format", "{{ .Spec.TaskTemplate.ContainerSpec.DNSConfig }}", name)
c.Assert(err, checker.IsNil)
c.Assert(strings.TrimSpace(out), checker.Equals, "{[1.2.3.4] [example.com] [timeout:3]}")
}