|
| 1 | +--- |
| 2 | +kep-number: 24 |
| 3 | +title: Kubectl Plugins |
| 4 | +authors: |
| 5 | + - "@juanvallejo" |
| 6 | +owning-sig: sig-cli |
| 7 | +participating-sigs: |
| 8 | + - sig-cli |
| 9 | +reviewers: |
| 10 | + - "@pwittrock" |
| 11 | + - "@deads2k" |
| 12 | + - "@liggitt" |
| 13 | + - "@soltysh" |
| 14 | +approvers: |
| 15 | + - "@pwittrock" |
| 16 | + - "@soltysh" |
| 17 | +editor: juanvallejo |
| 18 | +creation-date: 2018-07-24 |
| 19 | +last-updated: 2018-08-09 |
| 20 | +status: provisional |
| 21 | +see-also: |
| 22 | + - n/a |
| 23 | +replaces: |
| 24 | + - "https://github.com/kubernetes/community/blob/master/contributors/design-proposals/cli/kubectl-extension.md" |
| 25 | + - "https://github.com/kubernetes/community/pull/481" |
| 26 | +superseded-by: |
| 27 | + - n/a |
| 28 | +--- |
| 29 | + |
| 30 | +# Kubectl Plugins |
| 31 | + |
| 32 | +## Table of Contents |
| 33 | + |
| 34 | +* [Table of Contents](#table-of-contents) |
| 35 | +* [Summary](#summary) |
| 36 | +* [Motivation](#motivation) |
| 37 | + * [Limitations of the Existing Design](#limitations-of-the-existing-design) |
| 38 | + * [Goals](#goals) |
| 39 | + * [Non-Goals](#non-goals) |
| 40 | +* [Proposal](#proposal) |
| 41 | + * [Scenarios](#scenarios) |
| 42 | + * [Implementation Details/Design/Constraints](#implementation-detailsdesign) |
| 43 | + * [Naming Conventions](#naming-conventions) |
| 44 | + * [Implementation Notes/Constraints](#implementation-notesconstraints) |
| 45 | + * [Risks and Mitigations](#risks-and-mitigations) |
| 46 | +* [Graduation Criteria](#graduation-criteria) |
| 47 | +* [Implementation History](#implementation-history) |
| 48 | +* [Drawbacks](#drawbacks) |
| 49 | +* [Future Improvements/Considerations](#future-improvementsconsiderations) |
| 50 | + |
| 51 | +## Summary |
| 52 | + |
| 53 | +This proposal introduces the main design for a plugin mechanism in `kubectl`. |
| 54 | +The mechanism is a git-style system, that looks for executables on a user's `$PATH` whose name begins with `kubectl-`. |
| 55 | +This allows plugin binaries to override existing command paths and add custom commands and subcommands to `kubectl`. |
| 56 | + |
| 57 | +## Motivation |
| 58 | + |
| 59 | +The main motivation behind a plugin system for `kubectl` stems from being able to provide users with a way to extend |
| 60 | +the functionality of `kubectl`, beyond what is offered by its core commands. |
| 61 | + |
| 62 | +By picturing the core commands provided by `kubectl` as essential building blocks for interacting with a Kubernetes |
| 63 | +cluster, we can begin to think of plugins as a means of using these building blocks to provide more complex functionality. |
| 64 | +A new command, `kubectl set-ns`, for example, could take advantage of the rudimentary functionality already provided by |
| 65 | +the `kubectl config` command, and build on top of it to provide users with a powerful, yet easy-to-use way of switching |
| 66 | +to a new namespace. |
| 67 | + |
| 68 | +For example, the user experience for switching namespaces could go from: |
| 69 | + |
| 70 | +```bash |
| 71 | +kubectl config set-context $(kubectl config current-context) --namespace=mynewnamespace |
| 72 | +``` |
| 73 | + |
| 74 | +to: |
| 75 | + |
| 76 | +``` |
| 77 | +kubectl set-ns mynewnamespace |
| 78 | +``` |
| 79 | + |
| 80 | +where `set-ns` would be a user-provided plugin which would call the initial `kubectl config set-context ...` command |
| 81 | +and set the namespace flag according to the value provided as the plugin's first parameter. |
| 82 | + |
| 83 | +The `set-ns` command above could have multiple variations, or be expanded to support subcommands with relative ease. |
| 84 | +Since plugins would be distributed by their authors, independent from the core Kubernetes repository, plugins could |
| 85 | +release updates and changes at their own pace. |
| 86 | + |
| 87 | +### Limitations of the Existing Design |
| 88 | + |
| 89 | +The existing alpha plugin system in `kubectl` presents a few limitations with its current design. |
| 90 | +It forces plugin scripts and executables to exist in a pre-determined location, requires a per-plugin metadata file for |
| 91 | +interpretation, and does not provide a clear way to override existing command paths or provide additional subcommands |
| 92 | +without having to override a top-level command. |
| 93 | + |
| 94 | +The proposed git-style re-design of the plugin system allows us to implement extensibility requests from users that the |
| 95 | +current system is unable to address. |
| 96 | +See https://github.com/kubernetes/kubernetes/issues/53640 and https://github.com/kubernetes/kubernetes/issues/55708. |
| 97 | + |
| 98 | +### Goals |
| 99 | + |
| 100 | +* Avoid any kind of installation process (no additional config, users drop an executable in their `PATH`, for example, |
| 101 | + and they are then able to use that plugin with `kubectl`). |
| 102 | + No additional configuration is needed, only the plugin executable. |
| 103 | + A plugin's filename determines the plugin's intention, such as which path in the command tree it applies to: |
| 104 | + `/usr/bin/kubectl-educate-dolphins` would, for example be invoked under the command `kubectl educate dolphins --flag1 --flag2`. |
| 105 | + It is up to a plugin to parse any arguments and flags given to it. A plugin decides when an argument is a |
| 106 | + subcommand, as well as any limitations or constraints that its flags should have. |
| 107 | +* Relay all information given to `kubectl` (via command line args) to plugins as-is. |
| 108 | + Plugins receive all arguments and flags provided by users and are responsible for adjusting their behavior |
| 109 | + accordingly. |
| 110 | +* Provide a way to limit which command paths can and cannot be overriddden by plugins in the command tree. |
| 111 | + |
| 112 | +### Non-Goals |
| 113 | + |
| 114 | +* The new plugin mechanism will not be a "plugin installer" or wizard. It will not have specific or baked-in knowledge |
| 115 | + regarding a plugin's location or composition, nor will it it provide a way to download or unpack plugins in a correct |
| 116 | + location. |
| 117 | +* Plugin discovery is not a main focus of this mechanism. As such, it will not attempt to collect data about every |
| 118 | + plugin that exists in an environment. |
| 119 | +* Plugin management is out of the scope of this design. A mechanism for updating and managing lifecycle of existing |
| 120 | + plugins should be covered as a separate design (See https://github.com/kubernetes/community/pull/2340). |
| 121 | +* Provide a standard package of common cli utilities that is consumed by `kubectl` and plugins alike. |
| 122 | + This should be done as an independent effort of this plugin mechanism. |
| 123 | + |
| 124 | +## Proposal |
| 125 | + |
| 126 | +### Scenarios |
| 127 | + |
| 128 | +* Developer wants to create and expose a plugin to `kubectl`. |
| 129 | + They use a programming language of their choice and create an executable file. |
| 130 | + The executable's filename consists of the command path to implement, and is prefixed with `kubectl-`. |
| 131 | + The executable file is placed on the user's `PATH`. |
| 132 | + |
| 133 | +### Implementation Details/Design |
| 134 | + |
| 135 | +The proposed design passes through all environment variables, flags, input, and output streams exactly as they are given |
| 136 | +to the parent `kubectl` process. This has the effect of letting plugins run without the need for any special parsing |
| 137 | +or case-handling in `kubectl`. |
| 138 | + |
| 139 | +In essence, a plugin binary must be able to run as a standalone process, completely independent of `kubectl`. |
| 140 | + |
| 141 | +* When `kubectl` is executed with a subcommand _foo_ that does not exist exist in the command tree, it will attempt to look |
| 142 | +for a filename `kubectl-foo` (`kubectl-foo.exe` on Windows) in the user's `PATH` and execute it, relaying all arguments given |
| 143 | +as well as all environment variables to the plugin child-process. |
| 144 | + |
| 145 | +A brief example (not an actual prototype) is provided below to clarify the core logic of the proposed design: |
| 146 | + |
| 147 | +```go |
| 148 | +// treat all args given by the user as pieces of a plugin binary's filename |
| 149 | +// and short-circuit once we find an arg that appears to be a flag. |
| 150 | +remainingArgs := []string{} // all "non-flag" arguments |
| 151 | + |
| 152 | +for idx := range cmdArgs { |
| 153 | + if strings.HasPrefix(cmdArgs[idx], "-") { |
| 154 | + break |
| 155 | + } |
| 156 | + remainingArgs = append(remainingArgs, strings.Replace(cmdArgs[idx], "-", "_", -1)) |
| 157 | +} |
| 158 | + |
| 159 | +foundBinaryPath := "" |
| 160 | + |
| 161 | +// find binary in the user's PATH, starting with the longest possible filename |
| 162 | +// based on the given non-flag arguments by the user |
| 163 | +for len(remainingArgs) > 0 { |
| 164 | + path, err := exec.LookPath(fmt.Sprintf("kubectl-%s", strings.Join(remainingArgs, "-"))) |
| 165 | + if err != nil || len(path) == 0 { |
| 166 | + remainingArgs = remainingArgs[:len(remainingArgs)-1] |
| 167 | + continue |
| 168 | + } |
| 169 | + |
| 170 | + foundBinaryPath = path |
| 171 | + break |
| 172 | +} |
| 173 | + |
| 174 | +// if we are able to find a suitable plugin executable, perform a syscall.Exec call |
| 175 | +// and relay all remaining arguments (in order given), as well as environment vars. |
| 176 | +syscall.Exec(foundBinaryPath, append([]string{foundBinaryPath}, cmdArgs[len(remainingArgs):]...), os.Environ()) |
| 177 | +``` |
| 178 | + |
| 179 | +#### Naming Conventions |
| 180 | + |
| 181 | +Under this proposal, `kubectl` would identify plugins by looking for filenames beginning with the `kubectl-` prefix. |
| 182 | +A search for these names would occur on a user's `PATH`. Only files that are executable and begin with this prefix |
| 183 | +would be identified. |
| 184 | + |
| 185 | +### Implementation Notes/Constraints |
| 186 | + |
| 187 | +The current implementation details for the proposed design rely on using a plugin executable's name to determine what |
| 188 | +command the plugin is adding. |
| 189 | +For a given command `kubectl foo --bar baz`, an executable `kubectl-foo` will be matched on a user's `PATH`, |
| 190 | +and the arguments `--bar baz` will be passed to it in that order. |
| 191 | + |
| 192 | +A potential limitation of this could present itself in the order of arguments provided by a user. |
| 193 | +A user could intend to run a plugin `kubectl-foo-bar` with the flag `--baz` with the following command |
| 194 | +`kubectl foo --baz bar`, but instead end up matching `kubectl-foo` with the flag `--baz` and the argument `bar` based |
| 195 | +on the placement of the flag `--baz`. |
| 196 | + |
| 197 | +A notable constraint of this design is that it excludes any form of plugin lifecycle management, or version compatibility. |
| 198 | +A plugin may depend on other plugins based on the decision of a plugin author, however the proposed design does nothing |
| 199 | +to facilitate such dependencies. It is up to the plugin's author (or a separate / independent plugin management system) to |
| 200 | +provide documentation or instructions on how to meet any dependencies required by a plugin. |
| 201 | + |
| 202 | +Further, with the proposed design, plugins that rely on multiple "helper" files to properly function, should provide an |
| 203 | +"entrypoint" executable (which is placed on a user's `PATH`), with any additional files located elsewhere (e.g. ~/.kubeplugins/myplugin/helper1.py). |
| 204 | + |
| 205 | +### Risks and Mitigations |
| 206 | + |
| 207 | +Unlike the existing alpha plugin mechanism, the proposed design does not constrain commands added by plugins to exist as subcommands of the |
| 208 | +`kubectl plugin` design. Commands provided by plugins under the new mechanism can be invoked as first-class commands (`/usr/bin/kubectl-foo` provides the `kubectl foo` parent command). |
| 209 | + |
| 210 | +A potential risk associated with this could present in the form of a "land-rush" by plugin providers. |
| 211 | +Multiple plugin authors would be incentivized to provide their own version of plugin `foo`. |
| 212 | +Users would be at the mercy of whichever variation of `kubectl-foo` is discovered in their `PATH` first when executing that command. |
| 213 | + |
| 214 | +A way to mitigate the above scenario would be to have users take advantage of the proposed plugin mechanism's design by renaming multiple variations of `kubectl-foo` |
| 215 | +to include the provider's name, for example: `kubectl-acme-foo`, or `kubectl-companyB-foo`. |
| 216 | + |
| 217 | +Conflicts such as this one could further be mitigated by a plugin manager, which could perform conflict resolution among similarly named plugins on behalf of a user. |
| 218 | + |
| 219 | +## Graduation Criteria |
| 220 | + |
| 221 | +* Make this mechanism a part of `kubectl`'s command-lookup logic. |
| 222 | + |
| 223 | +## Implementation History |
| 224 | + |
| 225 | +This plugin design closely follows major aspects of the plugin system design for `git`. |
| 226 | + |
| 227 | +## Drawbacks |
| 228 | + |
| 229 | +Implementing this design could potentially conflict with any ongoing work that depends on the current alpha plugin system. |
| 230 | + |
| 231 | +## Future Improvements/Considerations |
| 232 | + |
| 233 | +The proposed design is flexible enough to accommodate future updates that could allow certain command paths to be overwritten |
| 234 | +or extended (with the addition of subcommands) via plugins. |
0 commit comments