Skip to content

Commit f2acc2a

Browse files
committed
Improve plugin phase 1.5 EP
Signed-off-by: Adrian Orive <[email protected]>
1 parent 2018a4f commit f2acc2a

File tree

1 file changed

+93
-30
lines changed

1 file changed

+93
-30
lines changed

designs/extensible-cli-and-scaffolding-plugins-phase-1-5.md

Lines changed: 93 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -46,36 +46,42 @@ Plugin chaining solves the aforementioned problems but the current plugin API, a
4646

4747
Design a Plugin API that combines the current [`Subcommand`](../pkg/plugin/interfaces.go) and
4848
[`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
49+
The new `Subcommand` hooks can be split in two different categories:
50+
- Initialization hooks
51+
- Execution hooks
5252

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).
53+
Initialization hooks are run during the dynamic creation of the CLI, which means that they are able to
54+
modify the CLI, e.g. providing descriptions and examples for subcommands or binding flags.
55+
Execution hooks are run after the CLI is created, and therefore cannot modify the CLI. On the other hand,
56+
as they are run during the CLI execution, they have access to user-provided flag values, project configuration,
57+
the new API resource or the filesystem abstraction, as opposed to the initialization hooks.
58+
59+
Additionally, some of these hooks may be optional, in which case a non-implemented hook will be skipped
60+
when it should be called and consider it succeeded. This also allows to create some hooks specific for
61+
a certain subcommand call (e.g.: `Resource`-related hooks for the `edit` subcommand are not needed).
5662

5763
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
64+
- Hook order guarantee: a hook for a plugin will be called after its previous hooks succeeded.
65+
- Steps order guarantee: hooks will be called when all plugins have finished the previous hook.
66+
- Plugin order guarantee: same hook for each plugin will be called in the order specified
6167
by the plugin position at the plugin chain.
6268

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
69+
All of the hooks will offer plugin order guarantee, as they all modify/update some item so the order
70+
of plugins is important. Execution hooks need to guarantee step order, as the items that are being modified
6571
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.
72+
initialization hooks that modify items (metadata and flagset) that are only used in their own methods,
73+
so they only need to guarantee hook order.
6874

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.
75+
Execution hooks will be able to return an error. A specific error can be returned to specify that
76+
no further hooks of this plugin should be called, but that the scaffold process should be continued.
7177
This enables plugins to exit early, e.g., a plugin that scaffolds some files only for cluster-scoped
7278
resources can detect if the resource is cluster-scoped at one of the first execution steps, and
7379
therefore, use this error to tell the CLI that no further execution step should be called for itself.
7480

75-
### Initialization methods
81+
### Initialization hooks
7682

7783
#### Update metadata
78-
This method will be used for two purposes. It provides CLI-related metadata to the Subcommand (e.g.,
84+
This hook will be used for two purposes. It provides CLI-related metadata to the Subcommand (e.g.,
7985
command name) and update the subcommands metadata such as the description or examples.
8086

8187
- Required/optional
@@ -88,7 +94,7 @@ command name) and update the subcommands metadata such as the description or exa
8894
- [x] Create webhook
8995

9096
#### Bind flags
91-
This method will allow subcommands to define specific flags.
97+
This hook will allow subcommands to define specific flags.
9298

9399
- Required/optional
94100
- [ ] Required
@@ -102,7 +108,7 @@ This method will allow subcommands to define specific flags.
102108
### Execution methods
103109

104110
#### Inject configuration
105-
This method will be used to inject the `Config` object that the plugin can modify at will.
111+
This hook will be used to inject the `Config` object that the plugin can modify at will.
106112
The CLI will create/load/save this configuration object.
107113

108114
- Required/optional
@@ -115,7 +121,7 @@ The CLI will create/load/save this configuration object.
115121
- [x] Create webhook
116122

117123
#### Inject resource
118-
This method will be used to inject the `Resource` object.
124+
This hook will be used to inject the `Resource` object created by the CLI.
119125

120126
- Required/optional
121127
- [x] Required
@@ -127,9 +133,9 @@ This method will be used to inject the `Resource` object.
127133
- [x] Create webhook
128134

129135
#### Pre-scaffold
130-
This method will be used to take actions before the main scaffolding is performed, e.g. validations.
136+
This hook will be used to take actions before the main scaffolding is performed, e.g. validations.
131137

132-
NOTE: a filesystem abstraction will be passed to this method that must be used for scaffolding.
138+
NOTE: a filesystem abstraction will be passed to this hook, but it should not be used for scaffolding.
133139

134140
- Required/optional
135141
- [ ] Required
@@ -141,9 +147,9 @@ NOTE: a filesystem abstraction will be passed to this method that must be used f
141147
- [x] Create webhook
142148

143149
#### Scaffold
144-
This method will be used to perform the main scaffolding.
150+
This hook will be used to perform the main scaffolding.
145151

146-
NOTE: a filesystem abstraction will be passed to this method that must be used for scaffolding.
152+
NOTE: a filesystem abstraction will be passed to this hook that must be used for scaffolding.
147153

148154
- Required/optional
149155
- [x] Required
@@ -155,11 +161,14 @@ NOTE: a filesystem abstraction will be passed to this method that must be used f
155161
- [x] Create webhook
156162

157163
#### Post-scaffold
158-
This method will be used to take actions after the main scaffolding is performed, e.g. cleanup.
164+
This hook will be used to take actions after the main scaffolding is performed, e.g. cleanup.
159165

160-
NOTE: a filesystem abstraction will **NOT** be passed to this method, as post-scaffold task do not require it.
166+
NOTE: a filesystem abstraction will **NOT** be passed to this hook, as post-scaffold task do not require it.
161167
In case some post-scaffold task requires a filesystem abstraction, it could be added.
162168

169+
NOTE 2: the project configuration is saved by the CLI before calling this hook, so changes done to the
170+
configuration at this hook will not be persisted.
171+
163172
- Required/optional
164173
- [ ] Required
165174
- [x] Optional
@@ -168,10 +177,56 @@ In case some post-scaffold task requires a filesystem abstraction, it could be a
168177
- [x] Edit
169178
- [x] Create API
170179
- [x] Create webhook
180+
181+
### Plugin chain persistence
182+
183+
Currently, the project configuration v3 offers two mechanisms for storing plugin-related information.
184+
185+
- A layout field (`string`) that is used for plugin resolution on initialized projects.
186+
- A plugin field (`map[string]interface{}`) that is used for plugin configuration raw storage.
187+
188+
Plugin resolution uses the `layout` field to resolve plugins. In this phase, it has to store a plugin
189+
chain and not a single plugin. As this value is stored as a string, comma-separated representation can
190+
be used to represent a chain of plugins instead.
191+
192+
NOTE: commas are not allowed in the plugin key.
193+
194+
While the `plugin` field may seem like a better fit to store the plugin chain, as it can already
195+
contain multiple values, there are several issues with this alternative approach:
196+
- A map does not provide any order guarantee, and the plugin chain order is relevant.
197+
- Some plugins do not store plugin-specific configuration information, e.g. the `go`-plugins. So
198+
the absence of a plugin key doesn't mean that the plugin is not part of the plugin chain.
199+
- The desire of running a different set of plugins for a single subcommand call has already been
200+
mentioned. Some of these out-of-chain plugins may need to store plugin-specific configuration,
201+
so the presence of a plugin doesn't mean that is part of the plugin chain.
202+
203+
The next project configuration version could consider this new requirements to define the
204+
names/types of these two fields.
205+
206+
### Plugin bundle
207+
208+
As a side-effect of plugin chaining, the user experience may suffer if they need to provide
209+
several plugin keys for the `--plugins` flag. Additionally, this would also mean a user-facing
210+
important breaking change.
211+
212+
In order to solve this issue, a plugin bundle concept will be introduced. A plugin bundle
213+
behaves as a plugin:
214+
- It has a name: provided at creation.
215+
- It has a version: provided at creation.
216+
- It has a list of supported project versions: computed from the common supported project
217+
versions of all the plugins in the bundled.
218+
219+
Instead of implementing the optional getter methods that return a subcommand, it offers a way
220+
to retrieve the list of bundled plugins. This process will be done after plugin resolution.
221+
222+
This way, CLIs will be able to define bundles, which will be used in the user-facing API and
223+
the plugin resolution process, but later they will be treated as separate plugins offering
224+
the maintainability and separation of concerns advantages that smaller plugins have in
225+
comparison with bigger monolithic plugins.
171226

172227
## Implementation
173228

174-
The following types are used as input/output values of the described methods:
229+
The following types are used as input/output values of the described hooks:
175230
```go
176231
// CLIMetadata is the runtime meta-data of the CLI
177232
type CLIMetadata struct {
@@ -197,7 +252,7 @@ func (e ExitError) Error() string {
197252
}
198253
```
199254

200-
The described methods are implemented through the use of the following interfaces.
255+
The described hooks are implemented through the use of the following interfaces.
201256
```go
202257
type RequiresCLIMetadata interface {
203258
InjectCLIMetadata(CLIMetadata)
@@ -220,11 +275,11 @@ type RequiresResource interface {
220275
}
221276

222277
type HasPreScaffold interface {
223-
PreScaffold(afero.Fs) error
278+
PreScaffold(machinery.Filesystem) error
224279
}
225280

226281
type Scaffolder interface {
227-
Scaffold(afero.Fs) error
282+
Scaffold(machinery.Filesystem) error
228283
}
229284

230285
type HasPostScaffold interface {
@@ -256,3 +311,11 @@ type CreateWebhookSubcommand interface {
256311
Scaffolder
257312
}
258313
```
314+
315+
An additional interface defines the bundle method to return the wrapped plugins:
316+
```go
317+
type Bundle interface {
318+
Plugin
319+
Plugins() []Plugin
320+
}
321+
```

0 commit comments

Comments
 (0)