2024-07-05 09:54:09 -04:00
package container
import (
2024-09-26 07:35:46 -04:00
"strings"
"sync"
2024-07-05 09:54:09 -04:00
"github.com/docker/cli/cli/command/completion"
2024-07-05 18:48:51 -04:00
"github.com/docker/docker/api/types/container"
2024-09-26 07:35:46 -04:00
"github.com/moby/sys/capability"
2024-07-05 19:46:47 -04:00
"github.com/moby/sys/signal"
2024-07-05 09:54:09 -04:00
"github.com/spf13/cobra"
)
2024-09-26 07:35:46 -04:00
// allCaps is the magic value for "all capabilities".
const allCaps = "ALL"
2024-07-05 09:54:09 -04:00
// allLinuxCapabilities is a list of all known Linux capabilities.
//
// TODO(thaJeztah): add descriptions, and enable descriptions for our completion scripts (cobra.CompletionOptions.DisableDescriptions is currently set to "true")
2024-09-26 07:35:46 -04:00
// 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
} )
2024-07-05 09:54:09 -04:00
2024-11-08 07:15:28 -05:00
// logDriverOptions provides the options for each built-in logging driver.
var logDriverOptions = map [ string ] [ ] string {
"awslogs" : {
"max-buffer-size" , "mode" , "awslogs-create-group" , "awslogs-credentials-endpoint" , "awslogs-datetime-format" ,
"awslogs-group" , "awslogs-multiline-pattern" , "awslogs-region" , "awslogs-stream" , "tag" ,
} ,
"fluentd" : {
"max-buffer-size" , "mode" , "env" , "env-regex" , "labels" , "fluentd-address" , "fluentd-async" ,
"fluentd-buffer-limit" , "fluentd-request-ack" , "fluentd-retry-wait" , "fluentd-max-retries" ,
"fluentd-sub-second-precision" , "tag" ,
} ,
"gcplogs" : {
"max-buffer-size" , "mode" , "env" , "env-regex" , "labels" , "gcp-log-cmd" , "gcp-meta-id" , "gcp-meta-name" ,
"gcp-meta-zone" , "gcp-project" ,
} ,
"gelf" : {
"max-buffer-size" , "mode" , "env" , "env-regex" , "labels" , "gelf-address" , "gelf-compression-level" ,
"gelf-compression-type" , "gelf-tcp-max-reconnect" , "gelf-tcp-reconnect-delay" , "tag" ,
} ,
"journald" : { "max-buffer-size" , "mode" , "env" , "env-regex" , "labels" , "tag" } ,
"json-file" : { "max-buffer-size" , "mode" , "env" , "env-regex" , "labels" , "compress" , "max-file" , "max-size" } ,
"local" : { "max-buffer-size" , "mode" , "compress" , "max-file" , "max-size" } ,
"none" : { } ,
"splunk" : {
"max-buffer-size" , "mode" , "env" , "env-regex" , "labels" , "splunk-caname" , "splunk-capath" , "splunk-format" ,
"splunk-gzip" , "splunk-gzip-level" , "splunk-index" , "splunk-insecureskipverify" , "splunk-source" ,
"splunk-sourcetype" , "splunk-token" , "splunk-url" , "splunk-verify-connection" , "tag" ,
} ,
"syslog" : {
"max-buffer-size" , "mode" , "env" , "env-regex" , "labels" , "syslog-address" , "syslog-facility" , "syslog-format" ,
"syslog-tls-ca-cert" , "syslog-tls-cert" , "syslog-tls-key" , "syslog-tls-skip-verify" , "tag" ,
} ,
}
// builtInLogDrivers provides a list of the built-in logging drivers.
var builtInLogDrivers = sync . OnceValue ( func ( ) [ ] string {
drivers := make ( [ ] string , 0 , len ( logDriverOptions ) )
for driver := range logDriverOptions {
drivers = append ( drivers , driver )
}
return drivers
} )
// allLogDriverOptions provides all options of the built-in logging drivers.
// The list does not contain duplicates.
var allLogDriverOptions = sync . OnceValue ( func ( ) [ ] string {
var result [ ] string
seen := make ( map [ string ] bool )
for driver := range logDriverOptions {
for _ , opt := range logDriverOptions [ driver ] {
if ! seen [ opt ] {
seen [ opt ] = true
result = append ( result , opt )
}
}
}
return result
} )
2024-07-05 18:48:51 -04:00
// restartPolicies is a list of all valid restart-policies..
//
// TODO(thaJeztah): add descriptions, and enable descriptions for our completion scripts (cobra.CompletionOptions.DisableDescriptions is currently set to "true")
var restartPolicies = [ ] string {
string ( container . RestartPolicyDisabled ) ,
string ( container . RestartPolicyAlways ) ,
string ( container . RestartPolicyOnFailure ) ,
string ( container . RestartPolicyUnlessStopped ) ,
}
2024-10-22 15:40:31 -04:00
// addCompletions adds the completions that `run` and `create` have in common.
func addCompletions ( cmd * cobra . Command , dockerCLI completion . APIClientProvider ) {
2024-10-23 09:09:13 -04:00
_ = cmd . RegisterFlagCompletionFunc ( "attach" , completion . FromList ( "stderr" , "stdin" , "stdout" ) )
2024-10-22 15:40:31 -04:00
_ = cmd . RegisterFlagCompletionFunc ( "cap-add" , completeLinuxCapabilityNames )
_ = cmd . RegisterFlagCompletionFunc ( "cap-drop" , completeLinuxCapabilityNames )
2024-10-30 11:55:19 -04:00
_ = cmd . RegisterFlagCompletionFunc ( "cgroupns" , completeCgroupns ( ) )
2024-10-22 15:40:31 -04:00
_ = cmd . RegisterFlagCompletionFunc ( "env" , completion . EnvVarNames )
_ = cmd . RegisterFlagCompletionFunc ( "env-file" , completion . FileNames )
2024-10-24 07:08:41 -04:00
_ = cmd . RegisterFlagCompletionFunc ( "ipc" , completeIpc ( dockerCLI ) )
2024-10-24 07:31:25 -04:00
_ = cmd . RegisterFlagCompletionFunc ( "link" , completeLink ( dockerCLI ) )
2024-11-08 07:15:28 -05:00
_ = cmd . RegisterFlagCompletionFunc ( "log-driver" , completeLogDriver ( dockerCLI ) )
_ = cmd . RegisterFlagCompletionFunc ( "log-opt" , completeLogOpt )
2024-10-22 15:40:31 -04:00
_ = cmd . RegisterFlagCompletionFunc ( "network" , completion . NetworkNames ( dockerCLI ) )
2024-10-24 09:12:51 -04:00
_ = cmd . RegisterFlagCompletionFunc ( "pid" , completePid ( dockerCLI ) )
2024-10-22 15:40:31 -04:00
_ = cmd . RegisterFlagCompletionFunc ( "platform" , completion . Platforms )
_ = cmd . RegisterFlagCompletionFunc ( "pull" , completion . FromList ( PullImageAlways , PullImageMissing , PullImageNever ) )
_ = cmd . RegisterFlagCompletionFunc ( "restart" , completeRestartPolicies )
2024-10-24 17:41:05 -04:00
_ = cmd . RegisterFlagCompletionFunc ( "security-opt" , completeSecurityOpt )
2024-10-22 15:40:31 -04:00
_ = cmd . RegisterFlagCompletionFunc ( "stop-signal" , completeSignals )
2024-10-24 09:45:23 -04:00
_ = cmd . RegisterFlagCompletionFunc ( "storage-opt" , completeStorageOpt )
2024-10-24 10:04:15 -04:00
_ = cmd . RegisterFlagCompletionFunc ( "ulimit" , completeUlimit )
2024-10-24 10:09:32 -04:00
_ = cmd . RegisterFlagCompletionFunc ( "userns" , completion . FromList ( "host" ) )
2024-10-30 10:55:19 -04:00
_ = cmd . RegisterFlagCompletionFunc ( "uts" , completion . FromList ( "host" ) )
2024-10-30 12:24:35 -04:00
_ = cmd . RegisterFlagCompletionFunc ( "volume-driver" , completeVolumeDriver ( dockerCLI ) )
2024-10-22 15:40:31 -04:00
_ = cmd . RegisterFlagCompletionFunc ( "volumes-from" , completion . ContainerNames ( dockerCLI , true ) )
}
2024-10-30 11:55:19 -04:00
// completeCgroupns implements shell completion for the `--cgroupns` option of `run` and `create`.
func completeCgroupns ( ) completion . ValidArgsFn {
return completion . FromList ( string ( container . CgroupnsModeHost ) , string ( container . CgroupnsModePrivate ) )
}
2024-10-24 16:14:49 -04:00
// completeDetachKeys implements shell completion for the `--detach-keys` option of `run` and `create`.
func completeDetachKeys ( _ * cobra . Command , _ [ ] string , _ string ) ( [ ] string , cobra . ShellCompDirective ) {
return [ ] string { "ctrl-" } , cobra . ShellCompDirectiveNoSpace
}
2024-10-24 07:08:41 -04:00
// completeIpc implements shell completion for the `--ipc` option of `run` and `create`.
// The completion is partly composite.
func completeIpc ( dockerCLI completion . APIClientProvider ) func ( cmd * cobra . Command , args [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
return func ( cmd * cobra . Command , args [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
if len ( toComplete ) > 0 && strings . HasPrefix ( "container" , toComplete ) { //nolint:gocritic // not swapped, matches partly typed "container"
return [ ] string { "container:" } , cobra . ShellCompDirectiveNoSpace
}
if strings . HasPrefix ( toComplete , "container:" ) {
names , _ := completion . ContainerNames ( dockerCLI , true ) ( cmd , args , toComplete )
return prefixWith ( "container:" , names ) , cobra . ShellCompDirectiveNoFileComp
}
return [ ] string {
string ( container . IPCModeContainer + ":" ) ,
string ( container . IPCModeHost ) ,
string ( container . IPCModeNone ) ,
string ( container . IPCModePrivate ) ,
string ( container . IPCModeShareable ) ,
} , cobra . ShellCompDirectiveNoFileComp
}
}
2024-10-24 07:31:25 -04:00
// completeLink implements shell completion for the `--link` option of `run` and `create`.
func completeLink ( dockerCLI completion . APIClientProvider ) func ( cmd * cobra . Command , args [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
return func ( cmd * cobra . Command , args [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
return postfixWith ( ":" , containerNames ( dockerCLI , cmd , args , toComplete ) ) , cobra . ShellCompDirectiveNoSpace
}
}
2024-11-08 07:15:28 -05:00
// completeLogDriver implements shell completion for the `--log-driver` option of `run` and `create`.
// The log drivers are collected from a call to the Info endpoint with a fallback to a hard-coded list
// of the build-in log drivers.
func completeLogDriver ( dockerCLI completion . APIClientProvider ) completion . ValidArgsFn {
return func ( cmd * cobra . Command , args [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
info , err := dockerCLI . Client ( ) . Info ( cmd . Context ( ) )
if err != nil {
return builtInLogDrivers ( ) , cobra . ShellCompDirectiveNoFileComp
}
drivers := info . Plugins . Log
return drivers , cobra . ShellCompDirectiveNoFileComp
}
}
// completeLogOpt implements shell completion for the `--log-opt` option of `run` and `create`.
// If the user supplied a log-driver, only options for that driver are returned.
func completeLogOpt ( cmd * cobra . Command , _ [ ] string , _ string ) ( [ ] string , cobra . ShellCompDirective ) {
driver , _ := cmd . Flags ( ) . GetString ( "log-driver" )
if options , exists := logDriverOptions [ driver ] ; exists {
return postfixWith ( "=" , options ) , cobra . ShellCompDirectiveNoSpace | cobra . ShellCompDirectiveNoFileComp
}
return postfixWith ( "=" , allLogDriverOptions ( ) ) , cobra . ShellCompDirectiveNoSpace
}
2024-10-24 09:12:51 -04:00
// completePid implements shell completion for the `--pid` option of `run` and `create`.
func completePid ( dockerCLI completion . APIClientProvider ) func ( cmd * cobra . Command , args [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
return func ( cmd * cobra . Command , args [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
if len ( toComplete ) > 0 && strings . HasPrefix ( "container" , toComplete ) { //nolint:gocritic // not swapped, matches partly typed "container"
return [ ] string { "container:" } , cobra . ShellCompDirectiveNoSpace
}
if strings . HasPrefix ( toComplete , "container:" ) {
names , _ := completion . ContainerNames ( dockerCLI , true ) ( cmd , args , toComplete )
return prefixWith ( "container:" , names ) , cobra . ShellCompDirectiveNoFileComp
}
return [ ] string { "container:" , "host" } , cobra . ShellCompDirectiveNoFileComp
}
}
2024-10-24 17:41:05 -04:00
// completeSecurityOpt implements shell completion for the `--security-opt` option of `run` and `create`.
// The completion is partly composite.
func completeSecurityOpt ( _ * cobra . Command , _ [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
if len ( toComplete ) > 0 && strings . HasPrefix ( "apparmor=" , toComplete ) { //nolint:gocritic // not swapped, matches partly typed "apparmor="
return [ ] string { "apparmor=" } , cobra . ShellCompDirectiveNoSpace
}
if len ( toComplete ) > 0 && strings . HasPrefix ( "label" , toComplete ) { //nolint:gocritic // not swapped, matches partly typed "label"
return [ ] string { "label=" } , cobra . ShellCompDirectiveNoSpace
}
if strings . HasPrefix ( toComplete , "label=" ) {
if strings . HasPrefix ( toComplete , "label=d" ) {
return [ ] string { "label=disable" } , cobra . ShellCompDirectiveNoFileComp
}
labels := [ ] string { "disable" , "level:" , "role:" , "type:" , "user:" }
return prefixWith ( "label=" , labels ) , cobra . ShellCompDirectiveNoSpace | cobra . ShellCompDirectiveNoFileComp
}
// length must be > 1 here so that completion of "s" falls through.
if len ( toComplete ) > 1 && strings . HasPrefix ( "seccomp" , toComplete ) { //nolint:gocritic // not swapped, matches partly typed "seccomp"
return [ ] string { "seccomp=" } , cobra . ShellCompDirectiveNoSpace
}
if strings . HasPrefix ( toComplete , "seccomp=" ) {
return [ ] string { "seccomp=unconfined" } , cobra . ShellCompDirectiveNoFileComp
}
return [ ] string { "apparmor=" , "label=" , "no-new-privileges" , "seccomp=" , "systempaths=unconfined" } , cobra . ShellCompDirectiveNoFileComp
}
2024-10-24 09:45:23 -04:00
// completeStorageOpt implements shell completion for the `--storage-opt` option of `run` and `create`.
func completeStorageOpt ( _ * cobra . Command , _ [ ] string , _ string ) ( [ ] string , cobra . ShellCompDirective ) {
return [ ] string { "size=" } , cobra . ShellCompDirectiveNoSpace
}
2024-10-24 10:04:15 -04:00
// completeUlimit implements shell completion for the `--ulimit` option of `run` and `create`.
func completeUlimit ( _ * cobra . Command , _ [ ] string , _ string ) ( [ ] string , cobra . ShellCompDirective ) {
limits := [ ] string {
"as" ,
"chroot" ,
"core" ,
"cpu" ,
"data" ,
"fsize" ,
"locks" ,
"maxlogins" ,
"maxsyslogins" ,
"memlock" ,
"msgqueue" ,
"nice" ,
"nofile" ,
"nproc" ,
"priority" ,
"rss" ,
"rtprio" ,
"sigpending" ,
"stack" ,
}
return postfixWith ( "=" , limits ) , cobra . ShellCompDirectiveNoSpace
}
2024-10-30 12:24:35 -04:00
// completeVolumeDriver contacts the API to get the built-in and installed volume drivers.
func completeVolumeDriver ( dockerCLI completion . APIClientProvider ) completion . ValidArgsFn {
return func ( cmd * cobra . Command , _ [ ] string , _ string ) ( [ ] string , cobra . ShellCompDirective ) {
info , err := dockerCLI . Client ( ) . Info ( cmd . Context ( ) )
if err != nil {
// fallback: the built-in drivers
return [ ] string { "local" } , cobra . ShellCompDirectiveNoFileComp
}
drivers := info . Plugins . Volume
return drivers , cobra . ShellCompDirectiveNoFileComp
}
}
2024-10-24 07:31:25 -04:00
// containerNames contacts the API to get names and optionally IDs of containers.
// In case of an error, an empty list is returned.
func containerNames ( dockerCLI completion . APIClientProvider , cmd * cobra . Command , args [ ] string , toComplete string ) [ ] string {
names , _ := completion . ContainerNames ( dockerCLI , true ) ( cmd , args , toComplete )
if names == nil {
return [ ] string { }
}
return names
}
2024-10-24 07:08:41 -04:00
// prefixWith prefixes every element in the slice with the given prefix.
func prefixWith ( prefix string , values [ ] string ) [ ] string {
result := make ( [ ] string , len ( values ) )
for i , v := range values {
result [ i ] = prefix + v
}
return result
}
2024-10-24 07:31:25 -04:00
// postfixWith appends postfix to every element in the slice.
func postfixWith ( postfix string , values [ ] string ) [ ] string {
result := make ( [ ] string , len ( values ) )
for i , v := range values {
result [ i ] = v + postfix
}
return result
}
2024-07-05 09:54:09 -04:00
func completeLinuxCapabilityNames ( cmd * cobra . Command , args [ ] string , toComplete string ) ( names [ ] string , _ cobra . ShellCompDirective ) {
2024-09-26 07:35:46 -04:00
return completion . FromList ( allLinuxCapabilities ( ) ... ) ( cmd , args , toComplete )
2024-07-05 09:54:09 -04:00
}
2024-07-05 18:48:51 -04:00
func completeRestartPolicies ( cmd * cobra . Command , args [ ] string , toComplete string ) ( names [ ] string , _ cobra . ShellCompDirective ) {
return completion . FromList ( restartPolicies ... ) ( cmd , args , toComplete )
}
2024-07-05 19:46:47 -04:00
func completeSignals ( cmd * cobra . Command , args [ ] string , toComplete string ) ( names [ ] string , _ cobra . ShellCompDirective ) {
// TODO(thaJeztah): do we want to provide the full list here, or a subset?
signalNames := make ( [ ] string , 0 , len ( signal . SignalMap ) )
for k := range signal . SignalMap {
signalNames = append ( signalNames , k )
}
return completion . FromList ( signalNames ... ) ( cmd , args , toComplete )
}