Add support capabilities list on services
Signed-off-by: Olli Janatuinen <olli.janatuinen@gmail.com>
This commit is contained in:
parent
c7a0eaf004
commit
f787b235de
8 changed files with 156 additions and 44 deletions
|
@ -95,4 +95,11 @@ func adjustForAPIVersion(cliVersion string, service *swarm.ServiceSpec) {
|
|||
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"
|
||||
additionalProperties:
|
||||
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:
|
||||
description: |
|
||||
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:
|
||||
// http://man7.org/linux/man-pages/man5/hosts.5.html
|
||||
// IP_address canonical_hostname [aliases...]
|
||||
Hosts []string `json:",omitempty"`
|
||||
DNSConfig *DNSConfig `json:",omitempty"`
|
||||
Secrets []*SecretReference `json:",omitempty"`
|
||||
Configs []*ConfigReference `json:",omitempty"`
|
||||
Isolation container.Isolation `json:",omitempty"`
|
||||
Sysctls map[string]string `json:",omitempty"`
|
||||
Hosts []string `json:",omitempty"`
|
||||
DNSConfig *DNSConfig `json:",omitempty"`
|
||||
Secrets []*SecretReference `json:",omitempty"`
|
||||
Configs []*ConfigReference `json:",omitempty"`
|
||||
Isolation container.Isolation `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
|
||||
}
|
||||
containerSpec := &types.ContainerSpec{
|
||||
Image: c.Image,
|
||||
Labels: c.Labels,
|
||||
Command: c.Command,
|
||||
Args: c.Args,
|
||||
Hostname: c.Hostname,
|
||||
Env: c.Env,
|
||||
Dir: c.Dir,
|
||||
User: c.User,
|
||||
Groups: c.Groups,
|
||||
StopSignal: c.StopSignal,
|
||||
TTY: c.TTY,
|
||||
OpenStdin: c.OpenStdin,
|
||||
ReadOnly: c.ReadOnly,
|
||||
Hosts: c.Hosts,
|
||||
Secrets: secretReferencesFromGRPC(c.Secrets),
|
||||
Configs: configReferencesFromGRPC(c.Configs),
|
||||
Isolation: IsolationFromGRPC(c.Isolation),
|
||||
Init: initFromGRPC(c.Init),
|
||||
Sysctls: c.Sysctls,
|
||||
Image: c.Image,
|
||||
Labels: c.Labels,
|
||||
Command: c.Command,
|
||||
Args: c.Args,
|
||||
Hostname: c.Hostname,
|
||||
Env: c.Env,
|
||||
Dir: c.Dir,
|
||||
User: c.User,
|
||||
Groups: c.Groups,
|
||||
StopSignal: c.StopSignal,
|
||||
TTY: c.TTY,
|
||||
OpenStdin: c.OpenStdin,
|
||||
ReadOnly: c.ReadOnly,
|
||||
Hosts: c.Hosts,
|
||||
Secrets: secretReferencesFromGRPC(c.Secrets),
|
||||
Configs: configReferencesFromGRPC(c.Configs),
|
||||
Isolation: IsolationFromGRPC(c.Isolation),
|
||||
Init: initFromGRPC(c.Init),
|
||||
Sysctls: c.Sysctls,
|
||||
Capabilities: c.Capabilities,
|
||||
}
|
||||
|
||||
if c.DNSConfig != nil {
|
||||
|
@ -244,24 +245,25 @@ func configReferencesFromGRPC(sr []*swarmapi.ConfigReference) []*types.ConfigRef
|
|||
|
||||
func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
|
||||
containerSpec := &swarmapi.ContainerSpec{
|
||||
Image: c.Image,
|
||||
Labels: c.Labels,
|
||||
Command: c.Command,
|
||||
Args: c.Args,
|
||||
Hostname: c.Hostname,
|
||||
Env: c.Env,
|
||||
Dir: c.Dir,
|
||||
User: c.User,
|
||||
Groups: c.Groups,
|
||||
StopSignal: c.StopSignal,
|
||||
TTY: c.TTY,
|
||||
OpenStdin: c.OpenStdin,
|
||||
ReadOnly: c.ReadOnly,
|
||||
Hosts: c.Hosts,
|
||||
Secrets: secretReferencesToGRPC(c.Secrets),
|
||||
Isolation: isolationToGRPC(c.Isolation),
|
||||
Init: initToGRPC(c.Init),
|
||||
Sysctls: c.Sysctls,
|
||||
Image: c.Image,
|
||||
Labels: c.Labels,
|
||||
Command: c.Command,
|
||||
Args: c.Args,
|
||||
Hostname: c.Hostname,
|
||||
Env: c.Env,
|
||||
Dir: c.Dir,
|
||||
User: c.User,
|
||||
Groups: c.Groups,
|
||||
StopSignal: c.StopSignal,
|
||||
TTY: c.TTY,
|
||||
OpenStdin: c.OpenStdin,
|
||||
ReadOnly: c.ReadOnly,
|
||||
Hosts: c.Hosts,
|
||||
Secrets: secretReferencesToGRPC(c.Secrets),
|
||||
Isolation: isolationToGRPC(c.Isolation),
|
||||
Init: initToGRPC(c.Init),
|
||||
Sysctls: c.Sysctls,
|
||||
Capabilities: c.Capabilities,
|
||||
}
|
||||
|
||||
if c.DNSConfig != nil {
|
||||
|
|
|
@ -362,6 +362,7 @@ func (c *containerConfig) hostConfig() *enginecontainer.HostConfig {
|
|||
Isolation: c.isolation(),
|
||||
Init: c.init(),
|
||||
Sysctls: c.spec().Sysctls,
|
||||
Capabilities: c.spec().Capabilities,
|
||||
}
|
||||
|
||||
if c.spec().DNSConfig != nil {
|
||||
|
|
|
@ -17,13 +17,18 @@ keywords: "API, Docker, rcli, REST, 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.
|
||||
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
|
||||
default is `host`, and can be changed by using the`CgroupNamespaceMode` daemon configuration
|
||||
parameter.
|
||||
|
||||
|
||||
## v1.40 API changes
|
||||
|
||||
[Docker Engine API v1.40](https://docs.docker.com/engine/api/v1.40/) documentation
|
||||
|
|
|
@ -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
|
||||
func GetRunningTasks(t *testing.T, c client.ServiceAPIClient, serviceID string) []swarmtypes.Task {
|
||||
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