Skip to content

Commit 9c117d3

Browse files
committed
cli/command/container: add shell completion for docker rm --link
When linking containers through legacy links, a container can get multiple names; its own name, and a name for each link it's providing: # create two containers with links between them docker run -d --name one nginx:alpine docker run -d --name two --link one:link1 --link one:link2 --link one:link3 nginx:alpine docker rm --link <tab> two/link1 two/link2 two/link3 Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
1 parent df57ff7 commit 9c117d3

File tree

3 files changed

+82
-3
lines changed

3 files changed

+82
-3
lines changed

cli/command/container/completion.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,35 @@ func completeLink(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
182182
}
183183
}
184184

185+
// completeLinks implements shell completion for the `--link` option of `rm --link`.
186+
//
187+
// It contacts the API to get names of legacy links on containers.
188+
// In case of an error, an empty list is returned.
189+
func completeLinks(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
190+
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
191+
res, err := dockerCLI.Client().ContainerList(cmd.Context(), client.ContainerListOptions{
192+
All: true,
193+
})
194+
if err != nil {
195+
return nil, cobra.ShellCompDirectiveError
196+
}
197+
var names []string
198+
for _, ctr := range res.Items {
199+
if len(ctr.Names) <= 1 {
200+
// Container has no links names.
201+
continue
202+
}
203+
for _, n := range ctr.Names {
204+
// Skip legacy link names: "/linked-container/link-name"
205+
if len(n) > 1 && strings.IndexByte(n[1:], '/') != -1 {
206+
names = append(names, strings.TrimPrefix(n, "/"))
207+
}
208+
}
209+
}
210+
return names, cobra.ShellCompDirectiveNoFileComp
211+
}
212+
}
213+
185214
// completeLogDriver implements shell completion for the `--log-driver` option of `run` and `create`.
186215
// The log drivers are collected from a call to the Info endpoint with a fallback to a hard-coded list
187216
// of the build-in log drivers.

cli/command/container/completion_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,44 @@ func TestCompleteSignals(t *testing.T) {
135135
assert.Check(t, len(values) > 1)
136136
assert.Check(t, is.Len(values, len(signal.SignalMap)))
137137
}
138+
139+
func TestCompleteLinks(t *testing.T) {
140+
tests := []struct {
141+
doc string
142+
showAll, showIDs bool
143+
filters []func(container.Summary) bool
144+
containers []container.Summary
145+
expOut []string
146+
expDirective cobra.ShellCompDirective
147+
}{
148+
{
149+
doc: "no results",
150+
expDirective: cobra.ShellCompDirectiveNoFileComp,
151+
},
152+
{
153+
doc: "all containers",
154+
showAll: true,
155+
containers: []container.Summary{
156+
{ID: "id-c", State: container.StateRunning, Names: []string{"/container-c", "/container-c/link-b", "/container-c/link-c"}},
157+
{ID: "id-b", State: container.StateCreated, Names: []string{"/container-b", "/container-b/link-a"}},
158+
{ID: "id-a", State: container.StateExited, Names: []string{"/container-a"}},
159+
},
160+
expOut: []string{"container-c/link-b", "container-c/link-c", "container-b/link-a"},
161+
expDirective: cobra.ShellCompDirectiveNoFileComp,
162+
},
163+
}
164+
165+
for _, tc := range tests {
166+
t.Run(tc.doc, func(t *testing.T) {
167+
comp := completeLinks(test.NewFakeCli(&fakeClient{
168+
containerListFunc: func(client.ContainerListOptions) (client.ContainerListResult, error) {
169+
return client.ContainerListResult{Items: tc.containers}, nil
170+
},
171+
}))
172+
173+
containers, directives := comp(&cobra.Command{}, nil, "")
174+
assert.Check(t, is.Equal(directives&tc.expDirective, tc.expDirective))
175+
assert.Check(t, is.DeepEqual(containers, tc.expOut))
176+
})
177+
}
178+
}

cli/command/container/rm.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ type rmOptions struct {
2727
func newRmCommand(dockerCLI command.Cli) *cobra.Command {
2828
var opts rmOptions
2929

30+
completeLinkNames := completeLinks(dockerCLI)
31+
completeNames := completion.ContainerNames(dockerCLI, true, func(ctr container.Summary) bool {
32+
return opts.force || ctr.State == container.StateExited || ctr.State == container.StateCreated
33+
})
34+
3035
cmd := &cobra.Command{
3136
Use: "rm [OPTIONS] CONTAINER [CONTAINER...]",
3237
Short: "Remove one or more containers",
@@ -38,9 +43,13 @@ func newRmCommand(dockerCLI command.Cli) *cobra.Command {
3843
Annotations: map[string]string{
3944
"aliases": "docker container rm, docker container remove, docker rm",
4045
},
41-
ValidArgsFunction: completion.ContainerNames(dockerCLI, true, func(ctr container.Summary) bool {
42-
return opts.force || ctr.State == container.StateExited || ctr.State == container.StateCreated
43-
}),
46+
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
47+
if opts.rmLink {
48+
// "--link" (remove link) is set; provide link names instead of container (primary) names.
49+
return completeLinkNames(cmd, args, toComplete)
50+
}
51+
return completeNames(cmd, args, toComplete)
52+
},
4453
DisableFlagsInUseLine: true,
4554
}
4655

0 commit comments

Comments
 (0)