Skip to content

Commit 757cc8a

Browse files
authored
Merge pull request #1990 from Adirio/plugin-phase-1-5
📖 Plugin phase 1.5 EP
2 parents c82530b + 3948289 commit 757cc8a

File tree

1 file changed

+258
-0
lines changed

1 file changed

+258
-0
lines changed
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
# Extensible CLI and Scaffolding Plugins - Phase 1.5
2+
3+
Continuation of [Extensible CLI and Scaffolding Plugins](./extensible-cli-and-scaffolding-plugins-phase-1.md).
4+
5+
## Goal
6+
7+
The goal of this phase is to achieve one of the goals proposed for Phase 2: chaining plugins.
8+
Phase 2 includes several other challenging goals, but being able to chain plugins will be beneficial
9+
for third-party developers that are using kubebuilder as a library.
10+
11+
## Table of contents
12+
- [Goal](#goal)
13+
- [Motivation](#motivation)
14+
- [Proposal](#proposal)
15+
- [Implementation](#implementation)
16+
17+
## Motivation
18+
19+
There are several cases of plugins that want to maintain most of the go plugin functionality and add
20+
certain features on top of it, both inside and outside kubebuilder repository:
21+
- [Addon pattern](../plugins/addon)
22+
- [Operator SDK](https://github.com/operator-framework/operator-sdk/tree/master/internal/plugins/golang)
23+
24+
This behavior fits perfectly under Phase 1.5, where plugins could be chained. However, as this feature is
25+
not available, the adopted temporal solution is to wrap the base go plugin and perform additional actions
26+
after its `Run` method has been executed. This solution faces several issues:
27+
28+
- Wrapper plugins are unable to access the data of the wrapped plugins, as they weren't designed for this
29+
purpose, and therefore, most of its internal data is non-exported. An example of this inaccessible data
30+
would be the `Resource` objects created inside the `create api` and `create webhook` commands.
31+
- Wrapper plugins are dependent on their wrapped plugins, and therefore can't be used for other plugins.
32+
- Under the hood, subcommands implement a second hidden interface: `RunOptions`, which further accentuates
33+
these issues.
34+
35+
Plugin chaining solves the aforementioned problems but the current plugin API, and more specifically the
36+
`Subcommand` interface, does not support plugin chaining.
37+
38+
- The `RunOptions` interface implemented under the hood is not part of the plugin API, and therefore
39+
the cli is not able to run post-scaffold logic (implemented in `RunOptions.PostScaffold` method) after
40+
all the plugins have scaffolded their part.
41+
- `Resource`-related commands can't bind flags like `--group`, `--version` or `--kind` in each plugin,
42+
it must be created outside the plugins and then injected into them similar to the approach followed
43+
currently for `Config` objects.
44+
45+
## Proposal
46+
47+
Design a Plugin API that combines the current [`Subcommand`](../pkg/plugin/interfaces.go) and
48+
[`RunOptions`](../pkg/plugins/internal/cmdutil/cmdutil.go) interfaces and enables plugin-chaining.
49+
The new `Subcommand` methods can be split in two different categories:
50+
- Initialization methods
51+
- Execution methods
52+
53+
Additionally, some of these methods may be optional, in which case a non-implemented method will be skipped
54+
when it should be called and consider it succeeded. This also allows to create some methods specific for
55+
a certain subcommand call (e.g.: `Resource`-related methods for the `edit` subcommand are not needed).
56+
57+
Different ordering guarantees can be considered:
58+
- Method order guarantee: a method for a plugin will be called after its previous methods succeeded.
59+
- Steps order guarantee: methods will be called when all plugins have finished the previous method.
60+
- Plugin order guarantee: same method for each plugin will be called in the order specified
61+
by the plugin position at the plugin chain.
62+
63+
All of the methods will offer plugin order guarantee, as they all modify/update some item so the order
64+
of plugins is important. Execution methods need to guarantee step order, as the items that are being modified
65+
in each step (config, resource, and filesystem) are also needed in the following steps. This is not true for
66+
initialization methods that modify items (metadata and flagset) that are only used in their own methods,
67+
so they only need to guarantee method order.
68+
69+
Execution methods will be able to return an error. A specific error can be returned to specify that
70+
no further methods of this plugin should be called, but that the scaffold process should be continued.
71+
This enables plugins to exit early, e.g., a plugin that scaffolds some files only for cluster-scoped
72+
resources can detect if the resource is cluster-scoped at one of the first execution steps, and
73+
therefore, use this error to tell the CLI that no further execution step should be called for itself.
74+
75+
### Initialization methods
76+
77+
#### Update metadata
78+
This method will be used for two purposes. It provides CLI-related metadata to the Subcommand (e.g.,
79+
command name) and update the subcommands metadata such as the description or examples.
80+
81+
- Required/optional
82+
- [ ] Required
83+
- [x] Optional
84+
- Subcommands
85+
- [x] Init
86+
- [x] Edit
87+
- [x] Create API
88+
- [x] Create webhook
89+
90+
#### Bind flags
91+
This method will allow subcommands to define specific flags.
92+
93+
- Required/optional
94+
- [ ] Required
95+
- [x] Optional
96+
- Subcommands
97+
- [x] Init
98+
- [x] Edit
99+
- [x] Create API
100+
- [x] Create webhook
101+
102+
### Execution methods
103+
104+
#### Inject configuration
105+
This method will be used to inject the `Config` object that the plugin can modify at will.
106+
The CLI will create/load/save this configuration object.
107+
108+
- Required/optional
109+
- [ ] Required
110+
- [x] Optional
111+
- Subcommands
112+
- [x] Init
113+
- [x] Edit
114+
- [x] Create API
115+
- [x] Create webhook
116+
117+
#### Inject resource
118+
This method will be used to inject the `Resource` object.
119+
120+
- Required/optional
121+
- [x] Required
122+
- [ ] Optional
123+
- Subcommands
124+
- [ ] Init
125+
- [ ] Edit
126+
- [x] Create API
127+
- [x] Create webhook
128+
129+
#### Pre-scaffold
130+
This method will be used to take actions before the main scaffolding is performed, e.g. validations.
131+
132+
NOTE: a filesystem abstraction will be passed to this method that must be used for scaffolding.
133+
134+
- Required/optional
135+
- [ ] Required
136+
- [x] Optional
137+
- Subcommands
138+
- [x] Init
139+
- [x] Edit
140+
- [x] Create API
141+
- [x] Create webhook
142+
143+
#### Scaffold
144+
This method will be used to perform the main scaffolding.
145+
146+
NOTE: a filesystem abstraction will be passed to this method that must be used for scaffolding.
147+
148+
- Required/optional
149+
- [x] Required
150+
- [ ] Optional
151+
- Subcommands
152+
- [x] Init
153+
- [x] Edit
154+
- [x] Create API
155+
- [x] Create webhook
156+
157+
#### Post-scaffold
158+
This method will be used to take actions after the main scaffolding is performed, e.g. cleanup.
159+
160+
NOTE: a filesystem abstraction will **NOT** be passed to this method, as post-scaffold task do not require it.
161+
In case some post-scaffold task requires a filesystem abstraction, it could be added.
162+
163+
- Required/optional
164+
- [ ] Required
165+
- [x] Optional
166+
- Subcommands
167+
- [x] Init
168+
- [x] Edit
169+
- [x] Create API
170+
- [x] Create webhook
171+
172+
## Implementation
173+
174+
The following types are used as input/output values of the described methods:
175+
```go
176+
// CLIMetadata is the runtime meta-data of the CLI
177+
type CLIMetadata struct {
178+
// CommandName is the root command name.
179+
CommandName string
180+
}
181+
182+
// SubcommandMetadata is the runtime meta-data for a subcommand
183+
type SubcommandMetadata struct {
184+
// Description is a description of what this subcommand does. It is used to display help.
185+
Description string
186+
// Examples are one or more examples of the command-line usage of this subcommand. It is used to display help.
187+
Examples string
188+
}
189+
190+
type ExitError struct {
191+
Plugin string
192+
Reason string
193+
}
194+
195+
func (e ExitError) Error() string {
196+
return fmt.Sprintf("plugin %s exit early: %s", e.Plugin, e.Reason)
197+
}
198+
```
199+
200+
The described methods are implemented through the use of the following interfaces.
201+
```go
202+
type RequiresCLIMetadata interface {
203+
InjectCLIMetadata(CLIMetadata)
204+
}
205+
206+
type UpdatesSubcommandMetadata interface {
207+
UpdateSubcommandMetadata(*SubcommandMetadata)
208+
}
209+
210+
type HasFlags interface {
211+
BindFlags(*pflag.FlagSet)
212+
}
213+
214+
type RequiresConfig interface {
215+
InjectConfig(config.Config) error
216+
}
217+
218+
type RequiresResource interface {
219+
InjectResource(*resource.Resource) error
220+
}
221+
222+
type HasPreScaffold interface {
223+
PreScaffold(afero.Fs) error
224+
}
225+
226+
type Scaffolder interface {
227+
Scaffold(afero.Fs) error
228+
}
229+
230+
type HasPostScaffold interface {
231+
PostScaffold() error
232+
}
233+
```
234+
235+
Additional interfaces define the required method for each type of plugin:
236+
```go
237+
// InitSubcommand is the specific interface for subcommands returned by init plugins.
238+
type InitSubcommand interface {
239+
Scaffolder
240+
}
241+
242+
// EditSubcommand is the specific interface for subcommands returned by edit plugins.
243+
type EditSubcommand interface {
244+
Scaffolder
245+
}
246+
247+
// CreateAPISubcommand is the specific interface for subcommands returned by create API plugins.
248+
type CreateAPISubcommand interface {
249+
RequiresResource
250+
Scaffolder
251+
}
252+
253+
// CreateWebhookSubcommand is the specific interface for subcommands returned by create webhook plugins.
254+
type CreateWebhookSubcommand interface {
255+
RequiresResource
256+
Scaffolder
257+
}
258+
```

0 commit comments

Comments
 (0)