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:
commit
1d5748d975
8 changed files with 156 additions and 43 deletions
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue