Skip to content

Commit 7dcac36

Browse files
committed
add nerdctl completion bash
Usage: add `source <(nerdctl completion bash)` to `~/.bash_profile`. Similar to `kubectl completion bash`. Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
1 parent 38397c0 commit 7dcac36

25 files changed

+477
-89
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ It does not necessarily mean that the corresponding features are missing in cont
194194
- [:whale: nerdctl events](#whale-nerdctl-events)
195195
- [:whale: nerdctl info](#whale-nerdctl-info)
196196
- [:whale: nerdctl version](#whale-nerdctl-version)
197+
- [Shell completion](#shell-completion)
198+
- [:nerd_face: nerdctl completion bash](#nerd_face-nerdctl-completion-bash)
197199
- [Global flags](#global-flags)
198200
- [Unimplemented Docker commands](#unimplemented-docker-commands)
199201
- [Additional documents](#additional-documents)
@@ -454,6 +456,17 @@ Display system-wide information
454456
### :whale: nerdctl version
455457
Show the nerdctl version information
456458

459+
## Shell completion
460+
461+
### :nerd_face: nerdctl completion bash
462+
463+
Show bash completion.
464+
465+
Usage: add the following line to `~/.bash_profile`:
466+
```bash
467+
source <(nerdctl completion bash)>
468+
```
469+
457470
## Global flags
458471
- :nerd_face: `-a`, `--address`: containerd address, optionally with "unix://" prefix
459472
- :whale: `-H`, `--host`: Docker-compatible alias for `-a`, `--address`

commit.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@ import (
3030

3131
var (
3232
commitCommand = &cli.Command{
33-
Name: "commit",
34-
Usage: "[flags] CONTAINER REPOSITORY[:TAG]",
35-
Description: "Create a new image from a container's changes",
36-
Action: commitAction,
33+
Name: "commit",
34+
Usage: "[flags] CONTAINER REPOSITORY[:TAG]",
35+
Description: "Create a new image from a container's changes",
36+
Action: commitAction,
37+
BashComplete: commitBashComplete,
3738
Flags: []cli.Flag{
3839
&cli.StringFlag{
3940
Name: "author",
@@ -103,3 +104,12 @@ func newCommitOpts(clicontext *cli.Context) (*commit.Opts, error) {
103104
Ref: named.String(),
104105
}, nil
105106
}
107+
108+
func commitBashComplete(clicontext *cli.Context) {
109+
if _, ok := isFlagCompletionContext(); ok {
110+
defaultBashComplete(clicontext)
111+
return
112+
}
113+
// show container names
114+
bashCompleteContainerNames(clicontext)
115+
}

completion.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
Copyright (C) nerdctl authors.
3+
Copyright (C) containerd authors.
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package main
19+
20+
import (
21+
"fmt"
22+
"os"
23+
"strings"
24+
25+
"github.com/urfave/cli/v2"
26+
)
27+
28+
var completionCommand = &cli.Command{
29+
Name: "completion",
30+
Usage: "Show shell completion",
31+
Subcommands: []*cli.Command{
32+
completionBashCommand,
33+
},
34+
}
35+
36+
var completionBashCommand = &cli.Command{
37+
Name: "bash",
38+
Usage: "Show bash completion (use with `source <(nerdctl completion bash)`)",
39+
Description: "Usage: add `source <(nerdctl completion bash)` to ~/.bash_profile",
40+
Action: completionBashAction,
41+
}
42+
43+
func completionBashAction(clicontext *cli.Context) error {
44+
tmpl := `#!/bin/bash
45+
# Autocompletion enabler for nerdctl.
46+
# Usage: add 'source <(nerdctl completion bash)' to ~/.bash_profile
47+
48+
# _cli_bash_autocomplete is from https://github.com/urfave/cli/blob/v2.3.0/autocomplete/bash_autocomplete (MIT License)
49+
_cli_bash_autocomplete() {
50+
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
51+
local cur opts base
52+
COMPREPLY=()
53+
cur="${COMP_WORDS[COMP_CWORD]}"
54+
if [[ "$cur" == "-"* ]]; then
55+
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion )
56+
else
57+
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
58+
fi
59+
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
60+
return 0
61+
fi
62+
}
63+
64+
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete nerdctl
65+
`
66+
_, err := fmt.Fprint(clicontext.App.Writer, tmpl)
67+
return err
68+
}
69+
70+
func isFlagCompletionContext() (string, bool) {
71+
args := os.Args
72+
// args[len(args)-1] == "--generate-bash-completion"
73+
// args[len(args)-2] == the current key stroke, e.g. "--ne" for "--net"
74+
if len(args) <= 2 {
75+
return "", false
76+
}
77+
userTyping := args[len(args)-2]
78+
if strings.HasPrefix(userTyping, "-") {
79+
return userTyping, true
80+
}
81+
return "", false
82+
}
83+
84+
func defaultBashComplete(clicontext *cli.Context) {
85+
cli.DefaultCompleteWithFlags(clicontext.Command)(clicontext)
86+
}

container_inspect.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,12 @@ import (
3131
)
3232

3333
var containerInspectCommand = &cli.Command{
34-
Name: "inspect",
35-
Usage: "Display detailed information on one or more containers.",
36-
ArgsUsage: "[flags] CONTAINER [CONTAINER, ...]",
37-
Description: "Hint: set `--mode=native` for showing the full output",
38-
Action: containerInspectAction,
34+
Name: "inspect",
35+
Usage: "Display detailed information on one or more containers.",
36+
ArgsUsage: "[flags] CONTAINER [CONTAINER, ...]",
37+
Description: "Hint: set `--mode=native` for showing the full output",
38+
Action: containerInspectAction,
39+
BashComplete: containerInspectBashComplete,
3940
Flags: []cli.Flag{
4041
&cli.StringFlag{
4142
Name: "mode",
@@ -113,3 +114,12 @@ func (x *containerInspector) Handler(ctx context.Context, found containerwalker.
113114
}
114115
return nil
115116
}
117+
118+
func containerInspectBashComplete(clicontext *cli.Context) {
119+
if _, ok := isFlagCompletionContext(); ok {
120+
defaultBashComplete(clicontext)
121+
return
122+
}
123+
// show container names
124+
bashCompleteContainerNames(clicontext)
125+
}

exec.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ var execCommand = &cli.Command{
7171
Usage: "Give extended privileges to the command",
7272
},
7373
},
74-
Action: execAction,
74+
Action: execAction,
75+
BashComplete: execBashComplete,
7576
}
7677

7778
func execAction(clicontext *cli.Context) error {
@@ -241,3 +242,12 @@ func generateExecProcessSpec(ctx context.Context, clicontext *cli.Context, conta
241242

242243
return pspec, nil
243244
}
245+
246+
func execBashComplete(clicontext *cli.Context) {
247+
if _, ok := isFlagCompletionContext(); ok {
248+
defaultBashComplete(clicontext)
249+
return
250+
}
251+
// show container names
252+
bashCompleteContainerNames(clicontext)
253+
}

image_convert.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ When '--all-platforms' is given all images in a manifest list must be available.
162162
fmt.Fprintln(context.App.Writer, newImg.Target.Digest.String())
163163
return nil
164164
},
165+
BashComplete: imageConvertBashComplete,
165166
}
166167

167168
func getESGZConvertOpts(context *cli.Context) ([]estargz.Option, error) {
@@ -203,3 +204,12 @@ func readPathsFromRecordFile(filename string) ([]string, error) {
203204
}
204205
return paths, nil
205206
}
207+
208+
func imageConvertBashComplete(clicontext *cli.Context) {
209+
if _, ok := isFlagCompletionContext(); ok {
210+
defaultBashComplete(clicontext)
211+
return
212+
}
213+
// show image names
214+
bashCompleteImageNames(clicontext)
215+
}

inspect.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ import (
2222
)
2323

2424
var inspectCommand = &cli.Command{
25-
Name: "inspect",
26-
Usage: "Return low-level information on objects. Currently, only supports container objects.",
27-
Description: containerInspectCommand.Description,
28-
Action: containerInspectAction,
29-
Flags: containerInspectCommand.Flags,
25+
Name: "inspect",
26+
Usage: "Return low-level information on objects. Currently, only supports container objects.",
27+
Description: containerInspectCommand.Description,
28+
Action: containerInspectAction,
29+
BashComplete: containerInspectBashComplete,
30+
Flags: containerInspectCommand.Flags,
3031
}

kill.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ import (
3434
)
3535

3636
var killCommand = &cli.Command{
37-
Name: "kill",
38-
Usage: "Kill one or more running containers",
39-
ArgsUsage: "[flags] CONTAINER [CONTAINER, ...]",
40-
Action: killAction,
37+
Name: "kill",
38+
Usage: "Kill one or more running containers",
39+
ArgsUsage: "[flags] CONTAINER [CONTAINER, ...]",
40+
Action: killAction,
41+
BashComplete: killBashComplete,
4142
Flags: []cli.Flag{
4243
&cli.StringFlag{
4344
Name: "signal",
@@ -127,3 +128,12 @@ func killContainer(ctx context.Context, container containerd.Container, signal s
127128
}
128129
return nil
129130
}
131+
132+
func killBashComplete(clicontext *cli.Context) {
133+
if _, ok := isFlagCompletionContext(); ok {
134+
defaultBashComplete(clicontext)
135+
return
136+
}
137+
// show container names (TODO: filter already stopped containers)
138+
bashCompleteContainerNames(clicontext)
139+
}

logs.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ import (
2929
)
3030

3131
var logsCommand = &cli.Command{
32-
Name: "logs",
33-
Usage: "Fetch the logs of a container. Currently, only containers created with `nerdctl run -d` are supported.",
34-
ArgsUsage: "[flags] CONTAINER",
35-
Action: logsAction,
32+
Name: "logs",
33+
Usage: "Fetch the logs of a container. Currently, only containers created with `nerdctl run -d` are supported.",
34+
ArgsUsage: "[flags] CONTAINER",
35+
Action: logsAction,
36+
BashComplete: logsBashComplete,
3637
}
3738

3839
func logsAction(clicontext *cli.Context) error {
@@ -81,3 +82,12 @@ func logsAction(clicontext *cli.Context) error {
8182
}
8283
return nil
8384
}
85+
86+
func logsBashComplete(clicontext *cli.Context) {
87+
if _, ok := isFlagCompletionContext(); ok {
88+
defaultBashComplete(clicontext)
89+
return
90+
}
91+
// show container names (TODO: only show containers with logs)
92+
bashCompleteContainerNames(clicontext)
93+
}

main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ func newApp() *cli.App {
5555
app.Name = "nerdctl"
5656
app.Usage = "Docker-compatible CLI for containerd"
5757
app.UseShortOptionHandling = true
58+
app.EnableBashCompletion = true
5859
app.Version = strings.TrimPrefix(version.Version, "v")
5960
app.Flags = []cli.Flag{
6061
&cli.BoolFlag{
@@ -176,6 +177,8 @@ func newApp() *cli.App {
176177
loginCommand,
177178
// Logout
178179
logoutCommand,
180+
// Completion
181+
completionCommand,
179182
}
180183
return app
181184
}

network_inspect.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,12 @@ import (
2828
)
2929

3030
var networkInspectCommand = &cli.Command{
31-
Name: "inspect",
32-
Usage: "Display detailed information on one or more networks",
33-
ArgsUsage: "[flags] NETWORK [NETWORK, ...]",
34-
Description: "NOTE: The output format is not compatible with Docker.",
35-
Action: networkInspectAction,
31+
Name: "inspect",
32+
Usage: "Display detailed information on one or more networks",
33+
ArgsUsage: "[flags] NETWORK [NETWORK, ...]",
34+
Description: "NOTE: The output format is not compatible with Docker.",
35+
Action: networkInspectAction,
36+
BashComplete: networkInspectBashComplete,
3637
}
3738

3839
func networkInspectAction(clicontext *cli.Context) error {
@@ -78,3 +79,16 @@ func networkInspectAction(clicontext *cli.Context) error {
7879
fmt.Fprintln(clicontext.App.Writer, string(b))
7980
return nil
8081
}
82+
83+
func networkInspectBashComplete(clicontext *cli.Context) {
84+
if _, ok := isFlagCompletionContext(); ok {
85+
defaultBashComplete(clicontext)
86+
return
87+
}
88+
// show network names
89+
bashCompleteNetworkNames(clicontext)
90+
91+
// For `nerdctl network inspect`, print built-in "bridge" as well
92+
w := clicontext.App.Writer
93+
fmt.Fprintln(w, "bridge")
94+
}

network_rm.go

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@ import (
2828
)
2929

3030
var networkRmCommand = &cli.Command{
31-
Name: "rm",
32-
Aliases: []string{"remove"},
33-
Usage: "Remove one or more networks",
34-
ArgsUsage: "[flags] NETWORK [NETWORK, ...]",
35-
Description: "NOTE: network in use is deleted without caution",
36-
Action: networkRmAction,
31+
Name: "rm",
32+
Aliases: []string{"remove"},
33+
Usage: "Remove one or more networks",
34+
ArgsUsage: "[flags] NETWORK [NETWORK, ...]",
35+
Description: "NOTE: network in use is deleted without caution",
36+
Action: networkRmAction,
37+
BashComplete: networkRmBashComplete,
3738
}
3839

3940
func networkRmAction(clicontext *cli.Context) error {
@@ -79,3 +80,27 @@ func networkRmAction(clicontext *cli.Context) error {
7980
}
8081
return lockutil.WithDirLock(netconfpath, fn)
8182
}
83+
84+
func networkRmBashComplete(clicontext *cli.Context) {
85+
if _, ok := isFlagCompletionContext(); ok {
86+
defaultBashComplete(clicontext)
87+
return
88+
}
89+
// show network names
90+
bashCompleteNetworkNames(clicontext)
91+
}
92+
93+
func bashCompleteNetworkNames(clicontext *cli.Context) {
94+
w := clicontext.App.Writer
95+
e := &netutil.CNIEnv{
96+
Path: clicontext.String("cni-path"),
97+
NetconfPath: clicontext.String("cni-netconfpath"),
98+
}
99+
ll, err := netutil.ConfigLists(e)
100+
if err != nil {
101+
return
102+
}
103+
for _, l := range ll {
104+
fmt.Fprintln(w, l.Name)
105+
}
106+
}

0 commit comments

Comments
 (0)