Skip to content

Commit 7d4f9bd

Browse files
authored
Merge pull request docker#6872 from thaJeztah/link_completions
shell completions: add shell completion for `docker rm --link` and exclude legacy links for container names
2 parents 2daa2c3 + 9c117d3 commit 7d4f9bd

File tree

5 files changed

+94
-10
lines changed

5 files changed

+94
-10
lines changed

cli/command/completion/functions.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"strings"
66

77
"github.com/distribution/reference"
8-
"github.com/docker/cli/cli/command/formatter"
98
"github.com/moby/moby/api/types/container"
109
"github.com/moby/moby/client"
1110
"github.com/spf13/cobra"
@@ -101,7 +100,13 @@ func ContainerNames(dockerCLI APIClientProvider, all bool, filters ...func(conta
101100
if showContainerIDs {
102101
names = append(names, ctr.ID)
103102
}
104-
names = append(names, formatter.StripNamePrefix(ctr.Names)...)
103+
for _, n := range ctr.Names {
104+
// Skip legacy link names: "/linked-container/link-name"
105+
if len(n) <= 1 || strings.IndexByte(n[1:], '/') != -1 {
106+
continue
107+
}
108+
names = append(names, strings.TrimPrefix(n, "/"))
109+
}
105110
}
106111
return names, cobra.ShellCompDirectiveNoFileComp
107112
})

cli/command/completion/functions_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func TestCompleteContainerNames(t *testing.T) {
8484
{ID: "id-b", State: container.StateCreated, Names: []string{"/container-b"}},
8585
{ID: "id-a", State: container.StateExited, Names: []string{"/container-a"}},
8686
},
87-
expOut: []string{"container-c", "container-c/link-b", "container-b", "container-a"},
87+
expOut: []string{"container-c", "container-b", "container-a"},
8888
expOpts: client.ContainerListOptions{All: true},
8989
expDirective: cobra.ShellCompDirectiveNoFileComp,
9090
},
@@ -97,7 +97,7 @@ func TestCompleteContainerNames(t *testing.T) {
9797
{ID: "id-b", State: container.StateCreated, Names: []string{"/container-b"}},
9898
{ID: "id-a", State: container.StateExited, Names: []string{"/container-a"}},
9999
},
100-
expOut: []string{"id-c", "container-c", "container-c/link-b", "id-b", "container-b", "id-a", "container-a"},
100+
expOut: []string{"id-c", "container-c", "id-b", "container-b", "id-a", "container-a"},
101101
expOpts: client.ContainerListOptions{All: true},
102102
expDirective: cobra.ShellCompDirectiveNoFileComp,
103103
},
@@ -107,7 +107,7 @@ func TestCompleteContainerNames(t *testing.T) {
107107
containers: []container.Summary{
108108
{ID: "id-c", State: container.StateRunning, Names: []string{"/container-c", "/container-c/link-b"}},
109109
},
110-
expOut: []string{"container-c", "container-c/link-b"},
110+
expOut: []string{"container-c"},
111111
expDirective: cobra.ShellCompDirectiveNoFileComp,
112112
},
113113
{
@@ -117,7 +117,7 @@ func TestCompleteContainerNames(t *testing.T) {
117117
func(ctr container.Summary) bool { return ctr.State == container.StateCreated },
118118
},
119119
containers: []container.Summary{
120-
{ID: "id-c", State: container.StateRunning, Names: []string{"/container-c", "/container-c/link-b"}},
120+
{ID: "id-c", State: container.StateRunning, Names: []string{"/container-c"}},
121121
{ID: "id-b", State: container.StateCreated, Names: []string{"/container-b"}},
122122
{ID: "id-a", State: container.StateExited, Names: []string{"/container-a"}},
123123
},
@@ -133,7 +133,7 @@ func TestCompleteContainerNames(t *testing.T) {
133133
func(ctr container.Summary) bool { return ctr.State == container.StateCreated },
134134
},
135135
containers: []container.Summary{
136-
{ID: "id-c", State: container.StateRunning, Names: []string{"/container-c", "/container-c/link-b"}},
136+
{ID: "id-c", State: container.StateRunning, Names: []string{"/container-c"}},
137137
{ID: "id-b", State: container.StateCreated, Names: []string{"/container-b"}},
138138
{ID: "id-a", State: container.StateCreated, Names: []string{"/container-a"}},
139139
},

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)