Skip to content

Commit e37d7db

Browse files
authored
feat: kubesafe completions (#18)
1 parent b5a2f78 commit e37d7db

19 files changed

Lines changed: 475 additions & 28 deletions

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ $ brew install kubesafe
6363
$ go install github.com/telemaco019/kubesafe/kubesafe@latest
6464
```
6565

66+
For shell completion setup, see [Shell completion](#shell-completion).
67+
6668
## Managing contexts
6769

6870
Kubesafe makes it easy to manage your safe contexts and protected commands. To see all available options, run:
@@ -183,6 +185,53 @@ use `kubesafe` as your `kubectl` command.
183185
184186
</details>
185187
188+
## Shell completion
189+
190+
Kubesafe supports shell completion for Bash, Zsh, and Fish. The completion scripts automatically delegate to the wrapped command's completion system when completing subcommands (e.g., `kubesafe kubectl get <TAB>` uses kubectl's completions).
191+
192+
### Bash
193+
194+
```bash
195+
# Load completions for commands you want to wrap (required for delegation)
196+
source <(kubectl completion bash)
197+
source <(helm completion bash) # Add other commands as needed
198+
199+
# Load kubesafe completion
200+
source <(kubesafe completion bash)
201+
```
202+
203+
To load completions for each session, add the above lines to your `~/.bashrc`.
204+
205+
### Zsh
206+
207+
```zsh
208+
# Load completions for commands you want to wrap (required for delegation)
209+
source <(kubectl completion zsh)
210+
source <(helm completion zsh) # Add other commands as needed
211+
212+
# Load kubesafe completion
213+
source <(kubesafe completion zsh)
214+
```
215+
216+
To load completions for each session, add the above lines to your `~/.zshrc`.
217+
218+
### Fish
219+
220+
```fish
221+
# Load completions for commands you want to wrap (required for delegation)
222+
kubectl completion fish | source
223+
helm completion fish | source # Add other commands as needed
224+
225+
# Load kubesafe completion
226+
kubesafe completion fish | source
227+
```
228+
229+
To load completions for each session, run:
230+
231+
```fish
232+
kubesafe completion fish > ~/.config/fish/completions/kubesafe.fish
233+
```
234+
186235
## Similar tools
187236
188237
Kubesafe draws inspiration from existing kubectl plugins that offer similar features but are restricted to working exclusively with kubectl:

internal/cmd/completion.go

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/*
2+
* Copyright 2026 Michele Zanotti <m.zanotti019@gmail.com>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package cmd
18+
19+
import (
20+
"bytes"
21+
"io"
22+
"os"
23+
24+
"github.com/spf13/cobra"
25+
)
26+
27+
const zshCompletionWrapper = `
28+
# Wrapper function that delegates to wrapped command's completion when appropriate
29+
_kubesafe_wrapper() {
30+
# words[1] is "kubesafe", words[2] is the wrapped command
31+
local wrapped_cmd="${words[2]}"
32+
33+
# If we have a wrapped command and we're completing after it, try to delegate
34+
if [[ -n "$wrapped_cmd" && $CURRENT -gt 2 ]]; then
35+
# Check if the wrapped command has a completion function
36+
if (( $+functions[_${wrapped_cmd}] )); then
37+
# Rebuild words array without kubesafe prefix
38+
words=("$wrapped_cmd" "${words[@]:3}")
39+
CURRENT=$((CURRENT - 1))
40+
_${wrapped_cmd}
41+
return
42+
fi
43+
fi
44+
45+
# Fall back to standard kubesafe completion
46+
_kubesafe
47+
}
48+
49+
compdef _kubesafe_wrapper kubesafe
50+
`
51+
52+
const bashCompletionWrapper = `
53+
# Wrapper function that delegates to wrapped command's completion when appropriate
54+
_kubesafe_wrapper() {
55+
# First positional argument after kubesafe is the wrapped command
56+
local wrapped_cmd="${COMP_WORDS[1]}"
57+
58+
# If we have a wrapped command and we're completing after it, try to delegate
59+
if [[ -n "$wrapped_cmd" && $COMP_CWORD -gt 1 ]]; then
60+
# Try to find the wrapped command's completion function
61+
local completion_func="__start_${wrapped_cmd}"
62+
if type "$completion_func" &>/dev/null; then
63+
# Build new COMP_WORDS without 'kubesafe' prefix
64+
local -a new_words=("${COMP_WORDS[@]:1}")
65+
local new_cword=$((COMP_CWORD - 1))
66+
67+
# Update completion variables and delegate
68+
COMP_WORDS=("${new_words[@]}")
69+
COMP_CWORD=$new_cword
70+
COMP_LINE="${new_words[*]}"
71+
COMP_POINT=${#COMP_LINE}
72+
$completion_func
73+
return
74+
fi
75+
fi
76+
77+
# Fall back to standard kubesafe completion
78+
__start_kubesafe
79+
}
80+
81+
complete -o default -F _kubesafe_wrapper kubesafe
82+
`
83+
84+
const fishCompletionWrapper = `
85+
# Function to check if we're completing after a wrapped command
86+
function __kubesafe_using_wrapped_command
87+
set -l cmd (commandline -opc)
88+
test (count $cmd) -gt 1
89+
end
90+
91+
# Function to get completions from the wrapped command
92+
function __kubesafe_complete_wrapped
93+
set -l cmd (commandline -opc)
94+
if test (count $cmd) -gt 1
95+
# Get the wrapped command (second word after kubesafe)
96+
set -l wrapped_cmd $cmd[2]
97+
# Build the command line as if it started with the wrapped command
98+
set -l wrapped_args $cmd[2..-1]
99+
# Get the current token being completed
100+
set -l current_token (commandline -ct)
101+
# Build the fake command line for completion
102+
set -l fake_cmdline (string join ' ' $wrapped_args)
103+
if test -n "$current_token"
104+
set fake_cmdline "$fake_cmdline"
105+
end
106+
# Get completions from the wrapped command
107+
complete -C "$fake_cmdline"
108+
end
109+
end
110+
111+
# Register completion that delegates to wrapped command when appropriate
112+
complete -c kubesafe -n '__kubesafe_using_wrapped_command' -a '(__kubesafe_complete_wrapped)' -f
113+
`
114+
115+
func genZshCompletionWithWrapper(cmd *cobra.Command, w io.Writer) error {
116+
buf := new(bytes.Buffer)
117+
if err := cmd.Root().GenZshCompletion(buf); err != nil {
118+
return err
119+
}
120+
if _, err := w.Write(buf.Bytes()); err != nil {
121+
return err
122+
}
123+
_, err := w.Write([]byte(zshCompletionWrapper))
124+
return err
125+
}
126+
127+
func genBashCompletionWithWrapper(cmd *cobra.Command, w io.Writer) error {
128+
buf := new(bytes.Buffer)
129+
if err := cmd.Root().GenBashCompletionV2(buf, true); err != nil {
130+
return err
131+
}
132+
if _, err := w.Write(buf.Bytes()); err != nil {
133+
return err
134+
}
135+
_, err := w.Write([]byte(bashCompletionWrapper))
136+
return err
137+
}
138+
139+
func genFishCompletionWithWrapper(cmd *cobra.Command, w io.Writer) error {
140+
buf := new(bytes.Buffer)
141+
if err := cmd.Root().GenFishCompletion(buf, true); err != nil {
142+
return err
143+
}
144+
if _, err := w.Write(buf.Bytes()); err != nil {
145+
return err
146+
}
147+
_, err := w.Write([]byte(fishCompletionWrapper))
148+
return err
149+
}
150+
151+
func NewCompletionCmd() *cobra.Command {
152+
completionCmd := &cobra.Command{
153+
Use: "completion [bash|zsh|fish]",
154+
Short: "Generate shell completion scripts",
155+
Long: `Generate shell completion scripts for kubesafe.
156+
157+
The generated scripts include support for completing wrapped commands
158+
when used through kubesafe (e.g., "kubesafe kubectl get <TAB>").
159+
Completions are delegated to the wrapped command's completion function
160+
if available.
161+
162+
To load completions:
163+
164+
Bash:
165+
$ source <(kubesafe completion bash)
166+
167+
# To load completions for each session, execute once:
168+
# Linux:
169+
$ kubesafe completion bash > /etc/bash_completion.d/kubesafe
170+
# macOS:
171+
$ kubesafe completion bash > $(brew --prefix)/etc/bash_completion.d/kubesafe
172+
173+
# Note: wrapped command completions must be loaded for delegation to work:
174+
$ source <(kubectl completion bash)
175+
176+
Zsh:
177+
# If shell completion is not already enabled in your environment,
178+
# you will need to enable it. You can execute the following once:
179+
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
180+
181+
# To load completions for each session, add to ~/.zshrc:
182+
$ source <(kubesafe completion zsh)
183+
184+
# Note: wrapped command completions must be loaded for delegation to work:
185+
$ source <(kubectl completion zsh)
186+
187+
# You will need to start a new shell for this setup to take effect.
188+
189+
Fish:
190+
$ kubesafe completion fish | source
191+
192+
# To load completions for each session, execute once:
193+
$ kubesafe completion fish > ~/.config/fish/completions/kubesafe.fish
194+
195+
# Note: wrapped command completions should also be loaded:
196+
$ kubectl completion fish | source
197+
`,
198+
DisableFlagsInUseLine: true,
199+
ValidArgs: []string{"bash", "zsh", "fish"},
200+
Args: cobra.ExactArgs(1),
201+
RunE: func(cmd *cobra.Command, args []string) error {
202+
switch args[0] {
203+
case "bash":
204+
return genBashCompletionWithWrapper(cmd, os.Stdout)
205+
case "zsh":
206+
return genZshCompletionWithWrapper(cmd, os.Stdout)
207+
case "fish":
208+
return genFishCompletionWithWrapper(cmd, os.Stdout)
209+
}
210+
return nil
211+
},
212+
}
213+
return completionCmd
214+
}

0 commit comments

Comments
 (0)