Skip to content

Commit

Permalink
Merge pull request #2687 from thaJeztah/carry_service_caps
Browse files Browse the repository at this point in the history
[carry 2663] Add capabilities support to stack/service commands
  • Loading branch information
cpuguy83 authored Sep 8, 2020
2 parents 551ac13 + 9503729 commit 1648029
Show file tree
Hide file tree
Showing 13 changed files with 636 additions and 2 deletions.
29 changes: 29 additions & 0 deletions cli/command/service/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ ContainerSpec:
{{- if .ContainerUser }}
User: {{ .ContainerUser }}
{{- end }}
{{- if .HasCapabilities }}
Capabilities:
{{- if .HasCapabilityAdd }}
Add: {{ .CapabilityAdd }}
{{- end }}
{{- if .HasCapabilityDrop }}
Drop: {{ .CapabilityDrop }}
{{- end }}
{{- end }}
{{- if .ContainerSysCtls }}
SysCtls:
{{- range $k, $v := .ContainerSysCtls }}
Expand Down Expand Up @@ -532,6 +541,26 @@ func (ctx *serviceInspectContext) Ports() []swarm.PortConfig {
return ctx.Service.Endpoint.Ports
}

func (ctx *serviceInspectContext) HasCapabilities() bool {
return len(ctx.Service.Spec.TaskTemplate.ContainerSpec.CapabilityAdd) > 0 || len(ctx.Service.Spec.TaskTemplate.ContainerSpec.CapabilityDrop) > 0
}

func (ctx *serviceInspectContext) HasCapabilityAdd() bool {
return len(ctx.Service.Spec.TaskTemplate.ContainerSpec.CapabilityAdd) > 0
}

func (ctx *serviceInspectContext) HasCapabilityDrop() bool {
return len(ctx.Service.Spec.TaskTemplate.ContainerSpec.CapabilityDrop) > 0
}

func (ctx *serviceInspectContext) CapabilityAdd() string {
return strings.Join(ctx.Service.Spec.TaskTemplate.ContainerSpec.CapabilityAdd, ", ")
}

func (ctx *serviceInspectContext) CapabilityDrop() string {
return strings.Join(ctx.Service.Spec.TaskTemplate.ContainerSpec.CapabilityDrop, ", ")
}

const (
defaultServiceTableFormat = "table {{.ID}}\t{{.Name}}\t{{.Mode}}\t{{.Replicas}}\t{{.Image}}\t{{.Ports}}"

Expand Down
14 changes: 14 additions & 0 deletions cli/command/service/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,8 @@ type serviceOptions struct {
dnsOption opts.ListOpts
hosts opts.ListOpts
sysctls opts.ListOpts
capAdd opts.ListOpts
capDrop opts.ListOpts

resources resourceOptions
stopGrace opts.DurationOpt
Expand Down Expand Up @@ -549,6 +551,8 @@ func newServiceOptions() *serviceOptions {
dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch),
hosts: opts.NewListOpts(opts.ValidateExtraHost),
sysctls: opts.NewListOpts(nil),
capAdd: opts.NewListOpts(nil),
capDrop: opts.NewListOpts(nil),
}
}

Expand Down Expand Up @@ -685,6 +689,8 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N
return service, err
}

capAdd, capDrop := opts.EffectiveCapAddCapDrop(options.capAdd.GetAll(), options.capDrop.GetAll())

service = swarm.ServiceSpec{
Annotations: swarm.Annotations{
Name: options.name,
Expand Down Expand Up @@ -716,6 +722,8 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N
Healthcheck: healthConfig,
Isolation: container.Isolation(options.isolation),
Sysctls: opts.ConvertKVStringsToMap(options.sysctls.GetAll()),
CapabilityAdd: capAdd,
CapabilityDrop: capDrop,
},
Networks: networks,
Resources: resources,
Expand Down Expand Up @@ -818,6 +826,10 @@ func addServiceFlags(flags *pflag.FlagSet, opts *serviceOptions, defaultFlagValu
flags.StringVar(&opts.hostname, flagHostname, "", "Container hostname")
flags.SetAnnotation(flagHostname, "version", []string{"1.25"})
flags.Var(&opts.entrypoint, flagEntrypoint, "Overwrite the default ENTRYPOINT of the image")
flags.Var(&opts.capAdd, flagCapAdd, "Add Linux capabilities")
flags.SetAnnotation(flagCapAdd, "version", []string{"1.41"})
flags.Var(&opts.capDrop, flagCapDrop, "Drop Linux capabilities")
flags.SetAnnotation(flagCapDrop, "version", []string{"1.41"})

flags.Var(&opts.resources.limitCPU, flagLimitCPU, "Limit CPUs")
flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "Limit Memory")
Expand Down Expand Up @@ -1001,6 +1013,8 @@ const (
flagConfigAdd = "config-add"
flagConfigRemove = "config-rm"
flagIsolation = "isolation"
flagCapAdd = "cap-add"
flagCapDrop = "cap-drop"
)

func validateAPIVersion(c swarm.ServiceSpec, serverAPIVersion string) error {
Expand Down
124 changes: 124 additions & 0 deletions cli/command/service/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,10 @@ func updateService(ctx context.Context, apiClient client.NetworkAPIClient, flags

updateString(flagStopSignal, &cspec.StopSignal)

if anyChanged(flags, flagCapAdd, flagCapDrop) {
updateCapabilities(flags, cspec)
}

return nil
}

Expand Down Expand Up @@ -1349,3 +1353,123 @@ func updateCredSpecConfig(flags *pflag.FlagSet, containerSpec *swarm.ContainerSp
containerSpec.Privileges.CredentialSpec = credSpec
}
}

// updateCapabilities calculates the list of capabilities to "drop" and to "add"
// after applying the capabilities passed through `--cap-add` and `--cap-drop`
// to the existing list of added/dropped capabilities in the service spec.
//
// Adding capabilities takes precedence over "dropping" the same capability, so
// if both `--cap-add` and `--cap-drop` are specifying the same capability, the
// `--cap-drop` is ignored.
//
// Capabilities to "drop" are removed from the existing list of "added"
// capabilities, and vice-versa (capabilities to "add" are removed from the existing
// list of capabilities to "drop").
//
// Capabilities are normalized, sorted, and duplicates are removed to prevent
// service tasks from being updated if no changes are made. If a list has the "ALL"
// capability set, then any other capability is removed from that list.
//
// Adding/removing capabilities when updating a service is handled as a tri-state;
//
// - if the capability was previously "dropped", then remove it from "CapabilityDrop",
// but NOT added to "CapabilityAdd". However, if the capability was not yet in
// the service's "CapabilityDrop", then it's simply added to the service's "CapabilityAdd"
// - likewise, if the capability was previously "added", then it's removed from
// "CapabilityAdd", but NOT added to "CapabilityDrop". If the capability was
// not yet in the service's "CapabilityAdd", then simply add it to the service's
// "CapabilityDrop".
//
// In other words, given a service with the following:
//
// | CapDrop | CapAdd |
// | -------------- | ------------- |
// | CAP_SOME_CAP | |
//
// When updating the service, and applying `--cap-add CAP_SOME_CAP`, the previously
// dropped capability is removed:
//
// | CapDrop | CapAdd |
// | -------------- | ------------- |
// | | |
//
// After updating the service a second time, applying `--cap-add CAP_SOME_CAP`,
// capability is now added:
//
// | CapDrop | CapAdd |
// | -------------- | ------------- |
// | | CAP_SOME_CAP |
//
func updateCapabilities(flags *pflag.FlagSet, containerSpec *swarm.ContainerSpec) {
var (
toAdd, toDrop map[string]bool

capDrop = opts.CapabilitiesMap(containerSpec.CapabilityDrop)
capAdd = opts.CapabilitiesMap(containerSpec.CapabilityAdd)
)
if flags.Changed(flagCapAdd) {
toAdd = opts.CapabilitiesMap(flags.Lookup(flagCapAdd).Value.(*opts.ListOpts).GetAll())
}
if flags.Changed(flagCapDrop) {
toDrop = opts.CapabilitiesMap(flags.Lookup(flagCapDrop).Value.(*opts.ListOpts).GetAll())
}

// First remove the capabilities to "drop" from the service's exiting
// list of capabilities to "add". If a capability is both added and dropped
// on update, then "adding" takes precedence.
//
// Dropping a capability when updating a service is considered a tri-state;
//
// - if the capability was previously "added", then remove it from
// "CapabilityAdd", and do NOT add it to "CapabilityDrop"
// - if the capability was not yet in the service's "CapabilityAdd",
// then simply add it to the service's "CapabilityDrop"
for c := range toDrop {
if !toAdd[c] {
if capAdd[c] {
delete(capAdd, c)
} else {
capDrop[c] = true
}
}
}

// And remove the capabilities we're "adding" from the service's existing
// list of capabilities to "drop".
//
// "Adding" capabilities takes precedence over "dropping" them, so if a
// capability is set both as "add" and "drop", remove the capability from
// the service's list of dropped capabilities (if present).
//
// Adding a capability when updating a service is considered a tri-state;
//
// - if the capability was previously "dropped", then remove it from
// "CapabilityDrop", and do NOT add it to "CapabilityAdd"
// - if the capability was not yet in the service's "CapabilityDrop",
// then simply add it to the service's "CapabilityAdd"
for c := range toAdd {
if capDrop[c] {
delete(capDrop, c)
} else {
capAdd[c] = true
}
}

// Now that the service's existing lists are updated, apply the new
// capabilities to add/drop to both lists. Sort the lists to prevent
// unneeded updates to service-tasks.
containerSpec.CapabilityDrop = capsList(capDrop)
containerSpec.CapabilityAdd = capsList(capAdd)
}

func capsList(caps map[string]bool) []string {
if caps[opts.AllCapabilities] {
return []string{opts.AllCapabilities}
}
var out []string
for c := range caps {
out = append(out, c)
}
sort.Strings(out)
return out
}
Loading

0 comments on commit 1648029

Please sign in to comment.