Browse code

Support `--group-add` and `--group-rm` in `docker service create/update`

This fix tries to address the issue raised in 25304 to support
`--group-add` and `--group-rm` in `docker service create`.

This fix adds `--group-add` to `docker service create` and `docker service update`,
adds `--group-rm` to `docker service update`.

This fix updates docs for `docker service create` and `docker service update`:
1. Add `--group-add` to `docker service create` and `docker service update`
2. Add `--group-rm` to `docker service update`

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

Yong Tang authored on 2016/08/02 10:34:51
Showing 7 changed files
... ...
@@ -399,6 +399,7 @@ type serviceOptions struct {
399 399
 	env             opts.ListOpts
400 400
 	workdir         string
401 401
 	user            string
402
+	groups          []string
402 403
 	mounts          MountOpt
403 404
 
404 405
 	resources resourceOptions
... ...
@@ -446,6 +447,7 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
446 446
 				Labels:          runconfigopts.ConvertKVStringsToMap(opts.containerLabels.GetAll()),
447 447
 				Dir:             opts.workdir,
448 448
 				User:            opts.user,
449
+				Groups:          opts.groups,
449 450
 				Mounts:          opts.mounts.Value(),
450 451
 				StopGracePeriod: opts.stopGrace.Value(),
451 452
 			},
... ...
@@ -491,6 +493,7 @@ func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
491 491
 
492 492
 	flags.StringVarP(&opts.workdir, flagWorkdir, "w", "", "Working directory inside the container")
493 493
 	flags.StringVarP(&opts.user, flagUser, "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
494
+	flags.StringSliceVar(&opts.groups, flagGroupAdd, []string{}, "Add additional user groups to the container")
494 495
 
495 496
 	flags.Var(&opts.resources.limitCPU, flagLimitCPU, "Limit CPUs")
496 497
 	flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "Limit Memory")
... ...
@@ -528,6 +531,8 @@ const (
528 528
 	flagEnv                  = "env"
529 529
 	flagEnvRemove            = "env-rm"
530 530
 	flagEnvAdd               = "env-add"
531
+	flagGroupAdd             = "group-add"
532
+	flagGroupRemove          = "group-rm"
531 533
 	flagLabel                = "label"
532 534
 	flagLabelRemove          = "label-rm"
533 535
 	flagLabelAdd             = "label-add"
... ...
@@ -39,6 +39,7 @@ func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
39 39
 	addServiceFlags(cmd, opts)
40 40
 
41 41
 	flags.Var(newListOptsVar(), flagEnvRemove, "Remove an environment variable")
42
+	flags.Var(newListOptsVar(), flagGroupRemove, "Remove previously added user groups from the container")
42 43
 	flags.Var(newListOptsVar(), flagLabelRemove, "Remove a label by its key")
43 44
 	flags.Var(newListOptsVar(), flagContainerLabelRemove, "Remove a container label by its key")
44 45
 	flags.Var(newListOptsVar(), flagMountRemove, "Remove a mount by its target path")
... ...
@@ -211,6 +212,12 @@ func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
211 211
 		spec.EndpointSpec.Mode = swarm.ResolutionMode(value)
212 212
 	}
213 213
 
214
+	if anyChanged(flags, flagGroupAdd, flagGroupRemove) {
215
+		if err := updateGroups(flags, &cspec.Groups); err != nil {
216
+			return err
217
+		}
218
+	}
219
+
214 220
 	if anyChanged(flags, flagPublishAdd, flagPublishRemove) {
215 221
 		if spec.EndpointSpec == nil {
216 222
 			spec.EndpointSpec = &swarm.EndpointSpec{}
... ...
@@ -370,6 +377,29 @@ func updateMounts(flags *pflag.FlagSet, mounts *[]mounttypes.Mount) {
370 370
 	*mounts = newMounts
371 371
 }
372 372
 
373
+func updateGroups(flags *pflag.FlagSet, groups *[]string) error {
374
+	if flags.Changed(flagGroupAdd) {
375
+		values, err := flags.GetStringSlice(flagGroupAdd)
376
+		if err != nil {
377
+			return err
378
+		}
379
+		*groups = append(*groups, values...)
380
+	}
381
+	toRemove := buildToRemoveSet(flags, flagGroupRemove)
382
+
383
+	newGroups := []string{}
384
+	for _, group := range *groups {
385
+		if _, exists := toRemove[group]; !exists {
386
+			newGroups = append(newGroups, group)
387
+		}
388
+	}
389
+	// Sort so that result is predictable.
390
+	sort.Strings(newGroups)
391
+
392
+	*groups = newGroups
393
+	return nil
394
+}
395
+
373 396
 type byPortConfig []swarm.PortConfig
374 397
 
375 398
 func (r byPortConfig) Len() int      { return len(r) }
... ...
@@ -100,6 +100,23 @@ func TestUpdateEnvironmentWithDuplicateKeys(t *testing.T) {
100 100
 	assert.Equal(t, envs[0], "A=b")
101 101
 }
102 102
 
103
+func TestUpdateGroups(t *testing.T) {
104
+	flags := newUpdateCommand(nil).Flags()
105
+	flags.Set("group-add", "wheel")
106
+	flags.Set("group-add", "docker")
107
+	flags.Set("group-rm", "root")
108
+	flags.Set("group-add", "foo")
109
+	flags.Set("group-rm", "docker")
110
+
111
+	groups := []string{"bar", "root"}
112
+
113
+	updateGroups(flags, &groups)
114
+	assert.Equal(t, len(groups), 3)
115
+	assert.Equal(t, groups[0], "bar")
116
+	assert.Equal(t, groups[1], "foo")
117
+	assert.Equal(t, groups[2], "wheel")
118
+}
119
+
103 120
 func TestUpdateMounts(t *testing.T) {
104 121
 	flags := newUpdateCommand(nil).Flags()
105 122
 	flags.Set("mount-add", "type=volume,target=/toadd")
... ...
@@ -19,6 +19,7 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) types.ContainerSpec {
19 19
 		Env:     c.Env,
20 20
 		Dir:     c.Dir,
21 21
 		User:    c.User,
22
+		Groups:  c.Groups,
22 23
 	}
23 24
 
24 25
 	// Mounts
... ...
@@ -67,6 +68,7 @@ func containerToGRPC(c types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
67 67
 		Env:     c.Env,
68 68
 		Dir:     c.Dir,
69 69
 		User:    c.User,
70
+		Groups:  c.Groups,
70 71
 	}
71 72
 
72 73
 	if c.StopGracePeriod != nil {
... ...
@@ -20,6 +20,7 @@ Options:
20 20
       --container-label value          Service container labels (default [])
21 21
       --endpoint-mode string           Endpoint mode (vip or dnsrr)
22 22
   -e, --env value                      Set environment variables (default [])
23
+      --group-add value                Add additional user groups to the container (default [])
23 24
       --help                           Print usage
24 25
   -l, --label value                    Service labels (default [])
25 26
       --limit-cpu value                Limit CPUs (default 0.000)
... ...
@@ -24,6 +24,8 @@ Options:
24 24
       --endpoint-mode string           Endpoint mode (vip or dnsrr)
25 25
       --env-add value                  Add or update environment variables (default [])
26 26
       --env-rm value                   Remove an environment variable (default [])
27
+      --group-add value                Add additional user groups to the container (default [])
28
+      --group-rm value                 Remove previously added user groups from the container (default [])
27 29
       --help                           Print usage
28 30
       --image string                   Service image tag
29 31
       --label-add value                Add or update service labels (default [])
... ...
@@ -220,3 +220,25 @@ func (s *DockerSwarmSuite) TestSwarmPublishAdd(c *check.C) {
220 220
 	c.Assert(err, checker.IsNil)
221 221
 	c.Assert(strings.TrimSpace(out), checker.Equals, "[{ tcp 20 80}]")
222 222
 }
223
+
224
+func (s *DockerSwarmSuite) TestSwarmServiceWithGroup(c *check.C) {
225
+	d := s.AddDaemon(c, true, true)
226
+
227
+	name := "top"
228
+	out, err := d.Cmd("service", "create", "--name", name, "--user", "root:root", "--group-add", "wheel", "--group-add", "audio", "--group-add", "staff", "--group-add", "777", "busybox", "sh", "-c", "id > /id && top")
229
+	c.Assert(err, checker.IsNil)
230
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
231
+
232
+	// make sure task has been deployed.
233
+	waitAndAssert(c, defaultReconciliationTimeout, d.checkActiveContainerCount, checker.Equals, 1)
234
+
235
+	out, err = d.Cmd("ps", "-q")
236
+	c.Assert(err, checker.IsNil)
237
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
238
+
239
+	container := strings.TrimSpace(out)
240
+
241
+	out, err = d.Cmd("exec", container, "cat", "/id")
242
+	c.Assert(err, checker.IsNil)
243
+	c.Assert(strings.TrimSpace(out), checker.Equals, "uid=0(root) gid=0(root) groups=10(wheel),29(audio),50(staff),777")
244
+}