Browse code

Merge pull request #39173 from olljanat/25885-capabilities-swarm

Add support for capabilities options in services

Kirill Kolyshkin authored on 2019/06/07 07:03:46
Showing 8 changed files
... ...
@@ -95,4 +95,11 @@ func adjustForAPIVersion(cliVersion string, service *swarm.ServiceSpec) {
95 95
 			service.TaskTemplate.Placement.MaxReplicas = 0
96 96
 		}
97 97
 	}
98
+	if versions.LessThan(cliVersion, "1.41") {
99
+		if service.TaskTemplate.ContainerSpec != nil {
100
+			// Capabilities for docker swarm services weren't supported before
101
+			// API version 1.41
102
+			service.TaskTemplate.ContainerSpec.Capabilities = nil
103
+		}
104
+	}
98 105
 }
... ...
@@ -2882,6 +2882,18 @@ definitions:
2882 2882
             type: "object"
2883 2883
             additionalProperties:
2884 2884
               type: "string"
2885
+          # This option is not used by Windows containers
2886
+          Capabilities:
2887
+            type: "array"
2888
+            description: |
2889
+              A list of kernel capabilities to be available for container (this overrides the default set).
2890
+            items:
2891
+              type: "string"
2892
+            example:
2893
+              - "CAP_NET_RAW"
2894
+              - "CAP_SYS_ADMIN"
2895
+              - "CAP_SYS_CHROOT"
2896
+              - "CAP_SYSLOG"
2885 2897
       NetworkAttachmentSpec:
2886 2898
         description: |
2887 2899
           Read-only spec type for non-swarm containers attached to swarm overlay
... ...
@@ -67,10 +67,11 @@ type ContainerSpec struct {
67 67
 	// The format of extra hosts on swarmkit is specified in:
68 68
 	// http://man7.org/linux/man-pages/man5/hosts.5.html
69 69
 	//    IP_address canonical_hostname [aliases...]
70
-	Hosts     []string            `json:",omitempty"`
71
-	DNSConfig *DNSConfig          `json:",omitempty"`
72
-	Secrets   []*SecretReference  `json:",omitempty"`
73
-	Configs   []*ConfigReference  `json:",omitempty"`
74
-	Isolation container.Isolation `json:",omitempty"`
75
-	Sysctls   map[string]string   `json:",omitempty"`
70
+	Hosts        []string            `json:",omitempty"`
71
+	DNSConfig    *DNSConfig          `json:",omitempty"`
72
+	Secrets      []*SecretReference  `json:",omitempty"`
73
+	Configs      []*ConfigReference  `json:",omitempty"`
74
+	Isolation    container.Isolation `json:",omitempty"`
75
+	Sysctls      map[string]string   `json:",omitempty"`
76
+	Capabilities []string            `json:",omitempty"`
76 77
 }
... ...
@@ -18,25 +18,26 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) *types.ContainerSpec {
18 18
 		return nil
19 19
 	}
20 20
 	containerSpec := &types.ContainerSpec{
21
-		Image:      c.Image,
22
-		Labels:     c.Labels,
23
-		Command:    c.Command,
24
-		Args:       c.Args,
25
-		Hostname:   c.Hostname,
26
-		Env:        c.Env,
27
-		Dir:        c.Dir,
28
-		User:       c.User,
29
-		Groups:     c.Groups,
30
-		StopSignal: c.StopSignal,
31
-		TTY:        c.TTY,
32
-		OpenStdin:  c.OpenStdin,
33
-		ReadOnly:   c.ReadOnly,
34
-		Hosts:      c.Hosts,
35
-		Secrets:    secretReferencesFromGRPC(c.Secrets),
36
-		Configs:    configReferencesFromGRPC(c.Configs),
37
-		Isolation:  IsolationFromGRPC(c.Isolation),
38
-		Init:       initFromGRPC(c.Init),
39
-		Sysctls:    c.Sysctls,
21
+		Image:        c.Image,
22
+		Labels:       c.Labels,
23
+		Command:      c.Command,
24
+		Args:         c.Args,
25
+		Hostname:     c.Hostname,
26
+		Env:          c.Env,
27
+		Dir:          c.Dir,
28
+		User:         c.User,
29
+		Groups:       c.Groups,
30
+		StopSignal:   c.StopSignal,
31
+		TTY:          c.TTY,
32
+		OpenStdin:    c.OpenStdin,
33
+		ReadOnly:     c.ReadOnly,
34
+		Hosts:        c.Hosts,
35
+		Secrets:      secretReferencesFromGRPC(c.Secrets),
36
+		Configs:      configReferencesFromGRPC(c.Configs),
37
+		Isolation:    IsolationFromGRPC(c.Isolation),
38
+		Init:         initFromGRPC(c.Init),
39
+		Sysctls:      c.Sysctls,
40
+		Capabilities: c.Capabilities,
40 41
 	}
41 42
 
42 43
 	if c.DNSConfig != nil {
... ...
@@ -244,24 +245,25 @@ func configReferencesFromGRPC(sr []*swarmapi.ConfigReference) []*types.ConfigRef
244 244
 
245 245
 func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
246 246
 	containerSpec := &swarmapi.ContainerSpec{
247
-		Image:      c.Image,
248
-		Labels:     c.Labels,
249
-		Command:    c.Command,
250
-		Args:       c.Args,
251
-		Hostname:   c.Hostname,
252
-		Env:        c.Env,
253
-		Dir:        c.Dir,
254
-		User:       c.User,
255
-		Groups:     c.Groups,
256
-		StopSignal: c.StopSignal,
257
-		TTY:        c.TTY,
258
-		OpenStdin:  c.OpenStdin,
259
-		ReadOnly:   c.ReadOnly,
260
-		Hosts:      c.Hosts,
261
-		Secrets:    secretReferencesToGRPC(c.Secrets),
262
-		Isolation:  isolationToGRPC(c.Isolation),
263
-		Init:       initToGRPC(c.Init),
264
-		Sysctls:    c.Sysctls,
247
+		Image:        c.Image,
248
+		Labels:       c.Labels,
249
+		Command:      c.Command,
250
+		Args:         c.Args,
251
+		Hostname:     c.Hostname,
252
+		Env:          c.Env,
253
+		Dir:          c.Dir,
254
+		User:         c.User,
255
+		Groups:       c.Groups,
256
+		StopSignal:   c.StopSignal,
257
+		TTY:          c.TTY,
258
+		OpenStdin:    c.OpenStdin,
259
+		ReadOnly:     c.ReadOnly,
260
+		Hosts:        c.Hosts,
261
+		Secrets:      secretReferencesToGRPC(c.Secrets),
262
+		Isolation:    isolationToGRPC(c.Isolation),
263
+		Init:         initToGRPC(c.Init),
264
+		Sysctls:      c.Sysctls,
265
+		Capabilities: c.Capabilities,
265 266
 	}
266 267
 
267 268
 	if c.DNSConfig != nil {
... ...
@@ -362,6 +362,7 @@ func (c *containerConfig) hostConfig() *enginecontainer.HostConfig {
362 362
 		Isolation:      c.isolation(),
363 363
 		Init:           c.init(),
364 364
 		Sysctls:        c.spec().Sysctls,
365
+		Capabilities:   c.spec().Capabilities,
365 366
 	}
366 367
 
367 368
 	if c.spec().DNSConfig != nil {
... ...
@@ -17,6 +17,12 @@ keywords: "API, Docker, rcli, REST, documentation"
17 17
 
18 18
 [Docker Engine API v1.41](https://docs.docker.com/engine/api/v1.41/) documentation
19 19
 
20
+* `GET /services` now returns `Capabilities` as part of the `ContainerSpec`.
21
+* `GET /services/{id}` now returns `Capabilities` as part of the `ContainerSpec`.
22
+* `POST /services/create` now accepts `Capabilities` as part of the `ContainerSpec`.
23
+* `POST /services/{id}/update` now accepts `Capabilities` as part of the `ContainerSpec`.
24
+* `GET /tasks` now  returns `Capabilities` as part of the `ContainerSpec`.
25
+* `GET /tasks/{id}` now  returns `Capabilities` as part of the `ContainerSpec`.
20 26
 * `POST /containers/create` on Linux now accepts the `HostConfig.CgroupnsMode` property.
21 27
   Set the property to `host` to create the container in the daemon's cgroup namespace, or
22 28
   `private` to create the container in its own private cgroup namespace.  The per-daemon
... ...
@@ -180,6 +180,14 @@ func ServiceWithSysctls(sysctls map[string]string) ServiceSpecOpt {
180 180
 	}
181 181
 }
182 182
 
183
+// ServiceWithCapabilities sets the Capabilities option of the service's ContainerSpec.
184
+func ServiceWithCapabilities(Capabilities []string) ServiceSpecOpt {
185
+	return func(spec *swarmtypes.ServiceSpec) {
186
+		ensureContainerSpec(spec)
187
+		spec.TaskTemplate.ContainerSpec.Capabilities = Capabilities
188
+	}
189
+}
190
+
183 191
 // GetRunningTasks gets the list of running tasks for a service
184 192
 func GetRunningTasks(t *testing.T, c client.ServiceAPIClient, serviceID string) []swarmtypes.Task {
185 193
 	t.Helper()
... ...
@@ -440,3 +440,79 @@ func TestCreateServiceSysctls(t *testing.T) {
440 440
 		)
441 441
 	}
442 442
 }
443
+
444
+// TestServiceCreateCapabilities tests that a service created with capabilities options in
445
+// the ContainerSpec correctly applies those options.
446
+//
447
+// To test this, we're going to create a service with the capabilities option
448
+//
449
+//   []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"}
450
+//
451
+// We'll get the service's tasks to get the container ID, and then we'll
452
+// inspect the container. If the output of the container inspect contains the
453
+// capabilities option with the correct value, we can assume that the capabilities has been
454
+// plumbed correctly.
455
+func TestCreateServiceCapabilities(t *testing.T) {
456
+	skip.If(
457
+		t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.41"),
458
+		"setting service capabilities is unsupported before api v1.41",
459
+	)
460
+
461
+	defer setupTest(t)()
462
+	d := swarm.NewSwarm(t, testEnv)
463
+	defer d.Stop(t)
464
+	client := d.NewClientT(t)
465
+	defer client.Close()
466
+
467
+	ctx := context.Background()
468
+
469
+	// store the map we're going to be using everywhere.
470
+	expectedCapabilities := []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"}
471
+
472
+	// Create the service with the capabilities options
473
+	var instances uint64 = 1
474
+	serviceID := swarm.CreateService(t, d,
475
+		swarm.ServiceWithCapabilities(expectedCapabilities),
476
+	)
477
+
478
+	// wait for the service to converge to 1 running task as expected
479
+	poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, instances))
480
+
481
+	// we're going to check 3 things:
482
+	//
483
+	//   1. Does the container, when inspected, have the capabilities option set?
484
+	//   2. Does the task have the capabilities in the spec?
485
+	//   3. Does the service have the capabilities in the spec?
486
+	//
487
+	// if all 3 of these things are true, we know that the capabilities has been
488
+	// plumbed correctly through the engine.
489
+	//
490
+	// We don't actually have to get inside the container and check its
491
+	// logs or anything. If we see the capabilities set on the container inspect,
492
+	// we know that the capabilities is plumbed correctly. everything below that
493
+	// level has been tested elsewhere.
494
+
495
+	// get all of the tasks of the service, so we can get the container
496
+	filter := filters.NewArgs()
497
+	filter.Add("service", serviceID)
498
+	tasks, err := client.TaskList(ctx, types.TaskListOptions{
499
+		Filters: filter,
500
+	})
501
+	assert.NilError(t, err)
502
+	assert.Check(t, is.Equal(len(tasks), 1))
503
+
504
+	// verify that the container has the capabilities option set
505
+	ctnr, err := client.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID)
506
+	assert.NilError(t, err)
507
+	assert.DeepEqual(t, ctnr.HostConfig.Capabilities, expectedCapabilities)
508
+
509
+	// verify that the task has the capabilities option set in the task object
510
+	assert.DeepEqual(t, tasks[0].Spec.ContainerSpec.Capabilities, expectedCapabilities)
511
+
512
+	// verify that the service also has the capabilities set in the spec.
513
+	service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
514
+	assert.NilError(t, err)
515
+	assert.DeepEqual(t,
516
+		service.Spec.TaskTemplate.ContainerSpec.Capabilities, expectedCapabilities,
517
+	)
518
+}