Skip to content

Commit 1d5748d

Browse files
authored
Merge pull request moby#39173 from olljanat/25885-capabilities-swarm
Add support for capabilities options in services
2 parents cf406eb + f787b23 commit 1d5748d

8 files changed

Lines changed: 156 additions & 43 deletions

File tree

api/server/router/swarm/helpers.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,11 @@ func adjustForAPIVersion(cliVersion string, service *swarm.ServiceSpec) {
9595
service.TaskTemplate.Placement.MaxReplicas = 0
9696
}
9797
}
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+
}
98105
}

api/swagger.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2882,6 +2882,18 @@ definitions:
28822882
type: "object"
28832883
additionalProperties:
28842884
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"
28852897
NetworkAttachmentSpec:
28862898
description: |
28872899
Read-only spec type for non-swarm containers attached to swarm overlay

api/types/swarm/container.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,11 @@ type ContainerSpec struct {
6767
// The format of extra hosts on swarmkit is specified in:
6868
// http://man7.org/linux/man-pages/man5/hosts.5.html
6969
// 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"`
7677
}

daemon/cluster/convert/container.go

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,26 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) *types.ContainerSpec {
1818
return nil
1919
}
2020
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,
4041
}
4142

4243
if c.DNSConfig != nil {
@@ -244,24 +245,25 @@ func configReferencesFromGRPC(sr []*swarmapi.ConfigReference) []*types.ConfigRef
244245

245246
func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
246247
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,
248+
Image: c.Image,
249+
Labels: c.Labels,
250+
Command: c.Command,
251+
Args: c.Args,
252+
Hostname: c.Hostname,
253+
Env: c.Env,
254+
Dir: c.Dir,
255+
User: c.User,
256+
Groups: c.Groups,
257+
StopSignal: c.StopSignal,
258+
TTY: c.TTY,
259+
OpenStdin: c.OpenStdin,
260+
ReadOnly: c.ReadOnly,
261+
Hosts: c.Hosts,
262+
Secrets: secretReferencesToGRPC(c.Secrets),
263+
Isolation: isolationToGRPC(c.Isolation),
264+
Init: initToGRPC(c.Init),
265+
Sysctls: c.Sysctls,
266+
Capabilities: c.Capabilities,
265267
}
266268

267269
if c.DNSConfig != nil {

daemon/cluster/executor/container/container.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ func (c *containerConfig) hostConfig() *enginecontainer.HostConfig {
362362
Isolation: c.isolation(),
363363
Init: c.init(),
364364
Sysctls: c.spec().Sysctls,
365+
Capabilities: c.spec().Capabilities,
365366
}
366367

367368
if c.spec().DNSConfig != nil {

docs/api/version-history.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ keywords: "API, Docker, rcli, REST, documentation"
1717

1818
[Docker Engine API v1.41](https://docs.docker.com/engine/api/v1.41/) documentation
1919

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`.
2026
* `POST /containers/create` on Linux now accepts the `HostConfig.CgroupnsMode` property.
2127
Set the property to `host` to create the container in the daemon's cgroup namespace, or
2228
`private` to create the container in its own private cgroup namespace. The per-daemon

integration/internal/swarm/service.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,14 @@ func ServiceWithSysctls(sysctls map[string]string) ServiceSpecOpt {
180180
}
181181
}
182182

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+
183191
// GetRunningTasks gets the list of running tasks for a service
184192
func GetRunningTasks(t *testing.T, c client.ServiceAPIClient, serviceID string) []swarmtypes.Task {
185193
t.Helper()

integration/service/create_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,3 +440,79 @@ func TestCreateServiceSysctls(t *testing.T) {
440440
)
441441
}
442442
}
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+
}

0 commit comments

Comments
 (0)