1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

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

Add support for capabilities options in services
This commit is contained in:
Kirill Kolyshkin 2019-06-06 15:03:46 -07:00 committed by GitHub
commit 1d5748d975
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 156 additions and 43 deletions

View file

@ -95,4 +95,11 @@ func adjustForAPIVersion(cliVersion string, service *swarm.ServiceSpec) {
service.TaskTemplate.Placement.MaxReplicas = 0 service.TaskTemplate.Placement.MaxReplicas = 0
} }
} }
if versions.LessThan(cliVersion, "1.41") {
if service.TaskTemplate.ContainerSpec != nil {
// Capabilities for docker swarm services weren't supported before
// API version 1.41
service.TaskTemplate.ContainerSpec.Capabilities = nil
}
}
} }

View file

@ -2882,6 +2882,18 @@ definitions:
type: "object" type: "object"
additionalProperties: additionalProperties:
type: "string" type: "string"
# This option is not used by Windows containers
Capabilities:
type: "array"
description: |
A list of kernel capabilities to be available for container (this overrides the default set).
items:
type: "string"
example:
- "CAP_NET_RAW"
- "CAP_SYS_ADMIN"
- "CAP_SYS_CHROOT"
- "CAP_SYSLOG"
NetworkAttachmentSpec: NetworkAttachmentSpec:
description: | description: |
Read-only spec type for non-swarm containers attached to swarm overlay Read-only spec type for non-swarm containers attached to swarm overlay

View file

@ -67,10 +67,11 @@ type ContainerSpec struct {
// The format of extra hosts on swarmkit is specified in: // The format of extra hosts on swarmkit is specified in:
// http://man7.org/linux/man-pages/man5/hosts.5.html // http://man7.org/linux/man-pages/man5/hosts.5.html
// IP_address canonical_hostname [aliases...] // IP_address canonical_hostname [aliases...]
Hosts []string `json:",omitempty"` Hosts []string `json:",omitempty"`
DNSConfig *DNSConfig `json:",omitempty"` DNSConfig *DNSConfig `json:",omitempty"`
Secrets []*SecretReference `json:",omitempty"` Secrets []*SecretReference `json:",omitempty"`
Configs []*ConfigReference `json:",omitempty"` Configs []*ConfigReference `json:",omitempty"`
Isolation container.Isolation `json:",omitempty"` Isolation container.Isolation `json:",omitempty"`
Sysctls map[string]string `json:",omitempty"` Sysctls map[string]string `json:",omitempty"`
Capabilities []string `json:",omitempty"`
} }

View file

@ -18,25 +18,26 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) *types.ContainerSpec {
return nil return nil
} }
containerSpec := &types.ContainerSpec{ containerSpec := &types.ContainerSpec{
Image: c.Image, Image: c.Image,
Labels: c.Labels, Labels: c.Labels,
Command: c.Command, Command: c.Command,
Args: c.Args, Args: c.Args,
Hostname: c.Hostname, Hostname: c.Hostname,
Env: c.Env, Env: c.Env,
Dir: c.Dir, Dir: c.Dir,
User: c.User, User: c.User,
Groups: c.Groups, Groups: c.Groups,
StopSignal: c.StopSignal, StopSignal: c.StopSignal,
TTY: c.TTY, TTY: c.TTY,
OpenStdin: c.OpenStdin, OpenStdin: c.OpenStdin,
ReadOnly: c.ReadOnly, ReadOnly: c.ReadOnly,
Hosts: c.Hosts, Hosts: c.Hosts,
Secrets: secretReferencesFromGRPC(c.Secrets), Secrets: secretReferencesFromGRPC(c.Secrets),
Configs: configReferencesFromGRPC(c.Configs), Configs: configReferencesFromGRPC(c.Configs),
Isolation: IsolationFromGRPC(c.Isolation), Isolation: IsolationFromGRPC(c.Isolation),
Init: initFromGRPC(c.Init), Init: initFromGRPC(c.Init),
Sysctls: c.Sysctls, Sysctls: c.Sysctls,
Capabilities: c.Capabilities,
} }
if c.DNSConfig != nil { if c.DNSConfig != nil {
@ -244,24 +245,25 @@ func configReferencesFromGRPC(sr []*swarmapi.ConfigReference) []*types.ConfigRef
func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) { func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
containerSpec := &swarmapi.ContainerSpec{ containerSpec := &swarmapi.ContainerSpec{
Image: c.Image, Image: c.Image,
Labels: c.Labels, Labels: c.Labels,
Command: c.Command, Command: c.Command,
Args: c.Args, Args: c.Args,
Hostname: c.Hostname, Hostname: c.Hostname,
Env: c.Env, Env: c.Env,
Dir: c.Dir, Dir: c.Dir,
User: c.User, User: c.User,
Groups: c.Groups, Groups: c.Groups,
StopSignal: c.StopSignal, StopSignal: c.StopSignal,
TTY: c.TTY, TTY: c.TTY,
OpenStdin: c.OpenStdin, OpenStdin: c.OpenStdin,
ReadOnly: c.ReadOnly, ReadOnly: c.ReadOnly,
Hosts: c.Hosts, Hosts: c.Hosts,
Secrets: secretReferencesToGRPC(c.Secrets), Secrets: secretReferencesToGRPC(c.Secrets),
Isolation: isolationToGRPC(c.Isolation), Isolation: isolationToGRPC(c.Isolation),
Init: initToGRPC(c.Init), Init: initToGRPC(c.Init),
Sysctls: c.Sysctls, Sysctls: c.Sysctls,
Capabilities: c.Capabilities,
} }
if c.DNSConfig != nil { if c.DNSConfig != nil {

View file

@ -362,6 +362,7 @@ func (c *containerConfig) hostConfig() *enginecontainer.HostConfig {
Isolation: c.isolation(), Isolation: c.isolation(),
Init: c.init(), Init: c.init(),
Sysctls: c.spec().Sysctls, Sysctls: c.spec().Sysctls,
Capabilities: c.spec().Capabilities,
} }
if c.spec().DNSConfig != nil { if c.spec().DNSConfig != nil {

View file

@ -17,6 +17,12 @@ keywords: "API, Docker, rcli, REST, documentation"
[Docker Engine API v1.41](https://docs.docker.com/engine/api/v1.41/) documentation [Docker Engine API v1.41](https://docs.docker.com/engine/api/v1.41/) documentation
* `GET /services` now returns `Capabilities` as part of the `ContainerSpec`.
* `GET /services/{id}` now returns `Capabilities` as part of the `ContainerSpec`.
* `POST /services/create` now accepts `Capabilities` as part of the `ContainerSpec`.
* `POST /services/{id}/update` now accepts `Capabilities` as part of the `ContainerSpec`.
* `GET /tasks` now returns `Capabilities` as part of the `ContainerSpec`.
* `GET /tasks/{id}` now returns `Capabilities` as part of the `ContainerSpec`.
* `POST /containers/create` on Linux now accepts the `HostConfig.CgroupnsMode` property. * `POST /containers/create` on Linux now accepts the `HostConfig.CgroupnsMode` property.
Set the property to `host` to create the container in the daemon's cgroup namespace, or Set the property to `host` to create the container in the daemon's cgroup namespace, or
`private` to create the container in its own private cgroup namespace. The per-daemon `private` to create the container in its own private cgroup namespace. The per-daemon

View file

@ -180,6 +180,14 @@ func ServiceWithSysctls(sysctls map[string]string) ServiceSpecOpt {
} }
} }
// ServiceWithCapabilities sets the Capabilities option of the service's ContainerSpec.
func ServiceWithCapabilities(Capabilities []string) ServiceSpecOpt {
return func(spec *swarmtypes.ServiceSpec) {
ensureContainerSpec(spec)
spec.TaskTemplate.ContainerSpec.Capabilities = Capabilities
}
}
// GetRunningTasks gets the list of running tasks for a service // GetRunningTasks gets the list of running tasks for a service
func GetRunningTasks(t *testing.T, c client.ServiceAPIClient, serviceID string) []swarmtypes.Task { func GetRunningTasks(t *testing.T, c client.ServiceAPIClient, serviceID string) []swarmtypes.Task {
t.Helper() t.Helper()

View file

@ -440,3 +440,79 @@ func TestCreateServiceSysctls(t *testing.T) {
) )
} }
} }
// TestServiceCreateCapabilities tests that a service created with capabilities options in
// the ContainerSpec correctly applies those options.
//
// To test this, we're going to create a service with the capabilities option
//
// []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"}
//
// We'll get the service's tasks to get the container ID, and then we'll
// inspect the container. If the output of the container inspect contains the
// capabilities option with the correct value, we can assume that the capabilities has been
// plumbed correctly.
func TestCreateServiceCapabilities(t *testing.T) {
skip.If(
t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.41"),
"setting service capabilities is unsupported before api v1.41",
)
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
ctx := context.Background()
// store the map we're going to be using everywhere.
expectedCapabilities := []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"}
// Create the service with the capabilities options
var instances uint64 = 1
serviceID := swarm.CreateService(t, d,
swarm.ServiceWithCapabilities(expectedCapabilities),
)
// wait for the service to converge to 1 running task as expected
poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, instances))
// we're going to check 3 things:
//
// 1. Does the container, when inspected, have the capabilities option set?
// 2. Does the task have the capabilities in the spec?
// 3. Does the service have the capabilities in the spec?
//
// if all 3 of these things are true, we know that the capabilities has been
// plumbed correctly through the engine.
//
// We don't actually have to get inside the container and check its
// logs or anything. If we see the capabilities set on the container inspect,
// we know that the capabilities is plumbed correctly. everything below that
// level has been tested elsewhere.
// get all of the tasks of the service, so we can get the container
filter := filters.NewArgs()
filter.Add("service", serviceID)
tasks, err := client.TaskList(ctx, types.TaskListOptions{
Filters: filter,
})
assert.NilError(t, err)
assert.Check(t, is.Equal(len(tasks), 1))
// verify that the container has the capabilities option set
ctnr, err := client.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID)
assert.NilError(t, err)
assert.DeepEqual(t, ctnr.HostConfig.Capabilities, expectedCapabilities)
// verify that the task has the capabilities option set in the task object
assert.DeepEqual(t, tasks[0].Spec.ContainerSpec.Capabilities, expectedCapabilities)
// verify that the service also has the capabilities set in the spec.
service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
assert.NilError(t, err)
assert.DeepEqual(t,
service.Spec.TaskTemplate.ContainerSpec.Capabilities, expectedCapabilities,
)
}