From 52ac568385c4fb81a3d06e728903d9fb6adb99c3 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 26 Sep 2024 13:35:46 +0200 Subject: [PATCH 01/15] cli/container: use github.com/moby/sys/capability for completions We used a hard-coded list of capabilities that we copied from containerd, but the new "capability" package allows use to have a maintained list of capabilities. There's likely still some improvements to be made; First of all, the capability package could provide a function to get the list of strings. On the completion-side, we need to consider what format is most convenient; currently we use the canonical name (uppercase and "CAP_" prefix), however, tab-completion is case-sensitive by default, so requires the user to type uppercase letters to filter the list of options. Bash completion provides a `completion-ignore-case on` option to make completion case-insensitive (https://askubuntu.com/a/87066), but it looks to be a global option; the current cobra.CompletionOptions also don't provide this as an option to be used in the generated completion-script. Fish completion has `smartcase` (by default?) which matches any case if all of the input is lowercase. Zsh does not have a dedicated option, but allows setting matching-rules (see https://superuser.com/a/1092328). Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 462e08219d91b6695d2dc814eadd97c544051914) Signed-off-by: Sebastiaan van Stijn --- cli/command/container/completion.go | 90 ++- cli/command/container/completion_test.go | 21 + vendor.mod | 1 + vendor.sum | 2 + .../moby/sys/capability/.codespellrc | 3 + .../moby/sys/capability/.golangci.yml | 6 + .../moby/sys/capability/CHANGELOG.md | 90 +++ vendor/github.com/moby/sys/capability/LICENSE | 25 + .../github.com/moby/sys/capability/README.md | 13 + .../moby/sys/capability/capability.go | 144 +++++ .../moby/sys/capability/capability_linux.go | 541 ++++++++++++++++++ .../moby/sys/capability/capability_noop.go | 26 + vendor/github.com/moby/sys/capability/enum.go | 330 +++++++++++ .../moby/sys/capability/enum_gen.go | 137 +++++ .../moby/sys/capability/syscall_linux.go | 153 +++++ vendor/modules.txt | 3 + 16 files changed, 1529 insertions(+), 56 deletions(-) create mode 100644 cli/command/container/completion_test.go create mode 100644 vendor/github.com/moby/sys/capability/.codespellrc create mode 100644 vendor/github.com/moby/sys/capability/.golangci.yml create mode 100644 vendor/github.com/moby/sys/capability/CHANGELOG.md create mode 100644 vendor/github.com/moby/sys/capability/LICENSE create mode 100644 vendor/github.com/moby/sys/capability/README.md create mode 100644 vendor/github.com/moby/sys/capability/capability.go create mode 100644 vendor/github.com/moby/sys/capability/capability_linux.go create mode 100644 vendor/github.com/moby/sys/capability/capability_noop.go create mode 100644 vendor/github.com/moby/sys/capability/enum.go create mode 100644 vendor/github.com/moby/sys/capability/enum_gen.go create mode 100644 vendor/github.com/moby/sys/capability/syscall_linux.go diff --git a/cli/command/container/completion.go b/cli/command/container/completion.go index 24c046a8292e..e6ca17308aee 100644 --- a/cli/command/container/completion.go +++ b/cli/command/container/completion.go @@ -1,70 +1,48 @@ package container import ( + "strings" + "sync" + "github.com/docker/cli/cli/command/completion" "github.com/docker/docker/api/types/container" + "github.com/moby/sys/capability" "github.com/moby/sys/signal" "github.com/spf13/cobra" ) +// allCaps is the magic value for "all capabilities". +const allCaps = "ALL" + // allLinuxCapabilities is a list of all known Linux capabilities. // -// This list was based on the containerd pkg/cap package; -// https://github.com/containerd/containerd/blob/v1.7.19/pkg/cap/cap_linux.go#L133-L181 -// // TODO(thaJeztah): add descriptions, and enable descriptions for our completion scripts (cobra.CompletionOptions.DisableDescriptions is currently set to "true") -var allLinuxCapabilities = []string{ - "ALL", // magic value for "all capabilities" - - // caps35 is the caps of kernel 3.5 (37 entries) - "CAP_CHOWN", // 2.2 - "CAP_DAC_OVERRIDE", // 2.2 - "CAP_DAC_READ_SEARCH", // 2.2 - "CAP_FOWNER", // 2.2 - "CAP_FSETID", // 2.2 - "CAP_KILL", // 2.2 - "CAP_SETGID", // 2.2 - "CAP_SETUID", // 2.2 - "CAP_SETPCAP", // 2.2 - "CAP_LINUX_IMMUTABLE", // 2.2 - "CAP_NET_BIND_SERVICE", // 2.2 - "CAP_NET_BROADCAST", // 2.2 - "CAP_NET_ADMIN", // 2.2 - "CAP_NET_RAW", // 2.2 - "CAP_IPC_LOCK", // 2.2 - "CAP_IPC_OWNER", // 2.2 - "CAP_SYS_MODULE", // 2.2 - "CAP_SYS_RAWIO", // 2.2 - "CAP_SYS_CHROOT", // 2.2 - "CAP_SYS_PTRACE", // 2.2 - "CAP_SYS_PACCT", // 2.2 - "CAP_SYS_ADMIN", // 2.2 - "CAP_SYS_BOOT", // 2.2 - "CAP_SYS_NICE", // 2.2 - "CAP_SYS_RESOURCE", // 2.2 - "CAP_SYS_TIME", // 2.2 - "CAP_SYS_TTY_CONFIG", // 2.2 - "CAP_MKNOD", // 2.4 - "CAP_LEASE", // 2.4 - "CAP_AUDIT_WRITE", // 2.6.11 - "CAP_AUDIT_CONTROL", // 2.6.11 - "CAP_SETFCAP", // 2.6.24 - "CAP_MAC_OVERRIDE", // 2.6.25 - "CAP_MAC_ADMIN", // 2.6.25 - "CAP_SYSLOG", // 2.6.37 - "CAP_WAKE_ALARM", // 3.0 - "CAP_BLOCK_SUSPEND", // 3.5 - - // caps316 is the caps of kernel 3.16 (38 entries) - "CAP_AUDIT_READ", - - // caps58 is the caps of kernel 5.8 (40 entries) - "CAP_PERFMON", - "CAP_BPF", - - // caps59 is the caps of kernel 5.9 (41 entries) - "CAP_CHECKPOINT_RESTORE", -} +// TODO(thaJeztah): consider what casing we want to use for completion (see below); +// +// We need to consider what format is most convenient; currently we use the +// canonical name (uppercase and "CAP_" prefix), however, tab-completion is +// case-sensitive by default, so requires the user to type uppercase letters +// to filter the list of options. +// +// Bash completion provides a `completion-ignore-case on` option to make completion +// case-insensitive (https://askubuntu.com/a/87066), but it looks to be a global +// option; the current cobra.CompletionOptions also don't provide this as an option +// to be used in the generated completion-script. +// +// Fish completion has `smartcase` (by default?) which matches any case if +// all of the input is lowercase. +// +// Zsh does not appear have a dedicated option, but allows setting matching-rules +// (see https://superuser.com/a/1092328). +var allLinuxCapabilities = sync.OnceValue(func() []string { + caps := capability.ListKnown() + out := make([]string, 0, len(caps)+1) + out = append(out, allCaps) + for _, c := range caps { + out = append(out, "CAP_"+strings.ToUpper(c.String())) + } + return out +}) // restartPolicies is a list of all valid restart-policies.. // @@ -77,7 +55,7 @@ var restartPolicies = []string{ } func completeLinuxCapabilityNames(cmd *cobra.Command, args []string, toComplete string) (names []string, _ cobra.ShellCompDirective) { - return completion.FromList(allLinuxCapabilities...)(cmd, args, toComplete) + return completion.FromList(allLinuxCapabilities()...)(cmd, args, toComplete) } func completeRestartPolicies(cmd *cobra.Command, args []string, toComplete string) (names []string, _ cobra.ShellCompDirective) { diff --git a/cli/command/container/completion_test.go b/cli/command/container/completion_test.go new file mode 100644 index 000000000000..25ab31676f92 --- /dev/null +++ b/cli/command/container/completion_test.go @@ -0,0 +1,21 @@ +package container + +import ( + "strings" + "testing" + + "github.com/spf13/cobra" + "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" +) + +func TestCompleteLinuxCapabilityNames(t *testing.T) { + names, directives := completeLinuxCapabilityNames(nil, nil, "") + assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion") + assert.Assert(t, len(names) > 1) + assert.Check(t, names[0] == allCaps) + for _, name := range names[1:] { + assert.Check(t, strings.HasPrefix(name, "CAP_")) + assert.Check(t, is.Equal(name, strings.ToUpper(name)), "Should be formatted uppercase") + } +} diff --git a/vendor.mod b/vendor.mod index 2bc60591df1e..49fc5c954fab 100644 --- a/vendor.mod +++ b/vendor.mod @@ -26,6 +26,7 @@ require ( github.com/mattn/go-runewidth v0.0.15 github.com/moby/patternmatcher v0.6.0 github.com/moby/swarmkit/v2 v2.0.0-20240611172349-ea1a7cec35cb + github.com/moby/sys/capability v0.3.0 github.com/moby/sys/sequential v0.6.0 github.com/moby/sys/signal v0.7.1 github.com/moby/term v0.5.0 diff --git a/vendor.sum b/vendor.sum index 76bf0508c4fe..4b7c2b90755e 100644 --- a/vendor.sum +++ b/vendor.sum @@ -182,6 +182,8 @@ github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkV github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/swarmkit/v2 v2.0.0-20240611172349-ea1a7cec35cb h1:1UTTg2EgO3nuyV03wREDzldqqePzQ4+0a5G1C1y1bIo= github.com/moby/swarmkit/v2 v2.0.0-20240611172349-ea1a7cec35cb/go.mod h1:kNy225f/gWAnF8wPftteMc5nbAHhrH+HUfvyjmhFjeQ= +github.com/moby/sys/capability v0.3.0 h1:kEP+y6te0gEXIaeQhIi0s7vKs/w0RPoH1qPa6jROcVg= +github.com/moby/sys/capability v0.3.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0= diff --git a/vendor/github.com/moby/sys/capability/.codespellrc b/vendor/github.com/moby/sys/capability/.codespellrc new file mode 100644 index 000000000000..e874be5634b5 --- /dev/null +++ b/vendor/github.com/moby/sys/capability/.codespellrc @@ -0,0 +1,3 @@ +[codespell] +skip = ./.git +ignore-words-list = nd diff --git a/vendor/github.com/moby/sys/capability/.golangci.yml b/vendor/github.com/moby/sys/capability/.golangci.yml new file mode 100644 index 000000000000..d775aadd6faf --- /dev/null +++ b/vendor/github.com/moby/sys/capability/.golangci.yml @@ -0,0 +1,6 @@ +linters: + enable: + - unconvert + - unparam + - gofumpt + - errorlint diff --git a/vendor/github.com/moby/sys/capability/CHANGELOG.md b/vendor/github.com/moby/sys/capability/CHANGELOG.md new file mode 100644 index 000000000000..037ef010a673 --- /dev/null +++ b/vendor/github.com/moby/sys/capability/CHANGELOG.md @@ -0,0 +1,90 @@ +# Changelog +This file documents all notable changes made to this project since the initial fork +from https://github.com/syndtr/gocapability/commit/42c35b4376354fd5. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.3.0] - 2024-09-25 + +### Added +* Added [ListKnown] and [ListSupported] functions. (#153) +* [LastCap] is now available on non-Linux platforms (where it returns an error). (#152) + +### Changed +* [List] is now deprecated in favor of [ListKnown] and [ListSupported]. (#153) + +### Fixed +* Various documentation improvements. (#151) +* Fix "generated code" comment. (#153) + +## [0.2.0] - 2024-09-16 + +This is the first release after the move to a new home in +github.com/moby/sys/capability. + +### Fixed + * Fixed URLs in documentation to reflect the new home. + +## [0.1.1] - 2024-08-01 + +This is a maintenance release, fixing a few minor issues. + +### Fixed + * Fixed future kernel compatibility, for real this time. [#11] + * Fixed [LastCap] to be a function. [#12] + +## [0.1.0] - 2024-07-31 + +This is an initial release since the fork. + +### Breaking changes + + * The `CAP_LAST_CAP` variable is removed; users need to modify the code to + use [LastCap] to get the value. [#6] + * The code now requires Go >= 1.21. + +### Added + * `go.mod` and `go.sum` files. [#2] + * New [LastCap] function. [#6] + * Basic CI using GHA infra. [#8], [#9] + * README and CHANGELOG. [#10] + +### Fixed + * Fixed ambient capabilities error handling in [Apply]. [#3] + * Fixed future kernel compatibility. [#1] + * Fixed various linter warnings. [#4], [#7] + +### Changed + * Go build tags changed from old-style (`+build`) to new Go 1.17+ style (`go:build`). [#2] + +### Removed + * Removed support for capabilities v1 and v2. [#1] + * Removed init function so programs that use this package start faster. [#6] + * Removed `CAP_LAST_CAP` (use [LastCap] instead). [#6] + + +[Apply]: https://pkg.go.dev/github.com/moby/sys/capability#Capabilities.Apply +[LastCap]: https://pkg.go.dev/github.com/moby/sys/capability#LastCap +[List]: https://pkg.go.dev/github.com/moby/sys/capability#List +[ListKnown]: https://pkg.go.dev/github.com/moby/sys/capability#ListKnown +[ListSupported]: https://pkg.go.dev/github.com/moby/sys/capability#ListSupported + + +[0.3.0]: https://github.com/moby/sys/releases/tag/capability%2Fv0.3.0 +[0.2.0]: https://github.com/moby/sys/releases/tag/capability%2Fv0.2.0 +[0.1.1]: https://github.com/kolyshkin/capability/compare/v0.1.0...v0.1.1 +[0.1.0]: https://github.com/kolyshkin/capability/compare/42c35b4376354fd5...v0.1.0 + + +[#1]: https://github.com/kolyshkin/capability/pull/1 +[#2]: https://github.com/kolyshkin/capability/pull/2 +[#3]: https://github.com/kolyshkin/capability/pull/3 +[#4]: https://github.com/kolyshkin/capability/pull/4 +[#6]: https://github.com/kolyshkin/capability/pull/6 +[#7]: https://github.com/kolyshkin/capability/pull/7 +[#8]: https://github.com/kolyshkin/capability/pull/8 +[#9]: https://github.com/kolyshkin/capability/pull/9 +[#10]: https://github.com/kolyshkin/capability/pull/10 +[#11]: https://github.com/kolyshkin/capability/pull/11 +[#12]: https://github.com/kolyshkin/capability/pull/12 diff --git a/vendor/github.com/moby/sys/capability/LICENSE b/vendor/github.com/moby/sys/capability/LICENSE new file mode 100644 index 000000000000..08adcd6ecfb6 --- /dev/null +++ b/vendor/github.com/moby/sys/capability/LICENSE @@ -0,0 +1,25 @@ +Copyright 2023 The Capability Authors. +Copyright 2013 Suryandaru Triandana +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/moby/sys/capability/README.md b/vendor/github.com/moby/sys/capability/README.md new file mode 100644 index 000000000000..84b74871aa21 --- /dev/null +++ b/vendor/github.com/moby/sys/capability/README.md @@ -0,0 +1,13 @@ +This is a fork of (apparently no longer maintained) +https://github.com/syndtr/gocapability package. It provides basic primitives to +work with [Linux capabilities][capabilities(7)]. + +For changes, see [CHANGELOG.md](./CHANGELOG.md). + +[![Go Reference](https://pkg.go.dev/badge/github.com/moby/sys/capability/capability.svg)](https://pkg.go.dev/github.com/moby/sys/capability) + +## Alternatives + + * https://pkg.go.dev/kernel.org/pub/linux/libs/security/libcap/cap + +[capabilities(7)]: https://man7.org/linux/man-pages/man7/capabilities.7.html diff --git a/vendor/github.com/moby/sys/capability/capability.go b/vendor/github.com/moby/sys/capability/capability.go new file mode 100644 index 000000000000..1b36f5f22a25 --- /dev/null +++ b/vendor/github.com/moby/sys/capability/capability.go @@ -0,0 +1,144 @@ +// Copyright 2023 The Capability Authors. +// Copyright 2013 Suryandaru Triandana +// All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package capability provides utilities for manipulating POSIX capabilities. +package capability + +type Capabilities interface { + // Get check whether a capability present in the given + // capabilities set. The 'which' value should be one of EFFECTIVE, + // PERMITTED, INHERITABLE, BOUNDING or AMBIENT. + Get(which CapType, what Cap) bool + + // Empty check whether all capability bits of the given capabilities + // set are zero. The 'which' value should be one of EFFECTIVE, + // PERMITTED, INHERITABLE, BOUNDING or AMBIENT. + Empty(which CapType) bool + + // Full check whether all capability bits of the given capabilities + // set are one. The 'which' value should be one of EFFECTIVE, + // PERMITTED, INHERITABLE, BOUNDING or AMBIENT. + Full(which CapType) bool + + // Set sets capabilities of the given capabilities sets. The + // 'which' value should be one or combination (OR'ed) of EFFECTIVE, + // PERMITTED, INHERITABLE, BOUNDING or AMBIENT. + Set(which CapType, caps ...Cap) + + // Unset unsets capabilities of the given capabilities sets. The + // 'which' value should be one or combination (OR'ed) of EFFECTIVE, + // PERMITTED, INHERITABLE, BOUNDING or AMBIENT. + Unset(which CapType, caps ...Cap) + + // Fill sets all bits of the given capabilities kind to one. The + // 'kind' value should be one or combination (OR'ed) of CAPS, + // BOUNDS or AMBS. + Fill(kind CapType) + + // Clear sets all bits of the given capabilities kind to zero. The + // 'kind' value should be one or combination (OR'ed) of CAPS, + // BOUNDS or AMBS. + Clear(kind CapType) + + // String return current capabilities state of the given capabilities + // set as string. The 'which' value should be one of EFFECTIVE, + // PERMITTED, INHERITABLE BOUNDING or AMBIENT + StringCap(which CapType) string + + // String return current capabilities state as string. + String() string + + // Load load actual capabilities value. This will overwrite all + // outstanding changes. + Load() error + + // Apply apply the capabilities settings, so all changes will take + // effect. + Apply(kind CapType) error +} + +// NewPid initializes a new [Capabilities] object for given pid when +// it is nonzero, or for the current process if pid is 0. +// +// Deprecated: Replace with [NewPid2] followed by [Capabilities.Load]. +// For example, replace: +// +// c, err := NewPid(0) +// if err != nil { +// return err +// } +// +// with: +// +// c, err := NewPid2(0) +// if err != nil { +// return err +// } +// err = c.Load() +// if err != nil { +// return err +// } +func NewPid(pid int) (Capabilities, error) { + c, err := newPid(pid) + if err != nil { + return c, err + } + err = c.Load() + return c, err +} + +// NewPid2 initializes a new [Capabilities] object for given pid when +// it is nonzero, or for the current process if pid is 0. This +// does not load the process's current capabilities; to do that you +// must call [Capabilities.Load] explicitly. +func NewPid2(pid int) (Capabilities, error) { + return newPid(pid) +} + +// NewFile initializes a new Capabilities object for given file path. +// +// Deprecated: Replace with [NewFile2] followed by [Capabilities.Load]. +// For example, replace: +// +// c, err := NewFile(path) +// if err != nil { +// return err +// } +// +// with: +// +// c, err := NewFile2(path) +// if err != nil { +// return err +// } +// err = c.Load() +// if err != nil { +// return err +// } +func NewFile(path string) (Capabilities, error) { + c, err := newFile(path) + if err != nil { + return c, err + } + err = c.Load() + return c, err +} + +// NewFile2 creates a new initialized [Capabilities] object for given +// file path. This does not load the process's current capabilities; +// to do that you must call [Capabilities.Load] explicitly. +func NewFile2(path string) (Capabilities, error) { + return newFile(path) +} + +// LastCap returns highest valid capability of the running kernel, +// or an error if it can not be obtained. +// +// See also: [ListSupported]. +func LastCap() (Cap, error) { + return lastCap() +} diff --git a/vendor/github.com/moby/sys/capability/capability_linux.go b/vendor/github.com/moby/sys/capability/capability_linux.go new file mode 100644 index 000000000000..aa600e1d9fc5 --- /dev/null +++ b/vendor/github.com/moby/sys/capability/capability_linux.go @@ -0,0 +1,541 @@ +// Copyright 2023 The Capability Authors. +// Copyright 2013 Suryandaru Triandana +// All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package capability + +import ( + "bufio" + "errors" + "fmt" + "io" + "os" + "strconv" + "strings" + "sync" + "syscall" +) + +const ( + linuxCapVer1 = 0x19980330 // No longer supported. + linuxCapVer2 = 0x20071026 // No longer supported. + linuxCapVer3 = 0x20080522 +) + +var lastCap = sync.OnceValues(func() (Cap, error) { + f, err := os.Open("/proc/sys/kernel/cap_last_cap") + if err != nil { + return 0, err + } + + buf := make([]byte, 11) + l, err := f.Read(buf) + f.Close() + if err != nil { + return 0, err + } + buf = buf[:l] + + last, err := strconv.Atoi(strings.TrimSpace(string(buf))) + if err != nil { + return 0, err + } + return Cap(last), nil +}) + +func capUpperMask() uint32 { + last, err := lastCap() + if err != nil || last < 32 { + return 0 + } + return (uint32(1) << (uint(last) - 31)) - 1 +} + +func mkStringCap(c Capabilities, which CapType) (ret string) { + last, err := lastCap() + if err != nil { + return "" + } + for i, first := Cap(0), true; i <= last; i++ { + if !c.Get(which, i) { + continue + } + if first { + first = false + } else { + ret += ", " + } + ret += i.String() + } + return +} + +func mkString(c Capabilities, max CapType) (ret string) { + ret = "{" + for i := CapType(1); i <= max; i <<= 1 { + ret += " " + i.String() + "=\"" + if c.Empty(i) { + ret += "empty" + } else if c.Full(i) { + ret += "full" + } else { + ret += c.StringCap(i) + } + ret += "\"" + } + ret += " }" + return +} + +var capVersion = sync.OnceValues(func() (uint32, error) { + var hdr capHeader + err := capget(&hdr, nil) + return hdr.version, err +}) + +func newPid(pid int) (c Capabilities, retErr error) { + ver, err := capVersion() + if err != nil { + retErr = fmt.Errorf("unable to get capability version from the kernel: %w", err) + return + } + switch ver { + case linuxCapVer1, linuxCapVer2: + retErr = errors.New("old/unsupported capability version (kernel older than 2.6.26?)") + default: + // Either linuxCapVer3, or an unknown/future version (such as v4). + // In the latter case, we fall back to v3 as the latest version known + // to this package, as kernel should be backward-compatible to v3. + p := new(capsV3) + p.hdr.version = linuxCapVer3 + p.hdr.pid = int32(pid) + c = p + } + return +} + +type capsV3 struct { + hdr capHeader + data [2]capData + bounds [2]uint32 + ambient [2]uint32 +} + +func (c *capsV3) Get(which CapType, what Cap) bool { + var i uint + if what > 31 { + i = uint(what) >> 5 + what %= 32 + } + + switch which { + case EFFECTIVE: + return (1< 31 { + i = uint(what) >> 5 + what %= 32 + } + + if which&EFFECTIVE != 0 { + c.data[i].effective |= 1 << uint(what) + } + if which&PERMITTED != 0 { + c.data[i].permitted |= 1 << uint(what) + } + if which&INHERITABLE != 0 { + c.data[i].inheritable |= 1 << uint(what) + } + if which&BOUNDING != 0 { + c.bounds[i] |= 1 << uint(what) + } + if which&AMBIENT != 0 { + c.ambient[i] |= 1 << uint(what) + } + } +} + +func (c *capsV3) Unset(which CapType, caps ...Cap) { + for _, what := range caps { + var i uint + if what > 31 { + i = uint(what) >> 5 + what %= 32 + } + + if which&EFFECTIVE != 0 { + c.data[i].effective &= ^(1 << uint(what)) + } + if which&PERMITTED != 0 { + c.data[i].permitted &= ^(1 << uint(what)) + } + if which&INHERITABLE != 0 { + c.data[i].inheritable &= ^(1 << uint(what)) + } + if which&BOUNDING != 0 { + c.bounds[i] &= ^(1 << uint(what)) + } + if which&AMBIENT != 0 { + c.ambient[i] &= ^(1 << uint(what)) + } + } +} + +func (c *capsV3) Fill(kind CapType) { + if kind&CAPS == CAPS { + c.data[0].effective = 0xffffffff + c.data[0].permitted = 0xffffffff + c.data[0].inheritable = 0 + c.data[1].effective = 0xffffffff + c.data[1].permitted = 0xffffffff + c.data[1].inheritable = 0 + } + + if kind&BOUNDS == BOUNDS { + c.bounds[0] = 0xffffffff + c.bounds[1] = 0xffffffff + } + if kind&AMBS == AMBS { + c.ambient[0] = 0xffffffff + c.ambient[1] = 0xffffffff + } +} + +func (c *capsV3) Clear(kind CapType) { + if kind&CAPS == CAPS { + c.data[0].effective = 0 + c.data[0].permitted = 0 + c.data[0].inheritable = 0 + c.data[1].effective = 0 + c.data[1].permitted = 0 + c.data[1].inheritable = 0 + } + + if kind&BOUNDS == BOUNDS { + c.bounds[0] = 0 + c.bounds[1] = 0 + } + if kind&AMBS == AMBS { + c.ambient[0] = 0 + c.ambient[1] = 0 + } +} + +func (c *capsV3) StringCap(which CapType) (ret string) { + return mkStringCap(c, which) +} + +func (c *capsV3) String() (ret string) { + return mkString(c, BOUNDING) +} + +func (c *capsV3) Load() (err error) { + err = capget(&c.hdr, &c.data[0]) + if err != nil { + return + } + + path := "/proc/self/status" + if c.hdr.pid != 0 { + path = fmt.Sprintf("/proc/%d/status", c.hdr.pid) + } + + f, err := os.Open(path) + if err != nil { + return + } + b := bufio.NewReader(f) + for { + line, e := b.ReadString('\n') + if e != nil { + if e != io.EOF { + err = e + } + break + } + if strings.HasPrefix(line, "CapB") { + _, err = fmt.Sscanf(line[4:], "nd: %08x%08x", &c.bounds[1], &c.bounds[0]) + if err != nil { + break + } + continue + } + if strings.HasPrefix(line, "CapA") { + _, err = fmt.Sscanf(line[4:], "mb: %08x%08x", &c.ambient[1], &c.ambient[0]) + if err != nil { + break + } + continue + } + } + f.Close() + + return +} + +func (c *capsV3) Apply(kind CapType) (err error) { + last, err := LastCap() + if err != nil { + return err + } + if kind&BOUNDS == BOUNDS { + var data [2]capData + err = capget(&c.hdr, &data[0]) + if err != nil { + return + } + if (1< 31 { + if c.data.version == 1 { + return false + } + i = uint(what) >> 5 + what %= 32 + } + + switch which { + case EFFECTIVE: + return (1< 31 { + if c.data.version == 1 { + continue + } + i = uint(what) >> 5 + what %= 32 + } + + if which&EFFECTIVE != 0 { + c.data.effective[i] |= 1 << uint(what) + } + if which&PERMITTED != 0 { + c.data.data[i].permitted |= 1 << uint(what) + } + if which&INHERITABLE != 0 { + c.data.data[i].inheritable |= 1 << uint(what) + } + } +} + +func (c *capsFile) Unset(which CapType, caps ...Cap) { + for _, what := range caps { + var i uint + if what > 31 { + if c.data.version == 1 { + continue + } + i = uint(what) >> 5 + what %= 32 + } + + if which&EFFECTIVE != 0 { + c.data.effective[i] &= ^(1 << uint(what)) + } + if which&PERMITTED != 0 { + c.data.data[i].permitted &= ^(1 << uint(what)) + } + if which&INHERITABLE != 0 { + c.data.data[i].inheritable &= ^(1 << uint(what)) + } + } +} + +func (c *capsFile) Fill(kind CapType) { + if kind&CAPS == CAPS { + c.data.effective[0] = 0xffffffff + c.data.data[0].permitted = 0xffffffff + c.data.data[0].inheritable = 0 + if c.data.version == 2 { + c.data.effective[1] = 0xffffffff + c.data.data[1].permitted = 0xffffffff + c.data.data[1].inheritable = 0 + } + } +} + +func (c *capsFile) Clear(kind CapType) { + if kind&CAPS == CAPS { + c.data.effective[0] = 0 + c.data.data[0].permitted = 0 + c.data.data[0].inheritable = 0 + if c.data.version == 2 { + c.data.effective[1] = 0 + c.data.data[1].permitted = 0 + c.data.data[1].inheritable = 0 + } + } +} + +func (c *capsFile) StringCap(which CapType) (ret string) { + return mkStringCap(c, which) +} + +func (c *capsFile) String() (ret string) { + return mkString(c, INHERITABLE) +} + +func (c *capsFile) Load() (err error) { + return getVfsCap(c.path, &c.data) +} + +func (c *capsFile) Apply(kind CapType) (err error) { + if kind&CAPS == CAPS { + return setVfsCap(c.path, &c.data) + } + return +} diff --git a/vendor/github.com/moby/sys/capability/capability_noop.go b/vendor/github.com/moby/sys/capability/capability_noop.go new file mode 100644 index 000000000000..ba819ff057e9 --- /dev/null +++ b/vendor/github.com/moby/sys/capability/capability_noop.go @@ -0,0 +1,26 @@ +// Copyright 2023 The Capability Authors. +// Copyright 2013 Suryandaru Triandana +// All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !linux + +package capability + +import "errors" + +var errNotSup = errors.New("not supported") + +func newPid(_ int) (Capabilities, error) { + return nil, errNotSup +} + +func newFile(_ string) (Capabilities, error) { + return nil, errNotSup +} + +func lastCap() (Cap, error) { + return -1, errNotSup +} diff --git a/vendor/github.com/moby/sys/capability/enum.go b/vendor/github.com/moby/sys/capability/enum.go new file mode 100644 index 000000000000..f89f0273aa47 --- /dev/null +++ b/vendor/github.com/moby/sys/capability/enum.go @@ -0,0 +1,330 @@ +// Copyright 2024 The Capability Authors. +// Copyright 2013 Suryandaru Triandana +// All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package capability + +import "slices" + +type CapType uint + +func (c CapType) String() string { + switch c { + case EFFECTIVE: + return "effective" + case PERMITTED: + return "permitted" + case INHERITABLE: + return "inheritable" + case BOUNDING: + return "bounding" + case CAPS: + return "caps" + case AMBIENT: + return "ambient" + } + return "unknown" +} + +const ( + EFFECTIVE CapType = 1 << iota + PERMITTED + INHERITABLE + BOUNDING + AMBIENT + + CAPS = EFFECTIVE | PERMITTED | INHERITABLE + BOUNDS = BOUNDING + AMBS = AMBIENT +) + +//go:generate go run enumgen/gen.go +type Cap int + +// POSIX-draft defined capabilities and Linux extensions. +// +// Defined in https://github.com/torvalds/linux/blob/master/include/uapi/linux/capability.h +const ( + // In a system with the [_POSIX_CHOWN_RESTRICTED] option defined, this + // overrides the restriction of changing file ownership and group + // ownership. + CAP_CHOWN = Cap(0) + + // Override all DAC access, including ACL execute access if + // [_POSIX_ACL] is defined. Excluding DAC access covered by + // CAP_LINUX_IMMUTABLE. + CAP_DAC_OVERRIDE = Cap(1) + + // Overrides all DAC restrictions regarding read and search on files + // and directories, including ACL restrictions if [_POSIX_ACL] is + // defined. Excluding DAC access covered by CAP_LINUX_IMMUTABLE. + CAP_DAC_READ_SEARCH = Cap(2) + + // Overrides all restrictions about allowed operations on files, where + // file owner ID must be equal to the user ID, except where CAP_FSETID + // is applicable. It doesn't override MAC and DAC restrictions. + CAP_FOWNER = Cap(3) + + // Overrides the following restrictions that the effective user ID + // shall match the file owner ID when setting the S_ISUID and S_ISGID + // bits on that file; that the effective group ID (or one of the + // supplementary group IDs) shall match the file owner ID when setting + // the S_ISGID bit on that file; that the S_ISUID and S_ISGID bits are + // cleared on successful return from chown(2) (not implemented). + CAP_FSETID = Cap(4) + + // Overrides the restriction that the real or effective user ID of a + // process sending a signal must match the real or effective user ID + // of the process receiving the signal. + CAP_KILL = Cap(5) + + // Allows setgid(2) manipulation + // Allows setgroups(2) + // Allows forged gids on socket credentials passing. + CAP_SETGID = Cap(6) + + // Allows set*uid(2) manipulation (including fsuid). + // Allows forged pids on socket credentials passing. + CAP_SETUID = Cap(7) + + // Linux-specific capabilities + + // Without VFS support for capabilities: + // Transfer any capability in your permitted set to any pid, + // remove any capability in your permitted set from any pid + // With VFS support for capabilities (neither of above, but) + // Add any capability from current's capability bounding set + // to the current process' inheritable set + // Allow taking bits out of capability bounding set + // Allow modification of the securebits for a process + CAP_SETPCAP = Cap(8) + + // Allow modification of S_IMMUTABLE and S_APPEND file attributes + CAP_LINUX_IMMUTABLE = Cap(9) + + // Allows binding to TCP/UDP sockets below 1024 + // Allows binding to ATM VCIs below 32 + CAP_NET_BIND_SERVICE = Cap(10) + + // Allow broadcasting, listen to multicast + CAP_NET_BROADCAST = Cap(11) + + // Allow interface configuration + // Allow administration of IP firewall, masquerading and accounting + // Allow setting debug option on sockets + // Allow modification of routing tables + // Allow setting arbitrary process / process group ownership on + // sockets + // Allow binding to any address for transparent proxying (also via NET_RAW) + // Allow setting TOS (type of service) + // Allow setting promiscuous mode + // Allow clearing driver statistics + // Allow multicasting + // Allow read/write of device-specific registers + // Allow activation of ATM control sockets + CAP_NET_ADMIN = Cap(12) + + // Allow use of RAW sockets + // Allow use of PACKET sockets + // Allow binding to any address for transparent proxying (also via NET_ADMIN) + CAP_NET_RAW = Cap(13) + + // Allow locking of shared memory segments + // Allow mlock and mlockall (which doesn't really have anything to do + // with IPC) + CAP_IPC_LOCK = Cap(14) + + // Override IPC ownership checks + CAP_IPC_OWNER = Cap(15) + + // Insert and remove kernel modules - modify kernel without limit + CAP_SYS_MODULE = Cap(16) + + // Allow ioperm/iopl access + // Allow sending USB messages to any device via /proc/bus/usb + CAP_SYS_RAWIO = Cap(17) + + // Allow use of chroot() + CAP_SYS_CHROOT = Cap(18) + + // Allow ptrace() of any process + CAP_SYS_PTRACE = Cap(19) + + // Allow configuration of process accounting + CAP_SYS_PACCT = Cap(20) + + // Allow configuration of the secure attention key + // Allow administration of the random device + // Allow examination and configuration of disk quotas + // Allow setting the domainname + // Allow setting the hostname + // Allow calling bdflush() + // Allow mount() and umount(), setting up new smb connection + // Allow some autofs root ioctls + // Allow nfsservctl + // Allow VM86_REQUEST_IRQ + // Allow to read/write pci config on alpha + // Allow irix_prctl on mips (setstacksize) + // Allow flushing all cache on m68k (sys_cacheflush) + // Allow removing semaphores + // Used instead of CAP_CHOWN to "chown" IPC message queues, semaphores + // and shared memory + // Allow locking/unlocking of shared memory segment + // Allow turning swap on/off + // Allow forged pids on socket credentials passing + // Allow setting readahead and flushing buffers on block devices + // Allow setting geometry in floppy driver + // Allow turning DMA on/off in xd driver + // Allow administration of md devices (mostly the above, but some + // extra ioctls) + // Allow tuning the ide driver + // Allow access to the nvram device + // Allow administration of apm_bios, serial and bttv (TV) device + // Allow manufacturer commands in isdn CAPI support driver + // Allow reading non-standardized portions of pci configuration space + // Allow DDI debug ioctl on sbpcd driver + // Allow setting up serial ports + // Allow sending raw qic-117 commands + // Allow enabling/disabling tagged queuing on SCSI controllers and sending + // arbitrary SCSI commands + // Allow setting encryption key on loopback filesystem + // Allow setting zone reclaim policy + // Allow everything under CAP_BPF and CAP_PERFMON for backward compatibility + CAP_SYS_ADMIN = Cap(21) + + // Allow use of reboot() + CAP_SYS_BOOT = Cap(22) + + // Allow raising priority and setting priority on other (different + // UID) processes + // Allow use of FIFO and round-robin (realtime) scheduling on own + // processes and setting the scheduling algorithm used by another + // process. + // Allow setting cpu affinity on other processes + CAP_SYS_NICE = Cap(23) + + // Override resource limits. Set resource limits. + // Override quota limits. + // Override reserved space on ext2 filesystem + // Modify data journaling mode on ext3 filesystem (uses journaling + // resources) + // NOTE: ext2 honors fsuid when checking for resource overrides, so + // you can override using fsuid too + // Override size restrictions on IPC message queues + // Allow more than 64hz interrupts from the real-time clock + // Override max number of consoles on console allocation + // Override max number of keymaps + // Control memory reclaim behavior + CAP_SYS_RESOURCE = Cap(24) + + // Allow manipulation of system clock + // Allow irix_stime on mips + // Allow setting the real-time clock + CAP_SYS_TIME = Cap(25) + + // Allow configuration of tty devices + // Allow vhangup() of tty + CAP_SYS_TTY_CONFIG = Cap(26) + + // Allow the privileged aspects of mknod() + CAP_MKNOD = Cap(27) + + // Allow taking of leases on files + CAP_LEASE = Cap(28) + + CAP_AUDIT_WRITE = Cap(29) + CAP_AUDIT_CONTROL = Cap(30) + CAP_SETFCAP = Cap(31) + + // Override MAC access. + // The base kernel enforces no MAC policy. + // An LSM may enforce a MAC policy, and if it does and it chooses + // to implement capability based overrides of that policy, this is + // the capability it should use to do so. + CAP_MAC_OVERRIDE = Cap(32) + + // Allow MAC configuration or state changes. + // The base kernel requires no MAC configuration. + // An LSM may enforce a MAC policy, and if it does and it chooses + // to implement capability based checks on modifications to that + // policy or the data required to maintain it, this is the + // capability it should use to do so. + CAP_MAC_ADMIN = Cap(33) + + // Allow configuring the kernel's syslog (printk behaviour) + CAP_SYSLOG = Cap(34) + + // Allow triggering something that will wake the system + CAP_WAKE_ALARM = Cap(35) + + // Allow preventing system suspends + CAP_BLOCK_SUSPEND = Cap(36) + + // Allow reading the audit log via multicast netlink socket + CAP_AUDIT_READ = Cap(37) + + // Allow system performance and observability privileged operations + // using perf_events, i915_perf and other kernel subsystems + CAP_PERFMON = Cap(38) + + // CAP_BPF allows the following BPF operations: + // - Creating all types of BPF maps + // - Advanced verifier features + // - Indirect variable access + // - Bounded loops + // - BPF to BPF function calls + // - Scalar precision tracking + // - Larger complexity limits + // - Dead code elimination + // - And potentially other features + // - Loading BPF Type Format (BTF) data + // - Retrieve xlated and JITed code of BPF programs + // - Use bpf_spin_lock() helper + // + // CAP_PERFMON relaxes the verifier checks further: + // - BPF progs can use of pointer-to-integer conversions + // - speculation attack hardening measures are bypassed + // - bpf_probe_read to read arbitrary kernel memory is allowed + // - bpf_trace_printk to print kernel memory is allowed + // + // CAP_SYS_ADMIN is required to use bpf_probe_write_user. + // + // CAP_SYS_ADMIN is required to iterate system wide loaded + // programs, maps, links, BTFs and convert their IDs to file descriptors. + // + // CAP_PERFMON and CAP_BPF are required to load tracing programs. + // CAP_NET_ADMIN and CAP_BPF are required to load networking programs. + CAP_BPF = Cap(39) + + // Allow checkpoint/restore related operations. + // Introduced in kernel 5.9 + CAP_CHECKPOINT_RESTORE = Cap(40) +) + +// List returns the list of all capabilities known to the package. +// +// Deprecated: use [ListKnown] or [ListSupported] instead. +func List() []Cap { + return ListKnown() +} + +// ListKnown returns the list of all capabilities known to the package. +func ListKnown() []Cap { + return list() +} + +// ListSupported retuns the list of all capabilities known to the package, +// except those that are not supported by the currently running Linux kernel. +func ListSupported() ([]Cap, error) { + last, err := LastCap() + if err != nil { + return nil, err + } + return slices.DeleteFunc(list(), func(c Cap) bool { + // Remove caps not supported by the kernel. + return c > last + }), nil +} diff --git a/vendor/github.com/moby/sys/capability/enum_gen.go b/vendor/github.com/moby/sys/capability/enum_gen.go new file mode 100644 index 000000000000..f72cd43a6e0f --- /dev/null +++ b/vendor/github.com/moby/sys/capability/enum_gen.go @@ -0,0 +1,137 @@ +// Code generated by go generate; DO NOT EDIT. + +package capability + +func (c Cap) String() string { + switch c { + case CAP_CHOWN: + return "chown" + case CAP_DAC_OVERRIDE: + return "dac_override" + case CAP_DAC_READ_SEARCH: + return "dac_read_search" + case CAP_FOWNER: + return "fowner" + case CAP_FSETID: + return "fsetid" + case CAP_KILL: + return "kill" + case CAP_SETGID: + return "setgid" + case CAP_SETUID: + return "setuid" + case CAP_SETPCAP: + return "setpcap" + case CAP_LINUX_IMMUTABLE: + return "linux_immutable" + case CAP_NET_BIND_SERVICE: + return "net_bind_service" + case CAP_NET_BROADCAST: + return "net_broadcast" + case CAP_NET_ADMIN: + return "net_admin" + case CAP_NET_RAW: + return "net_raw" + case CAP_IPC_LOCK: + return "ipc_lock" + case CAP_IPC_OWNER: + return "ipc_owner" + case CAP_SYS_MODULE: + return "sys_module" + case CAP_SYS_RAWIO: + return "sys_rawio" + case CAP_SYS_CHROOT: + return "sys_chroot" + case CAP_SYS_PTRACE: + return "sys_ptrace" + case CAP_SYS_PACCT: + return "sys_pacct" + case CAP_SYS_ADMIN: + return "sys_admin" + case CAP_SYS_BOOT: + return "sys_boot" + case CAP_SYS_NICE: + return "sys_nice" + case CAP_SYS_RESOURCE: + return "sys_resource" + case CAP_SYS_TIME: + return "sys_time" + case CAP_SYS_TTY_CONFIG: + return "sys_tty_config" + case CAP_MKNOD: + return "mknod" + case CAP_LEASE: + return "lease" + case CAP_AUDIT_WRITE: + return "audit_write" + case CAP_AUDIT_CONTROL: + return "audit_control" + case CAP_SETFCAP: + return "setfcap" + case CAP_MAC_OVERRIDE: + return "mac_override" + case CAP_MAC_ADMIN: + return "mac_admin" + case CAP_SYSLOG: + return "syslog" + case CAP_WAKE_ALARM: + return "wake_alarm" + case CAP_BLOCK_SUSPEND: + return "block_suspend" + case CAP_AUDIT_READ: + return "audit_read" + case CAP_PERFMON: + return "perfmon" + case CAP_BPF: + return "bpf" + case CAP_CHECKPOINT_RESTORE: + return "checkpoint_restore" + } + return "unknown" +} + +func list() []Cap { + return []Cap{ + CAP_CHOWN, + CAP_DAC_OVERRIDE, + CAP_DAC_READ_SEARCH, + CAP_FOWNER, + CAP_FSETID, + CAP_KILL, + CAP_SETGID, + CAP_SETUID, + CAP_SETPCAP, + CAP_LINUX_IMMUTABLE, + CAP_NET_BIND_SERVICE, + CAP_NET_BROADCAST, + CAP_NET_ADMIN, + CAP_NET_RAW, + CAP_IPC_LOCK, + CAP_IPC_OWNER, + CAP_SYS_MODULE, + CAP_SYS_RAWIO, + CAP_SYS_CHROOT, + CAP_SYS_PTRACE, + CAP_SYS_PACCT, + CAP_SYS_ADMIN, + CAP_SYS_BOOT, + CAP_SYS_NICE, + CAP_SYS_RESOURCE, + CAP_SYS_TIME, + CAP_SYS_TTY_CONFIG, + CAP_MKNOD, + CAP_LEASE, + CAP_AUDIT_WRITE, + CAP_AUDIT_CONTROL, + CAP_SETFCAP, + CAP_MAC_OVERRIDE, + CAP_MAC_ADMIN, + CAP_SYSLOG, + CAP_WAKE_ALARM, + CAP_BLOCK_SUSPEND, + CAP_AUDIT_READ, + CAP_PERFMON, + CAP_BPF, + CAP_CHECKPOINT_RESTORE, + } +} diff --git a/vendor/github.com/moby/sys/capability/syscall_linux.go b/vendor/github.com/moby/sys/capability/syscall_linux.go new file mode 100644 index 000000000000..d6b6932a94b2 --- /dev/null +++ b/vendor/github.com/moby/sys/capability/syscall_linux.go @@ -0,0 +1,153 @@ +// Copyright 2024 The Capability Authors. +// Copyright 2013 Suryandaru Triandana +// All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package capability + +import ( + "syscall" + "unsafe" +) + +type capHeader struct { + version uint32 + pid int32 +} + +type capData struct { + effective uint32 + permitted uint32 + inheritable uint32 +} + +func capget(hdr *capHeader, data *capData) (err error) { + _, _, e1 := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(hdr)), uintptr(unsafe.Pointer(data)), 0) + if e1 != 0 { + err = e1 + } + return +} + +func capset(hdr *capHeader, data *capData) (err error) { + _, _, e1 := syscall.Syscall(syscall.SYS_CAPSET, uintptr(unsafe.Pointer(hdr)), uintptr(unsafe.Pointer(data)), 0) + if e1 != 0 { + err = e1 + } + return +} + +// not yet in syscall +const ( + pr_CAP_AMBIENT = 47 + pr_CAP_AMBIENT_IS_SET = uintptr(1) + pr_CAP_AMBIENT_RAISE = uintptr(2) + pr_CAP_AMBIENT_LOWER = uintptr(3) + pr_CAP_AMBIENT_CLEAR_ALL = uintptr(4) +) + +func prctl(option int, arg2, arg3, arg4, arg5 uintptr) (err error) { + _, _, e1 := syscall.Syscall6(syscall.SYS_PRCTL, uintptr(option), arg2, arg3, arg4, arg5, 0) + if e1 != 0 { + err = e1 + } + return +} + +const ( + vfsXattrName = "security.capability" + + vfsCapVerMask = 0xff000000 + vfsCapVer1 = 0x01000000 + vfsCapVer2 = 0x02000000 + + vfsCapFlagMask = ^vfsCapVerMask + vfsCapFlageffective = 0x000001 + + vfscapDataSizeV1 = 4 * (1 + 2*1) + vfscapDataSizeV2 = 4 * (1 + 2*2) +) + +type vfscapData struct { + magic uint32 + data [2]struct { + permitted uint32 + inheritable uint32 + } + effective [2]uint32 + version int8 +} + +var _vfsXattrName *byte + +func init() { + _vfsXattrName, _ = syscall.BytePtrFromString(vfsXattrName) +} + +func getVfsCap(path string, dest *vfscapData) (err error) { + var _p0 *byte + _p0, err = syscall.BytePtrFromString(path) + if err != nil { + return + } + r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_vfsXattrName)), uintptr(unsafe.Pointer(dest)), vfscapDataSizeV2, 0, 0) + if e1 != 0 { + if e1 == syscall.ENODATA { + dest.version = 2 + return + } + err = e1 + } + switch dest.magic & vfsCapVerMask { + case vfsCapVer1: + dest.version = 1 + if r0 != vfscapDataSizeV1 { + return syscall.EINVAL + } + dest.data[1].permitted = 0 + dest.data[1].inheritable = 0 + case vfsCapVer2: + dest.version = 2 + if r0 != vfscapDataSizeV2 { + return syscall.EINVAL + } + default: + return syscall.EINVAL + } + if dest.magic&vfsCapFlageffective != 0 { + dest.effective[0] = dest.data[0].permitted | dest.data[0].inheritable + dest.effective[1] = dest.data[1].permitted | dest.data[1].inheritable + } else { + dest.effective[0] = 0 + dest.effective[1] = 0 + } + return +} + +func setVfsCap(path string, data *vfscapData) (err error) { + var _p0 *byte + _p0, err = syscall.BytePtrFromString(path) + if err != nil { + return + } + var size uintptr + if data.version == 1 { + data.magic = vfsCapVer1 + size = vfscapDataSizeV1 + } else if data.version == 2 { + data.magic = vfsCapVer2 + if data.effective[0] != 0 || data.effective[1] != 0 { + data.magic |= vfsCapFlageffective + } + size = vfscapDataSizeV2 + } else { + return syscall.EINVAL + } + _, _, e1 := syscall.Syscall6(syscall.SYS_SETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_vfsXattrName)), uintptr(unsafe.Pointer(data)), size, 0, 0) + if e1 != 0 { + err = e1 + } + return +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 18382a8af07d..a28249e17200 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -204,6 +204,9 @@ github.com/moby/swarmkit/v2/api/defaults github.com/moby/swarmkit/v2/api/genericresource github.com/moby/swarmkit/v2/manager/raftselector github.com/moby/swarmkit/v2/protobuf/plugin +# github.com/moby/sys/capability v0.3.0 +## explicit; go 1.21 +github.com/moby/sys/capability # github.com/moby/sys/sequential v0.6.0 ## explicit; go 1.17 github.com/moby/sys/sequential From e6c66585631ebbb8199ef252ce7acf17eedb449f Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 2 Oct 2024 10:38:25 +0200 Subject: [PATCH 02/15] cli/command/container: add unit tests for completion helpers Signed-off-by: Sebastiaan van Stijn (cherry picked from commit d49e72c0ac99945141a1e3e1d3de35f6637937f5) Signed-off-by: Sebastiaan van Stijn --- cli/command/container/completion_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cli/command/container/completion_test.go b/cli/command/container/completion_test.go index 25ab31676f92..b85aae32d5dc 100644 --- a/cli/command/container/completion_test.go +++ b/cli/command/container/completion_test.go @@ -4,6 +4,7 @@ import ( "strings" "testing" + "github.com/moby/sys/signal" "github.com/spf13/cobra" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" @@ -19,3 +20,17 @@ func TestCompleteLinuxCapabilityNames(t *testing.T) { assert.Check(t, is.Equal(name, strings.ToUpper(name)), "Should be formatted uppercase") } } + +func TestCompleteRestartPolicies(t *testing.T) { + values, directives := completeRestartPolicies(nil, nil, "") + assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion") + expected := restartPolicies + assert.Check(t, is.DeepEqual(values, expected)) +} + +func TestCompleteSignals(t *testing.T) { + values, directives := completeSignals(nil, nil, "") + assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion") + assert.Check(t, len(values) > 1) + assert.Check(t, is.Len(values, len(signal.SignalMap))) +} From 64e98fa375ab78615e88d9b299965ac44621547e Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 8 Oct 2024 13:09:50 +0200 Subject: [PATCH 03/15] cli/command/completion: add Platforms Add a utility for completing platform strings. Platforms offers completion for platform-strings. It provides a non-exhaustive list of platforms to be used for completion. Platform-strings are based on [runtime.GOOS] and [runtime.GOARCH], but with (optional) variants added. A list of recognised os/arch combinations from the Go runtime can be obtained through "go tool dist list". Some noteworthy exclusions from this list: - arm64 images ("windows/arm64", "windows/arm64/v8") do not yet exist for windows. - we don't (yet) include `os-variant` for completion (as can be used for Windows images) - we don't (yet) include platforms for which we don't build binaries, such as BSD platforms (freebsd, netbsd, openbsd), android, macOS (darwin). - we currently exclude architectures that may have unofficial builds, but don't have wide adoption (and no support), such as loong64, mipsXXX, ppc64 (non-le) to prevent confusion. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit ce1aebcc307622fe000f4d201727fe1cf5b874cb) Signed-off-by: Sebastiaan van Stijn --- cli/command/completion/functions.go | 44 ++++++++++++++++++++++++ cli/command/completion/functions_test.go | 15 ++++++++ 2 files changed, 59 insertions(+) create mode 100644 cli/command/completion/functions_test.go diff --git a/cli/command/completion/functions.go b/cli/command/completion/functions.go index c25330a4b699..f4fb98ca9d05 100644 --- a/cli/command/completion/functions.go +++ b/cli/command/completion/functions.go @@ -146,3 +146,47 @@ func FileNames(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCom func NoComplete(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { return nil, cobra.ShellCompDirectiveNoFileComp } + +var commonPlatforms = []string{ + "linux", + "linux/386", + "linux/amd64", + "linux/arm", + "linux/arm/v5", + "linux/arm/v6", + "linux/arm/v7", + "linux/arm64", + "linux/arm64/v8", + + // IBM power and z platforms + "linux/ppc64le", + "linux/s390x", + + // Not yet supported + "linux/riscv64", + + "windows", + "windows/amd64", + + "wasip1", + "wasip1/wasm", +} + +// Platforms offers completion for platform-strings. It provides a non-exhaustive +// list of platforms to be used for completion. Platform-strings are based on +// [runtime.GOOS] and [runtime.GOARCH], but with (optional) variants added. A +// list of recognised os/arch combinations from the Go runtime can be obtained +// through "go tool dist list". +// +// Some noteworthy exclusions from this list: +// +// - arm64 images ("windows/arm64", "windows/arm64/v8") do not yet exist for windows. +// - we don't (yet) include `os-variant` for completion (as can be used for Windows images) +// - we don't (yet) include platforms for which we don't build binaries, such as +// BSD platforms (freebsd, netbsd, openbsd), android, macOS (darwin). +// - we currently exclude architectures that may have unofficial builds, +// but don't have wide adoption (and no support), such as loong64, mipsXXX, +// ppc64 (non-le) to prevent confusion. +func Platforms(_ *cobra.Command, _ []string, _ string) (platforms []string, _ cobra.ShellCompDirective) { + return commonPlatforms, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cli/command/completion/functions_test.go b/cli/command/completion/functions_test.go new file mode 100644 index 000000000000..a4bb6f543857 --- /dev/null +++ b/cli/command/completion/functions_test.go @@ -0,0 +1,15 @@ +package completion + +import ( + "testing" + + "github.com/spf13/cobra" + "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" +) + +func TestCompletePlatforms(t *testing.T) { + values, directives := Platforms(nil, nil, "") + assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion") + assert.Check(t, is.DeepEqual(values, commonPlatforms)) +} From dd4b370d3cad637b9deaf42f8d8201ea4889e0f0 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 8 Oct 2024 13:13:32 +0200 Subject: [PATCH 04/15] cli/command/container: add shell completion for --platform flags With this patch, completion is provided for `--platform` flags: docker run --platform linux linux/amd64 linux/arm/v5 linux/arm/v7 linux/arm64/v8 linux/riscv64 wasip1 windows linux/386 linux/arm linux/arm/v6 linux/arm64 linux/ppc64le linux/s390x wasip1/wasm windows/amd64 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 8c7f713db609b84475b7033c6dd946c1d6b32afa) Signed-off-by: Sebastiaan van Stijn --- cli/command/container/create.go | 1 + cli/command/container/run.go | 1 + 2 files changed, 2 insertions(+) diff --git a/cli/command/container/create.go b/cli/command/container/create.go index 0e449bb68a6e..144b2f648110 100644 --- a/cli/command/container/create.go +++ b/cli/command/container/create.go @@ -83,6 +83,7 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command { _ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames) _ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames) _ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCli)) + _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) _ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever)) _ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies) _ = cmd.RegisterFlagCompletionFunc("stop-signal", completeSignals) diff --git a/cli/command/container/run.go b/cli/command/container/run.go index ef0986cba281..222a8dc93a91 100644 --- a/cli/command/container/run.go +++ b/cli/command/container/run.go @@ -74,6 +74,7 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command { _ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames) _ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames) _ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCli)) + _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) _ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever)) _ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies) _ = cmd.RegisterFlagCompletionFunc("stop-signal", completeSignals) From 099d4baeedca1a1bb41e49c4f8ec983e1608c295 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 8 Oct 2024 13:15:28 +0200 Subject: [PATCH 05/15] cli/command/image: add shell completion for --platform flags With this patch, completion is provided for `--platform` flags: docker pull --platform linux linux/amd64 linux/arm/v5 linux/arm/v7 linux/arm64/v8 linux/riscv64 wasip1 windows linux/386 linux/arm linux/arm/v6 linux/arm64 linux/ppc64le linux/s390x wasip1/wasm windows/amd64 Note that `docker buildx build` (with BuildKit) does not yet provide completion; it's provided through buildx, and uses a different format (accepting multiple comma-separated platforms). Interestingly, tab-completion for `docker build` currently uses completion for non-buildkit, and has some other issues that may have to be looked into. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit d42cf96e151972a04cc22eebce823f9578d61833) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/build.go | 3 +++ cli/command/image/import.go | 2 ++ cli/command/image/pull.go | 2 ++ cli/command/image/push.go | 2 ++ 4 files changed, 9 insertions(+) diff --git a/cli/command/image/build.go b/cli/command/image/build.go index cb5292be85c0..4a258646dcce 100644 --- a/cli/command/image/build.go +++ b/cli/command/image/build.go @@ -18,6 +18,7 @@ import ( "github.com/docker/cli-docs-tool/annotation" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/image/build" "github.com/docker/cli/opts" "github.com/docker/docker/api" @@ -159,6 +160,8 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command { flags.SetAnnotation("squash", "experimental", nil) flags.SetAnnotation("squash", "version", []string{"1.25"}) + _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) + return cmd } diff --git a/cli/command/image/import.go b/cli/command/image/import.go index 9920b5e0cdc4..f6ad73d9baa4 100644 --- a/cli/command/image/import.go +++ b/cli/command/image/import.go @@ -7,6 +7,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/command/completion" dockeropts "github.com/docker/cli/opts" "github.com/docker/docker/api/types/image" "github.com/docker/docker/pkg/jsonmessage" @@ -47,6 +48,7 @@ func NewImportCommand(dockerCli command.Cli) *cobra.Command { flags.VarP(&options.changes, "change", "c", "Apply Dockerfile instruction to the created image") flags.StringVarP(&options.message, "message", "m", "", "Set commit message for imported image") command.AddPlatformFlag(flags, &options.platform) + _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) return cmd } diff --git a/cli/command/image/pull.go b/cli/command/image/pull.go index 9ec9771e2885..93388253f75a 100644 --- a/cli/command/image/pull.go +++ b/cli/command/image/pull.go @@ -50,6 +50,8 @@ func NewPullCommand(dockerCli command.Cli) *cobra.Command { command.AddPlatformFlag(flags, &opts.platform) command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled()) + _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) + return cmd } diff --git a/cli/command/image/push.go b/cli/command/image/push.go index d28fd93e7a41..92ef1a159a20 100644 --- a/cli/command/image/push.go +++ b/cli/command/image/push.go @@ -68,6 +68,8 @@ Image index won't be pushed, meaning that other manifests, including attestation 'os[/arch[/variant]]': Explicit platform (eg. linux/amd64)`) flags.SetAnnotation("platform", "version", []string{"1.46"}) + _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) + return cmd } From b6e7eba4470ecdca460e4b63270fba8179674ad6 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sun, 13 Oct 2024 17:47:07 +0200 Subject: [PATCH 06/15] completion: ContainerNames: don't panic on nil filter Signed-off-by: Sebastiaan van Stijn (cherry picked from commit b8cddc63add5704c0ae434040ed490b97bb1b28b) Signed-off-by: Sebastiaan van Stijn --- cli/command/completion/functions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/command/completion/functions.go b/cli/command/completion/functions.go index f4fb98ca9d05..7ed09a2ab885 100644 --- a/cli/command/completion/functions.go +++ b/cli/command/completion/functions.go @@ -60,7 +60,7 @@ func ContainerNames(dockerCLI APIClientProvider, all bool, filters ...func(types for _, ctr := range list { skip := false for _, fn := range filters { - if !fn(ctr) { + if fn != nil && !fn(ctr) { skip = true break } From 7d1fa132fbc0f1d6ab9ec55d0a2f2f06071226f8 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sun, 13 Oct 2024 17:48:49 +0200 Subject: [PATCH 07/15] completion: add test for EnvVarNames Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 8f2e5662e7e4d74f5cc33887749f9378287df627) Signed-off-by: Sebastiaan van Stijn --- cli/command/completion/functions_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cli/command/completion/functions_test.go b/cli/command/completion/functions_test.go index a4bb6f543857..817aef27a61b 100644 --- a/cli/command/completion/functions_test.go +++ b/cli/command/completion/functions_test.go @@ -1,13 +1,28 @@ package completion import ( + "sort" "testing" "github.com/spf13/cobra" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" + "gotest.tools/v3/env" ) +func TestCompleteEnvVarNames(t *testing.T) { + env.PatchAll(t, map[string]string{ + "ENV_A": "hello-a", + "ENV_B": "hello-b", + }) + values, directives := EnvVarNames(nil, nil, "") + assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion") + + sort.Strings(values) + expected := []string{"ENV_A", "ENV_B"} + assert.Check(t, is.DeepEqual(values, expected)) +} + func TestCompletePlatforms(t *testing.T) { values, directives := Platforms(nil, nil, "") assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion") From 954dba448272b13edab0a5b6ed8b491358139757 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sun, 13 Oct 2024 17:52:50 +0200 Subject: [PATCH 08/15] completion: add test for FileNames Signed-off-by: Sebastiaan van Stijn (cherry picked from commit a5ca5b33f11eb19111a29c425c9a75f50c1aefd5) Signed-off-by: Sebastiaan van Stijn --- cli/command/completion/functions_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cli/command/completion/functions_test.go b/cli/command/completion/functions_test.go index 817aef27a61b..d915ccdc362c 100644 --- a/cli/command/completion/functions_test.go +++ b/cli/command/completion/functions_test.go @@ -23,6 +23,12 @@ func TestCompleteEnvVarNames(t *testing.T) { assert.Check(t, is.DeepEqual(values, expected)) } +func TestCompleteFileNames(t *testing.T) { + values, directives := FileNames(nil, nil, "") + assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveDefault)) + assert.Check(t, is.Len(values, 0)) +} + func TestCompletePlatforms(t *testing.T) { values, directives := Platforms(nil, nil, "") assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion") From 62230c7ec293602f63906039deb6c27ce7c5d550 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sun, 13 Oct 2024 17:50:29 +0200 Subject: [PATCH 09/15] completion: add test for FromList Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 51713196c9d5312cf98f59ff0a73452dda5049e2) Signed-off-by: Sebastiaan van Stijn --- cli/command/completion/functions_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cli/command/completion/functions_test.go b/cli/command/completion/functions_test.go index d915ccdc362c..7a98e2d3c954 100644 --- a/cli/command/completion/functions_test.go +++ b/cli/command/completion/functions_test.go @@ -29,6 +29,14 @@ func TestCompleteFileNames(t *testing.T) { assert.Check(t, is.Len(values, 0)) } +func TestCompleteFromList(t *testing.T) { + expected := []string{"one", "two", "three"} + + values, directives := FromList(expected...)(nil, nil, "") + assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion") + assert.Check(t, is.DeepEqual(values, expected)) +} + func TestCompletePlatforms(t *testing.T) { values, directives := Platforms(nil, nil, "") assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion") From 02da13fb18985df4d447368fd68f7563fbc83040 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sun, 13 Oct 2024 17:54:40 +0200 Subject: [PATCH 10/15] completion: add test for NoComplete Signed-off-by: Sebastiaan van Stijn (cherry picked from commit be197da6b848c4f3efbaf6bcdde5f50868acbb6c) Signed-off-by: Sebastiaan van Stijn --- cli/command/completion/functions_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cli/command/completion/functions_test.go b/cli/command/completion/functions_test.go index 7a98e2d3c954..f5227539302f 100644 --- a/cli/command/completion/functions_test.go +++ b/cli/command/completion/functions_test.go @@ -37,6 +37,12 @@ func TestCompleteFromList(t *testing.T) { assert.Check(t, is.DeepEqual(values, expected)) } +func TestCompleteNoComplete(t *testing.T) { + values, directives := NoComplete(nil, nil, "") + assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveNoFileComp)) + assert.Check(t, is.Len(values, 0)) +} + func TestCompletePlatforms(t *testing.T) { values, directives := Platforms(nil, nil, "") assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion") From 95e329d3e3ff97b66e4e007cf8ae71cd6ef4abdd Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sun, 13 Oct 2024 17:57:46 +0200 Subject: [PATCH 11/15] completion: add test for ContainerNames Signed-off-by: Sebastiaan van Stijn (cherry picked from commit f3b4094eb05815083ba0a7e7e064dfccd05bd091) Signed-off-by: Sebastiaan van Stijn --- cli/command/completion/functions_test.go | 136 +++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/cli/command/completion/functions_test.go b/cli/command/completion/functions_test.go index f5227539302f..943ae49169d7 100644 --- a/cli/command/completion/functions_test.go +++ b/cli/command/completion/functions_test.go @@ -1,15 +1,151 @@ package completion import ( + "context" + "errors" "sort" "testing" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/client" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/spf13/cobra" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/env" ) +type fakeCLI struct { + *fakeClient +} + +// Client implements [APIClientProvider]. +func (c fakeCLI) Client() client.APIClient { + return c.fakeClient +} + +type fakeClient struct { + client.Client + containerListFunc func(options container.ListOptions) ([]types.Container, error) +} + +func (c *fakeClient) ContainerList(_ context.Context, options container.ListOptions) ([]types.Container, error) { + if c.containerListFunc != nil { + return c.containerListFunc(options) + } + return []types.Container{}, nil +} + +func TestCompleteContainerNames(t *testing.T) { + tests := []struct { + doc string + showAll, showIDs bool + filters []func(types.Container) bool + containers []types.Container + expOut []string + expOpts container.ListOptions + expDirective cobra.ShellCompDirective + }{ + { + doc: "no results", + expDirective: cobra.ShellCompDirectiveNoFileComp, + }, + { + doc: "all containers", + showAll: true, + containers: []types.Container{ + {ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}}, + {ID: "id-b", State: "created", Names: []string{"/container-b"}}, + {ID: "id-a", State: "exited", Names: []string{"/container-a"}}, + }, + expOut: []string{"container-c", "container-c/link-b", "container-b", "container-a"}, + expOpts: container.ListOptions{All: true}, + expDirective: cobra.ShellCompDirectiveNoFileComp, + }, + { + doc: "all containers with ids", + showAll: true, + showIDs: true, + containers: []types.Container{ + {ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}}, + {ID: "id-b", State: "created", Names: []string{"/container-b"}}, + {ID: "id-a", State: "exited", Names: []string{"/container-a"}}, + }, + expOut: []string{"id-c", "container-c", "container-c/link-b", "id-b", "container-b", "id-a", "container-a"}, + expOpts: container.ListOptions{All: true}, + expDirective: cobra.ShellCompDirectiveNoFileComp, + }, + { + doc: "only running containers", + showAll: false, + containers: []types.Container{ + {ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}}, + }, + expOut: []string{"container-c", "container-c/link-b"}, + expDirective: cobra.ShellCompDirectiveNoFileComp, + }, + { + doc: "with filter", + showAll: true, + filters: []func(types.Container) bool{ + func(container types.Container) bool { return container.State == "created" }, + }, + containers: []types.Container{ + {ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}}, + {ID: "id-b", State: "created", Names: []string{"/container-b"}}, + {ID: "id-a", State: "exited", Names: []string{"/container-a"}}, + }, + expOut: []string{"container-b"}, + expOpts: container.ListOptions{All: true}, + expDirective: cobra.ShellCompDirectiveNoFileComp, + }, + { + doc: "multiple filters", + showAll: true, + filters: []func(types.Container) bool{ + func(container types.Container) bool { return container.ID == "id-a" }, + func(container types.Container) bool { return container.State == "created" }, + }, + containers: []types.Container{ + {ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}}, + {ID: "id-b", State: "created", Names: []string{"/container-b"}}, + {ID: "id-a", State: "created", Names: []string{"/container-a"}}, + }, + expOut: []string{"container-a"}, + expOpts: container.ListOptions{All: true}, + expDirective: cobra.ShellCompDirectiveNoFileComp, + }, + { + doc: "with error", + expDirective: cobra.ShellCompDirectiveError, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.doc, func(t *testing.T) { + if tc.showIDs { + t.Setenv("DOCKER_COMPLETION_SHOW_CONTAINER_IDS", "yes") + } + comp := ContainerNames(fakeCLI{&fakeClient{ + containerListFunc: func(opts container.ListOptions) ([]types.Container, error) { + assert.Check(t, is.DeepEqual(opts, tc.expOpts, cmpopts.IgnoreUnexported(container.ListOptions{}, filters.Args{}))) + if tc.expDirective == cobra.ShellCompDirectiveError { + return nil, errors.New("some error occurred") + } + return tc.containers, nil + }, + }}, tc.showAll, tc.filters...) + + containers, directives := comp(&cobra.Command{}, nil, "") + assert.Check(t, is.Equal(directives&tc.expDirective, tc.expDirective)) + assert.Check(t, is.DeepEqual(containers, tc.expOut)) + }) + } +} + func TestCompleteEnvVarNames(t *testing.T) { env.PatchAll(t, map[string]string{ "ENV_A": "hello-a", From 77d002ae25ec2020463d2adafeafb588a41e5d87 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sun, 13 Oct 2024 18:00:20 +0200 Subject: [PATCH 12/15] completion: add test for ImageNames Signed-off-by: Sebastiaan van Stijn (cherry picked from commit ab418a38d826d7137802840d2b406eead9d82cdb) Signed-off-by: Sebastiaan van Stijn --- cli/command/completion/functions_test.go | 55 ++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/cli/command/completion/functions_test.go b/cli/command/completion/functions_test.go index 943ae49169d7..dde30d2f662d 100644 --- a/cli/command/completion/functions_test.go +++ b/cli/command/completion/functions_test.go @@ -9,6 +9,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/image" "github.com/docker/docker/client" "github.com/google/go-cmp/cmp/cmpopts" "github.com/spf13/cobra" @@ -29,6 +30,7 @@ func (c fakeCLI) Client() client.APIClient { type fakeClient struct { client.Client containerListFunc func(options container.ListOptions) ([]types.Container, error) + imageListFunc func(options image.ListOptions) ([]image.Summary, error) } func (c *fakeClient) ContainerList(_ context.Context, options container.ListOptions) ([]types.Container, error) { @@ -38,6 +40,13 @@ func (c *fakeClient) ContainerList(_ context.Context, options container.ListOpti return []types.Container{}, nil } +func (c *fakeClient) ImageList(_ context.Context, options image.ListOptions) ([]image.Summary, error) { + if c.imageListFunc != nil { + return c.imageListFunc(options) + } + return []image.Summary{}, nil +} + func TestCompleteContainerNames(t *testing.T) { tests := []struct { doc string @@ -173,6 +182,52 @@ func TestCompleteFromList(t *testing.T) { assert.Check(t, is.DeepEqual(values, expected)) } +func TestCompleteImageNames(t *testing.T) { + tests := []struct { + doc string + images []image.Summary + expOut []string + expDirective cobra.ShellCompDirective + }{ + { + doc: "no results", + expDirective: cobra.ShellCompDirectiveNoFileComp, + }, + { + doc: "with results", + images: []image.Summary{ + {RepoTags: []string{"image-c:latest", "image-c:other"}}, + {RepoTags: []string{"image-b:latest", "image-b:other"}}, + {RepoTags: []string{"image-a:latest", "image-a:other"}}, + }, + expOut: []string{"image-c:latest", "image-c:other", "image-b:latest", "image-b:other", "image-a:latest", "image-a:other"}, + expDirective: cobra.ShellCompDirectiveNoFileComp, + }, + { + doc: "with error", + expDirective: cobra.ShellCompDirectiveError, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.doc, func(t *testing.T) { + comp := ImageNames(fakeCLI{&fakeClient{ + imageListFunc: func(options image.ListOptions) ([]image.Summary, error) { + if tc.expDirective == cobra.ShellCompDirectiveError { + return nil, errors.New("some error occurred") + } + return tc.images, nil + }, + }}) + + volumes, directives := comp(&cobra.Command{}, nil, "") + assert.Check(t, is.Equal(directives&tc.expDirective, tc.expDirective)) + assert.Check(t, is.DeepEqual(volumes, tc.expOut)) + }) + } +} + func TestCompleteNoComplete(t *testing.T) { values, directives := NoComplete(nil, nil, "") assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveNoFileComp)) From b68bf3afe44309e52f1bf993488d6b038586947e Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sun, 13 Oct 2024 18:02:35 +0200 Subject: [PATCH 13/15] completion: add test for NetworkNames Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 302d73f990f7ddba8ac5c61d84cd000dcf6c802c) Signed-off-by: Sebastiaan van Stijn --- cli/command/completion/functions_test.go | 55 ++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/cli/command/completion/functions_test.go b/cli/command/completion/functions_test.go index dde30d2f662d..fdf347450fa5 100644 --- a/cli/command/completion/functions_test.go +++ b/cli/command/completion/functions_test.go @@ -10,6 +10,7 @@ import ( "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/image" + "github.com/docker/docker/api/types/network" "github.com/docker/docker/client" "github.com/google/go-cmp/cmp/cmpopts" "github.com/spf13/cobra" @@ -31,6 +32,7 @@ type fakeClient struct { client.Client containerListFunc func(options container.ListOptions) ([]types.Container, error) imageListFunc func(options image.ListOptions) ([]image.Summary, error) + networkListFunc func(ctx context.Context, options network.ListOptions) ([]network.Summary, error) } func (c *fakeClient) ContainerList(_ context.Context, options container.ListOptions) ([]types.Container, error) { @@ -47,6 +49,13 @@ func (c *fakeClient) ImageList(_ context.Context, options image.ListOptions) ([] return []image.Summary{}, nil } +func (c *fakeClient) NetworkList(ctx context.Context, options network.ListOptions) ([]network.Summary, error) { + if c.networkListFunc != nil { + return c.networkListFunc(ctx, options) + } + return []network.Inspect{}, nil +} + func TestCompleteContainerNames(t *testing.T) { tests := []struct { doc string @@ -228,6 +237,52 @@ func TestCompleteImageNames(t *testing.T) { } } +func TestCompleteNetworkNames(t *testing.T) { + tests := []struct { + doc string + networks []network.Summary + expOut []string + expDirective cobra.ShellCompDirective + }{ + { + doc: "no results", + expDirective: cobra.ShellCompDirectiveNoFileComp, + }, + { + doc: "with results", + networks: []network.Summary{ + {ID: "nw-c", Name: "network-c"}, + {ID: "nw-b", Name: "network-b"}, + {ID: "nw-a", Name: "network-a"}, + }, + expOut: []string{"network-c", "network-b", "network-a"}, + expDirective: cobra.ShellCompDirectiveNoFileComp, + }, + { + doc: "with error", + expDirective: cobra.ShellCompDirectiveError, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.doc, func(t *testing.T) { + comp := NetworkNames(fakeCLI{&fakeClient{ + networkListFunc: func(ctx context.Context, options network.ListOptions) ([]network.Summary, error) { + if tc.expDirective == cobra.ShellCompDirectiveError { + return nil, errors.New("some error occurred") + } + return tc.networks, nil + }, + }}) + + volumes, directives := comp(&cobra.Command{}, nil, "") + assert.Check(t, is.Equal(directives&tc.expDirective, tc.expDirective)) + assert.Check(t, is.DeepEqual(volumes, tc.expOut)) + }) + } +} + func TestCompleteNoComplete(t *testing.T) { values, directives := NoComplete(nil, nil, "") assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveNoFileComp)) From e2831282ee177716595e536ccadf7dbc3932e1c7 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sun, 13 Oct 2024 18:03:32 +0200 Subject: [PATCH 14/15] completion: add test for VolumeNames Signed-off-by: Sebastiaan van Stijn (cherry picked from commit e1c472a436067150ff035f33fc49b943b363c216) Signed-off-by: Sebastiaan van Stijn --- cli/command/completion/functions_test.go | 55 ++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/cli/command/completion/functions_test.go b/cli/command/completion/functions_test.go index fdf347450fa5..c886d0e4bdb7 100644 --- a/cli/command/completion/functions_test.go +++ b/cli/command/completion/functions_test.go @@ -11,6 +11,7 @@ import ( "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/volume" "github.com/docker/docker/client" "github.com/google/go-cmp/cmp/cmpopts" "github.com/spf13/cobra" @@ -33,6 +34,7 @@ type fakeClient struct { containerListFunc func(options container.ListOptions) ([]types.Container, error) imageListFunc func(options image.ListOptions) ([]image.Summary, error) networkListFunc func(ctx context.Context, options network.ListOptions) ([]network.Summary, error) + volumeListFunc func(filter filters.Args) (volume.ListResponse, error) } func (c *fakeClient) ContainerList(_ context.Context, options container.ListOptions) ([]types.Container, error) { @@ -56,6 +58,13 @@ func (c *fakeClient) NetworkList(ctx context.Context, options network.ListOption return []network.Inspect{}, nil } +func (c *fakeClient) VolumeList(_ context.Context, options volume.ListOptions) (volume.ListResponse, error) { + if c.volumeListFunc != nil { + return c.volumeListFunc(options.Filters) + } + return volume.ListResponse{}, nil +} + func TestCompleteContainerNames(t *testing.T) { tests := []struct { doc string @@ -294,3 +303,49 @@ func TestCompletePlatforms(t *testing.T) { assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion") assert.Check(t, is.DeepEqual(values, commonPlatforms)) } + +func TestCompleteVolumeNames(t *testing.T) { + tests := []struct { + doc string + volumes []*volume.Volume + expOut []string + expDirective cobra.ShellCompDirective + }{ + { + doc: "no results", + expDirective: cobra.ShellCompDirectiveNoFileComp, + }, + { + doc: "with results", + volumes: []*volume.Volume{ + {Name: "volume-c"}, + {Name: "volume-b"}, + {Name: "volume-a"}, + }, + expOut: []string{"volume-c", "volume-b", "volume-a"}, + expDirective: cobra.ShellCompDirectiveNoFileComp, + }, + { + doc: "with error", + expDirective: cobra.ShellCompDirectiveError, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.doc, func(t *testing.T) { + comp := VolumeNames(fakeCLI{&fakeClient{ + volumeListFunc: func(filter filters.Args) (volume.ListResponse, error) { + if tc.expDirective == cobra.ShellCompDirectiveError { + return volume.ListResponse{}, errors.New("some error occurred") + } + return volume.ListResponse{Volumes: tc.volumes}, nil + }, + }}) + + volumes, directives := comp(&cobra.Command{}, nil, "") + assert.Check(t, is.Equal(directives&tc.expDirective, tc.expDirective)) + assert.Check(t, is.DeepEqual(volumes, tc.expOut)) + }) + } +} From b018e55ca4c7c830cf72d943e7cdaf40a1c9ec2f Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Thu, 10 Oct 2024 18:58:35 +0000 Subject: [PATCH 15/15] Only complete removable containers if --force is not given Signed-off-by: Harald Albers (cherry picked from commit 147630a3093c67625780b84ccb5b62fb7bbbc380) Signed-off-by: Sebastiaan van Stijn --- cli/command/container/rm.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cli/command/container/rm.go b/cli/command/container/rm.go index 171e8e81e9e4..b234b6009874 100644 --- a/cli/command/container/rm.go +++ b/cli/command/container/rm.go @@ -8,6 +8,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/errdefs" "github.com/pkg/errors" @@ -38,7 +39,9 @@ func NewRmCommand(dockerCli command.Cli) *cobra.Command { Annotations: map[string]string{ "aliases": "docker container rm, docker container remove, docker rm", }, - ValidArgsFunction: completion.ContainerNames(dockerCli, true), + ValidArgsFunction: completion.ContainerNames(dockerCli, true, func(ctr types.Container) bool { + return opts.force || ctr.State == "exited" || ctr.State == "created" + }), } flags := cmd.Flags()