-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Adrian Orive <[email protected]>
- Loading branch information
Showing
1 changed file
with
276 additions
and
0 deletions.
There are no files selected for viewing
276 changes: 276 additions & 0 deletions
276
designs/extensible-cli-and-scaffolding-plugins-phase-1-5.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,276 @@ | ||
# Extensible CLI and Scaffolding Plugins - Phase 1.5 | ||
|
||
Continuation of [Extensible CLI and Scaffolding Plugins](./extensible-cli-and-scaffolding-plugins-phase-1.md). | ||
|
||
<a id="goal" /> | ||
|
||
## Goal | ||
|
||
The goal of this phase is to achieve one of the goals proposed for Phase 2: chaining plugins. | ||
Phase 2 includes several other challenging goals, but being able to chain plugins will be beneficial | ||
for third-party developers that are using kubebuilder as a library. | ||
|
||
## Table of contents | ||
- [Goal](#goal) | ||
- [Motivation](#motivation) | ||
- [Proposal](#proposal) | ||
- [Implementation](#implementation) | ||
|
||
<a id="motivation" /> | ||
|
||
## Motivation | ||
|
||
There are several cases of plugins that want to maintain most of the go plugin functionality and add | ||
certain features on top of it, both inside and outside kubebuilder repository: | ||
- [Addon pattern](../plugins/addon) | ||
- [Operator SDK](https://github.com/operator-framework/operator-sdk/tree/master/internal/plugins/golang) | ||
|
||
This behavior fits perfectly under Phase 1.5, where plugins could be chained. However, as this feature is | ||
not available, the adopted temporal solution is to wrap the base go plugin and perform additional actions | ||
after its `Run` method has been executed. This solution faces several issues: | ||
|
||
- Wrapper plugins are unable to access the data of the wrapped plugins, as they weren't designed for this | ||
purpose, and therefore, most of its internal data is non-exported. An example of this inaccessible data | ||
would be the `Resource` objects created inside the `create api` and `create webhook` commands. | ||
- Wrapper plugins are dependent on their wrapped plugins, and therefore can't be used for other plugins. | ||
- Under the hood, subcommands implement a second hidden interface: `RunOptions`, which further accentuates | ||
these issues. | ||
|
||
<a id="proposal" /> | ||
|
||
## Proposal | ||
|
||
Design a Plugin API that combines the current [`Subcommand`](../pkg/plugin/interfaces.go) and | ||
[`RunOptions`](../pkg/plugins/internal/cmdutil/cmdutil.go) interfaces and enables plugin-chaining. | ||
The new `Subcommand` methods can be split in two different categories: | ||
- Initialization methods | ||
- Execution methods | ||
|
||
Additionally, some of this methods may be optional, in which case a non-implemented method will be skipped | ||
when it should be called and consider it succeeded. This also allows to create some methods specific for | ||
a certain subcommand call (e.g.: `Resource`-related methods for the `edit` subcommand are not needed). | ||
|
||
Different ordering guarantees can be considered: | ||
- Method order guarantee: a method for a plugin will be called after its previous methods succeeded. | ||
- Steps order guarantee: methods will be called when all plugins have finished the previous method. | ||
- Plugin order guarantee: same method for each plugin will be called in the order specified | ||
by the plugin position at the plugin chain. | ||
|
||
Each method will specify which of these order guarantees are fulfilled. | ||
|
||
Execution methods will be able to return an error. A specific error can be returned to specify that | ||
no further methods of this plugin should be called, but that the scaffold process should be continued. | ||
This enables plugins to exit early, e.g., a plugin that scaffolds some files only for cluster-scoped | ||
resources can detect if the resource is cluster-scoped at one of the first execution steps, and | ||
therefore, use this error to tell the CLI that no further execution step should be called for itself. | ||
|
||
### Initialization methods | ||
|
||
#### Update metadata | ||
This method will be used for two purposes. It provides CLI-related metadata to the Subcommand (e.g., | ||
command name) and update the subcommands metadata such as the description or examples. | ||
|
||
- Required/optional | ||
- [ ] Required | ||
- [x] Optional | ||
- Subcommands | ||
- [x] Init | ||
- [x] Edit | ||
- [x] Create API | ||
- [x] Create webhook | ||
- Order guarantees | ||
- [x] Method order guarantee: first method. | ||
- [x] Step order guarantee: first method. | ||
- [x] Plugin order guarantee. | ||
|
||
#### Bind flags | ||
This method will allow subcommands to define specific flags. | ||
|
||
- Required/optional | ||
- [ ] Required | ||
- [x] Optional | ||
- Subcommands | ||
- [x] Init | ||
- [x] Edit | ||
- [x] Create API | ||
- [x] Create webhook | ||
- Order guarantees | ||
- [x] Method order guarantee. | ||
- [ ] Step order guarantee. | ||
- [x] Plugin order guarantee. | ||
|
||
### Execution methods | ||
|
||
#### Inject configuration | ||
This method will be used to inject the `Config` object that the plugin can modify at will. | ||
The CLI will create/load/save this configuration object. | ||
|
||
- Required/optional | ||
- [ ] Required | ||
- [x] Optional | ||
- Subcommands | ||
- [x] Init | ||
- [x] Edit | ||
- [x] Create API | ||
- [x] Create webhook | ||
- Order guarantees | ||
- [x] Method order guarantee. | ||
- [x] Step order guarantee. | ||
- [ ] Plugin order guarantee. | ||
|
||
#### Inject resource | ||
This method will be used to inject the `Resource` object. | ||
|
||
- Required/optional | ||
- [x] Required | ||
- [ ] Optional | ||
- Subcommands | ||
- [ ] Init | ||
- [ ] Edit | ||
- [x] Create API | ||
- [x] Create webhook | ||
- Order guarantees | ||
- [x] Method order guarantee. | ||
- [x] Step order guarantee. | ||
- [ ] Plugin order guarantee. | ||
|
||
#### Pre-scaffold | ||
This method will be used to take actions before the main scaffolding is performed, e.g. validations. | ||
NOTE: a filesystem abstraction will be passed to this method that must be used for scaffolding. | ||
|
||
- Required/optional | ||
- [ ] Required | ||
- [x] Optional | ||
- Subcommands | ||
- [x] Init | ||
- [x] Edit | ||
- [x] Create API | ||
- [x] Create webhook | ||
- Order guarantees | ||
- [x] Method order guarantee. | ||
- [x] Step order guarantee. | ||
- [x] Plugin order guarantee. | ||
|
||
#### Scaffold | ||
This method will be used to perform the main scaffolding. | ||
NOTE: a filesystem abstraction will be passed to this method that must be used for scaffolding. | ||
|
||
- Required/optional | ||
- [x] Required | ||
- [ ] Optional | ||
- Subcommands | ||
- [x] Init | ||
- [x] Edit | ||
- [x] Create API | ||
- [x] Create webhook | ||
- Order guarantees | ||
- [x] Method order guarantee. | ||
- [x] Step order guarantee. | ||
- [x] Plugin order guarantee. | ||
|
||
#### Post-scaffold | ||
This method will be used to take actions after the main scaffolding is performed, e.g. cleanup. | ||
NOTE: a filesystem abstraction will **NOT** be passed to this method, it should interact directly. | ||
|
||
- Required/optional | ||
- [ ] Required | ||
- [x] Optional | ||
- Subcommands | ||
- [x] Init | ||
- [x] Edit | ||
- [x] Create API | ||
- [x] Create webhook | ||
- Order guarantees | ||
- [x] Method order guarantee. | ||
- [x] Step order guarantee. | ||
- [x] Plugin order guarantee. | ||
|
||
<a id="implementation" /> | ||
|
||
## Implementation | ||
|
||
The following types are used as input/output values of the described methods: | ||
```go | ||
// CLIMetadata is the runtime meta-data of the CLI | ||
type CLIMetadata struct { | ||
// CommandName is the root command name. | ||
CommandName string | ||
} | ||
|
||
// SubcommandMetadata is the runtime meta-data for a subcommand | ||
type SubcommandMetadata struct { | ||
// Description is a description of what this subcommand does. It is used to display help. | ||
Description string | ||
// Examples are one or more examples of the command-line usage of this subcommand. It is used to display help. | ||
Examples string | ||
} | ||
|
||
type ExitError struct { | ||
Plugin string | ||
Reason string | ||
} | ||
|
||
func (e ExitError) Error() string { | ||
return fmt.Sprintf("plugin %s exit early: %s", e.Plugin, e.Reason) | ||
} | ||
``` | ||
|
||
The described methods are implemented through the use of the following interfaces. | ||
```go | ||
type RequiresCLIMetadata interface { | ||
InjectCLIMetadata(CLIMetadata) | ||
} | ||
|
||
type UpdatesSubcommandMetadata interface { | ||
UpdateSubcommandMetadata(*SubcommandMetadata) | ||
} | ||
|
||
type HasFlags interface { | ||
BindFlags(*pflag.FlagSet) | ||
} | ||
|
||
type RequiresConfig interface { | ||
InjectConfig(config.Config) error | ||
} | ||
|
||
type RequiresResource interface { | ||
InjectResource(afero.Fs) error | ||
} | ||
|
||
type HasPreScaffold interface { | ||
PreScaffold(afero.Fs) error | ||
} | ||
|
||
type Scaffolder interface { | ||
Scaffold(afero.Fs) error | ||
} | ||
|
||
type HasPostScaffold interface { | ||
PostScaffold() error | ||
} | ||
``` | ||
|
||
Additional interfaces define the required method for each type of plugin: | ||
```go | ||
// InitSubcommand is the specific interface for subcommands returned by init plugins. | ||
type InitSubcommand interface { | ||
Scaffolder | ||
} | ||
|
||
// EditSubcommand is the specific interface for subcommands returned by edit plugins. | ||
type EditSubcommand interface { | ||
Scaffolder | ||
} | ||
|
||
// CreateAPISubcommand is the specific interface for subcommands returned by create API plugins. | ||
type CreateAPISubcommand interface { | ||
RequiresResource | ||
Scaffolder | ||
} | ||
|
||
// CreateWebhookSubcommand is the specific interface for subcommands returned by create webhook plugins. | ||
type CreateWebhookSubcommand interface { | ||
RequiresResource | ||
Scaffolder | ||
} | ||
``` |