Skip to content

Commit

Permalink
Plugin phase 1.5 EP
Browse files Browse the repository at this point in the history
Signed-off-by: Adrian Orive <[email protected]>
  • Loading branch information
Adirio committed Feb 4, 2021
1 parent 3c8e370 commit 8bc3276
Showing 1 changed file with 276 additions and 0 deletions.
276 changes: 276 additions & 0 deletions designs/extensible-cli-and-scaffolding-plugins-phase-1-5.md
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
}
```

0 comments on commit 8bc3276

Please sign in to comment.