-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Begin prototyping plugin invocation #9
Conversation
0ce1e8d
to
68259c2
Compare
Example: $ cat ./plugins/kubectl-plugin-foo
#!/bin/bash
echo "Hello world" $ kubectl plugin foo
Hello world $ kubectl plugin nonexistent
error: unknown command "nonexistent"
See 'kubectl plugin -h' for help and examples. |
68259c2
to
261838b
Compare
TODO:
$ ls plugins
kubectl-plugin-foo kubectl-plugin-foo-bar $ kubectl plugin foo bar baz
# executing binary `kubectl-plugin-foo-bar` $ kubectl plugin foo baz buz
# executing binary `kubectl-plugin-foo` |
a6baaa6
to
95199b2
Compare
cc @liggitt |
pkg/kubectl/cmd/cmd.go
Outdated
|
||
platformSuffix := "" | ||
if runtime.GOOS == "windows" { | ||
platformSuffix = ".exe" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LookPath on windows appears to append this on its own - https://golang.org/src/os/exec/lp_windows.go#L59
pkg/kubectl/cmd/cmd.go
Outdated
} | ||
|
||
// invoke cmd binary relaying the current environment and args given | ||
if err := syscall.Exec(foundBinaryPath, os.Args[1:], os.Environ()); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- does this preserve the current working dir of the kubectl invocation?
- should we pass
os.Args[len(remainingArgs):]
, so thatkubectl plugin foo bar baz
passesbaz
tokubectl-plugin-foo-bar
? - should we pass
cmdArgs[...]
rather thanos.Args[...]
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this preserve the current working dir of the kubectl invocation?
At least on a local env, I saw this to be true - os.Getwd()
in kubectl returned the same value as pwd
in the plugin executable when run via the execve call.
should we pass os.Args[len(remainingArgs):], so that kubectl plugin foo bar baz passes baz to kubectl-plugin-foo-bar?
I was not sure about this since there was some discussion about passing args "as-is", but I'm not against this.
pkg/kubectl/cmd/cmd.go
Outdated
func handleEndpointExtensions(cmdArgs []string) { | ||
remainingArgs := cmdArgs | ||
for idx := range remainingArgs { | ||
remainingArgs[idx] = strings.Replace(remainingArgs[idx], "-", "_", -1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- should we stop as soon as we encounter an option (an arg starting with
-
)? - append the normalized form to a new slice, overwriting here modifies cmdArgs and cmdPathPieces and os.Args
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we stop as soon as we encounter an option (an arg starting with -)?
Would this be an invalid operation? Could we instead "move" those from remainingArgs to a separate slice, then append them to the list of args that we ultimately pass to the subprocess? I would be in support of allowing flags to be passed as arguments to plugins
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
append the normalized form to a new slice, overwriting here modifies cmdArgs and cmdPathPieces and os.Args
ack
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we instead "move" those from remainingArgs to a separate slice, then append them to the list of args that we ultimately pass to the subprocess?
we don't have enough info to move flags here. you would also have to move accompanying value args (e.g. move --foo bar
as a unit, without knowing whether --foo
is an arg that expects a value).
I would be in support of allowing flags to be passed as arguments to plugins
absolutely, I'm saying we should not include them in the list of args considered when searching for a named plugin.
If I call kubectl plugins foo bar --baz qux
, we should start our search with kubectl-plugins-foo-bar
. If we find kubectl-plugins-foo
, I'd expect to pass the args ["bar","--baz","qux"]
to it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this implies all flags come after the command name, which is sometimes-required-and-sometimes-not for native kubectl commands, depending on whether they are global flags like --namespace or command-specific flags like --dry-run
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I call kubectl plugins foo bar --baz qux, we should start our search with kubectl-plugins-foo-bar. If we find kubectl-plugins-foo, I'd expect to pass the args ["bar","--baz","qux"] to it
This was my original approach when searching for a plugin binary, however @deads2k pointed out that if a user has two binaries kubectl-plugins-foo
and kubectl-plugins-foo-bar
in his PATH, it is more reasonable for our use-case that we match kubectl-plugins-foo-bar
over the other one, hence why I switched to reverse matching the binary name.
Also, thinking about flag handling a bit more, another scenario to consider would be when a user specifies a flag [and a potential value for it] in the middle of a command invocation: kubectl plugin foo --flagname maybeflagvalue bar baz
(binary to match here could be kubectl-plugin-foo-bar)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was my original approach when searching for a plugin binary, however @deads2k pointed out that if a user has two binaries kubectl-plugins-foo and kubectl-plugins-foo-bar in his PATH, it is more reasonable for our use-case that we match kubectl-plugins-foo-bar over the other one, hence why I switched to reverse matching the binary name.
I agree with the reverse search. In my example, the reverse search would consist of:
- kubectl-plugins-foo-bar
- kubectl-plugins-foo
It would not include kubectl-plugins-foo-bar-__bar-qux
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, thinking about flag handling a bit more, another scenario to consider would be when a user specifies a flag [and a potential value for it] in the middle of a command invocation: kubectl plugin foo --flagname maybeflagvalue bar baz (binary to match here could be kubectl-plugin-foo-bar)
We don't have sufficient information to support that case. That's what I was trying to describe in this comment:
we don't have enough info to move flags here. you would also have to move accompanying value args (e.g. move --foo bar as a unit, without knowing whether --foo is an arg that expects a value).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't have sufficient information to support that case. That's what I was trying to describe in this comment:
Makes sense, will update reverse search to take "flag-value" pairs into account. Thanks
is that not inherited by an exec call? |
@liggitt it is, we use same way to execute in |
e8b71b0
to
91f8077
Compare
Example with --flags $ ls plugins
kubectl-plugin-foo kubectl-plugin-foo-bar $ kubectl plugin foo bar --flag buz baz
exec plugin "foo bar"
with options: baz --flag buz $ kubectl plugin foo bar --flag=buz baz
exec plugin "foo bar"
with options: baz --flag=buz $ touch plugins/kubectl-plugin-foo-bar-baz
# populate new plugin file...
$ ls plugins
kubectl-plugin-foo kubectl-plugin-foo-bar kubectl-plugin-foo-bar-baz $ kubectl plugin foo bar --flag buz baz
exec plugin "foo bar baz"
with options: --flag buz |
don't reorder args at all... you don't know if the arg following a flag is associated with the flag or not
notably,
|
Done in latest commit: $ kubectl plugin foo bar --flag buz baz
exec plugin "foo bar"
with options: --flag buz baz $ kubectl plugin foo bar baz --flag buz
exec plugin "foo bar"
with options: baz --flag buz $ kubectl plugin foo --flag buz bar baz
exec plugin "foo bar"
with options: --flag buz baz |
close, but the last one didn't look right:
as soon as you hit a flag arg, stop searching for plugins |
105e962
to
fe78d6d
Compare
ack, updated last commit |
pkg/kubectl/cmd/cmd.go
Outdated
// if dealing with a binary extension. | ||
currentCmd := cmd | ||
canBeExtended := false | ||
for _, target := range cmdPathPieces { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can this be simplified to:
args := os.Args[1:]
leafCmd, _, _ := cmd.Find(args)
if leafCmd != nil && leafCmd.Annotations[EndpointCanBeExtendedAnnotation] == "true" {
handleEndpointExtensions(args)
}
fe78d6d
to
1ef6617
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
601080a
to
b4cc10d
Compare
b4cc10d
to
d5c2c4d
Compare
d5c2c4d
to
da2908e
Compare
This has been opened against upstream. |
Opening here first to generate some discussion about implementation details.
With the current design, a user is able to place an executable file anywhere on their PATH.
A search is performed based on the command line arguments given by the user.
For a path
kubectl plugin foo
, for example, we:kubectl
cannot be "extended", therefore, we treat that as a "command-not-found" error always.foo
's parent was marked as "can-be-extended", we attempt to look for the executablekubectl-plugin-foo
[3] (kubectl-plugin-foo.exe
on windows) [2] in the user's PATH.kubectl-plugin-foo
) is not found in the user's PATH, we exit with error [1].foo
's parent was not marked as "can-be-extended", we exit with error [1].kubectl-plugin-...
, if we were writing a plugin for "create", for example, we would look for a binarykubectl-create-foo
kubectl plugin foo bar baz
, and the user's PATH contains two binaries (kubectl-plugin-foo
, andkubectl-plugin-foo-bar-baz
), thenkubectl-plugin-foo-bar-baz
would always be executed.cc @deads2k @soltysh @mfojtik