diff --git a/cli/command/container/completion.go b/cli/command/container/completion.go index 23a1dedfd7..b69c969e71 100644 --- a/cli/command/container/completion.go +++ b/cli/command/container/completion.go @@ -68,6 +68,7 @@ func addCompletions(cmd *cobra.Command, dockerCLI completion.APIClientProvider) _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) _ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever)) _ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies) + _ = cmd.RegisterFlagCompletionFunc("security-opt", completeSecurityOpt) _ = cmd.RegisterFlagCompletionFunc("stop-signal", completeSignals) _ = cmd.RegisterFlagCompletionFunc("storage-opt", completeStorageOpt) _ = cmd.RegisterFlagCompletionFunc("ulimit", completeUlimit) @@ -122,6 +123,32 @@ func completePid(dockerCLI completion.APIClientProvider) func(cmd *cobra.Command } } +// 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 +} + // 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 diff --git a/cli/command/container/completion_test.go b/cli/command/container/completion_test.go index 62d69b533a..0977fdc519 100644 --- a/cli/command/container/completion_test.go +++ b/cli/command/container/completion_test.go @@ -73,6 +73,59 @@ func TestCompleteRestartPolicies(t *testing.T) { assert.Check(t, is.DeepEqual(values, expected)) } +func TestCompleteSecurityOpt(t *testing.T) { + tests := []struct { + toComplete string + expectedCompletions []string + expectedDirective cobra.ShellCompDirective + }{ + { + toComplete: "", + expectedCompletions: []string{"apparmor=", "label=", "no-new-privileges", "seccomp=", "systempaths=unconfined"}, + expectedDirective: cobra.ShellCompDirectiveNoFileComp, + }, + { + toComplete: "apparmor=", + expectedCompletions: []string{"apparmor="}, + expectedDirective: cobra.ShellCompDirectiveNoSpace, + }, + { + toComplete: "label=", + expectedCompletions: []string{"label=disable", "label=level:", "label=role:", "label=type:", "label=user:"}, + expectedDirective: cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp, + }, + { + toComplete: "s", + // We do not filter matching completions but delegate this task to the shell script. + expectedCompletions: []string{"apparmor=", "label=", "no-new-privileges", "seccomp=", "systempaths=unconfined"}, + expectedDirective: cobra.ShellCompDirectiveNoFileComp, + }, + { + toComplete: "se", + expectedCompletions: []string{"seccomp="}, + expectedDirective: cobra.ShellCompDirectiveNoSpace, + }, + { + toComplete: "seccomp=", + expectedCompletions: []string{"seccomp=unconfined"}, + expectedDirective: cobra.ShellCompDirectiveNoFileComp, + }, + { + toComplete: "sy", + expectedCompletions: []string{"apparmor=", "label=", "no-new-privileges", "seccomp=", "systempaths=unconfined"}, + expectedDirective: cobra.ShellCompDirectiveNoFileComp, + }, + } + + for _, tc := range tests { + t.Run(tc.toComplete, func(t *testing.T) { + completions, directive := completeSecurityOpt(nil, nil, tc.toComplete) + assert.Check(t, is.DeepEqual(completions, tc.expectedCompletions)) + assert.Check(t, is.Equal(directive, tc.expectedDirective)) + }) + } +} + 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")