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