DockerCLI/opts/capabilities.go

79 lines
2.2 KiB
Go
Raw Normal View History

Service cap-add/cap-drop: improve handling of combinations and special "ALL" value When creating and updating services, we need to avoid unneeded service churn. The interaction of separate lists to "add" and "drop" capabilities, a special ("ALL") capability, as well as a "relaxed" format for accepted capabilities (case-insensitive, `CAP_` prefix optional) make this rather involved. This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as well as _updating_, with the following rules/assumptions applied: - both existing (service spec) and new (values passed through flags or in the compose-file) are normalized and de-duplicated before use. - the special "ALL" capability is equivalent to "all capabilities" and taken into account when normalizing capabilities. Combining "ALL" capabilities and other capabilities is therefore equivalent to just specifying "ALL". - adding capabilities takes precedence over dropping, which means that if a capability is both set to be "dropped" and to be "added", it is removed from the list to "drop". - the final lists should be sorted and normalized to reduce service churn - no validation of capabilities is handled by the client. Validation is delegated to the daemon/server. When deploying a service using a docker-compose file, the docker-compose file is *mostly* handled as being "declarative". However, many of the issues outlined above also apply to compose-files, so similar handling is applied to compose files as well to prevent service churn. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
package opts
import (
"sort"
"strings"
)
const (
// AllCapabilities is a special value to add or drop all capabilities
AllCapabilities = "ALL"
)
// NormalizeCapability normalizes a capability by upper-casing, trimming white space
// and adding a CAP_ prefix (if not yet present). This function also accepts the
// "ALL" magic-value, as used by CapAdd/CapDrop.
//
// This function only handles rudimentary formatting; no validation is performed,
// as the list of available capabilities can be updated over time, thus should be
// handled by the daemon.
func NormalizeCapability(cap string) string {
cap = strings.ToUpper(strings.TrimSpace(cap))
if cap == AllCapabilities {
return cap
}
if !strings.HasPrefix(cap, "CAP_") {
cap = "CAP_" + cap
}
return cap
}
// CapabilitiesMap normalizes the given capabilities and converts them to a map.
func CapabilitiesMap(caps []string) map[string]bool {
normalized := make(map[string]bool)
for _, c := range caps {
normalized[NormalizeCapability(c)] = true
}
return normalized
}
// EffectiveCapAddCapDrop normalizes and sorts capabilities to "add" and "drop",
// and returns the effective capabilities to include in both.
//
// "CapAdd" takes precedence over "CapDrop", so capabilities included in both
// lists are removed from the list of capabilities to drop. The special "ALL"
// capability is also taken into account.
//
// Duplicates are removed, and the resulting lists are sorted.
func EffectiveCapAddCapDrop(add, drop []string) (capAdd, capDrop []string) {
var (
addCaps = CapabilitiesMap(add)
dropCaps = CapabilitiesMap(drop)
)
if addCaps[AllCapabilities] {
// Special case: "ALL capabilities" trumps any other capability added.
addCaps = map[string]bool{AllCapabilities: true}
}
if dropCaps[AllCapabilities] {
// Special case: "ALL capabilities" trumps any other capability added.
dropCaps = map[string]bool{AllCapabilities: true}
}
for c := range dropCaps {
if addCaps[c] {
// Adding a capability takes precedence, so skip dropping
continue
}
capDrop = append(capDrop, c)
}
for c := range addCaps {
capAdd = append(capAdd, c)
}
sort.Strings(capAdd)
sort.Strings(capDrop)
return capAdd, capDrop
}