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>
| ... | ... |
@@ -475,8 +475,14 @@ const ( |
| 475 | 475 |
flagContainerLabelRemove = "container-label-rm" |
| 476 | 476 |
flagContainerLabelAdd = "container-label-add" |
| 477 | 477 |
flagDNS = "dns" |
| 478 |
- flagDNSOptions = "dns-opt" |
|
| 478 |
+ flagDNSRemove = "dns-rm" |
|
| 479 |
+ flagDNSAdd = "dns-add" |
|
| 480 |
+ flagDNSOptions = "dns-options" |
|
| 481 |
+ flagDNSOptionsRemove = "dns-options-rm" |
|
| 482 |
+ flagDNSOptionsAdd = "dns-options-add" |
|
| 479 | 483 |
flagDNSSearch = "dns-search" |
| 484 |
+ flagDNSSearchRemove = "dns-search-rm" |
|
| 485 |
+ flagDNSSearchAdd = "dns-search-add" |
|
| 480 | 486 |
flagEndpointMode = "endpoint-mode" |
| 481 | 487 |
flagHostname = "hostname" |
| 482 | 488 |
flagEnv = "env" |
| ... | ... |
@@ -48,6 +48,9 @@ func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 48 | 48 |
flags.Var(newListOptsVar(), flagMountRemove, "Remove a mount by its target path") |
| 49 | 49 |
flags.Var(newListOptsVar(), flagPublishRemove, "Remove a published port by its target port") |
| 50 | 50 |
flags.Var(newListOptsVar(), flagConstraintRemove, "Remove a constraint") |
| 51 |
+ flags.Var(newListOptsVar(), flagDNSRemove, "Remove custom DNS servers") |
|
| 52 |
+ flags.Var(newListOptsVar(), flagDNSOptionsRemove, "Remove DNS options") |
|
| 53 |
+ flags.Var(newListOptsVar(), flagDNSSearchRemove, "Remove DNS search domains") |
|
| 51 | 54 |
flags.Var(&opts.labels, flagLabelAdd, "Add or update a service label") |
| 52 | 55 |
flags.Var(&opts.containerLabels, flagContainerLabelAdd, "Add or update a container label") |
| 53 | 56 |
flags.Var(&opts.env, flagEnvAdd, "Add or update an environment variable") |
| ... | ... |
@@ -55,6 +58,10 @@ func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 55 | 55 |
flags.StringSliceVar(&opts.constraints, flagConstraintAdd, []string{}, "Add or update a placement constraint")
|
| 56 | 56 |
flags.Var(&opts.endpoint.ports, flagPublishAdd, "Add or update a published port") |
| 57 | 57 |
flags.StringSliceVar(&opts.groups, flagGroupAdd, []string{}, "Add an additional supplementary user group to the container")
|
| 58 |
+ flags.Var(&opts.dns, flagDNSAdd, "Add or update custom DNS servers") |
|
| 59 |
+ flags.Var(&opts.dnsOptions, flagDNSOptionsAdd, "Add or update DNS options") |
|
| 60 |
+ flags.Var(&opts.dnsSearch, flagDNSSearchAdd, "Add or update custom DNS search domains") |
|
| 61 |
+ |
|
| 58 | 62 |
return cmd |
| 59 | 63 |
} |
| 60 | 64 |
|
| ... | ... |
@@ -257,6 +264,15 @@ func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
|
| 257 | 257 |
} |
| 258 | 258 |
} |
| 259 | 259 |
|
| 260 |
+ if anyChanged(flags, flagDNSAdd, flagDNSRemove, flagDNSOptionsAdd, flagDNSOptionsRemove, flagDNSSearchAdd, flagDNSSearchRemove) {
|
|
| 261 |
+ if cspec.DNSConfig == nil {
|
|
| 262 |
+ cspec.DNSConfig = &swarm.DNSConfig{}
|
|
| 263 |
+ } |
|
| 264 |
+ if err := updateDNSConfig(flags, &cspec.DNSConfig); err != nil {
|
|
| 265 |
+ return err |
|
| 266 |
+ } |
|
| 267 |
+ } |
|
| 268 |
+ |
|
| 260 | 269 |
if err := updateLogDriver(flags, &spec.TaskTemplate); err != nil {
|
| 261 | 270 |
return err |
| 262 | 271 |
} |
| ... | ... |
@@ -484,6 +500,71 @@ func updateGroups(flags *pflag.FlagSet, groups *[]string) error {
|
| 484 | 484 |
return nil |
| 485 | 485 |
} |
| 486 | 486 |
|
| 487 |
+func removeDuplicates(entries []string) []string {
|
|
| 488 |
+ hit := map[string]bool{}
|
|
| 489 |
+ newEntries := []string{}
|
|
| 490 |
+ for _, v := range entries {
|
|
| 491 |
+ if !hit[v] {
|
|
| 492 |
+ newEntries = append(newEntries, v) |
|
| 493 |
+ hit[v] = true |
|
| 494 |
+ } |
|
| 495 |
+ } |
|
| 496 |
+ return newEntries |
|
| 497 |
+} |
|
| 498 |
+ |
|
| 499 |
+func updateDNSConfig(flags *pflag.FlagSet, config **swarm.DNSConfig) error {
|
|
| 500 |
+ newConfig := &swarm.DNSConfig{}
|
|
| 501 |
+ |
|
| 502 |
+ nameservers := (*config).Nameservers |
|
| 503 |
+ if flags.Changed(flagDNSAdd) {
|
|
| 504 |
+ values := flags.Lookup(flagDNSAdd).Value.(*opts.ListOpts).GetAll() |
|
| 505 |
+ nameservers = append(nameservers, values...) |
|
| 506 |
+ } |
|
| 507 |
+ nameservers = removeDuplicates(nameservers) |
|
| 508 |
+ toRemove := buildToRemoveSet(flags, flagDNSRemove) |
|
| 509 |
+ for _, nameserver := range nameservers {
|
|
| 510 |
+ if _, exists := toRemove[nameserver]; !exists {
|
|
| 511 |
+ newConfig.Nameservers = append(newConfig.Nameservers, nameserver) |
|
| 512 |
+ |
|
| 513 |
+ } |
|
| 514 |
+ } |
|
| 515 |
+ // Sort so that result is predictable. |
|
| 516 |
+ sort.Strings(newConfig.Nameservers) |
|
| 517 |
+ |
|
| 518 |
+ search := (*config).Search |
|
| 519 |
+ if flags.Changed(flagDNSSearchAdd) {
|
|
| 520 |
+ values := flags.Lookup(flagDNSSearchAdd).Value.(*opts.ListOpts).GetAll() |
|
| 521 |
+ search = append(search, values...) |
|
| 522 |
+ } |
|
| 523 |
+ search = removeDuplicates(search) |
|
| 524 |
+ toRemove = buildToRemoveSet(flags, flagDNSSearchRemove) |
|
| 525 |
+ for _, entry := range search {
|
|
| 526 |
+ if _, exists := toRemove[entry]; !exists {
|
|
| 527 |
+ newConfig.Search = append(newConfig.Search, entry) |
|
| 528 |
+ } |
|
| 529 |
+ } |
|
| 530 |
+ // Sort so that result is predictable. |
|
| 531 |
+ sort.Strings(newConfig.Search) |
|
| 532 |
+ |
|
| 533 |
+ options := (*config).Options |
|
| 534 |
+ if flags.Changed(flagDNSOptionsAdd) {
|
|
| 535 |
+ values := flags.Lookup(flagDNSOptionsAdd).Value.(*opts.ListOpts).GetAll() |
|
| 536 |
+ options = append(options, values...) |
|
| 537 |
+ } |
|
| 538 |
+ options = removeDuplicates(options) |
|
| 539 |
+ toRemove = buildToRemoveSet(flags, flagDNSOptionsRemove) |
|
| 540 |
+ for _, option := range options {
|
|
| 541 |
+ if _, exists := toRemove[option]; !exists {
|
|
| 542 |
+ newConfig.Options = append(newConfig.Options, option) |
|
| 543 |
+ } |
|
| 544 |
+ } |
|
| 545 |
+ // Sort so that result is predictable. |
|
| 546 |
+ sort.Strings(newConfig.Options) |
|
| 547 |
+ |
|
| 548 |
+ *config = newConfig |
|
| 549 |
+ return nil |
|
| 550 |
+} |
|
| 551 |
+ |
|
| 487 | 552 |
type byPortConfig []swarm.PortConfig |
| 488 | 553 |
|
| 489 | 554 |
func (r byPortConfig) Len() int { return len(r) }
|
| ... | ... |
@@ -120,6 +120,52 @@ func TestUpdateGroups(t *testing.T) {
|
| 120 | 120 |
assert.Equal(t, groups[2], "wheel") |
| 121 | 121 |
} |
| 122 | 122 |
|
| 123 |
+func TestUpdateDNSConfig(t *testing.T) {
|
|
| 124 |
+ flags := newUpdateCommand(nil).Flags() |
|
| 125 |
+ |
|
| 126 |
+ // IPv4, with duplicates |
|
| 127 |
+ flags.Set("dns-add", "1.1.1.1")
|
|
| 128 |
+ flags.Set("dns-add", "1.1.1.1")
|
|
| 129 |
+ flags.Set("dns-add", "2.2.2.2")
|
|
| 130 |
+ flags.Set("dns-rm", "3.3.3.3")
|
|
| 131 |
+ flags.Set("dns-rm", "2.2.2.2")
|
|
| 132 |
+ // IPv6 |
|
| 133 |
+ flags.Set("dns-add", "2001:db8:abc8::1")
|
|
| 134 |
+ // Invalid dns record |
|
| 135 |
+ assert.Error(t, flags.Set("dns-add", "x.y.z.w"), "x.y.z.w is not an ip address")
|
|
| 136 |
+ |
|
| 137 |
+ // domains with duplicates |
|
| 138 |
+ flags.Set("dns-search-add", "example.com")
|
|
| 139 |
+ flags.Set("dns-search-add", "example.com")
|
|
| 140 |
+ flags.Set("dns-search-add", "example.org")
|
|
| 141 |
+ flags.Set("dns-search-rm", "example.org")
|
|
| 142 |
+ // Invalid dns search domain |
|
| 143 |
+ assert.Error(t, flags.Set("dns-search-add", "example$com"), "example$com is not a valid domain")
|
|
| 144 |
+ |
|
| 145 |
+ flags.Set("dns-options-add", "ndots:9")
|
|
| 146 |
+ flags.Set("dns-options-rm", "timeout:3")
|
|
| 147 |
+ |
|
| 148 |
+ config := &swarm.DNSConfig{
|
|
| 149 |
+ Nameservers: []string{"3.3.3.3", "5.5.5.5"},
|
|
| 150 |
+ Search: []string{"localdomain"},
|
|
| 151 |
+ Options: []string{"timeout:3"},
|
|
| 152 |
+ } |
|
| 153 |
+ |
|
| 154 |
+ updateDNSConfig(flags, &config) |
|
| 155 |
+ |
|
| 156 |
+ assert.Equal(t, len(config.Nameservers), 3) |
|
| 157 |
+ assert.Equal(t, config.Nameservers[0], "1.1.1.1") |
|
| 158 |
+ assert.Equal(t, config.Nameservers[1], "2001:db8:abc8::1") |
|
| 159 |
+ assert.Equal(t, config.Nameservers[2], "5.5.5.5") |
|
| 160 |
+ |
|
| 161 |
+ assert.Equal(t, len(config.Search), 2) |
|
| 162 |
+ assert.Equal(t, config.Search[0], "example.com") |
|
| 163 |
+ assert.Equal(t, config.Search[1], "localdomain") |
|
| 164 |
+ |
|
| 165 |
+ assert.Equal(t, len(config.Options), 1) |
|
| 166 |
+ assert.Equal(t, config.Options[0], "ndots:9") |
|
| 167 |
+} |
|
| 168 |
+ |
|
| 123 | 169 |
func TestUpdateMounts(t *testing.T) {
|
| 124 | 170 |
flags := newUpdateCommand(nil).Flags() |
| 125 | 171 |
flags.Set("mount-add", "type=volume,source=vol2,target=/toadd")
|
| ... | ... |
@@ -168,6 +168,7 @@ This section lists each version from latest to oldest. Each listing includes a |
| 168 | 168 |
* `GET /info` now returns more structured information about security options. |
| 169 | 169 |
* The `HostConfig` field now includes `CpuCount` that represents the number of CPUs available for execution by the container. Windows daemon only. |
| 170 | 170 |
* `POST /services/create` and `POST /services/(id or name)/update` now accept the `TTY` parameter, which allocate a pseudo-TTY in container. |
| 171 |
+* `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`. |
|
| 171 | 172 |
|
| 172 | 173 |
### v1.24 API changes |
| 173 | 174 |
|
| ... | ... |
@@ -5113,7 +5113,12 @@ image](#create-an-image) section for more details. |
| 5113 | 5113 |
} |
| 5114 | 5114 |
], |
| 5115 | 5115 |
"User": "33", |
| 5116 |
- "TTY": false |
|
| 5116 |
+ "TTY": false, |
|
| 5117 |
+ "DNSConfig": {
|
|
| 5118 |
+ "Nameservers": ["8.8.8.8"], |
|
| 5119 |
+ "Search": ["example.org"], |
|
| 5120 |
+ "Options": ["timeout:3"] |
|
| 5121 |
+ } |
|
| 5117 | 5122 |
}, |
| 5118 | 5123 |
"LogDriver": {
|
| 5119 | 5124 |
"Name": "json-file", |
| ... | ... |
@@ -5208,6 +5213,11 @@ image](#create-an-image) section for more details. |
| 5208 | 5208 |
- **Options** - key/value map of driver specific options. |
| 5209 | 5209 |
- **StopGracePeriod** – Amount of time to wait for the container to terminate before |
| 5210 | 5210 |
forcefully killing it. |
| 5211 |
+ - **DNSConfig** – Specification for DNS related configurations in |
|
| 5212 |
+ resolver configuration file (resolv.conf). |
|
| 5213 |
+ - **Nameservers** – A list of the IP addresses of the name servers. |
|
| 5214 |
+ - **Search** – A search list for host-name lookup. |
|
| 5215 |
+ - **Options** – A list of internal resolver variables to be modified (e.g., `debug`, `ndots:3`, etc.). |
|
| 5211 | 5216 |
- **LogDriver** - Log configuration for containers created as part of the |
| 5212 | 5217 |
service. |
| 5213 | 5218 |
- **Name** - Name of the logging driver to use (`json-file`, `syslog`, |
| ... | ... |
@@ -5393,7 +5403,12 @@ image](#create-an-image) section for more details. |
| 5393 | 5393 |
"Args": [ |
| 5394 | 5394 |
"top" |
| 5395 | 5395 |
], |
| 5396 |
- "TTY": true |
|
| 5396 |
+ "TTY": true, |
|
| 5397 |
+ "DNSConfig": {
|
|
| 5398 |
+ "Nameservers": ["8.8.8.8"], |
|
| 5399 |
+ "Search": ["example.org"], |
|
| 5400 |
+ "Options": ["timeout:3"] |
|
| 5401 |
+ } |
|
| 5397 | 5402 |
}, |
| 5398 | 5403 |
"Resources": {
|
| 5399 | 5404 |
"Limits": {},
|
| ... | ... |
@@ -5459,6 +5474,11 @@ image](#create-an-image) section for more details. |
| 5459 | 5459 |
- **Options** - key/value map of driver specific options |
| 5460 | 5460 |
- **StopGracePeriod** – Amount of time to wait for the container to terminate before |
| 5461 | 5461 |
forcefully killing it. |
| 5462 |
+ - **DNSConfig** – Specification for DNS related configurations in |
|
| 5463 |
+ resolver configuration file (resolv.conf). |
|
| 5464 |
+ - **Nameservers** – A list of the IP addresses of the name servers. |
|
| 5465 |
+ - **Search** – A search list for host-name lookup. |
|
| 5466 |
+ - **Options** – A list of internal resolver variables to be modified (e.g., `debug`, `ndots:3`, etc.). |
|
| 5462 | 5467 |
- **Resources** – Resource requirements which apply to each individual container created as part |
| 5463 | 5468 |
of the service. |
| 5464 | 5469 |
- **Limits** – Define resources limits. |
| ... | ... |
@@ -24,7 +24,7 @@ Options: |
| 24 | 24 |
--constraint value Placement constraints (default []) |
| 25 | 25 |
--container-label value Service container labels (default []) |
| 26 | 26 |
--dns list Set custom DNS servers (default []) |
| 27 |
- --dns-opt list Set DNS options (default []) |
|
| 27 |
+ --dns-options list Set DNS options (default []) |
|
| 28 | 28 |
--dns-search list Set custom DNS search domains (default []) |
| 29 | 29 |
--endpoint-mode string Endpoint mode (vip or dnsrr) |
| 30 | 30 |
-e, --env value Set environment variables (default []) |
| ... | ... |
@@ -26,6 +26,12 @@ Options: |
| 26 | 26 |
--constraint-rm list Remove a constraint (default []) |
| 27 | 27 |
--container-label-add list Add or update a container label (default []) |
| 28 | 28 |
--container-label-rm list Remove a container label by its key (default []) |
| 29 |
+ --dns-add list Add or update custom DNS servers (default []) |
|
| 30 |
+ --dns-options-add list Add or update DNS options (default []) |
|
| 31 |
+ --dns-options-rm list Remove DNS options (default []) |
|
| 32 |
+ --dns-rm list Remove custom DNS servers (default []) |
|
| 33 |
+ --dns-search-add list Add or update custom DNS search domains (default []) |
|
| 34 |
+ --dns-search-rm list Remove DNS search domains (default []) |
|
| 29 | 35 |
--endpoint-mode string Endpoint mode (vip or dnsrr) |
| 30 | 36 |
--env-add list Add or update an environment variable (default []) |
| 31 | 37 |
--env-rm list Remove an environment variable (default []) |
| ... | ... |
@@ -795,7 +795,7 @@ func (s *DockerSwarmSuite) TestDNSConfig(c *check.C) {
|
| 795 | 795 |
|
| 796 | 796 |
// Create a service |
| 797 | 797 |
name := "top" |
| 798 |
- _, err := d.Cmd("service", "create", "--name", name, "--dns=1.2.3.4", "--dns-search=example.com", "--dns-opt=timeout:3", "busybox", "top")
|
|
| 798 |
+ _, err := d.Cmd("service", "create", "--name", name, "--dns=1.2.3.4", "--dns-search=example.com", "--dns-options=timeout:3", "busybox", "top")
|
|
| 799 | 799 |
c.Assert(err, checker.IsNil) |
| 800 | 800 |
|
| 801 | 801 |
// Make sure task has been deployed. |
| ... | ... |
@@ -816,3 +816,22 @@ func (s *DockerSwarmSuite) TestDNSConfig(c *check.C) {
|
| 816 | 816 |
c.Assert(out, checker.Contains, expectedOutput2, check.Commentf("Expected '%s', but got %q", expectedOutput2, out))
|
| 817 | 817 |
c.Assert(out, checker.Contains, expectedOutput3, check.Commentf("Expected '%s', but got %q", expectedOutput3, out))
|
| 818 | 818 |
} |
| 819 |
+ |
|
| 820 |
+func (s *DockerSwarmSuite) TestDNSConfigUpdate(c *check.C) {
|
|
| 821 |
+ d := s.AddDaemon(c, true, true) |
|
| 822 |
+ |
|
| 823 |
+ // Create a service |
|
| 824 |
+ name := "top" |
|
| 825 |
+ _, err := d.Cmd("service", "create", "--name", name, "busybox", "top")
|
|
| 826 |
+ c.Assert(err, checker.IsNil) |
|
| 827 |
+ |
|
| 828 |
+ // Make sure task has been deployed. |
|
| 829 |
+ waitAndAssert(c, defaultReconciliationTimeout, d.checkActiveContainerCount, checker.Equals, 1) |
|
| 830 |
+ |
|
| 831 |
+ _, err = d.Cmd("service", "update", "--dns-add=1.2.3.4", "--dns-search-add=example.com", "--dns-options-add=timeout:3", name)
|
|
| 832 |
+ c.Assert(err, checker.IsNil) |
|
| 833 |
+ |
|
| 834 |
+ out, err := d.Cmd("service", "inspect", "--format", "{{ .Spec.TaskTemplate.ContainerSpec.DNSConfig }}", name)
|
|
| 835 |
+ c.Assert(err, checker.IsNil) |
|
| 836 |
+ c.Assert(strings.TrimSpace(out), checker.Equals, "{[1.2.3.4] [example.com] [timeout:3]}")
|
|
| 837 |
+} |